diff --git a/CMakeLists.txt b/CMakeLists.txt index be67eb457..c1b4baade 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -203,68 +203,11 @@ list(APPEND OLIVE_LIBRARIES # Link OFX HostSupport wherever libolive-editor objects are used. list(APPEND OLIVE_LIBRARIES OfxHost) -# Link FFmpeg -find_package(FFMPEG 6.0 REQUIRED - COMPONENTS - avutil - avcodec - avformat - avfilter - swscale - swresample -) -list(APPEND OLIVE_INCLUDE_DIRS ${FFMPEG_INCLUDE_DIRS}) -list(APPEND OLIVE_LIBRARIES - FFMPEG::avutil - FFMPEG::avcodec - FFMPEG::avformat - FFMPEG::avfilter - FFMPEG::swscale - FFMPEG::swresample -) - -# FFmpeg isolation: all FFmpeg access is being moved behind this shared -# library's pure C API. Built early so the app can link it. +# FFmpeg isolation: all FFmpeg access in the editor goes through this shared +# library's pure C API. It is the only component that links FFmpeg. add_subdirectory(ffmpeg_bridge) - -# Detect FFmpeg pixel formats that may not exist in all versions -include(CheckCXXSourceCompiles) -set(CMAKE_REQUIRED_INCLUDES ${FFMPEG_INCLUDE_DIRS}) -check_cxx_source_compiles(" -#include -int main() { AVPixelFormat f = AV_PIX_FMT_GRAYF16; (void)f; return 0; } -" HAVE_AV_PIX_FMT_GRAYF16) -check_cxx_source_compiles(" -#include -int main() { AVPixelFormat f = AV_PIX_FMT_RGBF16; (void)f; return 0; } -" HAVE_AV_PIX_FMT_RGBF16) -check_cxx_source_compiles(" -#include -int main() { AVPixelFormat f = AV_PIX_FMT_RGBAF16; (void)f; return 0; } -" HAVE_AV_PIX_FMT_RGBAF16) -unset(CMAKE_REQUIRED_INCLUDES) - -if(HAVE_AV_PIX_FMT_GRAYF16) - add_compile_definitions(HAVE_AV_PIX_FMT_GRAYF16) -endif() -if(HAVE_AV_PIX_FMT_RGBF16) - add_compile_definitions(HAVE_AV_PIX_FMT_RGBF16) -endif() -if(HAVE_AV_PIX_FMT_RGBAF16) - add_compile_definitions(HAVE_AV_PIX_FMT_RGBAF16) -endif() - -# Static FFmpeg (e.g. our Linux CI build) needs system libs for libavcodec/libavformat. -find_package(ZLIB REQUIRED) -list(APPEND OLIVE_LIBRARIES ZLIB::ZLIB) - -find_package(BZip2 REQUIRED) -list(APPEND OLIVE_LIBRARIES BZip2::BZip2) - -find_package(LibLZMA) -if (LIBLZMA_FOUND) - list(APPEND OLIVE_LIBRARIES ${LIBLZMA_LIBRARIES}) -endif() +list(APPEND OLIVE_LIBRARIES ffmpeg_bridge) +list(APPEND OLIVE_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/ffmpeg_bridge/include) # Link PortAudio find_package(PortAudio REQUIRED) diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index bffdc5688..54f1fdf30 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -313,14 +313,16 @@ elseif (APPLE) OUTPUT_NAME "Oak" ) - # Copy the render worker and dynamic render backends into the app bundle. - # They are looked up in QCoreApplication::applicationDirPath(), which on - # macOS points to Oak.app/Contents/MacOS. + # Copy the render worker, dynamic render backends, and the FFmpeg bridge + # library into the app bundle. They are looked up in + # QCoreApplication::applicationDirPath(), which on macOS points to + # Oak.app/Contents/MacOS. add_custom_command(TARGET olive-editor POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory $/Contents/MacOS COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $/Contents/MacOS/ COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $/Contents/MacOS/ - COMMENT "Copying oak-render-worker and render backends into Oak.app" + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $/Contents/MacOS/ + COMMENT "Copying oak-render-worker, render backends, and ffmpeg_bridge into Oak.app" ) if (TARGET oakvulkan) add_custom_command(TARGET olive-editor POST_BUILD @@ -337,6 +339,30 @@ target_link_libraries(olive-editor PRIVATE ${OLIVE_LIBRARIES}) target_link_libraries(libolive-editor PRIVATE ${OLIVE_LIBRARIES}) target_link_libraries(olive-render-worker PRIVATE ${OLIVE_LIBRARIES}) +# The ffmpeg_bridge shared library ships next to the binaries: inside the +# macOS app bundle (Contents/MacOS, resolved via @loader_path), and in +# ffmpeg_bridge/bin beside bin/ on other platforms. (Build-tree binaries get +# the correct RPATH from CMake automatically.) +if (APPLE) + # macOS bundles are distributed via POST_BUILD copies (not install()), so + # @loader_path must already be in the build-tree binaries' RPATH. + set(OLIVE_FB_RPATH "@loader_path") + set_target_properties(olive-editor olive-render-worker PROPERTIES + BUILD_RPATH "@loader_path") +elseif (UNIX) + set(OLIVE_FB_RPATH "$ORIGIN/../ffmpeg_bridge/bin") +endif () +if (OLIVE_FB_RPATH) + set_target_properties(olive-editor olive-render-worker PROPERTIES + INSTALL_RPATH "${OLIVE_FB_RPATH}") + if (TARGET oakgl) + set_target_properties(oakgl PROPERTIES INSTALL_RPATH "${OLIVE_FB_RPATH}") + endif () + if (TARGET oakvulkan) + set_target_properties(oakvulkan PROPERTIES INSTALL_RPATH "${OLIVE_FB_RPATH}") + endif () +endif () + # Set compile options target_compile_options(olive-editor PRIVATE ${OLIVE_COMPILE_OPTIONS}) target_compile_options(libolive-editor PRIVATE ${OLIVE_COMPILE_OPTIONS}) diff --git a/app/audio/audioprocessor.cpp b/app/audio/audioprocessor.cpp index 4f1b6f51e..be6113f7a 100644 --- a/app/audio/audioprocessor.cpp +++ b/app/audio/audioprocessor.cpp @@ -21,10 +21,7 @@ #include "audioprocessor.h" -extern "C" { -#include -#include -} +#include #include @@ -34,50 +31,36 @@ namespace olive { /** - * @brief Ensure an AudioParams has a usable native channel layout mask. + * @brief Ensure an AudioParams has a usable channel layout mask. * - * FFmpeg's abuffer/aformat filters reject channel_layout=0x0 / unspecified - * layouts (e.g. when the user config or a source stream reports a mask of 0). - * If the provided layout is not a valid native mask, fall back to a default - * layout derived from the channel count (stereo when unknown). + * The bridge's abuffer/aformat filters reject a channel layout mask of 0 + * (e.g. when the user config or a source stream reports a mask of 0). + * If the mask is zero, fall back to a default layout derived from the + * channel count (stereo when unknown). */ static AudioParams FixChannelLayout(const AudioParams ¶ms) { - AudioParams result = params; - const AVChannelLayout &layout = params.channel_layout(); + AudioParams result = params; - bool needs_fix = false; - if (!av_channel_layout_check(&layout)) { - needs_fix = true; - } else if (layout.order != AV_CHANNEL_ORDER_NATIVE) { - needs_fix = true; - } else if (layout.u.mask == 0) { - needs_fix = true; - } + if (params.channel_layout() == 0) { + int channels = params.channel_count(); + if (channels <= 0) { + channels = 2; + } - if (needs_fix) { - int channels = params.channel_count(); - if (channels <= 0) { - channels = 2; - } + qWarning() << "AudioProcessor: fixing unspecified channel layout" + << "(channels=" << params.channel_count() << ") -> default" + << channels << "channel layout"; - qWarning() << "AudioProcessor: fixing invalid/unspecified channel layout" - << "(channels=" << params.channel_count() << ") -> default" - << channels << "channel layout"; + result.set_channel_layout(fb_channel_layout_default(channels)); + } - AVChannelLayout fallback; - av_channel_layout_default(&fallback, channels); - result.set_channel_layout(fallback); - av_channel_layout_uninit(&fallback); - } - - return result; + return result; } AudioProcessor::AudioProcessor() { - filter_graph_ = nullptr; - in_frame_ = nullptr; + graph_ = nullptr; out_frame_ = nullptr; } @@ -89,161 +72,45 @@ AudioProcessor::~AudioProcessor() bool AudioProcessor::Open(const AudioParams &from, const AudioParams &to, double tempo) { - if (filter_graph_) { + if (graph_) { qWarning() << "Tried to open a processor that was already open"; return false; } - filter_graph_ = avfilter_graph_alloc(); - if (!filter_graph_) { - qCritical() << "Failed to allocate filter graph"; - return false; - } - AudioParams from_fixed = FixChannelLayout(from); AudioParams to_fixed = FixChannelLayout(to); qDebug() << "AudioProcessor::Open: from sample_rate=" << from_fixed.sample_rate() << "channels=" << from_fixed.channel_count() << "layout_mask=0x" << Qt::hex - << from_fixed.channel_layout().u.mask << "to sample_rate=" + << from_fixed.channel_layout() << "to sample_rate=" << to_fixed.sample_rate() << "channels=" << to_fixed.channel_count() - << "layout_mask=0x" << to_fixed.channel_layout().u.mask << Qt::dec; + << "layout_mask=0x" << to_fixed.channel_layout() << Qt::dec; - // Set up audio buffer args - char filter_args[200]; - from_fmt_ = FFmpegUtils::GetFFmpegSampleFormat(from_fixed.format()); - to_fmt_ = FFmpegUtils::GetFFmpegSampleFormat(to_fixed.format()); - snprintf( - filter_args, 200, - "time_base=%d/%d:sample_rate=%d:sample_fmt=%d:channel_layout=0x%" PRIx64, - 1, from_fixed.sample_rate(), from_fixed.sample_rate(), from_fmt_, - from_fixed.channel_layout().u.mask); + FBAudioGraphConfig config; + memset(&config, 0, sizeof(config)); + config.in_sample_rate = from_fixed.sample_rate(); + config.in_channel_layout_mask = from_fixed.channel_layout(); + config.in_sample_format = + FFmpegUtils::GetFFmpegSampleFormat(from_fixed.format()); + config.in_channels = from_fixed.channel_count(); - int r; + config.out_sample_rate = to_fixed.sample_rate(); + config.out_channel_layout_mask = to_fixed.channel_layout(); + config.out_sample_format = + FFmpegUtils::GetFFmpegSampleFormat(to_fixed.format()); + config.out_channels = to_fixed.channel_count(); + config.out_is_planar = to_fixed.format().is_planar() ? 1 : 0; - // Create buffersrc (input) - r = avfilter_graph_create_filter(&buffersrc_ctx_, - avfilter_get_by_name("abuffer"), "in", - filter_args, nullptr, filter_graph_); - if (r < 0) { - qCritical() << "Failed to create buffersrc:" << r; - Close(); + config.tempo = tempo; + + graph_ = fb_audio_graph_create(&config); + if (!graph_) { + qCritical() << "Failed to create audio filter graph"; return false; } - // Store "previous" filter for linking - AVFilterContext *previous_filter = buffersrc_ctx_; - - // Create tempo - bool create_tempo; - if ((create_tempo = !qFuzzyCompare(tempo, 1.0))) { - // Create audio tempo filters: FFmpeg's atempo can only be set between 0.5 and 2.0. If the requested speed is outside - // those boundaries, we need to daisychain more than one together. - double base = (tempo > 1.0) ? 2.0 : 0.5; - double speed_log = log(tempo) / log(base); - - // This is the number of how many 0.5 or 2.0 tempos we need to daisychain - int whole = std::floor(speed_log); - - // Set speed_log to the remainder - speed_log -= whole; - - for (int i = 0; i <= whole; i++) { - double filter_tempo = (i == whole) ? std::pow(base, speed_log) : - base; - /* - if (qFuzzyCompare(filter_tempo, 1.0)) { - // This filter would do nothing - continue; - }*/ - - previous_filter = - CreateTempoFilter(filter_graph_, previous_filter, filter_tempo); - - if (!previous_filter) { - qCritical() << "Failed to create audio tempo filter"; - Close(); - return false; - } - } - } - - // Create conversion filter - auto ch1 = from_fixed.channel_layout(); - auto ch2 = to_fixed.channel_layout(); - if (from_fixed.sample_rate() != to_fixed.sample_rate() || - av_channel_layout_compare(&ch1, &ch2) || - from_fixed.format() != to_fixed.format() || - (to_fixed.format().is_planar() && - create_tempo)) { // Tempo processor automatically converts to packed, - // so if the desired output is planar, it'll need - // to be converted - snprintf(filter_args, 200, - "sample_fmts=%s:sample_rates=%d:channel_layouts=0x%" PRIx64, - av_get_sample_fmt_name(to_fmt_), to_fixed.sample_rate(), - to_fixed.channel_layout().u.mask); - - AVFilterContext *c; - r = avfilter_graph_create_filter(&c, avfilter_get_by_name("aformat"), - "fmt", filter_args, nullptr, - filter_graph_); - if (r < 0) { - qCritical() << "Failed to create format conversion filter:" << r - << filter_args; - Close(); - return false; - } - - r = avfilter_link(previous_filter, 0, c, 0); - if (r < 0) { - qCritical() << "Failed to link filters:" << r; - Close(); - return false; - } - - previous_filter = c; - } - - // Create buffersink (output) - r = avfilter_graph_create_filter(&buffersink_ctx_, - avfilter_get_by_name("abuffersink"), "out", - nullptr, nullptr, filter_graph_); - if (r < 0) { - qCritical() << "Failed to create buffersink:" << r; - Close(); - return false; - } - - r = avfilter_link(previous_filter, 0, buffersink_ctx_, 0); - if (r < 0) { - qCritical() << "Failed to link filters:" << r; - Close(); - return false; - } - char *dump = avfilter_graph_dump(filter_graph_, nullptr); - qDebug() << dump; - av_free(dump); - r = avfilter_graph_config(filter_graph_, nullptr); - if (r < 0) { - qCritical() << "Failed to configure graph:" << r; - Close(); - return false; - } - - in_frame_ = av_frame_alloc(); - if (in_frame_) { - in_frame_->sample_rate = from_fixed.sample_rate(); - in_frame_->format = from_fmt_; - in_frame_->ch_layout = from_fixed.channel_layout(); - in_frame_->pts = 0; - } else { - qCritical() << "Failed to allocate input frame"; - Close(); - return false; - } - - out_frame_ = av_frame_alloc(); + out_frame_ = fb_frame_alloc(); if (!out_frame_) { qCritical() << "Failed to allocate output frame"; Close(); @@ -258,21 +125,12 @@ bool AudioProcessor::Open(const AudioParams &from, const AudioParams &to, void AudioProcessor::Close() { - if (filter_graph_) { - avfilter_graph_free(&filter_graph_); - filter_graph_ = nullptr; - buffersrc_ctx_ = nullptr; - buffersink_ctx_ = nullptr; - } - - if (in_frame_) { - av_frame_free(&in_frame_); - in_frame_ = nullptr; + if (graph_) { + fb_audio_graph_free(&graph_); } if (out_frame_) { - av_frame_free(&out_frame_); - out_frame_ = nullptr; + fb_frame_free(&out_frame_); } } @@ -287,15 +145,9 @@ int AudioProcessor::Convert(float **in, int nb_in_samples, int r = 0; if (in && nb_in_samples) { - // Set frame parameters - in_frame_->nb_samples = nb_in_samples; - for (int i = 0; i < from_.channel_count(); i++) { - in_frame_->data[i] = reinterpret_cast(in[i]); - in_frame_->linesize[i] = from_.samples_to_bytes(nb_in_samples); - } - - r = av_buffersrc_add_frame_flags(buffersrc_ctx_, in_frame_, - AV_BUFFERSRC_FLAG_KEEP_REF); + r = fb_audio_graph_push( + graph_, reinterpret_cast(in), + nb_in_samples); if (r < 0) { qCritical() << "Failed to add frame to buffersrc:" << r; return r; @@ -315,10 +167,10 @@ int AudioProcessor::Convert(float **in, int nb_in_samples, int byte_offset = 0; while (true) { - av_frame_unref(out_frame_); - r = av_buffersink_get_frame(buffersink_ctx_, out_frame_); - if (r < 0) { - if (r == AVERROR(EAGAIN)) { + r = fb_audio_graph_pull(graph_, out_frame_); + if (r <= 0) { + if (r == 0) { + // No more output available right now r = 0; } else { // Handle unexpected error @@ -327,20 +179,19 @@ int AudioProcessor::Convert(float **in, int nb_in_samples, break; } - int nb_bytes = - out_frame_->nb_samples * to_.bytes_per_sample_per_channel(); + int nb_bytes = fb_frame_get_nb_samples(out_frame_) * + to_.bytes_per_sample_per_channel(); if (to_.format().is_packed()) { nb_bytes *= to_.channel_count(); } for (int i = 0; i < nb_channels; i++) { result[i].resize(byte_offset + nb_bytes); - memcpy(result[i].data() + byte_offset, out_frame_->data[i], - nb_bytes); + memcpy(result[i].data() + byte_offset, + fb_frame_get_data(out_frame_, i), nb_bytes); } byte_offset += nb_bytes; } - av_frame_unref(out_frame_); } return r; @@ -348,31 +199,10 @@ int AudioProcessor::Convert(float **in, int nb_in_samples, void AudioProcessor::Flush() { - int r = av_buffersrc_add_frame_flags(buffersrc_ctx_, nullptr, - AV_BUFFERSRC_FLAG_KEEP_REF); + int r = fb_audio_graph_push(graph_, nullptr, 0); if (r < 0) { qCritical() << "Failed to flush:" << r; } } -AVFilterContext *AudioProcessor::CreateTempoFilter(AVFilterGraph *graph, - AVFilterContext *link, - const double &tempo) -{ - // Set up tempo param, which is taken as a C string - char speed_param[20]; - snprintf(speed_param, 20, "%f", tempo); - - AVFilterContext *tempo_ctx = nullptr; - - if (avfilter_graph_create_filter(&tempo_ctx, avfilter_get_by_name("atempo"), - "atempo", speed_param, nullptr, - graph) >= 0 && - avfilter_link(link, 0, tempo_ctx, 0) == 0) { - return tempo_ctx; - } - - return nullptr; -} - } diff --git a/app/audio/audioprocessor.h b/app/audio/audioprocessor.h index 9044c1657..a5e88c112 100644 --- a/app/audio/audioprocessor.h +++ b/app/audio/audioprocessor.h @@ -26,9 +26,7 @@ #include #include -extern "C" { -#include -} +#include #include "common/define.h" @@ -52,7 +50,7 @@ public: bool IsOpen() const { - return filter_graph_; + return graph_; } using Buffer = QVector; @@ -70,25 +68,13 @@ public: } private: - static AVFilterContext *CreateTempoFilter(AVFilterGraph *graph, - AVFilterContext *link, - const double &tempo); - - AVFilterGraph *filter_graph_; - - AVFilterContext *buffersrc_ctx_; - - AVFilterContext *buffersink_ctx_; + FBAudioGraph *graph_; AudioParams from_; - AVSampleFormat from_fmt_; AudioParams to_; - AVSampleFormat to_fmt_; - AVFrame *in_frame_; - - AVFrame *out_frame_; + FBFrame *out_frame_; }; } diff --git a/app/codec/conformmanager.cpp b/app/codec/conformmanager.cpp index b9ca6b355..a14f5f37c 100644 --- a/app/codec/conformmanager.cpp +++ b/app/codec/conformmanager.cpp @@ -98,7 +98,7 @@ ConformManager::GetConformedFilename(const QString &cache_path, QString::number(stream.stream()), QString::number(params.sample_rate()), QString::number(params.format()), - QString::number(params.channel_layout().u.mask), + QString::number(params.channel_layout()), QString::number(i)); filenames[i] = QDir(cache_path).filePath(index_fn); diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index 4e8c3988b..8592a1dd2 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -252,7 +252,7 @@ DecoderPtr Decoder::CreateFromID(const QString &id) void Decoder::SignalProcessingProgress(int64_t ts, int64_t duration) { - if (duration != AV_NOPTS_VALUE && duration != 0) { + if (duration != FB_NOPTS_VALUE && duration != 0) { emit IndexProgress(static_cast(ts) / static_cast(duration)); } diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 8b918b926..8bb0bf4c9 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -22,10 +22,6 @@ #ifndef DECODER_H #define DECODER_H -extern "C" { -#include -} - #include #include #include diff --git a/app/codec/encoder.cpp b/app/codec/encoder.cpp index a27b3048d..4300031da 100644 --- a/app/codec/encoder.cpp +++ b/app/codec/encoder.cpp @@ -300,7 +300,7 @@ void EncodingParams::Save(QXmlStreamWriter *writer) const writer->writeTextElement( QStringLiteral("channellayout"), - QString::number(audio_params().channel_layout().u.mask)); + QString::number(audio_params().channel_layout())); writer->writeTextElement( QStringLiteral("format"), QString::fromStdString(audio_params_.format().to_string())); @@ -542,12 +542,8 @@ bool EncodingParams::LoadV1(QXmlStreamReader *reader) audio_params_.set_sample_rate( reader->readElementText().toInt()); } else if (reader->name() == QStringLiteral("channellayout")) { - AVChannelLayout av_channel_layout; - av_channel_layout_from_mask( - &av_channel_layout, + audio_params_.set_channel_layout( reader->readElementText().toLongLong()); - audio_params_.set_channel_layout(av_channel_layout); - av_channel_layout_uninit(&av_channel_layout); } else if (reader->name() == QStringLiteral("format")) { audio_params_.set_format(SampleFormat::from_string( reader->readElementText().toStdString())); diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index ef8ce51b1..215f61902 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -21,60 +21,7 @@ #include "ffmpegdecoder.h" -extern "C" { -#include -} - -namespace olive -{ - -static FramePtr CopyPackedAVFrameToFrame(const AVFramePtr &src, - PixelFormat format, int channel_count, - const rational ×tamp) -{ - if (!src || !src->data[0]) { - return nullptr; - } - - VideoParams params(src->width, src->height, format, channel_count); - FramePtr frame = Frame::Create(); - frame->set_video_params(params); - frame->set_timestamp(timestamp); - if (!frame->allocate()) { - return nullptr; - } - - const int row_bytes = params.effective_width() * - VideoParams::GetBytesPerPixel(format, channel_count); - for (int y = 0; y < frame->height(); y++) { - memcpy(frame->data() + y * frame->linesize_bytes(), - src->data[0] + y * src->linesize[0], size_t(row_bytes)); - } - - return frame; -} - -static VideoParams::Interlacing FFmpegFieldOrderToOlive(AVFieldOrder fo) -{ - switch (fo) { - case AV_FIELD_TT: - return VideoParams::kInterlacedTopFirst; - case AV_FIELD_BB: - return VideoParams::kInterlacedBottomFirst; - case AV_FIELD_PROGRESSIVE: - default: - return VideoParams::kInterlaceNone; - } -} - -} - -extern "C" { -#include -#include -#include -#include -} +#include #include #include @@ -93,69 +40,79 @@ extern "C" { namespace olive { +static FramePtr CopyPackedAVFrameToFrame(const AVFramePtr &src, + PixelFormat format, int channel_count, + const rational ×tamp) +{ + if (!src || !src->data(0)) { + return nullptr; + } + + VideoParams params(src->width(), src->height(), format, channel_count); + FramePtr frame = Frame::Create(); + frame->set_video_params(params); + frame->set_timestamp(timestamp); + if (!frame->allocate()) { + return nullptr; + } + + const int row_bytes = params.effective_width() * + VideoParams::GetBytesPerPixel(format, channel_count); + for (int y = 0; y < frame->height(); y++) { + memcpy(frame->data() + y * frame->linesize_bytes(), + src->data(0) + y * src->linesize(0), size_t(row_bytes)); + } + + return frame; +} + +static VideoParams::Interlacing FFmpegFieldOrderToOlive(int fo) +{ + switch (fo) { + case FB_FIELD_ORDER_TT: + return VideoParams::kInterlacedTopFirst; + case FB_FIELD_ORDER_BB: + return VideoParams::kInterlacedBottomFirst; + case FB_FIELD_ORDER_PROGRESSIVE: + default: + return VideoParams::kInterlaceNone; + } +} + QVariant Yuv2RgbShader; QVariant DeinterlaceShader; namespace { -constexpr int64_t kAnalyzeDurationUs = 5000000; -constexpr int64_t kProbeSizeBytes = 20000000; - -void ApplyFormatOpenOptions(AVDictionary **opts) +int CancelThunk(void *userdata) { - av_dict_set_int(opts, "analyzeduration", kAnalyzeDurationUs, 0); - av_dict_set_int(opts, "probesize", kProbeSizeBytes, 0); + CancelAtom *cancelled = static_cast(userdata); + return (cancelled && cancelled->IsCancelled()) ? 1 : 0; } -void TuneFormatContext(AVFormatContext *ctx) -{ - if (!ctx) { - return; - } - - ctx->probesize = kProbeSizeBytes; - ctx->max_analyze_duration = kAnalyzeDurationUs; -} - -void DiscardSubtitleStreams(AVFormatContext *ctx) -{ - if (!ctx) { - return; - } - - for (unsigned int i = 0; i < ctx->nb_streams; i++) { - AVStream *stream = ctx->streams[i]; - if (stream && stream->codecpar && - stream->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) { - stream->discard = AVDISCARD_ALL; - } - } -} - -TimecodeMetadata::SourceTime ExtractSourceStartTime(AVDictionary *metadata, +TimecodeMetadata::SourceTime ExtractSourceStartTime(FBProbe *probe, + int stream_index, const rational &timebase, int sample_rate) { - if (!metadata) { - return TimecodeMetadata::SourceTime(); - } + char buf[1024]; - if (AVDictionaryEntry *entry = - av_dict_get(metadata, "timecode", nullptr, AV_DICT_IGNORE_SUFFIX)) { + if (fb_probe_get_metadata(probe, stream_index, "timecode", buf, + sizeof(buf)) == 1) { TimecodeMetadata::SourceTime parsed = - TimecodeMetadata::FromTimecodeString( - QString::fromUtf8(entry->value), timebase); + TimecodeMetadata::FromTimecodeString(QString::fromUtf8(buf), + timebase); if (parsed.valid) { return parsed; } } - if (AVDictionaryEntry *entry = av_dict_get( - metadata, "time_reference", nullptr, AV_DICT_IGNORE_SUFFIX)) { + if (fb_probe_get_metadata(probe, stream_index, "time_reference", buf, + sizeof(buf)) == 1) { TimecodeMetadata::SourceTime parsed = - TimecodeMetadata::FromBwfTimeReference( - QString::fromUtf8(entry->value), sample_rate); + TimecodeMetadata::FromBwfTimeReference(QString::fromUtf8(buf), + sample_rate); if (parsed.valid) { return parsed; } @@ -164,28 +121,73 @@ TimecodeMetadata::SourceTime ExtractSourceStartTime(AVDictionary *metadata, return TimecodeMetadata::SourceTime(); } +struct SubtitleReadContext { + SubtitleParams *sub; + rational time_base; +}; + +void SubtitleReadThunk(int64_t pts, int64_t duration, const char *text, + int text_size, void *userdata) +{ + SubtitleReadContext *ctx = static_cast(userdata); + + TimeRange time(Timecode::timestamp_to_time(pts, ctx->time_base), + Timecode::timestamp_to_time(pts + duration, ctx->time_base)); + + ctx->sub->push_back( + Subtitle(time, QString::fromUtf8(text, text_size))); +} + } // namespace FFmpegDecoder::FFmpegDecoder() - : sws_ctx_(nullptr) + : scaler_(nullptr) , working_packet_(nullptr) , cache_at_zero_(false) , cache_at_eof_(false) + , instance_(nullptr) + , stream_start_time_(0) + , stream_duration_(0) + , format_start_time_(FB_NOPTS_VALUE) + , input_sample_format_(FB_SAMPLE_FMT_NONE) + , input_sample_rate_(0) + , input_channel_layout_mask_(0) { } bool FFmpegDecoder::OpenInternal() { - if (instance_.Open(stream().filename().toUtf8(), stream().stream())) { - AVStream *s = instance_.avstream(); + instance_ = fb_decoder_create(); + if (!instance_) { + return false; + } + + if (fb_decoder_open(instance_, stream().filename().toUtf8(), + stream().stream()) == 0) { + // Cache the stream parameters the decoder logic needs; the stream + // object itself always lives inside the bridge library + FBStreamInfo info; + if (fb_decoder_get_stream_info(instance_, &info) != 0) { + fb_decoder_free(&instance_); + return false; + } + + stream_time_base_ = rational(info.time_base_num, info.time_base_den); + stream_start_time_ = info.start_time; + stream_duration_ = info.duration; + format_start_time_ = fb_decoder_get_format_start_time(instance_); + input_sample_format_ = info.sample_format; + input_sample_rate_ = info.sample_rate; + input_channel_layout_mask_ = info.channel_layout_mask; // Store one second in the source's timebase - second_ts_ = qRound64(av_q2d(av_inv_q(s->time_base))); + second_ts_ = qRound64(stream_time_base_.flipped().toDouble()); - working_packet_ = av_packet_alloc(); + working_packet_ = fb_packet_alloc(); return true; } + fb_decoder_free(&instance_); return false; } @@ -194,29 +196,35 @@ TexturePtr FFmpegDecoder::ProcessFrameIntoTexture(AVFramePtr f, const AVFramePtr original) { // Determine native format - AVPixelFormat ideal_fmt = FFmpegUtils::GetCompatiblePixelFormat( - static_cast(f->format)); + int ideal_fmt = FFmpegUtils::GetCompatibleBridgePixelFormat(f->format()); PixelFormat native_fmt = GetNativePixelFormat(ideal_fmt); int native_channels = GetNativeChannelCount(ideal_fmt); + // Determine pixel aspect ratio + int sar_num, sar_den; + rational pixel_aspect_ratio(1, 1); + if (fb_decoder_guess_sample_aspect_ratio(instance_, nullptr, &sar_num, + &sar_den) == 0 && + sar_den != 0) { + pixel_aspect_ratio = rational(sar_num, sar_den); + } + // Set up video params - VideoParams vp(original->width, original->height, native_fmt, - native_channels, - av_guess_sample_aspect_ratio(instance_.fmt_ctx(), - instance_.avstream(), nullptr), + VideoParams vp(original->width(), original->height(), native_fmt, + native_channels, pixel_aspect_ratio, VideoParams::kInterlaceNone, p.divider); // For YUV formats, force the output texture to F32 RGBA for maximum precision - switch (f->format) { - case AV_PIX_FMT_YUV420P: - case AV_PIX_FMT_YUV422P: - case AV_PIX_FMT_YUV444P: - case AV_PIX_FMT_YUV420P10LE: - case AV_PIX_FMT_YUV422P10LE: - case AV_PIX_FMT_YUV444P10LE: - case AV_PIX_FMT_YUV420P12LE: - case AV_PIX_FMT_YUV422P12LE: - case AV_PIX_FMT_YUV444P12LE: + switch (f->format()) { + case FB_PIX_FMT_YUV420P: + case FB_PIX_FMT_YUV422P: + case FB_PIX_FMT_YUV444P: + case FB_PIX_FMT_YUV420P10LE: + case FB_PIX_FMT_YUV422P10LE: + case FB_PIX_FMT_YUV444P10LE: + case FB_PIX_FMT_YUV420P12LE: + case FB_PIX_FMT_YUV422P12LE: + case FB_PIX_FMT_YUV444P12LE: vp.set_format(PixelFormat::F32); vp.set_channel_count(VideoParams::kRGBAChannelCount); break; @@ -227,16 +235,16 @@ TexturePtr FFmpegDecoder::ProcessFrameIntoTexture(AVFramePtr f, // Create texture TexturePtr tex = p.renderer->CreateTexture(vp); - switch (f->format) { - case AV_PIX_FMT_YUV420P: - case AV_PIX_FMT_YUV422P: - case AV_PIX_FMT_YUV444P: - case AV_PIX_FMT_YUV420P10LE: - case AV_PIX_FMT_YUV422P10LE: - case AV_PIX_FMT_YUV444P10LE: - case AV_PIX_FMT_YUV420P12LE: - case AV_PIX_FMT_YUV422P12LE: - case AV_PIX_FMT_YUV444P12LE: { + switch (f->format()) { + case FB_PIX_FMT_YUV420P: + case FB_PIX_FMT_YUV422P: + case FB_PIX_FMT_YUV444P: + case FB_PIX_FMT_YUV420P10LE: + case FB_PIX_FMT_YUV422P10LE: + case FB_PIX_FMT_YUV444P10LE: + case FB_PIX_FMT_YUV420P12LE: + case FB_PIX_FMT_YUV422P12LE: + case FB_PIX_FMT_YUV444P12LE: { // Run through YUV to RGB shader if (Yuv2RgbShader.isNull()) { // Compile shader @@ -250,23 +258,23 @@ TexturePtr FFmpegDecoder::ProcessFrameIntoTexture(AVFramePtr f, int px_size; int bits_per_pixel; - switch (f->format) { - case AV_PIX_FMT_YUV420P: - case AV_PIX_FMT_YUV422P: - case AV_PIX_FMT_YUV444P: + switch (f->format()) { + case FB_PIX_FMT_YUV420P: + case FB_PIX_FMT_YUV422P: + case FB_PIX_FMT_YUV444P: default: px_size = 1; bits_per_pixel = 8; break; - case AV_PIX_FMT_YUV420P10LE: - case AV_PIX_FMT_YUV422P10LE: - case AV_PIX_FMT_YUV444P10LE: + case FB_PIX_FMT_YUV420P10LE: + case FB_PIX_FMT_YUV422P10LE: + case FB_PIX_FMT_YUV444P10LE: px_size = 2; bits_per_pixel = 10; break; - case AV_PIX_FMT_YUV420P12LE: - case AV_PIX_FMT_YUV422P12LE: - case AV_PIX_FMT_YUV444P12LE: + case FB_PIX_FMT_YUV420P12LE: + case FB_PIX_FMT_YUV422P12LE: + case FB_PIX_FMT_YUV444P12LE: px_size = 2; bits_per_pixel = 12; break; @@ -279,33 +287,33 @@ TexturePtr FFmpegDecoder::ProcessFrameIntoTexture(AVFramePtr f, plane_params.set_format(native_fmt); TexturePtr y_plane = p.renderer->CreateTexture( - plane_params, hw_in->data[0], hw_in->linesize[0] / px_size); + plane_params, hw_in->data(0), hw_in->linesize(0) / px_size); y_plane->handleFrame(hw_in); - switch (f->format) { - case AV_PIX_FMT_YUV420P: - case AV_PIX_FMT_YUV422P: - case AV_PIX_FMT_YUV420P10LE: - case AV_PIX_FMT_YUV422P10LE: - case AV_PIX_FMT_YUV420P12LE: - case AV_PIX_FMT_YUV422P12LE: + switch (f->format()) { + case FB_PIX_FMT_YUV420P: + case FB_PIX_FMT_YUV422P: + case FB_PIX_FMT_YUV420P10LE: + case FB_PIX_FMT_YUV422P10LE: + case FB_PIX_FMT_YUV420P12LE: + case FB_PIX_FMT_YUV422P12LE: plane_params.set_width(plane_params.width() / 2); break; } - switch (f->format) { - case AV_PIX_FMT_YUV420P: - case AV_PIX_FMT_YUV420P10LE: - case AV_PIX_FMT_YUV420P12LE: + switch (f->format()) { + case FB_PIX_FMT_YUV420P: + case FB_PIX_FMT_YUV420P10LE: + case FB_PIX_FMT_YUV420P12LE: plane_params.set_height(plane_params.height() / 2); break; } TexturePtr u_plane = p.renderer->CreateTexture( - plane_params, hw_in->data[1], hw_in->linesize[1] / px_size); + plane_params, hw_in->data(1), hw_in->linesize(1) / px_size); u_plane->handleFrame(hw_in); TexturePtr v_plane = p.renderer->CreateTexture( - plane_params, hw_in->data[2], hw_in->linesize[2] / px_size); + plane_params, hw_in->data(2), hw_in->linesize(2) / px_size); v_plane->handleFrame(hw_in); ShaderJob job; @@ -322,33 +330,33 @@ TexturePtr FFmpegDecoder::ProcessFrameIntoTexture(AVFramePtr f, NodeValue(NodeValue::kInt, bits_per_pixel)); job.Insert(QStringLiteral("full_range"), NodeValue(NodeValue::kBoolean, - hw_in->color_range == AVCOL_RANGE_JPEG)); + hw_in->color_range() == FB_COLOR_RANGE_JPEG)); - const int *yuv_coeffs = sws_getCoefficients( - FFmpegUtils::GetSwsColorspaceFromAVColorSpace(hw_in->colorspace)); + double yuv_coeffs[4]; + fb_get_yuv_coefficients(hw_in->colorspace(), yuv_coeffs); job.Insert(QStringLiteral("yuv_crv"), - NodeValue(NodeValue::kFloat, yuv_coeffs[0] / 65536.0)); + NodeValue(NodeValue::kFloat, yuv_coeffs[0])); job.Insert(QStringLiteral("yuv_cgu"), - NodeValue(NodeValue::kFloat, yuv_coeffs[2] / 65536.0)); + NodeValue(NodeValue::kFloat, yuv_coeffs[2])); job.Insert(QStringLiteral("yuv_cgv"), - NodeValue(NodeValue::kFloat, yuv_coeffs[3] / 65536.0)); + NodeValue(NodeValue::kFloat, yuv_coeffs[3])); job.Insert(QStringLiteral("yuv_cbu"), - NodeValue(NodeValue::kFloat, yuv_coeffs[1] / 65536.0)); + NodeValue(NodeValue::kFloat, yuv_coeffs[1])); tex = p.renderer->CreateTexture(vp); p.renderer->BlitToTexture(Yuv2RgbShader, job, tex.get(), false); break; } - case AV_PIX_FMT_RGBA: - case AV_PIX_FMT_RGBA64LE: + case FB_PIX_FMT_RGBA: + case FB_PIX_FMT_RGBA64LE: // RGBA can be uploaded directly to the texture tex->handleFrame(f); - tex->Upload(f->data[0], f->linesize[0] / vp.GetBytesPerPixel()); + tex->Upload(f->data(0), f->linesize(0) / vp.GetBytesPerPixel()); break; - case AV_PIX_FMT_RGBAF32: + case FB_PIX_FMT_RGBAF32LE: // RGBA F32 can be uploaded directly to the texture tex->handleFrame(f); - tex->Upload(f->data[0], f->linesize[0] / vp.GetBytesPerPixel()); + tex->Upload(f->data(0), f->linesize(0) / vp.GetBytesPerPixel()); break; } @@ -364,8 +372,13 @@ TexturePtr FFmpegDecoder::ProcessFrameIntoTexture(AVFramePtr f, } } - rational frame_rate_tb = av_guess_frame_rate( - instance_.fmt_ctx(), instance_.avstream(), original.get()); + int fr_num, fr_den; + rational frame_rate_tb; + if (fb_decoder_guess_frame_rate(instance_, original->handle(), &fr_num, + &fr_den) == 0 && + fr_num != 0) { + frame_rate_tb = rational(fr_num, fr_den); + } // Double frame rate for interlaced fields frame_rate_tb *= 2; @@ -374,10 +387,10 @@ TexturePtr FFmpegDecoder::ProcessFrameIntoTexture(AVFramePtr f, frame_rate_tb.flip(); int64_t req = Timecode::time_to_timestamp( - p.time + rational(instance_.fmt_ctx()->start_time, AV_TIME_BASE), - frame_rate_tb); - int64_t frm = Timecode::rescale_timestamp( - original->pts, instance_.avstream()->time_base, frame_rate_tb); + p.time + rational(format_start_time_, FB_TIME_BASE), frame_rate_tb); + int64_t frm = Timecode::rescale_timestamp(original->pts(), + stream_time_base_, + frame_rate_tb); bool first = (req == frm); bool top_first = @@ -393,7 +406,7 @@ TexturePtr FFmpegDecoder::ProcessFrameIntoTexture(AVFramePtr f, job.Insert(QStringLiteral("interlacing"), NodeValue(NodeValue::kInt, interlacing)); job.Insert(QStringLiteral("pixel_height"), - NodeValue(NodeValue::kInt, original->height)); + NodeValue(NodeValue::kInt, original->height())); p.renderer->BlitToTexture(DeinterlaceShader, job, deinterlaced.get(), false); @@ -414,13 +427,12 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(const RetrieveVideoParams &p) AVFramePtr original = f; // Disregard "JPEG" pixel formats because we allow the user to override that - f->format = FFmpegUtils::ConvertJPEGSpaceToRegularSpace( - static_cast(f->format)); + f->set_format(FFmpegUtils::ConvertJPEGSpaceToRegularSpace(f->format())); // Force frame's color range to whatever it's set to in Olive - f->color_range = p.force_range == VideoParams::kColorRangeFull ? - AVCOL_RANGE_JPEG : - AVCOL_RANGE_MPEG; + f->set_color_range(p.force_range == VideoParams::kColorRangeFull ? + FB_COLOR_RANGE_JPEG : + FB_COLOR_RANGE_MPEG); // Perform any CPU processing required AVFramePtr ptr = PreProcessFrame(f, p); @@ -450,70 +462,62 @@ FramePtr FFmpegDecoder::RetrieveVideoFrameInternal(const RetrieveVideoParams &p) return nullptr; } - f->format = FFmpegUtils::ConvertJPEGSpaceToRegularSpace( - static_cast(f->format)); - f->color_range = p.force_range == VideoParams::kColorRangeFull ? - AVCOL_RANGE_JPEG : - AVCOL_RANGE_MPEG; + f->set_format(FFmpegUtils::ConvertJPEGSpaceToRegularSpace(f->format())); + f->set_color_range(p.force_range == VideoParams::kColorRangeFull ? + FB_COLOR_RANGE_JPEG : + FB_COLOR_RANGE_MPEG); AVFramePtr dest = CreateAVFramePtr(); - dest->width = f->width; - dest->height = f->height; - dest->format = p.maximum_format == PixelFormat::U8 ? AV_PIX_FMT_RGBA : - AV_PIX_FMT_RGBA64; - dest->color_range = f->color_range; - dest->colorspace = f->colorspace; + dest->set_width(f->width()); + dest->set_height(f->height()); + dest->set_format(p.maximum_format == PixelFormat::U8 ? + FB_PIX_FMT_RGBA : + FB_PIX_FMT_RGBA64LE); + dest->set_color_range(f->color_range()); + dest->set_colorspace(f->colorspace()); if (p.divider > 1) { - dest->width = - VideoParams::GetScaledDimension(dest->width, p.divider); - dest->height = - VideoParams::GetScaledDimension(dest->height, p.divider); + dest->set_width( + VideoParams::GetScaledDimension(dest->width(), p.divider)); + dest->set_height( + VideoParams::GetScaledDimension(dest->height(), p.divider)); } - int r = av_frame_get_buffer(dest.get(), 0); + int r = dest->get_buffer(0); if (r < 0) { FFmpegError(r); return nullptr; } - SwsContext *cpu_sws = sws_getContext( - f->width, f->height, static_cast(f->format), - dest->width, dest->height, static_cast(dest->format), - SWS_POINT, nullptr, nullptr, nullptr); - if (!cpu_sws) { + FBScaler *cpu_scaler = fb_scaler_create(f->width(), f->height(), + f->format(), dest->width(), + dest->height(), dest->format(), + FB_SCALER_POINT); + if (!cpu_scaler) { qCritical() << "Failed to create CPU frame conversion context"; return nullptr; } - sws_setColorspaceDetails( - cpu_sws, - sws_getCoefficients(FFmpegUtils::GetSwsColorspaceFromAVColorSpace( - dest->colorspace)), - dest->color_range == AVCOL_RANGE_JPEG ? 1 : 0, - sws_getCoefficients(FFmpegUtils::GetSwsColorspaceFromAVColorSpace( - dest->colorspace)), - dest->color_range == AVCOL_RANGE_JPEG ? 1 : 0, 0, 0x10000, 0x10000); + fb_scaler_set_colorspace(cpu_scaler, dest->colorspace(), + dest->color_range() == FB_COLOR_RANGE_JPEG); - r = sws_scale_frame(cpu_sws, dest.get(), f.get()); - sws_freeContext(cpu_sws); + r = fb_scaler_scale_frame(cpu_scaler, dest->handle(), f->handle()); + fb_scaler_free(&cpu_scaler); if (r < 0) { FFmpegError(r); return nullptr; } // sws_scale does not initialize the alpha channel when converting - // from non-alpha source formats (e.g. YUV). av_frame_get_buffer + // from non-alpha source formats (e.g. YUV). fb_frame_get_buffer // zero-initializes the destination, leaving alpha at 0. The color // management shader later multiplies RGB by alpha, producing black. // Ensure alpha is opaque for source formats that have no alpha. - const AVPixFmtDescriptor *src_desc = - av_pix_fmt_desc_get(static_cast(f->format)); - if (src_desc && !(src_desc->flags & AV_PIX_FMT_FLAG_ALPHA)) { - const int bpc = (dest->format == AV_PIX_FMT_RGBA) ? 1 : 2; - const int stride = dest->linesize[0]; - for (int y = 0; y < dest->height; ++y) { - uchar *row = dest->data[0] + y * stride; - for (int x = 0; x < dest->width; ++x) { + if (!fb_pix_fmt_has_alpha(f->format())) { + const int bpc = (dest->format() == FB_PIX_FMT_RGBA) ? 1 : 2; + const int stride = dest->linesize(0); + for (int y = 0; y < dest->height(); ++y) { + uchar *row = dest->data(0) + y * stride; + for (int x = 0; x < dest->width(); ++x) { if (bpc == 1) { row[x * 4 + 3] = 0xFF; } else { @@ -524,7 +528,7 @@ FramePtr FFmpegDecoder::RetrieveVideoFrameInternal(const RetrieveVideoParams &p) } return CopyPackedAVFrameToFrame(dest, - dest->format == AV_PIX_FMT_RGBA ? + dest->format() == FB_PIX_FMT_RGBA ? PixelFormat::U8 : PixelFormat::U16, VideoParams::kRGBAChannelCount, p.time); @@ -536,24 +540,23 @@ FramePtr FFmpegDecoder::RetrieveVideoFrameInternal(const RetrieveVideoParams &p) void FFmpegDecoder::CloseInternal() { if (working_packet_) { - av_packet_free(&working_packet_); + fb_packet_free(&working_packet_); working_packet_ = nullptr; } ClearFrameCache(); FreeScaler(); - instance_.Close(); + if (instance_) { + fb_decoder_free(&instance_); + } } rational FFmpegDecoder::GetAudioStartOffset() const { - auto f = instance_.fmt_ctx(); - if (f) { - rational fmt_start = - rational(instance_.fmt_ctx()->start_time, AV_TIME_BASE); - rational str_start = rational(instance_.avstream()->time_base) * - instance_.avstream()->start_time; + if (instance_) { + rational fmt_start = rational(format_start_time_, FB_TIME_BASE); + rational str_start = stream_time_base_ * stream_start_time_; return str_start - fmt_start; } else { return 0; @@ -571,34 +574,24 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, // Return value FootageDescription desc(id()); - // Variable for receiving errors from FFmpeg + // Variable for receiving errors from the bridge int error_code; // Convert QString to a C string QByteArray filename_c = filename.toUtf8(); - // Open file in a format context - AVFormatContext *fmt_ctx = nullptr; - AVDictionary *format_opts = nullptr; - ApplyFormatOpenOptions(&format_opts); - error_code = - avformat_open_input(&fmt_ctx, filename_c, nullptr, &format_opts); - av_dict_free(&format_opts); - TuneFormatContext(fmt_ctx); - DiscardSubtitleStreams(fmt_ctx); + // Open file in the bridge library + FBProbe *probe = fb_probe_create(); + error_code = fb_probe_open(probe, filename_c); - // Handle format context error + // Handle open error if (error_code == 0) { - // Retrieve metadata about the media - avformat_find_stream_info(fmt_ctx, nullptr); - - int64_t footage_duration = fmt_ctx->duration; + int64_t footage_duration = fb_probe_get_duration(probe); TimecodeMetadata::SourceTime source_start_time = ExtractSourceStartTime( - fmt_ctx->metadata, rational(1, AV_TIME_BASE), 0); + probe, -1, rational(1, FB_TIME_BASE), 0); bool duration_guessed_from_bitrate = - (fmt_ctx->duration_estimation_method == - AVFMT_DURATION_FROM_BITRATE); + fb_probe_duration_from_bitrate(probe) != 0; if (duration_guessed_from_bitrate) { qWarning() << "Unreliable duration detected - we will manually determine it ourselves (this may take some time)"; @@ -607,198 +600,135 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, // Dump it into the Footage object int video_streams = 0, audio_streams = 0, still_streams = 0; - for (unsigned int i = 0; i < fmt_ctx->nb_streams; i++) { - // FFmpeg AVStream - AVStream *avstream = fmt_ctx->streams[i]; - if (!source_start_time.valid) { - source_start_time = ExtractSourceStartTime( - avstream->metadata, avstream->time_base, - avstream->codecpar->sample_rate); + int stream_count = fb_probe_get_stream_count(probe); + for (int i = 0; i < stream_count; i++) { + FBStreamInfo info; + if (fb_probe_get_stream_info(probe, i, &info) != 0) { + continue; } - // Find decoder for this stream, if it exists we can proceed - const AVCodec *decoder = - avcodec_find_decoder(avstream->codecpar->codec_id); + rational stream_tb(info.time_base_num, info.time_base_den); - if (decoder && - (avstream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO || - avstream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO || - avstream->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE)) { - if (avstream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { - { - // Read at least two frames to get more information about this video stream - AVPacket *pkt = av_packet_alloc(); - AVFrame *frame = av_frame_alloc(); + if (!source_start_time.valid) { + source_start_time = ExtractSourceStartTime(probe, i, stream_tb, + info.sample_rate); + } - VideoParams::Interlacing interlacing = - VideoParams::kInterlaceNone; - AVRational pixel_aspect_ratio = { 1, 1 }; - AVRational frame_rate = avstream->avg_frame_rate; - AVPixelFormat compatible_pix_fmt = - FFmpegUtils::GetCompatiblePixelFormat( - static_cast( - avstream->codecpar->format)); - bool image_is_still = false; + // Only proceed if a decoder exists for this stream + if (!info.has_decoder) { + continue; + } - { - Instance instance; - if (instance.Open(filename_c, avstream->index) != 0) - goto cleanup; + if (info.codec_type == FB_MEDIA_TYPE_VIDEO) { + // Read at least two frames to get more information about this video stream + VideoParams::Interlacing interlacing = + VideoParams::kInterlaceNone; + rational pixel_aspect_ratio(1, 1); + rational frame_rate(info.avg_frame_rate_num, + info.avg_frame_rate_den); + int compatible_pix_fmt = + FFmpegUtils::GetCompatibleBridgePixelFormat(info.pixel_format); + bool image_is_still = false; + int64_t stream_duration = info.duration; - AVCodecContext *avctx = instance.codec_ctx(); - interlacing = - FFmpegFieldOrderToOlive(avctx->field_order); + int decode_full_duration = + (info.duration == FB_NOPTS_VALUE || + duration_guessed_from_bitrate) ? + 1 : + 0; - if (instance.GetFrame(pkt, frame) >= 0) { - pixel_aspect_ratio = - av_guess_sample_aspect_ratio( - instance.fmt_ctx(), instance.avstream(), - frame); - frame_rate = av_guess_frame_rate( - instance.fmt_ctx(), instance.avstream(), - frame); - } - - int ret = instance.GetFrame(pkt, frame); - if (ret == AVERROR_EOF) { - image_is_still = true; - } else if (avstream->duration == AV_NOPTS_VALUE || - duration_guessed_from_bitrate) { - int64_t last_ts = frame->best_effort_timestamp; - while ( - instance.GetFrame(pkt, frame) >= 0 && - (!cancelled || !cancelled->IsCancelled())) - last_ts = frame->best_effort_timestamp; - avstream->duration = last_ts; - } - - instance.Close(); - } - -cleanup: - av_frame_free(&frame); - av_packet_free(&pkt); - - VideoParams stream; - stream.set_stream_index(i); - stream.set_width(avstream->codecpar->width); - stream.set_height(avstream->codecpar->height); - stream.set_video_type(image_is_still ? - VideoParams::kVideoTypeStill : - VideoParams::kVideoTypeVideo); - stream.set_format( - GetNativePixelFormat(compatible_pix_fmt)); - stream.set_channel_count( - GetNativeChannelCount(compatible_pix_fmt)); - stream.set_interlacing(interlacing); // <-- 已正确填充 - stream.set_pixel_aspect_ratio(pixel_aspect_ratio); - stream.set_frame_rate(frame_rate); - stream.set_start_time(avstream->start_time); - stream.set_time_base(avstream->time_base); - stream.set_duration(avstream->duration); - stream.set_color_range( - avstream->codecpar->color_range == - AVCOL_RANGE_JPEG ? - VideoParams::kColorRangeFull : - VideoParams::kColorRangeLimited); - stream.set_premultiplied_alpha(false); - - desc.AddVideoStream(stream); - image_is_still ? still_streams++ : video_streams++; + FBVideoStreamDetails details; + if (fb_probe_video_stream_details(filename_c, i, &details, + decode_full_duration, + CancelThunk, + cancelled) == 0) { + interlacing = FFmpegFieldOrderToOlive(details.field_order); + if (details.pixel_aspect_den != 0) { + pixel_aspect_ratio = rational(details.pixel_aspect_num, + details.pixel_aspect_den); } - - } else if (avstream->codecpar->codec_type == - AVMEDIA_TYPE_AUDIO) { - // Create an audio stream object - AVChannelLayout &channel_layout = - avstream->codecpar->ch_layout; - if (!av_channel_layout_check(&channel_layout)) { - av_channel_layout_default( - &channel_layout, - avstream->codecpar->ch_layout.nb_channels); + if (details.frame_rate_num != 0 && + details.frame_rate_den != 0) { + frame_rate = rational(details.frame_rate_num, + details.frame_rate_den); } + image_is_still = details.is_still != 0; + if (details.decoded_duration != FB_NOPTS_VALUE) { + stream_duration = details.decoded_duration; + } + } - if (avstream->duration == AV_NOPTS_VALUE || + VideoParams stream; + stream.set_stream_index(i); + stream.set_width(info.width); + stream.set_height(info.height); + stream.set_video_type(image_is_still ? + VideoParams::kVideoTypeStill : + VideoParams::kVideoTypeVideo); + stream.set_format(GetNativePixelFormat(compatible_pix_fmt)); + stream.set_channel_count( + GetNativeChannelCount(compatible_pix_fmt)); + stream.set_interlacing(interlacing); + stream.set_pixel_aspect_ratio(pixel_aspect_ratio); + stream.set_frame_rate(frame_rate); + stream.set_start_time(info.start_time); + stream.set_time_base(stream_tb); + stream.set_duration(stream_duration); + stream.set_color_range(info.color_range == FB_COLOR_RANGE_JPEG ? + VideoParams::kColorRangeFull : + VideoParams::kColorRangeLimited); + stream.set_premultiplied_alpha(false); + + desc.AddVideoStream(stream); + image_is_still ? still_streams++ : video_streams++; + + } else if (info.codec_type == FB_MEDIA_TYPE_AUDIO) { + int64_t stream_duration = info.duration; + + if (stream_duration == FB_NOPTS_VALUE || + duration_guessed_from_bitrate) { + // Loop through stream until we get the whole duration + if (footage_duration == FB_NOPTS_VALUE || duration_guessed_from_bitrate) { - // Loop through stream until we get the whole duration - if (footage_duration == AV_NOPTS_VALUE || - duration_guessed_from_bitrate) { - Instance instance; - instance.Open(filename_c, avstream->index); - - AVPacket *pkt = av_packet_alloc(); - AVFrame *frame = av_frame_alloc(); - - int64_t new_dur; - - do { - new_dur = frame->best_effort_timestamp; - } while (instance.GetFrame(pkt, frame) >= 0 && - (!cancelled || !cancelled->IsCancelled())); - - avstream->duration = new_dur; - - av_frame_free(&frame); - av_packet_free(&pkt); - - instance.Close(); - } else { - avstream->duration = - Timecode::rescale_timestamp_ceil( - footage_duration, rational(1, AV_TIME_BASE), - avstream->time_base); + int64_t decoded_duration = FB_NOPTS_VALUE; + if (fb_probe_audio_stream_duration( + filename_c, i, &decoded_duration, CancelThunk, + cancelled) == 0) { + stream_duration = decoded_duration; } + } else { + stream_duration = Timecode::rescale_timestamp_ceil( + footage_duration, rational(1, FB_TIME_BASE), + stream_tb); } + } - AudioParams stream; - stream.set_stream_index(i); - stream.set_channel_layout(channel_layout); - stream.set_sample_rate(avstream->codecpar->sample_rate); - stream.set_format(FFmpegUtils::GetNativeSampleFormat( - static_cast( - avstream->codecpar->format))); - stream.set_time_base(avstream->time_base); - stream.set_duration(avstream->duration); - desc.AddAudioStream(stream); + AudioParams stream; + stream.set_stream_index(i); + stream.set_channel_layout(info.channel_layout_mask); + stream.set_sample_rate(info.sample_rate); + stream.set_format( + FFmpegUtils::GetNativeSampleFormat(info.sample_format)); + stream.set_time_base(stream_tb); + stream.set_duration(stream_duration); + desc.AddAudioStream(stream); - audio_streams++; + audio_streams++; - } else if (avstream->codecpar->codec_type == - AVMEDIA_TYPE_SUBTITLE) { - // Limit to SRT for now... - if (avstream->codecpar->codec_id == AV_CODEC_ID_SUBRIP) { - SubtitleParams sub; + } else if (info.codec_type == FB_MEDIA_TYPE_SUBTITLE) { + // The bridge limits this to SRT, matching our historical behavior + SubtitleParams sub; + SubtitleReadContext ctx = { &sub, stream_tb }; - AVPacket *pkt = av_packet_alloc(); - { - Instance instance; - instance.Open(filename_c, avstream->index); - - while (instance.GetPacket(pkt) >= 0) { - TimeRange time(Timecode::timestamp_to_time( - pkt->pts, - avstream->time_base), - Timecode::timestamp_to_time( - pkt->pts + pkt->duration, - avstream->time_base)); - - QString text = QString::fromUtf8( - (const char *)pkt->data, pkt->size); - - sub.push_back(Subtitle(time, text)); - } - - instance.Close(); - } - av_packet_free(&pkt); - - desc.AddSubtitleStream(sub); - } + if (fb_probe_read_subtitle_stream(filename_c, i, + SubtitleReadThunk, + &ctx) == 0) { + desc.AddSubtitleStream(sub); } } } - desc.SetStreamCount(fmt_ctx->nb_streams); + desc.SetStreamCount(stream_count); if (source_start_time.valid) { desc.SetSourceStartTime(source_start_time.time, source_start_time.source); @@ -816,7 +746,7 @@ cleanup: } // Free all memory - avformat_close_input(&fmt_ctx); + fb_probe_free(&probe); return desc; } @@ -824,7 +754,7 @@ cleanup: QString FFmpegDecoder::FFmpegError(int error_code) { char err[1024]; - av_strerror(error_code, err, 512); + fb_error_string(error_code, err, 512); return QStringLiteral("%1 %2").arg(QString::number(error_code), err); } @@ -835,42 +765,39 @@ bool FFmpegDecoder::ConformAudioInternal(const QVector &filenames, // Iterate through each audio frame and extract the PCM data // Seek to starting point - instance_.Seek(0); + fb_decoder_seek(instance_, 0); - // Handle NULL channel layout - AVChannelLayout channel_layout = - ValidateChannelLayout(instance_.avstream()); - if (!av_channel_layout_check(&channel_layout)) { + // The channel layout was validated by the bridge when the stream info was read + if (!input_channel_layout_mask_) { qCritical() << "Failed to determine channel layout of audio file, could not conform"; return false; } - // Create resampling context - AVChannelLayout layout = params.channel_layout(); - SwrContext *resampler = NULL; - swr_alloc_set_opts2( - &resampler, &layout, - FFmpegUtils::GetFFmpegSampleFormat(params.format()), - params.sample_rate(), &channel_layout, - static_cast(instance_.avstream()->codecpar->format), - instance_.avstream()->codecpar->sample_rate, 0, nullptr); - av_channel_layout_uninit(&layout); - swr_init(resampler); - AVPacket *pkt = av_packet_alloc(); - AVFrame *frame = av_frame_alloc(); + // Create resampler + FBResampler *resampler = fb_resampler_create( + params.channel_layout(), + FFmpegUtils::GetFFmpegSampleFormat(params.format()), + params.sample_rate(), input_channel_layout_mask_, input_sample_format_, + input_sample_rate_); + if (!resampler) { + qCritical() << "Failed to create resampler, could not conform"; + return false; + } + + FBPacket *pkt = fb_packet_alloc(); + FBFrame *frame = fb_frame_alloc(); int ret; bool success = false; - int64_t duration = instance_.avstream()->duration; - if (duration == 0 || duration == AV_NOPTS_VALUE) { - duration = instance_.fmt_ctx()->duration; - if (!(duration == 0 || duration == AV_NOPTS_VALUE)) { - // Rescale from AVFormatContext timebase to AVStream timebase - duration = av_rescale_q_rnd(duration, { 1, AV_TIME_BASE }, - instance_.avstream()->time_base, - AV_ROUND_UP); + int64_t duration = stream_duration_; + if (duration == 0 || duration == FB_NOPTS_VALUE) { + duration = fb_decoder_get_format_duration(instance_); + if (!(duration == 0 || duration == FB_NOPTS_VALUE)) { + // Rescale from format timebase to stream timebase + duration = Timecode::rescale_timestamp_ceil( + duration, rational(1, FB_TIME_BASE), stream_time_base_); } } @@ -886,32 +813,33 @@ bool FFmpegDecoder::ConformAudioInternal(const QVector &filenames, break; } - ret = instance_.GetFrame(pkt, frame); + ret = fb_decoder_get_frame(instance_, pkt, frame); if (ret < 0) { - if (ret == AVERROR_EOF) { + if (ret == FB_ERROR_EOF) { success = true; } else { char err_str[512]; - av_strerror(ret, err_str, 512); + fb_error_string(ret, err_str, 512); qWarning() << "Failed to conform:" << ret << err_str; } break; } // Allocate buffers - int nb_samples = swr_get_out_samples(resampler, frame->nb_samples); + int nb_samples = + fb_resampler_get_out_samples(resampler, + fb_frame_get_nb_samples(frame)); int nb_bytes_per_channel = params.samples_to_bytes(nb_samples) / nb_channels; data.set_sample_count(nb_bytes_per_channel); data.allocate(); // Resample audio to our destination parameters - nb_samples = swr_convert( + nb_samples = fb_resampler_convert_frame( resampler, reinterpret_cast(data.to_raw_ptrs().data()), - nb_samples, const_cast(frame->data), - frame->nb_samples); + nb_samples, frame); // If no error, write to files if (nb_samples > 0) { @@ -932,13 +860,14 @@ bool FFmpegDecoder::ConformAudioInternal(const QVector &filenames, // Handle error now after freeing if (nb_samples < 0) { char err_str[512]; - av_strerror(nb_samples, err_str, 512); + fb_error_string(nb_samples, err_str, 512); qWarning() << "libswresample failed with error:" << nb_samples << err_str; break; } - SignalProcessingProgress(frame->best_effort_timestamp, duration); + SignalProcessingProgress(fb_frame_get_best_effort_timestamp(frame), + duration); } wave_out.close(); @@ -946,84 +875,64 @@ bool FFmpegDecoder::ConformAudioInternal(const QVector &filenames, qWarning() << "Failed to open WAVE output for indexing"; } - swr_free(&resampler); + fb_resampler_free(&resampler); - av_frame_free(&frame); - av_packet_free(&pkt); + fb_frame_free(&frame); + fb_packet_free(&pkt); return success; } -PixelFormat FFmpegDecoder::GetNativePixelFormat(AVPixelFormat pix_fmt) +PixelFormat FFmpegDecoder::GetNativePixelFormat(int pix_fmt) { switch (pix_fmt) { - case AV_PIX_FMT_RGB24: - case AV_PIX_FMT_RGBA: + case FB_PIX_FMT_RGB24: + case FB_PIX_FMT_RGBA: return PixelFormat::U8; - case AV_PIX_FMT_RGB48: - case AV_PIX_FMT_RGBA64: + case FB_PIX_FMT_RGB48LE: + case FB_PIX_FMT_RGBA64LE: return PixelFormat::U16; - case AV_PIX_FMT_RGBF32: - case AV_PIX_FMT_RGBAF32: + case FB_PIX_FMT_RGBF32LE: + case FB_PIX_FMT_RGBAF32LE: return PixelFormat::F32; default: return PixelFormat::INVALID; } } -int FFmpegDecoder::GetNativeChannelCount(AVPixelFormat pix_fmt) +int FFmpegDecoder::GetNativeChannelCount(int pix_fmt) { switch (pix_fmt) { - case AV_PIX_FMT_RGB24: - case AV_PIX_FMT_RGB48: - case AV_PIX_FMT_RGBF32: + case FB_PIX_FMT_RGB24: + case FB_PIX_FMT_RGB48LE: + case FB_PIX_FMT_RGBF32LE: return VideoParams::kRGBChannelCount; - case AV_PIX_FMT_RGBA: - case AV_PIX_FMT_RGBA64: - case AV_PIX_FMT_RGBAF32: + case FB_PIX_FMT_RGBA: + case FB_PIX_FMT_RGBA64LE: + case FB_PIX_FMT_RGBAF32LE: return VideoParams::kRGBAChannelCount; default: return 0; } } -AVChannelLayout FFmpegDecoder::ValidateChannelLayout(AVStream *stream) -{ - if (av_channel_layout_check(&stream->codecpar->ch_layout)) { - return stream->codecpar->ch_layout; - } - AVChannelLayout layout; - av_channel_layout_default(&layout, stream->codecpar->ch_layout.nb_channels); - return layout; -} - -const char * -FFmpegDecoder::GetInterlacingModeInFFmpeg(VideoParams::Interlacing interlacing) -{ - if (interlacing == VideoParams::kInterlacedTopFirst) { - return "tff"; - } else { - return "bff"; - } -} - -bool FFmpegDecoder::IsPixelFormatGLSLCompatible(AVPixelFormat f) +bool FFmpegDecoder::IsPixelFormatGLSLCompatible(int f) { // NOTE: We don't include RGB24 or RGB48 here because those are slow on the GPU and performance // should be better if we convert to RGBA on the CPU beforehand switch (f) { - case AV_PIX_FMT_YUV420P: - case AV_PIX_FMT_YUV422P: - case AV_PIX_FMT_YUV444P: - case AV_PIX_FMT_YUV420P10LE: - case AV_PIX_FMT_YUV422P10LE: - case AV_PIX_FMT_YUV444P10LE: - case AV_PIX_FMT_YUV420P12LE: - case AV_PIX_FMT_YUV422P12LE: - case AV_PIX_FMT_YUV444P12LE: - case AV_PIX_FMT_RGBA: - case AV_PIX_FMT_RGBA64LE: - case AV_PIX_FMT_RGBAF32: + case FB_PIX_FMT_YUV420P: + case FB_PIX_FMT_YUV422P: + case FB_PIX_FMT_YUV444P: + case FB_PIX_FMT_YUV420P10LE: + case FB_PIX_FMT_YUV422P10LE: + case FB_PIX_FMT_YUV444P10LE: + case FB_PIX_FMT_YUV420P12LE: + case FB_PIX_FMT_YUV422P12LE: + case FB_PIX_FMT_YUV444P12LE: + case FB_PIX_FMT_RGBA: + case FB_PIX_FMT_RGBA64LE: + case FB_PIX_FMT_RGBAF32LE: return true; default: return false; @@ -1046,8 +955,7 @@ AVFramePtr FFmpegDecoder::PreProcessFrame(AVFramePtr f, // - If a divider is being used, scale down the image // - If a pixel format is not compatible with the GLSL shader, convert it to RGBA ourselves - if (p.divider == 1 && - IsPixelFormatGLSLCompatible(static_cast(f->format))) { + if (p.divider == 1 && IsPixelFormatGLSLCompatible(f->format())) { // No CPU processing required, the user wants this in full resolution and the pixel format can // be converted on the GPU return f; @@ -1056,70 +964,68 @@ AVFramePtr FFmpegDecoder::PreProcessFrame(AVFramePtr f, // Some scaling and/or format conversion needs to be done AVFramePtr dest = CreateAVFramePtr(); - dest->width = f->width; - dest->height = f->height; - dest->format = f->format; - dest->color_range = f->color_range; - dest->colorspace = f->colorspace; - dest->hw_frames_ctx = nullptr; + dest->set_width(f->width()); + dest->set_height(f->height()); + dest->set_format(f->format()); + dest->set_color_range(f->color_range()); + dest->set_colorspace(f->colorspace()); if (p.divider > 1) { - dest->width = VideoParams::GetScaledDimension(dest->width, p.divider); - dest->height = VideoParams::GetScaledDimension(dest->height, p.divider); + dest->set_width( + VideoParams::GetScaledDimension(dest->width(), p.divider)); + dest->set_height( + VideoParams::GetScaledDimension(dest->height(), p.divider)); } - if (!IsPixelFormatGLSLCompatible( - static_cast(dest->format))) { - dest->format = FFmpegUtils::GetCompatiblePixelFormat( - static_cast(dest->format), p.maximum_format); + if (!IsPixelFormatGLSLCompatible(dest->format())) { + dest->set_format(FFmpegUtils::GetCompatibleBridgePixelFormat(dest->format(), + p.maximum_format)); } // swscale does not support RGBAF32 as output, fallback to RGBA64 - if (dest->format == AV_PIX_FMT_RGBAF32) { - dest->format = AV_PIX_FMT_RGBA64; + if (dest->format() == FB_PIX_FMT_RGBAF32LE) { + dest->set_format(FB_PIX_FMT_RGBA64LE); } - int r = av_frame_get_buffer(dest.get(), 0); + int r = dest->get_buffer(0); if (r < 0) { FFmpegError(r); return nullptr; } - if (!sws_ctx_ || sws_src_width_ != f->width || - sws_src_height_ != f->height || sws_src_format_ != f->format || - sws_dst_width_ != dest->width || sws_dst_height_ != dest->height || - sws_dst_format_ != dest->format || sws_colrange_ != dest->color_range || - sws_colspace_ != dest->colorspace) { - // SwsContext must be recreated, destroy current if it exists + if (!scaler_ || scaler_src_width_ != f->width() || + scaler_src_height_ != f->height() || + scaler_src_format_ != f->format() || + scaler_dst_width_ != dest->width() || + scaler_dst_height_ != dest->height() || + scaler_dst_format_ != dest->format() || + scaler_colrange_ != dest->color_range() || + scaler_colspace_ != dest->colorspace()) { + // Scaler must be recreated, destroy current if it exists FreeScaler(); // Cache info - sws_src_width_ = f->width; - sws_src_height_ = f->height; - sws_src_format_ = static_cast(f->format); - sws_dst_width_ = dest->width; - sws_dst_height_ = dest->height; - sws_dst_format_ = static_cast(dest->format); - sws_colrange_ = dest->color_range; - sws_colspace_ = dest->colorspace; + scaler_src_width_ = f->width(); + scaler_src_height_ = f->height(); + scaler_src_format_ = f->format(); + scaler_dst_width_ = dest->width(); + scaler_dst_height_ = dest->height(); + scaler_dst_format_ = dest->format(); + scaler_colrange_ = dest->color_range(); + scaler_colspace_ = dest->colorspace(); // Create new scaler - sws_ctx_ = sws_getContext(sws_src_width_, sws_src_height_, - sws_src_format_, sws_dst_width_, - sws_dst_height_, sws_dst_format_, SWS_POINT, - nullptr, nullptr, nullptr); + scaler_ = fb_scaler_create(scaler_src_width_, scaler_src_height_, + scaler_src_format_, scaler_dst_width_, + scaler_dst_height_, scaler_dst_format_, + FB_SCALER_POINT); - // Set swscale's colorspace details - sws_setColorspaceDetails( - sws_ctx_, - sws_getCoefficients(FFmpegUtils::GetSwsColorspaceFromAVColorSpace( - dest->colorspace)), - dest->color_range == AVCOL_RANGE_JPEG ? 1 : 0, - sws_getCoefficients(FFmpegUtils::GetSwsColorspaceFromAVColorSpace( - dest->colorspace)), - dest->color_range == AVCOL_RANGE_JPEG ? 1 : 0, 0, 0x10000, 0x10000); + // Set the scaler's colorspace details + fb_scaler_set_colorspace( + scaler_, scaler_colspace_, + scaler_colrange_ == FB_COLOR_RANGE_JPEG); } - r = sws_scale_frame(sws_ctx_, dest.get(), f.get()); + r = fb_scaler_scale_frame(scaler_, dest->handle(), f->handle()); if (r < 0) { FFmpegError(r); @@ -1132,13 +1038,12 @@ AVFramePtr FFmpegDecoder::PreProcessFrame(AVFramePtr f, AVFramePtr FFmpegDecoder::RetrieveFrame(const rational &time, CancelAtom *cancelled) { - int64_t target_ts = - Timecode::time_to_timestamp(time, instance_.avstream()->time_base); + int64_t target_ts = Timecode::time_to_timestamp(time, stream_time_base_); - if (instance_.fmt_ctx()->start_time != AV_NOPTS_VALUE) { - target_ts += av_rescale_q(instance_.fmt_ctx()->start_time, - { 1, AV_TIME_BASE }, - instance_.avstream()->time_base); + if (format_start_time_ != FB_NOPTS_VALUE) { + target_ts += Timecode::rescale_timestamp(format_start_time_, + rational(1, FB_TIME_BASE), + stream_time_base_); } const int64_t min_seek = 0; @@ -1148,11 +1053,11 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational &time, if (time != kAnyTimecode) { // If the frame wasn't in the frame cache, see if this frame cache is too old to use if (cached_frames_.empty() || - (target_ts < cached_frames_.front()->pts || - target_ts > cached_frames_.back()->pts + 2 * second_ts_)) { + (target_ts < cached_frames_.front()->pts() || + target_ts > cached_frames_.back()->pts() + 2 * second_ts_)) { ClearFrameCache(); - instance_.Seek(seek_ts); + fb_decoder_seek(instance_, seek_ts); if (seek_ts == min_seek) { cache_at_zero_ = true; } @@ -1183,14 +1088,15 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational &time, } // Pull from the decoder - ret = instance_.GetFrame(working_packet_, filtered.get()); + ret = fb_decoder_get_frame(instance_, working_packet_, + filtered->handle()); if (cancelled && cancelled->IsCancelled()) { break; } // Handle any errors that aren't EOF (EOF is handled later on) - if (ret < 0 && ret != AVERROR_EOF) { + if (ret < 0 && ret != FB_ERROR_EOF) { qCritical() << "Failed to retrieve frame:" << ret; break; } @@ -1199,10 +1105,10 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational &time, // Handle a failure to seek (occurs on some media) // We'll only be here if the frame cache was emptied earlier if (!cache_at_zero_ && - (ret == AVERROR_EOF || - filtered->best_effort_timestamp > target_ts)) { + (ret == FB_ERROR_EOF || + filtered->best_effort_timestamp() > target_ts)) { seek_ts = qMax(min_seek, seek_ts - second_ts_); - instance_.Seek(seek_ts); + fb_decoder_seek(instance_, seek_ts); if (seek_ts == min_seek) { cache_at_zero_ = true; } @@ -1213,7 +1119,7 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational &time, } } - if (ret == AVERROR_EOF) { + if (ret == FB_ERROR_EOF) { // Handle an "expected" EOF by using the last frame of our cache cache_at_eof_ = true; @@ -1221,7 +1127,7 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational &time, if (!retried_after_eof) { retried_after_eof = true; ClearFrameCache(); - instance_.Seek(min_seek); + fb_decoder_seek(instance_, min_seek); cache_at_zero_ = true; still_seeking = true; continue; @@ -1256,10 +1162,10 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational &time, cached_frames_.push_back(filtered); // If this is a valid frame, see if this or the frame before it are the one we need - if (filtered->pts == target_ts || time == kAnyTimecode) { + if (filtered->pts() == target_ts || time == kAnyTimecode) { return_frame = filtered; break; - } else if (filtered->pts > target_ts) { + } else if (filtered->pts() > target_ts) { if (!previous && cache_at_zero_) { return_frame = filtered; break; @@ -1273,33 +1179,34 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational &time, filtered = nullptr; } - av_packet_unref(working_packet_); + fb_packet_unref(working_packet_); return return_frame; } AVFramePtr FFmpegDecoder::TransferHardwareFrame(AVFramePtr f) { - if (!instance_.hwaccel_enabled() || f->format != instance_.hw_pix_fmt()) { + if (!fb_decoder_hwaccel_enabled(instance_) || + f->format() != fb_decoder_hw_pix_fmt(instance_)) { return f; } - AVFrame *sw_frame = av_frame_alloc(); + FBFrame *sw_frame = fb_frame_alloc(); if (!sw_frame) { qCritical() << "Failed to allocate software frame for hardware transfer"; return nullptr; } - int ret = av_hwframe_transfer_data(sw_frame, f.get(), 0); + int ret = fb_frame_hw_transfer_data(sw_frame, f->handle()); if (ret < 0) { qWarning() << "Failed to transfer hardware frame to system memory:" << FFmpegError(ret); - av_frame_free(&sw_frame); + fb_frame_free(&sw_frame); return nullptr; } - ret = av_frame_copy_props(sw_frame, f.get()); + ret = fb_frame_copy_props(sw_frame, f->handle()); if (ret < 0) { qWarning() << "Failed to copy frame properties during hardware transfer:" @@ -1311,20 +1218,19 @@ AVFramePtr FFmpegDecoder::TransferHardwareFrame(AVFramePtr f) void FFmpegDecoder::FreeScaler() { - if (sws_ctx_) { - sws_freeContext(sws_ctx_); - sws_ctx_ = nullptr; + if (scaler_) { + fb_scaler_free(&scaler_); } } AVFramePtr FFmpegDecoder::GetFrameFromCache(const int64_t &t) const { - if (t < cached_frames_.front()->pts) { + if (t < cached_frames_.front()->pts()) { if (cache_at_zero_) { return cached_frames_.front(); } - } else if (t > cached_frames_.back()->pts) { + } else if (t > cached_frames_.back()->pts()) { if (cache_at_eof_) { return cached_frames_.back(); } @@ -1338,10 +1244,10 @@ AVFramePtr FFmpegDecoder::GetFrameFromCache(const int64_t &t) const auto next = it; next++; - if (this_frame->pts == t // Test for an exact match + if (this_frame->pts() == t // Test for an exact match || (next != cached_frames_.cend() && - (*next)->pts > t)) { // Or for this frame to be the "closest" + (*next)->pts() > t)) { // Or for this frame to be the "closest" return this_frame; } @@ -1366,340 +1272,4 @@ int FFmpegDecoder::MaximumQueueSize() return 2; } -FFmpegDecoder::Instance::Instance() - : fmt_ctx_(nullptr) - , codec_ctx_(nullptr) - , avstream_(nullptr) - , opts_(nullptr) - , hw_device_ctx_(nullptr) - , hw_device_type_(AV_HWDEVICE_TYPE_NONE) - , hw_pix_fmt_(AV_PIX_FMT_NONE) - , hwaccel_enabled_(false) -{ -} - -bool FFmpegDecoder::Instance::Open(const char *filename, int stream_index) -{ - // Open file in a format context - AVDictionary *format_opts = nullptr; - ApplyFormatOpenOptions(&format_opts); - int error_code = - avformat_open_input(&fmt_ctx_, filename, nullptr, &format_opts); - av_dict_free(&format_opts); - TuneFormatContext(fmt_ctx_); - DiscardSubtitleStreams(fmt_ctx_); - - // Handle format context error - if (error_code != 0) { - qCritical() - << "Failed to open input:" << filename << FFmpegError(error_code); - return false; - } - - // Get stream information from format - error_code = avformat_find_stream_info(fmt_ctx_, nullptr); - - // Handle get stream information error - if (error_code < 0) { - qCritical() << "Failed to find stream info:" << FFmpegError(error_code); - return false; - } - - // Get reference to correct AVStream - avstream_ = fmt_ctx_->streams[stream_index]; - - // Find decoder - const AVCodec *codec = avcodec_find_decoder(avstream_->codecpar->codec_id); - - // Handle failure to find decoder - if (codec == nullptr) { - qCritical() - << "Failed to find appropriate decoder for this codec:" << filename - << stream_index << avstream_->codecpar->codec_id; - return false; - } - - // Allocate context for the decoder - codec_ctx_ = avcodec_alloc_context3(codec); - if (codec_ctx_ == nullptr) { - qCritical() << "Failed to allocate codec context"; - return false; - } - - // Copy parameters from the AVStream to the AVCodecContext - error_code = avcodec_parameters_to_context(codec_ctx_, avstream_->codecpar); - - // Handle failure to copy parameters - if (error_code < 0) { - qCritical() - << "Failed to copy parameters from AVStream to AVCodecContext"; - return false; - } - - // Set multithreading setting - error_code = av_dict_set(&opts_, "threads", "auto", 0); - - // Handle failure to set multithreaded decoding - if (error_code < 0) { - qCritical() << "Failed to set codec options, performance may suffer"; - } - - // Attempt hardware accelerated decoding first, then fall back to software. - if (InitHardwareAcceleration(codec)) { - error_code = avcodec_open2(codec_ctx_, codec, &opts_); - if (error_code == 0) { - hwaccel_enabled_ = true; - qDebug() << "Hardware decoding enabled for" << filename << "using" - << av_hwdevice_get_type_name(hw_device_type_) - << "pixel format" << av_get_pix_fmt_name(hw_pix_fmt_); - return true; - } - - qWarning() - << "Failed to open hardware codec, falling back to software decoding:"; - char buf[512]; - av_strerror(error_code, buf, 512); - qWarning() << FFmpegError(error_code) << buf; - - // Free the failed context and recreate it for software decoding. - avcodec_free_context(&codec_ctx_); - CleanupHardwareAcceleration(); - - codec_ctx_ = avcodec_alloc_context3(codec); - if (codec_ctx_ == nullptr) { - qCritical() - << "Failed to allocate codec context for software fallback"; - return false; - } - - error_code = - avcodec_parameters_to_context(codec_ctx_, avstream_->codecpar); - if (error_code < 0) { - qCritical() - << "Failed to copy parameters from AVStream to AVCodecContext"; - return false; - } - } - - // Open codec (software path, or if hardware was not available) - error_code = avcodec_open2(codec_ctx_, codec, &opts_); - if (error_code < 0) { - char buf[512]; - av_strerror(error_code, buf, 512); - qCritical() << "Failed to open codec" << codec->id << error_code << buf; - return false; - } - - return true; -} - -AVHWDeviceType FFmpegDecoder::Instance::ChooseHardwareDevice() -{ - if (qEnvironmentVariableIsSet("OAK_DISABLE_HWACCEL")) { - return AV_HWDEVICE_TYPE_NONE; - } - -#ifdef Q_OS_LINUX - // Prefer NVIDIA's NVDEC where available, then VAAPI/VDPAU. - for (AVHWDeviceType type : { AV_HWDEVICE_TYPE_CUDA, AV_HWDEVICE_TYPE_VAAPI, - AV_HWDEVICE_TYPE_VDPAU }) { - if (av_hwdevice_find_type_by_name(av_hwdevice_get_type_name(type)) != - AV_HWDEVICE_TYPE_NONE) { - return type; - } - } -#elif defined(Q_OS_WIN) - for (AVHWDeviceType type : - { AV_HWDEVICE_TYPE_D3D11VA, AV_HWDEVICE_TYPE_DXVA2, - AV_HWDEVICE_TYPE_CUDA }) { - if (av_hwdevice_find_type_by_name(av_hwdevice_get_type_name(type)) != - AV_HWDEVICE_TYPE_NONE) { - return type; - } - } -#elif defined(Q_OS_MACOS) - if (av_hwdevice_find_type_by_name(av_hwdevice_get_type_name( - AV_HWDEVICE_TYPE_VIDEOTOOLBOX)) != AV_HWDEVICE_TYPE_NONE) { - return AV_HWDEVICE_TYPE_VIDEOTOOLBOX; - } -#endif - return AV_HWDEVICE_TYPE_NONE; -} - -AVPixelFormat -FFmpegDecoder::Instance::GetHardwareFormat(AVCodecContext *ctx, - const AVPixelFormat *pix_fmts) -{ - const Instance *inst = static_cast(ctx->opaque); - for (const AVPixelFormat *p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { - if (*p == inst->hw_pix_fmt_) { - return *p; - } - } - - qWarning() - << "Hardware pixel format not supported by decoder, using first software format"; - return pix_fmts[0]; -} - -bool FFmpegDecoder::Instance::InitHardwareAcceleration(const AVCodec *codec) -{ - const AVHWDeviceType device_type = ChooseHardwareDevice(); - if (device_type == AV_HWDEVICE_TYPE_NONE) { - return false; - } - - // Find the pixel format associated with this device type for this codec. - hw_pix_fmt_ = AV_PIX_FMT_NONE; - for (int i = 0;; i++) { - const AVCodecHWConfig *config = avcodec_get_hw_config(codec, i); - if (!config) { - break; - } - if ((config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) && - config->device_type == device_type) { - hw_pix_fmt_ = config->pix_fmt; - break; - } - } - - if (hw_pix_fmt_ == AV_PIX_FMT_NONE) { - qDebug() - << "Codec" << codec->id << "does not support hardware device type" - << av_hwdevice_get_type_name(device_type); - return false; - } - - hw_device_type_ = device_type; - - int ret = av_hwdevice_ctx_create(&hw_device_ctx_, device_type, nullptr, - nullptr, 0); - if (ret < 0) { - qWarning() << "Failed to create hardware device context for" - << av_hwdevice_get_type_name(device_type) << ":" - << FFmpegError(ret); - CleanupHardwareAcceleration(); - return false; - } - - codec_ctx_->hw_device_ctx = av_buffer_ref(hw_device_ctx_); - codec_ctx_->opaque = this; - codec_ctx_->get_format = GetHardwareFormat; - // Most hardware decoders do not support frame threading. - av_dict_set(&opts_, "threads", "1", 0); - - return true; -} - -void FFmpegDecoder::Instance::CleanupHardwareAcceleration() -{ - hwaccel_enabled_ = false; - hw_device_type_ = AV_HWDEVICE_TYPE_NONE; - hw_pix_fmt_ = AV_PIX_FMT_NONE; - - if (hw_device_ctx_) { - av_buffer_unref(&hw_device_ctx_); - hw_device_ctx_ = nullptr; - } -} - -void FFmpegDecoder::Instance::Close() -{ - if (opts_) { - av_dict_free(&opts_); - opts_ = nullptr; - } - - if (codec_ctx_) { - avcodec_free_context(&codec_ctx_); - codec_ctx_ = nullptr; - } - - CleanupHardwareAcceleration(); - - if (fmt_ctx_) { - avformat_close_input(&fmt_ctx_); - fmt_ctx_ = nullptr; - } -} - -int FFmpegDecoder::Instance::GetFrame(AVPacket *pkt, AVFrame *frame) -{ - bool eof = false; - - int ret; - - // Clear any previous frames - av_frame_unref(frame); - - while ((ret = avcodec_receive_frame(codec_ctx_, frame)) == - AVERROR(EAGAIN) && - !eof) { - // Find next packet in the correct stream index - ret = GetPacket(pkt); - - if (ret == AVERROR_EOF) { - // Don't break so that receive gets called again, but don't try to read again - eof = true; - - // Send a null packet to signal end of - avcodec_send_packet(codec_ctx_, nullptr); - } else if (ret < 0) { - // Handle other error by breaking loop and returning the code we received - break; - } else { - // Successful read, send the packet - ret = avcodec_send_packet(codec_ctx_, pkt); - - // We don't need the packet anymore, so free it - av_packet_unref(pkt); - - if (ret < 0) { - break; - } - } - } - - return ret; -} - -const char *FFmpegDecoder::Instance::GetSubtitleHeader() const -{ - return reinterpret_cast(codec_ctx_->subtitle_header); -} - -int FFmpegDecoder::Instance::GetSubtitle(AVPacket *pkt, AVSubtitle *sub) -{ - int ret = GetPacket(pkt); - - if (ret >= 0) { - int got_sub; - ret = avcodec_decode_subtitle2(codec_ctx_, sub, &got_sub, pkt); - if (!got_sub) { - ret = -1; - } - } - - return ret; -} - -int FFmpegDecoder::Instance::GetPacket(AVPacket *pkt) -{ - int ret; - - do { - av_packet_unref(pkt); - - ret = av_read_frame(fmt_ctx_, pkt); - } while (pkt->stream_index != avstream_->index && ret >= 0); - - return ret; -} - -void FFmpegDecoder::Instance::Seek(int64_t timestamp) -{ - avcodec_flush_buffers(codec_ctx_); - av_seek_frame(fmt_ctx_, avstream_->index, timestamp, AVSEEK_FLAG_BACKWARD); -} - } diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index 0a54492af..7019b8c60 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -22,17 +22,9 @@ #ifndef FFMPEGDECODER_H #define FFMPEGDECODER_H -// Fixes weird define issue when including #include -extern "C" { -#include -#include -#include -#include -#include -#include -} +#include #include #include @@ -45,7 +37,10 @@ namespace olive { /** - * @brief A Decoder derivative that wraps FFmpeg functions as on Olive decoder + * @brief A Decoder derivative that uses the ffmpeg_bridge library as an Olive decoder + * + * All media access goes through the pure C API of the ffmpeg_bridge shared + * library; this class never sees an FFmpeg structure or function. */ class FFmpegDecoder : public Decoder { Q_OBJECT @@ -84,88 +79,10 @@ protected: virtual rational GetAudioStartOffset() const override; private: - class Instance { - public: - Instance(); - - ~Instance() - { - Close(); - } - - bool Open(const char *filename, int stream_index); - - bool IsOpen() const - { - return fmt_ctx_; - } - - void Close(); - - /** - * @brief Uses the FFmpeg API to retrieve a packet (stored in pkt_) and decode it (stored in frame_) - * - * @return - * - * An FFmpeg error code, or >= 0 on success - */ - int GetFrame(AVPacket *pkt, AVFrame *frame); - - const char *GetSubtitleHeader() const; - - int GetSubtitle(AVPacket *pkt, AVSubtitle *sub); - - int GetPacket(AVPacket *pkt); - - void Seek(int64_t timestamp); - - AVFormatContext *fmt_ctx() const - { - return fmt_ctx_; - } - - AVStream *avstream() const - { - return avstream_; - } - AVCodecContext *codec_ctx() - { - return codec_ctx_; - } - - bool hwaccel_enabled() const - { - return hwaccel_enabled_; - } - - AVPixelFormat hw_pix_fmt() const - { - return hw_pix_fmt_; - } - - private: - static AVHWDeviceType ChooseHardwareDevice(); - static AVPixelFormat GetHardwareFormat(AVCodecContext *ctx, - const AVPixelFormat *pix_fmts); - - bool InitHardwareAcceleration(const AVCodec *codec); - void CleanupHardwareAcceleration(); - - AVFormatContext *fmt_ctx_; - AVCodecContext *codec_ctx_; - AVStream *avstream_; - AVDictionary *opts_; - - AVBufferRef *hw_device_ctx_; - AVHWDeviceType hw_device_type_; - AVPixelFormat hw_pix_fmt_; - bool hwaccel_enabled_; - }; - /** - * @brief Handle an FFmpeg error code + * @brief Handle a bridge error code * - * Uses the FFmpeg API to retrieve a descriptive string for this error code and sends it to Error(). As such, this + * Uses the bridge API to retrieve a descriptive string for this error code and sends it to Error(). As such, this * function also automatically closes the Decoder. * * @param error_code @@ -176,15 +93,10 @@ private: AVFramePtr TransferHardwareFrame(AVFramePtr f); - static PixelFormat GetNativePixelFormat(AVPixelFormat pix_fmt); - static int GetNativeChannelCount(AVPixelFormat pix_fmt); + static PixelFormat GetNativePixelFormat(int pix_fmt); + static int GetNativeChannelCount(int pix_fmt); - static AVChannelLayout ValidateChannelLayout(AVStream *stream); - - static const char * - GetInterlacingModeInFFmpeg(VideoParams::Interlacing interlacing); - - static bool IsPixelFormatGLSLCompatible(AVPixelFormat f); + static bool IsPixelFormatGLSLCompatible(int f); AVFramePtr GetFrameFromCache(const int64_t &t) const; @@ -202,17 +114,17 @@ private: static int MaximumQueueSize(); - SwsContext *sws_ctx_; - int sws_src_width_; - int sws_src_height_; - AVPixelFormat sws_src_format_; - int sws_dst_width_; - int sws_dst_height_; - AVPixelFormat sws_dst_format_; - AVColorRange sws_colrange_; - AVColorSpace sws_colspace_; + FBScaler *scaler_; + int scaler_src_width_; + int scaler_src_height_; + int scaler_src_format_; + int scaler_dst_width_; + int scaler_dst_height_; + int scaler_dst_format_; + int scaler_colrange_; + int scaler_colspace_; - AVPacket *working_packet_; + FBPacket *working_packet_; int64_t second_ts_; @@ -221,7 +133,17 @@ private: bool cache_at_zero_; bool cache_at_eof_; - Instance instance_; + FBDecoder *instance_; + + // Stream parameters cached on open (the stream object itself lives + // inside the bridge library) + rational stream_time_base_; + int64_t stream_start_time_; + int64_t stream_duration_; + int64_t format_start_time_; + int input_sample_format_; + int input_sample_rate_; + uint64_t input_channel_layout_mask_; }; } diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index 983b275dd..0dacd9176 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -21,17 +21,8 @@ #include "ffmpegencoder.h" -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#endif - -extern "C" { -#include -#include -#include -#include -} +#include +#include #include @@ -42,16 +33,7 @@ namespace olive FFmpegEncoder::FFmpegEncoder(const EncodingParams ¶ms) : Encoder(params) - , fmt_ctx_(nullptr) - , video_stream_(nullptr) - , video_codec_ctx_(nullptr) - , video_scale_ctx_(nullptr) - , video_buffersrc_ctx_(nullptr) - , video_buffersink_ctx_(nullptr) - , audio_stream_(nullptr) - , audio_codec_ctx_(nullptr) - , audio_resample_ctx_(nullptr) - , audio_frame_(nullptr) + , encoder_(nullptr) , open_(false) { } @@ -60,19 +42,17 @@ QStringList FFmpegEncoder::GetPixelFormatsForCodec(ExportCodec::Codec c) const { QStringList pix_fmts; - const AVCodec *codec_info = GetEncoder(c, SampleFormat::INVALID); - - if (codec_info) { - for (int i = 0; codec_info->pix_fmts[i] != -1; i++) { - if (FFmpegUtils::ConvertJPEGSpaceToRegularSpace( - codec_info->pix_fmts[i]) != codec_info->pix_fmts[i]) { - // This is a deprecated "JPEG" space, skip it - continue; + int bridge_codec = ExportCodecToBridge(c); + if (bridge_codec != FB_CODEC_NONE) { + int count = + fb_encoder_codec_get_pixel_formats(bridge_codec, nullptr, 0); + if (count > 0) { + std::vector names(static_cast(count)); + fb_encoder_codec_get_pixel_formats(bridge_codec, names.data(), + count); + for (int i = 0; i < count; i++) { + pix_fmts.append(QString::fromUtf8(names[size_t(i)])); } - - const char *pix_fmt_name = - av_get_pix_fmt_name(codec_info->pix_fmts[i]); - pix_fmts.append(pix_fmt_name); } } @@ -87,18 +67,24 @@ FFmpegEncoder::GetSampleFormatsForCodec(ExportCodec::Codec c) const if (c == ExportCodec::kCodecPCM) { // FFmpeg lists these as separate codecs so we need custom functionality here // We list signed 16 first because ExportDialog will always use the first element by default - // (because first element is the "default" in tFFmpeg) + // (because first element is the "default" in FFmpeg) f = { SampleFormat::S16, SampleFormat::U8, SampleFormat::S32, SampleFormat::S64, SampleFormat::F32, SampleFormat::F64 }; } else { - const AVCodec *codec_info = GetEncoder(c, SampleFormat::INVALID); - - if (codec_info && codec_info->sample_fmts) { - for (int i = 0; codec_info->sample_fmts[i] != -1; i++) { - SampleFormat this_format = FFmpegUtils::GetNativeSampleFormat( - static_cast(codec_info->sample_fmts[i])); - if (this_format != SampleFormat::INVALID) { - f.push_back(this_format); + int bridge_codec = ExportCodecToBridge(c); + if (bridge_codec != FB_CODEC_NONE) { + int count = + fb_encoder_codec_get_sample_formats(bridge_codec, nullptr, 0); + if (count > 0) { + std::vector fmts(static_cast(count)); + fb_encoder_codec_get_sample_formats(bridge_codec, fmts.data(), + count); + for (int fmt : fmts) { + SampleFormat native = + FFmpegUtils::GetNativeSampleFormat(fmt); + if (native != SampleFormat::INVALID) { + f.push_back(native); + } } } } @@ -113,153 +99,142 @@ bool FFmpegEncoder::Open() return true; } - int error_code; - - // Convert QString to C string that FFmpeg expects + // Convert QString to C string QByteArray filename_bytes = params().filename().toUtf8(); - const char *filename_c_str = filename_bytes.constData(); - // Create output format context - error_code = avformat_alloc_output_context2(&fmt_ctx_, nullptr, nullptr, - filename_c_str); + FBEncoderConfig config; + memset(&config, 0, sizeof(config)); + config.filename = filename_bytes.constData(); - // Check error code - if (error_code < 0) { - FFmpegError(tr("Failed to allocate output context"), error_code); - return false; - } + // Storage keeping C strings alive until fb_encoder_create deep-copies them + QByteArray pix_fmt_bytes; + QByteArray subtitle_header; + std::vector opt_key_storage; + std::vector opt_value_storage; + std::vector opt_keys; + std::vector opt_values; - // Initialize a video stream if it's enabled + // Set up video if it's enabled if (params().video_enabled()) { - if (!InitializeStream(AVMEDIA_TYPE_VIDEO, &video_stream_, - &video_codec_ctx_, params().video_codec())) { - return false; - } + config.video_enabled = 1; + config.video_codec = ExportCodecToBridge(params().video_codec()); + config.video_width = params().video_params().width(); + config.video_height = params().video_params().height(); + config.video_pixel_aspect_num = + params().video_params().pixel_aspect_ratio().numerator(); + config.video_pixel_aspect_den = + params().video_params().pixel_aspect_ratio().denominator(); + config.video_time_base_num = + params().video_params().frame_rate_as_time_base().numerator(); + config.video_time_base_den = + params().video_params().frame_rate_as_time_base().denominator(); + config.video_frame_rate_num = + params().video_params().frame_rate().numerator(); + config.video_frame_rate_den = + params().video_params().frame_rate().denominator(); + + pix_fmt_bytes = params().video_pix_fmt().toUtf8(); + config.video_pix_fmt = pix_fmt_bytes.constData(); // This is the format we will expect frames received in Write() to be in PixelFormat native_pixel_fmt = params().video_params().format(); - // This is the format we will need to convert the frame to for swscale to understand it + // This is the format we will need to convert the frame to for the bridge to understand it video_conversion_fmt_ = FFmpegUtils::GetCompatiblePixelFormat(native_pixel_fmt); - // This is the equivalent pixel format above as an AVPixelFormat that swscale can understand - AVPixelFormat src_alpha_pix_fmt = FFmpegUtils::GetFFmpegPixelFormat( + // These are the equivalent pixel formats as bridge pixel formats + int src_alpha_pix_fmt = FFmpegUtils::GetFFmpegPixelFormat( video_conversion_fmt_, VideoParams::kRGBAChannelCount); - - AVPixelFormat src_noalpha_pix_fmt = FFmpegUtils::GetFFmpegPixelFormat( + int src_noalpha_pix_fmt = FFmpegUtils::GetFFmpegPixelFormat( video_conversion_fmt_, VideoParams::kRGBChannelCount); - if (src_alpha_pix_fmt == AV_PIX_FMT_NONE || - src_noalpha_pix_fmt == AV_PIX_FMT_NONE) { + if (src_alpha_pix_fmt == FB_PIX_FMT_NONE || + src_noalpha_pix_fmt == FB_PIX_FMT_NONE) { SetError( tr("Failed to find suitable pixel format for this buffer")); return false; } - // This is the pixel format the encoder wants to encode to - AVPixelFormat encoder_pix_fmt = video_codec_ctx_->pix_fmt; + config.video_src_pix_fmt = src_alpha_pix_fmt; - video_scale_ctx_ = avfilter_graph_alloc(); - if (!video_scale_ctx_) { - return false; + config.video_color_range = + params().video_params().color_range() == + VideoParams::kColorRangeFull ? + FB_COLOR_RANGE_JPEG : + FB_COLOR_RANGE_MPEG; + + switch (params().video_params().interlacing()) { + case VideoParams::kInterlacedTopFirst: + config.video_field_order = FB_FIELD_ORDER_TT; + break; + case VideoParams::kInterlacedBottomFirst: + config.video_field_order = FB_FIELD_ORDER_BB; + break; + default: + config.video_field_order = FB_FIELD_ORDER_PROGRESSIVE; + break; } - static const int FILTER_ARG_SZ = 1024; - char filter_args[FILTER_ARG_SZ]; + config.video_bit_rate = params().video_bit_rate(); + config.video_min_bit_rate = params().video_min_bit_rate(); + config.video_max_bit_rate = params().video_max_bit_rate(); + config.video_buffer_size = params().video_buffer_size(); + config.video_threads = params().video_threads(); + config.video_color_srgb = + params().color_transform().output().contains( + QStringLiteral("sRGB"), Qt::CaseInsensitive) ? + 1 : + 0; - snprintf( - filter_args, FILTER_ARG_SZ, - "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", - params().video_params().effective_width(), - params().video_params().effective_height(), src_alpha_pix_fmt, - params().video_params().time_base().numerator(), - params().video_params().time_base().denominator(), - params().video_params().pixel_aspect_ratio().numerator(), - params().video_params().pixel_aspect_ratio().denominator()); - - avfilter_graph_create_filter(&video_buffersrc_ctx_, - avfilter_get_by_name("buffer"), "in", - filter_args, nullptr, video_scale_ctx_); - avfilter_graph_create_filter(&video_buffersink_ctx_, - avfilter_get_by_name("buffersink"), "out", - nullptr, nullptr, video_scale_ctx_); - - AVFilterContext *last_filter = video_buffersrc_ctx_; - - { - // Set color range - AVFilterContext *range_filter; - - snprintf(filter_args, FILTER_ARG_SZ, "in_range=full:out_range=%s", - params().video_params().color_range() == - VideoParams::kColorRangeFull ? - "full" : - "limited"); - - avfilter_graph_create_filter(&range_filter, - avfilter_get_by_name("scale"), "range", - filter_args, nullptr, - video_scale_ctx_); - - avfilter_link(last_filter, 0, range_filter, 0); - last_filter = range_filter; + // Custom options (skip Olive-internal keys) + for (auto i = params().video_opts().begin(); + i != params().video_opts().end(); i++) { + if (!i.key().startsWith(QStringLiteral("ove_"))) { + opt_key_storage.push_back(i.key().toUtf8()); + opt_value_storage.push_back(i.value().toUtf8()); + } } - - if (src_alpha_pix_fmt != encoder_pix_fmt) { - // Transform pixel format - AVFilterContext *format_filter; - - snprintf(filter_args, FILTER_ARG_SZ, "pix_fmts=%u", - encoder_pix_fmt); - - avfilter_graph_create_filter(&format_filter, - avfilter_get_by_name("format"), - "format", filter_args, nullptr, - video_scale_ctx_); - - avfilter_link(last_filter, 0, format_filter, 0); - last_filter = format_filter; - } - - avfilter_link(last_filter, 0, video_buffersink_ctx_, 0); - - if (avfilter_graph_config(video_scale_ctx_, nullptr) < 0) { - SetError(tr("Failed to configure filter graph")); - return false; + for (size_t i = 0; i < opt_key_storage.size(); i++) { + opt_keys.push_back(opt_key_storage[i].constData()); + opt_values.push_back(opt_value_storage[i].constData()); } + config.video_opt_keys = opt_keys.data(); + config.video_opt_values = opt_values.data(); + config.video_opt_count = int(opt_keys.size()); } - // Initialize an audio stream if it's enabled + // Set up audio if it's enabled if (params().audio_enabled()) { - if (!InitializeStream(AVMEDIA_TYPE_AUDIO, &audio_stream_, - &audio_codec_ctx_, params().audio_codec())) { - return false; - } + config.audio_enabled = 1; + config.audio_codec = ExportCodecToBridge(params().audio_codec()); + config.audio_sample_rate = params().audio_params().sample_rate(); + config.audio_channel_layout_mask = + params().audio_params().channel_layout(); + config.audio_sample_format = FFmpegUtils::GetFFmpegSampleFormat( + params().audio_params().format()); + config.audio_bit_rate = params().audio_bit_rate(); } - // Initialize a subtitle stream if it's enabled + // Set up subtitles if they're enabled if (params().subtitles_enabled()) { - if (!InitializeStream(AVMEDIA_TYPE_SUBTITLE, &subtitle_stream_, - &subtitle_codec_ctx_, - params().subtitles_codec())) { - return false; - } + config.subtitles_enabled = 1; + config.subtitle_codec = ExportCodecToBridge(params().subtitles_codec()); + subtitle_header = SubtitleParams::GenerateASSHeader().toUtf8(); + config.subtitle_header = + reinterpret_cast(subtitle_header.constData()); + config.subtitle_header_size = subtitle_header.size(); } - av_dump_format(fmt_ctx_, 0, filename_c_str, 1); - - // Open output file for writing - error_code = avio_open(&fmt_ctx_->pb, filename_c_str, AVIO_FLAG_WRITE); - if (error_code < 0) { - FFmpegError(tr("Failed to open IO context"), error_code); + encoder_ = fb_encoder_create(&config); + if (!encoder_) { + SetError(tr("Failed to create encoder")); return false; } - // Write header - error_code = avformat_write_header(fmt_ctx_, nullptr); - if (error_code < 0) { - FFmpegError(tr("Failed to write format header"), error_code); + if (fb_encoder_open(encoder_) != 0) { + SetErrorFromBridge(); + fb_encoder_free(&encoder_); return false; } @@ -269,44 +244,24 @@ bool FFmpegEncoder::Open() bool FFmpegEncoder::WriteFrame(FramePtr frame, rational time) { - // We may need to convert this frame to a frame that swscale will understand + // We may need to convert this frame to a frame that the bridge will understand if (frame->format() != video_conversion_fmt_) { frame = frame->convert(video_conversion_fmt_); } - // Use swscale context to convert formats/linesizes - AVFramePtr input_frame = CreateAVFramePtr(av_frame_alloc()); - input_frame->width = frame->width(); - input_frame->height = frame->height(); - input_frame->format = FFmpegUtils::GetFFmpegPixelFormat( - frame->format(), frame->channel_count()); - input_frame->data[0] = reinterpret_cast(frame->data()); - input_frame->linesize[0] = frame->linesize_bytes(); + int src_pix_fmt = FFmpegUtils::GetFFmpegPixelFormat(frame->format(), + frame->channel_count()); - input_frame->color_primaries = video_codec_ctx_->color_primaries; - input_frame->color_trc = video_codec_ctx_->color_trc; - input_frame->colorspace = video_codec_ctx_->colorspace; - input_frame->color_range = video_codec_ctx_->color_range; - - int r; - r = av_buffersrc_add_frame_flags(video_buffersrc_ctx_, input_frame.get(), - AV_BUFFERSRC_FLAG_KEEP_REF); - if (r < 0) { - FFmpegError(tr("Failed to add frame to filter graph"), r); + int r = fb_encoder_write_video_frame( + encoder_, frame->width(), frame->height(), src_pix_fmt, + reinterpret_cast(frame->data()), + frame->linesize_bytes(), time.toDouble()); + if (r != 0) { + SetErrorFromBridge(); return false; } - AVFramePtr encoded_frame = CreateAVFramePtr(av_frame_alloc()); - r = av_buffersink_get_frame(video_buffersink_ctx_, encoded_frame.get()); - if (r < 0) { - FFmpegError(tr("Failed to retrieve frame from buffer sink"), r); - return false; - } - - encoded_frame->pts = - qRound64(time.toDouble() / av_q2d(video_codec_ctx_->time_base)); - - return WriteAVFrame(encoded_frame.get(), video_codec_ctx_, video_stream_); + return true; } bool FFmpegEncoder::WriteAudio(const SampleBuffer &audio) @@ -315,736 +270,124 @@ bool FFmpegEncoder::WriteAudio(const SampleBuffer &audio) return true; } - bool result = true; + const AudioParams &audio_params = audio.audio_params().is_valid() ? + audio.audio_params() : + params().audio_params(); - size_t start = 0; - size_t end = audio.sample_count(); - const size_t max_frame = 48000; - - while (result && start < end) { - // Create input buffer - uint8_t **input_data = nullptr; - size_t input_sample_count = std::min(end - start, max_frame); - int input_linesize; - - int r = av_samples_alloc_array_and_samples( - &input_data, &input_linesize, audio.audio_params().channel_count(), - input_sample_count, - FFmpegUtils::GetFFmpegSampleFormat(audio.audio_params().format()), - 0); - - if (r < 0) { - FFmpegError(tr("Failed to allocate sample array"), r); - return false; - } else { - int bpsc = audio.audio_params().bytes_per_sample_per_channel(); - for (int i = 0; i < audio.audio_params().channel_count(); i++) { - memcpy(input_data[i], audio.data(i) + start, - input_sample_count * bpsc); - } - - start += input_sample_count; - } - - result = WriteAudioData(audio.audio_params().is_valid() ? - audio.audio_params() : - params().audio_params(), - const_cast(input_data), - input_sample_count); - - if (input_data) { - av_freep(&input_data[0]); - av_freep(&input_data); - } + std::vector channel_data( + size_t(audio.audio_params().channel_count())); + for (size_t i = 0; i < channel_data.size(); i++) { + channel_data[i] = + reinterpret_cast(audio.data(int(i))); } - return result; -} - -bool FFmpegEncoder::WriteAudioData(const AudioParams &audio_params, - const uint8_t **input_data, - int input_sample_count) -{ - if (!InitializeResampleContext(audio_params)) { - qCritical() << "Failed to initialize resample context"; + int r = fb_encoder_write_audio( + encoder_, channel_data.data(), + audio.audio_params().channel_count(), + FFmpegUtils::GetFFmpegSampleFormat(audio.audio_params().format()), + audio_params.sample_rate(), audio_params.channel_layout(), + int64_t(audio.sample_count())); + if (r != 0) { + SetErrorFromBridge(); return false; } - bool result = true; - - // Create output buffer - int output_sample_count = - input_sample_count ? - swr_get_out_samples(audio_resample_ctx_, input_sample_count) : - 102400; - uint8_t **output_data = nullptr; - int output_linesize; - av_samples_alloc_array_and_samples( - &output_data, &output_linesize, - audio_stream_->codecpar->ch_layout.nb_channels, output_sample_count, - static_cast(audio_stream_->codecpar->format), 0); - - // Perform conversion - int converted = swr_convert(audio_resample_ctx_, output_data, - output_sample_count, - const_cast(input_data), - input_sample_count); - if (converted > 0) { - // Split sample buffer into frames - for (int i = 0; i < converted;) { - int frame_remaining_samples = - audio_max_samples_ - audio_frame_offset_; - int converted_remaining_samples = converted - i; - - int copy_length = - qMin(frame_remaining_samples, converted_remaining_samples); - - av_samples_copy(audio_frame_->data, output_data, - audio_frame_offset_, i, copy_length, - audio_frame_->ch_layout.nb_channels, - static_cast(audio_frame_->format)); - - audio_frame_offset_ += copy_length; - i += copy_length; - - if (audio_frame_offset_ == audio_max_samples_ || - (i == converted && !input_data)) { - // Got all the samples we needed, write the frame - audio_frame_->pts = av_rescale_q( - audio_write_count_, { 1, audio_codec_ctx_->sample_rate }, - audio_codec_ctx_->time_base); - - WriteAVFrame(audio_frame_, audio_codec_ctx_, audio_stream_); - audio_write_count_ += audio_frame_offset_; - audio_frame_offset_ = 0; - } - } - } else if (converted < 0) { - FFmpegError(tr("Failed to resample audio"), converted); - result = false; - } - - if (!input_data && audio_frame_offset_ > 0) { - audio_frame_->nb_samples = audio_frame_offset_; - audio_frame_->pts = av_rescale_q(audio_write_count_, - { 1, audio_codec_ctx_->sample_rate }, - audio_codec_ctx_->time_base); - WriteAVFrame(audio_frame_, audio_codec_ctx_, audio_stream_); - } - - // Free buffers created - if (output_data) { - av_freep(&output_data[0]); - av_freep(&output_data); - } - - return result; + return true; } -QString GetAssTime(const rational &time) +bool FFmpegEncoder::WriteAudioData(const AudioParams &audio_params, + const uint8_t **data, + int input_sample_count) { - int64_t total_centiseconds = qRound64(time.toDouble() * 100); + int r = fb_encoder_write_audio( + encoder_, data, audio_params.channel_count(), + FFmpegUtils::GetFFmpegSampleFormat(audio_params.format()), + audio_params.sample_rate(), audio_params.channel_layout(), + input_sample_count); + if (r != 0) { + SetErrorFromBridge(); + return false; + } - int64_t cs = total_centiseconds % 100; - int64_t ss = (total_centiseconds / 100) % 60; - int64_t mm = (total_centiseconds / 6000) % 60; - int64_t hh = total_centiseconds / 360000; - - return QStringLiteral("%1:%2:%3.%4") - .arg(QString::number(hh), - QStringLiteral("%1").arg(mm, 2, 10, QLatin1Char('0')), - QStringLiteral("%1").arg(ss, 2, 10, QLatin1Char('0')), - QStringLiteral("%1").arg(cs, 2, 10, QLatin1Char('0'))); + return true; } bool FFmpegEncoder::WriteSubtitle(const SubtitleBlock *sub_block) { QByteArray utf8_sub = sub_block->GetText().toUtf8(); - AVPacket *pkt = av_packet_alloc(); - - pkt->stream_index = subtitle_stream_->index; - pkt->data = (uint8_t *)utf8_sub.data(); - pkt->size = utf8_sub.size(); - pkt->pts = Timecode::time_to_timestamp( - sub_block->in(), subtitle_codec_ctx_->time_base, Timecode::kFloor); - pkt->duration = - av_rescale_q(qRound64(sub_block->length().toDouble() * 1000), - { 1, 1000 }, subtitle_codec_ctx_->time_base); - pkt->dts = pkt->pts; - av_packet_rescale_ts(pkt, subtitle_codec_ctx_->time_base, - subtitle_stream_->time_base); - - int err = av_interleaved_write_frame(fmt_ctx_, pkt); - bool ret = true; - - if (err < 0) { - FFmpegError(tr("Failed to write interleaved packet"), err); - ret = false; + int r = fb_encoder_write_subtitle(encoder_, utf8_sub.constData(), + sub_block->in().toDouble(), + sub_block->length().toDouble()); + if (r != 0) { + SetErrorFromBridge(); + return false; } - av_packet_free(&pkt); - - return ret; + return true; } -/* -void FFmpegEncoder::WriteAudio(AudioParams pcm_info, QIODevice* file) -{ - - - // Keep track of sample count to use as each frame's timebase - int sample_counter = 0; - - while (true) { - // Calculate how many samples should input this frame - int64_t samples_needed = av_rescale_rnd(maximum_frame_samples + swr_get_delay(swr_ctx, pcm_info.sample_rate()), - audio_codec_ctx_->sample_rate, - pcm_info.sample_rate(), - AV_ROUND_UP); - - // Calculate how many bytes this is - int max_read = pcm_info.samples_to_bytes(samples_needed); - - // Read bytes from PCM - QByteArray input_data = file->read(max_read); - - // Use swresample to convert the data into the correct format - const char* input_data_array = input_data.constData(); - int converted = swr_convert(swr_ctx, - - // output data - frame->data, - - // output sample count (maximum amount of samples in output) - maximum_frame_samples, - - // input data - reinterpret_cast(&input_data_array), - - // input sample count (maximum amount of samples we read from pcm file) - pcm_info.bytes_to_samples(input_data.size())); - - // Update the frame's number of samples to the amount we actually received - frame->nb_samples = converted; - - // Update frame timestamp - frame->pts = sample_counter; - - // Increment timestamp for the next frame by the amount of samples in this one - sample_counter += converted; - - // Write the frame - if (!WriteAVFrame(frame, audio_codec_ctx_, audio_stream_)) { - qCritical() << "Failed to write audio AVFrame"; - break; - } - - // Break if we've reached the end point - if (file->atEnd()) { - break; - } - } -} -*/ - void FFmpegEncoder::Close() { - if (open_) { - // Flush encoders - FlushEncoders(); - - // We've written a header, so we'll write a trailer - av_write_trailer(fmt_ctx_); - avio_closep(&fmt_ctx_->pb); - - open_ = false; + if (encoder_) { + // Flushes encoders, writes the trailer, and frees everything + fb_encoder_free(&encoder_); } - if (audio_resample_ctx_) { - swr_init(audio_resample_ctx_); - audio_resample_ctx_ = nullptr; - } - - if (audio_frame_) { - av_frame_free(&audio_frame_); - audio_frame_ = nullptr; - } - - if (video_scale_ctx_) { - avfilter_graph_free(&video_scale_ctx_); - video_scale_ctx_ = nullptr; - video_buffersrc_ctx_ = nullptr; - video_buffersink_ctx_ = nullptr; - } - - if (video_codec_ctx_) { - avcodec_free_context(&video_codec_ctx_); - video_codec_ctx_ = nullptr; - } - - if (audio_codec_ctx_) { - avcodec_free_context(&audio_codec_ctx_); - audio_codec_ctx_ = nullptr; - } - - if (fmt_ctx_) { - // NOTE: This also frees video_stream_ and audio_stream_ - avformat_free_context(fmt_ctx_); - fmt_ctx_ = nullptr; - video_stream_ = nullptr; - audio_stream_ = nullptr; - } + open_ = false; } -void FFmpegEncoder::FFmpegError(const QString &context, int error_code) +void FFmpegEncoder::SetErrorFromBridge() { - char err[1024]; - av_strerror(error_code, err, 1024); - - QString formatted_err = - tr("%1: %2 %3").arg(context, err, QString::number(error_code)); - qDebug() << formatted_err; - SetError(formatted_err); + SetError(QString::fromUtf8(fb_encoder_get_error(encoder_))); } -bool FFmpegEncoder::WriteAVFrame(AVFrame *frame, AVCodecContext *codec_ctx, - AVStream *stream) -{ - // Send raw frame to the encoder - int error_code = avcodec_send_frame(codec_ctx, frame); - if (error_code < 0) { - FFmpegError(tr("Failed to send frame to encoder"), error_code); - return false; - } - - bool succeeded = false; - - AVPacket *pkt = av_packet_alloc(); - - // Retrieve packets from encoder - while (error_code >= 0) { - error_code = avcodec_receive_packet(codec_ctx, pkt); - - // EAGAIN just means the encoder wants another frame before encoding - if (error_code == AVERROR(EAGAIN)) { - break; - } else if (error_code < 0) { - FFmpegError(tr("Failed to receive packet from decoder"), - error_code); - goto fail; - } - - // Set packet stream index - pkt->stream_index = stream->index; - - av_packet_rescale_ts(pkt, codec_ctx->time_base, stream->time_base); - - // Write packet to file - error_code = av_interleaved_write_frame(fmt_ctx_, pkt); - if (error_code < 0) { - FFmpegError(tr("Failed to write interleaved packet"), error_code); - goto fail; - } - - // Unref packet in case we're getting another - av_packet_unref(pkt); - } - - succeeded = true; - -fail: - av_packet_free(&pkt); - - return succeeded; -} - -bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream **stream_ptr, - AVCodecContext **codec_ctx_ptr, - const ExportCodec::Codec &codec) -{ - if (type != AVMEDIA_TYPE_VIDEO && type != AVMEDIA_TYPE_AUDIO && - type != AVMEDIA_TYPE_SUBTITLE) { - SetError(tr( - "Cannot initialize a stream that is not a video, audio, or subtitle type")); - return false; - } - - // Find encoder - const AVCodec *encoder = - GetEncoder(codec, params().audio_params().format()); - if (!encoder) { - SetError(tr("Failed to find codec for 0x%1").arg(codec, 16)); - return false; - } - - if (encoder->type != type) { - SetError( - tr("Retrieved unexpected codec type %1 for codec %2") - .arg(QString::number(encoder->type), QString::number(codec))); - return false; - } - - if (!InitializeCodecContext(stream_ptr, codec_ctx_ptr, encoder)) { - return false; - } - - // Set codec parameters - AVCodecContext *codec_ctx = *codec_ctx_ptr; - AVStream *stream = *stream_ptr; - - if (type == AVMEDIA_TYPE_VIDEO) { - codec_ctx->width = params().video_params().width(); - codec_ctx->height = params().video_params().height(); - codec_ctx->sample_aspect_ratio = - params().video_params().pixel_aspect_ratio().toAVRational(); - codec_ctx->time_base = - params().video_params().frame_rate_as_time_base().toAVRational(); - codec_ctx->framerate = - params().video_params().frame_rate().toAVRational(); - codec_ctx->pix_fmt = av_get_pix_fmt(params().video_pix_fmt().toUtf8()); - codec_ctx->color_range = params().video_params().color_range() == - VideoParams::kColorRangeFull ? - AVCOL_RANGE_JPEG : - AVCOL_RANGE_MPEG; - - if (params().video_params().interlacing() != - VideoParams::kInterlaceNone) { - // FIXME: I actually don't know what these flags do, the documentation helpfully doesn't - // explain them at all. I hope using both of them is the right thing to do. - codec_ctx->flags |= AV_CODEC_FLAG_INTERLACED_DCT | - AV_CODEC_FLAG_INTERLACED_ME; - - if (params().video_params().interlacing() == - VideoParams::kInterlacedTopFirst) { - codec_ctx->field_order = AV_FIELD_TT; - } else { - codec_ctx->field_order = AV_FIELD_BB; - - if (codec == ExportCodec::kCodecH264 || - codec == ExportCodec::kCodecH264rgb) { - // For some reason, FFmpeg doesn't set libx264's bff flag so we have to do it ourselves - av_opt_set(codec_ctx->priv_data, "x264opts", "bff=1", - AV_OPT_SEARCH_CHILDREN); - } - } - } - - // Set custom options - { - for (auto i = params().video_opts().begin(); - i != params().video_opts().end(); i++) { - if (!i.key().startsWith(QStringLiteral("ove_"))) { - av_opt_set(codec_ctx->priv_data, i.key().toUtf8(), - i.value().toUtf8(), AV_OPT_SEARCH_CHILDREN); - } - } - - if (params().video_bit_rate() > 0) { - codec_ctx->bit_rate = params().video_bit_rate(); - } - - if (params().video_min_bit_rate() > 0) { - codec_ctx->rc_min_rate = params().video_min_bit_rate(); - } - - if (params().video_max_bit_rate() > 0) { - codec_ctx->rc_max_rate = params().video_max_bit_rate(); - } - - if (params().video_buffer_size() > 0) { - codec_ctx->rc_buffer_size = - static_cast(params().video_buffer_size()); - } - - // nclc tags. See https://ffmpeg.org/doxygen/4.0/pixfmt_8h.html#ad384ee5a840bafd73daef08e6d9cafe7 - // ffprobe -v error -show_format -show_streams "C:\Users\Tom\Documents\srgb correct tags.mov" - if (params().color_transform().output().contains( - QStringLiteral("sRGB"), Qt::CaseInsensitive)) { - codec_ctx->color_primaries = AVCOL_PRI_BT709; - codec_ctx->color_trc = AVCOL_TRC_IEC61966_2_1; - codec_ctx->colorspace = AVCOL_SPC_BT709; - } else { // Assume Rec.709 - codec_ctx->color_primaries = AVCOL_PRI_BT709; - codec_ctx->color_trc = AVCOL_TRC_BT709; - codec_ctx->colorspace = AVCOL_SPC_BT709; - } - } - - } else if (type == AVMEDIA_TYPE_AUDIO) { - // Assume audio stream - codec_ctx->sample_rate = params().audio_params().sample_rate(); - codec_ctx->ch_layout = params().audio_params().channel_layout(); - codec_ctx->sample_fmt = FFmpegUtils::GetFFmpegSampleFormat( - params().audio_params().format()); - codec_ctx->time_base = { 1, codec_ctx->sample_rate }; - - if (params().audio_bit_rate() > 0) { - codec_ctx->bit_rate = params().audio_bit_rate(); - } - - } else if (type == AVMEDIA_TYPE_SUBTITLE) { - codec_ctx->time_base = av_get_time_base_q(); - - QByteArray ass_header = SubtitleParams::GenerateASSHeader().toUtf8(); - codec_ctx->subtitle_header = new uint8_t[ass_header.size()]; - memcpy(codec_ctx->subtitle_header, ass_header.constData(), - ass_header.size()); - codec_ctx->subtitle_header_size = ass_header.size(); - } - - if (!SetupCodecContext(stream, codec_ctx, encoder)) { - return false; - } - - return true; -} - -bool FFmpegEncoder::InitializeCodecContext(AVStream **stream, - AVCodecContext **codec_ctx, - const AVCodec *codec) -{ - *stream = avformat_new_stream(fmt_ctx_, nullptr); - if (!(*stream)) { - SetError(tr("Failed to allocate AVStream")); - return false; - } - - // Allocate a codec context - *codec_ctx = avcodec_alloc_context3(codec); - if (!(*codec_ctx)) { - SetError(tr("Failed to allocate AVCodecContext")); - return false; - } - - return true; -} - -bool FFmpegEncoder::SetupCodecContext(AVStream *stream, - AVCodecContext *codec_ctx, - const AVCodec *codec) -{ - int error_code; - - if (fmt_ctx_->oformat->flags & AVFMT_GLOBALHEADER) { - codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; - } - - AVDictionary *codec_opts = nullptr; - - // Set thread count - if (params().video_threads() == 0) { - av_dict_set(&codec_opts, "threads", "auto", 0); - } else { - QString thread_val = QString::number(params().video_threads()); - av_dict_set(&codec_opts, "threads", thread_val.toUtf8(), 0); - } - - // Try to open encoder - error_code = avcodec_open2(codec_ctx, codec, &codec_opts); - if (error_code < 0) { - FFmpegError(tr("Failed to open encoder"), error_code); - return false; - } - - // Copy context settings to codecpar object - error_code = avcodec_parameters_from_context(stream->codecpar, codec_ctx); - if (error_code < 0) { - FFmpegError(tr("Failed to copy codec parameters to stream"), - error_code); - return false; - } - - if (codec->type == AVMEDIA_TYPE_VIDEO) { - stream->avg_frame_rate = codec_ctx->framerate; - } - - return true; -} - -void FFmpegEncoder::FlushEncoders() -{ - if (video_codec_ctx_) { - FlushCodecCtx(video_codec_ctx_, video_stream_); - } - - if (audio_codec_ctx_) { - WriteAudio(SampleBuffer()); - - FlushCodecCtx(audio_codec_ctx_, audio_stream_); - } - - if (fmt_ctx_) { - if (fmt_ctx_->oformat->flags) { - int r = av_interleaved_write_frame(fmt_ctx_, nullptr); - if (r < 0) { - FFmpegError(tr("Failed to write interleaved packet"), r); - } - } - } -} - -void FFmpegEncoder::FlushCodecCtx(AVCodecContext *codec_ctx, AVStream *stream) -{ - avcodec_send_frame(codec_ctx, nullptr); - AVPacket *pkt = av_packet_alloc(); - - int error_code; - do { - error_code = avcodec_receive_packet(codec_ctx, pkt); - - if (error_code < 0) { - break; - } - - pkt->stream_index = stream->index; - av_packet_rescale_ts(pkt, codec_ctx->time_base, stream->time_base); - int r = av_interleaved_write_frame(fmt_ctx_, pkt); - if (r < 0) { - FFmpegError(tr("Failed to write interleaved packet"), r); - break; - } - av_packet_unref(pkt); - } while (error_code >= 0); - - av_packet_free(&pkt); -} - -bool FFmpegEncoder::InitializeResampleContext(const AudioParams &audio) -{ - if (audio_resample_ctx_) { - return true; - } - AVChannelLayout layout = audio.channel_layout(); - // Create resample context - swr_alloc_set_opts2(&audio_resample_ctx_, &audio_codec_ctx_->ch_layout, - audio_codec_ctx_->sample_fmt, - audio_codec_ctx_->sample_rate, &layout, - FFmpegUtils::GetFFmpegSampleFormat(audio.format()), - audio.sample_rate(), 0, nullptr); - if (!audio_resample_ctx_) { - return false; - } - - int err = swr_init(audio_resample_ctx_); - if (err < 0) { - FFmpegError(tr("Failed to create resampling context"), err); - return false; - } - - audio_max_samples_ = audio_codec_ctx_->frame_size; - if (!audio_max_samples_) { - // If not, use another frame size - if (params().video_enabled()) { - // If we're encoding video, use enough samples to cover roughly one frame of video - audio_max_samples_ = params().audio_params().time_to_samples( - params().video_params().frame_rate_as_time_base()); - } else { - // If no video, just use an arbitrary number - audio_max_samples_ = 256; - } - } - - audio_frame_ = av_frame_alloc(); - if (!audio_frame_) { - return false; - } - - audio_frame_->ch_layout = audio_codec_ctx_->ch_layout; - audio_frame_->format = audio_codec_ctx_->sample_fmt; - audio_frame_->nb_samples = audio_max_samples_; - - err = av_frame_get_buffer(audio_frame_, 0); - if (err < 0) { - FFmpegError(tr("Failed to create audio frame"), err); - return false; - } - - audio_frame_offset_ = 0; - audio_write_count_ = 0; - - return true; -} - -const AVCodec *FFmpegEncoder::GetEncoder(ExportCodec::Codec c, - SampleFormat aformat) +int FFmpegEncoder::ExportCodecToBridge(ExportCodec::Codec c) { switch (c) { case ExportCodec::kCodecH264: - return avcodec_find_encoder_by_name("libx264"); + return FB_CODEC_H264; case ExportCodec::kCodecH264rgb: - return avcodec_find_encoder_by_name("libx264rgb"); + return FB_CODEC_H264RGB; case ExportCodec::kCodecDNxHD: - return avcodec_find_encoder(AV_CODEC_ID_DNXHD); + return FB_CODEC_DNXHD; case ExportCodec::kCodecProRes: - return avcodec_find_encoder(AV_CODEC_ID_PRORES); + return FB_CODEC_PRORES; case ExportCodec::kCodecCineform: - return avcodec_find_encoder(AV_CODEC_ID_CFHD); + return FB_CODEC_CINEFORM; case ExportCodec::kCodecH265: - return avcodec_find_encoder(AV_CODEC_ID_HEVC); + return FB_CODEC_H265; case ExportCodec::kCodecVP9: - return avcodec_find_encoder(AV_CODEC_ID_VP9); - case ExportCodec::kCodecAV1: { - const AVCodec *encoder = avcodec_find_encoder_by_name("libsvtav1"); - if (!encoder) - encoder = avcodec_find_encoder(AV_CODEC_ID_AV1); - return encoder; - } + return FB_CODEC_VP9; + case ExportCodec::kCodecAV1: + return FB_CODEC_AV1; case ExportCodec::kCodecOpenEXR: - return avcodec_find_encoder(AV_CODEC_ID_EXR); + return FB_CODEC_OPENEXR; case ExportCodec::kCodecPNG: - return avcodec_find_encoder(AV_CODEC_ID_PNG); + return FB_CODEC_PNG; case ExportCodec::kCodecTIFF: - return avcodec_find_encoder(AV_CODEC_ID_TIFF); + return FB_CODEC_TIFF; case ExportCodec::kCodecMP2: - return avcodec_find_encoder(AV_CODEC_ID_MP2); + return FB_CODEC_MP2; case ExportCodec::kCodecMP3: - return avcodec_find_encoder(AV_CODEC_ID_MP3); + return FB_CODEC_MP3; case ExportCodec::kCodecAAC: - return avcodec_find_encoder(AV_CODEC_ID_AAC); + return FB_CODEC_AAC; case ExportCodec::kCodecPCM: - switch (aformat) { - case SampleFormat::INVALID: - case SampleFormat::COUNT: - case SampleFormat::U8P: - case SampleFormat::S16P: - case SampleFormat::S32P: - case SampleFormat::S64P: - case SampleFormat::F32P: - case SampleFormat::F64P: - break; - case SampleFormat::U8: - return avcodec_find_encoder(AV_CODEC_ID_PCM_U8); - case SampleFormat::S16: - return avcodec_find_encoder(AV_CODEC_ID_PCM_S16LE); - case SampleFormat::S32: - return avcodec_find_encoder(AV_CODEC_ID_PCM_S32LE); - case SampleFormat::S64: - return avcodec_find_encoder(AV_CODEC_ID_PCM_S64LE); - case SampleFormat::F32: - return avcodec_find_encoder(AV_CODEC_ID_PCM_F32LE); - case SampleFormat::F64: - return avcodec_find_encoder(AV_CODEC_ID_PCM_F64LE); - } - break; + return FB_CODEC_PCM; case ExportCodec::kCodecFLAC: - return avcodec_find_encoder(AV_CODEC_ID_FLAC); + return FB_CODEC_FLAC; case ExportCodec::kCodecOpus: - return avcodec_find_encoder(AV_CODEC_ID_OPUS); + return FB_CODEC_OPUS; case ExportCodec::kCodecVorbis: - return avcodec_find_encoder(AV_CODEC_ID_VORBIS); + return FB_CODEC_VORBIS; case ExportCodec::kCodecSRT: - return avcodec_find_encoder(AV_CODEC_ID_SUBRIP); + return FB_CODEC_SRT; case ExportCodec::kCodecCount: - // These are audio or invalid codecs and therefore have no pixel formats break; } - return nullptr; + return FB_CODEC_NONE; } -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - } diff --git a/app/codec/ffmpeg/ffmpegencoder.h b/app/codec/ffmpeg/ffmpegencoder.h index c5f3350e7..90187651e 100644 --- a/app/codec/ffmpeg/ffmpegencoder.h +++ b/app/codec/ffmpeg/ffmpegencoder.h @@ -22,19 +22,20 @@ #ifndef FFMPEGENCODER_H #define FFMPEGENCODER_H -extern "C" { -#include -#include -#include -#include -#include -} +#include #include "codec/encoder.h" namespace olive { +/** + * @brief An Encoder derivative that uses the ffmpeg_bridge library for encoding + * + * All encoding work happens inside the ffmpeg_bridge shared library through + * its pure C API; this class only translates EncodingParams into a bridge + * configuration and forwards calls. + */ class FFmpegEncoder : public Encoder { Q_OBJECT public: @@ -67,54 +68,16 @@ public: private: /** - * @brief Handle an FFmpeg error code - * - * Uses the FFmpeg API to retrieve a descriptive string for this error code and sends it to Error(). As such, this - * function also automatically closes the Decoder. - * - * @param error_code + * @brief Copy the last error message from the bridge into the encoder error state */ - void FFmpegError(const QString &context, int error_code); + void SetErrorFromBridge(); - bool WriteAVFrame(AVFrame *frame, AVCodecContext *codec_ctx, - AVStream *stream); + static int ExportCodecToBridge(ExportCodec::Codec c); - bool InitializeStream(enum AVMediaType type, AVStream **stream, - AVCodecContext **codec_ctx, - const ExportCodec::Codec &codec); - bool InitializeCodecContext(AVStream **stream, AVCodecContext **codec_ctx, - const AVCodec *codec); - bool SetupCodecContext(AVStream *stream, AVCodecContext *codec_ctx, - const AVCodec *codec); + FBEncoder *encoder_; - void FlushEncoders(); - void FlushCodecCtx(AVCodecContext *codec_ctx, AVStream *stream); - - bool InitializeResampleContext(const AudioParams &audio); - - static const AVCodec *GetEncoder(ExportCodec::Codec c, - SampleFormat aformat); - - AVFormatContext *fmt_ctx_; - - AVStream *video_stream_; - AVCodecContext *video_codec_ctx_; - AVFilterGraph *video_scale_ctx_; - AVFilterContext *video_buffersrc_ctx_; - AVFilterContext *video_buffersink_ctx_; PixelFormat video_conversion_fmt_; - AVStream *audio_stream_; - AVCodecContext *audio_codec_ctx_; - SwrContext *audio_resample_ctx_; - AVFrame *audio_frame_; - int audio_max_samples_; - int audio_frame_offset_; - int audio_write_count_; - - AVStream *subtitle_stream_; - AVCodecContext *subtitle_codec_ctx_; - bool open_; }; diff --git a/app/common/CMakeLists.txt b/app/common/CMakeLists.txt index 36c838363..708a67e36 100644 --- a/app/common/CMakeLists.txt +++ b/app/common/CMakeLists.txt @@ -17,7 +17,6 @@ target_sources(libolive-editor PRIVATE cancelableobject.h - channellayout.h commandlineparser.cpp commandlineparser.h crashpadinterface.cpp diff --git a/app/common/avframeptr.h b/app/common/avframeptr.h index 957f9635d..8e9170121 100644 --- a/app/common/avframeptr.h +++ b/app/common/avframeptr.h @@ -22,25 +22,116 @@ #ifndef AVFRAMEPTR_H #define AVFRAMEPTR_H -extern "C" { -#include -} +#include #include +#include + namespace olive { +/** + * @brief C++ adapter around the ffmpeg_bridge frame handle + * + * Mirrors the AVFrame field access the codebase used to perform directly, + * but every operation goes through the pure C bridge API so the editor + * never touches FFmpeg itself. The underlying frame object always lives + * inside the bridge library. + */ +class AVFrame { +public: + AVFrame() : + handle_(fb_frame_alloc()) + { + } + + explicit AVFrame(FBFrame *handle) : + handle_(handle) + { + } + + ~AVFrame() + { + if (handle_) { + fb_frame_free(&handle_); + } + } + + AVFrame(const AVFrame &) = delete; + AVFrame &operator=(const AVFrame &) = delete; + + FBFrame *handle() const { return handle_; } + + int width() const { return fb_frame_get_width(handle_); } + void set_width(int w) { fb_frame_set_width(handle_, w); } + int height() const { return fb_frame_get_height(handle_); } + void set_height(int h) { fb_frame_set_height(handle_, h); } + int format() const { return fb_frame_get_format(handle_); } + void set_format(int f) { fb_frame_set_format(handle_, f); } + int64_t pts() const { return fb_frame_get_pts(handle_); } + void set_pts(int64_t p) { fb_frame_set_pts(handle_, p); } + int64_t best_effort_timestamp() const + { + return fb_frame_get_best_effort_timestamp(handle_); + } + int nb_samples() const { return fb_frame_get_nb_samples(handle_); } + void set_nb_samples(int n) { fb_frame_set_nb_samples(handle_, n); } + int sample_rate() const { return fb_frame_get_sample_rate(handle_); } + void set_sample_rate(int r) { fb_frame_set_sample_rate(handle_, r); } + int color_range() const { return fb_frame_get_color_range(handle_); } + void set_color_range(int r) { fb_frame_set_color_range(handle_, r); } + int colorspace() const { return fb_frame_get_colorspace(handle_); } + void set_colorspace(int cs) { fb_frame_set_colorspace(handle_, cs); } + uint64_t channel_layout_mask() const + { + return fb_frame_get_channel_layout_mask(handle_); + } + void set_channel_layout_mask(uint64_t m) + { + fb_frame_set_channel_layout_mask(handle_, m); + } + + bool is_hw() const { return fb_frame_is_hw(handle_) != 0; } + int hw_transfer_data(const AVFrame *src) + { + return fb_frame_hw_transfer_data(handle_, src->handle_); + } + int get_buffer(int align) { return fb_frame_get_buffer(handle_, align); } + int make_writable() { return fb_frame_make_writable(handle_); } + + uint8_t *data(int plane) { return fb_frame_get_data(handle_, plane); } + const uint8_t *data(int plane) const + { + return fb_frame_get_data_const(handle_, plane); + } + void set_data(int plane, uint8_t *d) + { + fb_frame_set_data(handle_, plane, d); + } + int linesize(int plane) const + { + return fb_frame_get_linesize(handle_, plane); + } + void set_linesize(int plane, int l) + { + fb_frame_set_linesize(handle_, plane, l); + } + +private: + FBFrame *handle_; +}; + using AVFramePtr = std::shared_ptr; -inline AVFramePtr CreateAVFramePtr(AVFrame *f) +inline AVFramePtr CreateAVFramePtr(FBFrame *f) { - return std::shared_ptr(f, [](AVFrame *g) { av_frame_free(&g); }); + return std::make_shared(f); } inline AVFramePtr CreateAVFramePtr() { - return CreateAVFramePtr(av_frame_alloc()); + return std::make_shared(); } } diff --git a/app/common/channellayout.h b/app/common/channellayout.h deleted file mode 100644 index 830726cae..000000000 --- a/app/common/channellayout.h +++ /dev/null @@ -1,35 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2022 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#ifndef CHANNELLAYOUT_H -#define CHANNELLAYOUT_H - -/** - * Channel Layouts header - * - * We don't do much here at the moment, audio is a much simpler beast than video nowadays and FFmpeg seems to cover it - * fairly well. - */ - -extern "C" { -#include -} - -#endif // CHANNELLAYOUT_H diff --git a/app/common/ffmpegutils.cpp b/app/common/ffmpegutils.cpp index f6d9a5391..3730a29cf 100644 --- a/app/common/ffmpegutils.cpp +++ b/app/common/ffmpegutils.cpp @@ -24,135 +24,110 @@ namespace olive { -AVPixelFormat -FFmpegUtils::GetCompatiblePixelFormat(const AVPixelFormat &pix_fmt, - PixelFormat maximum) +int FFmpegUtils::GetCompatibleBridgePixelFormat(int pix_fmt, + PixelFormat maximum) { - AVPixelFormat possible_pix_fmts[4]; + int possible_pix_fmts[4]; - possible_pix_fmts[0] = AV_PIX_FMT_RGBA; + possible_pix_fmts[0] = FB_PIX_FMT_RGBA; if (maximum == PixelFormat::U8) { - possible_pix_fmts[1] = AV_PIX_FMT_NONE; + possible_pix_fmts[1] = FB_PIX_FMT_NONE; } else { - possible_pix_fmts[1] = AV_PIX_FMT_RGBA64; + possible_pix_fmts[1] = FB_PIX_FMT_RGBA64LE; if (maximum == PixelFormat::F32) { - possible_pix_fmts[2] = AV_PIX_FMT_RGBAF32; - possible_pix_fmts[3] = AV_PIX_FMT_NONE; + possible_pix_fmts[2] = FB_PIX_FMT_RGBAF32LE; + possible_pix_fmts[3] = FB_PIX_FMT_NONE; } else { - possible_pix_fmts[2] = AV_PIX_FMT_NONE; + possible_pix_fmts[2] = FB_PIX_FMT_NONE; } } - return avcodec_find_best_pix_fmt_of_list(possible_pix_fmts, pix_fmt, 1, - nullptr); + return fb_find_best_pix_fmt_of_list(possible_pix_fmts, pix_fmt); } -SampleFormat FFmpegUtils::GetNativeSampleFormat(const AVSampleFormat &smp_fmt) +SampleFormat FFmpegUtils::GetNativeSampleFormat(int smp_fmt) { switch (smp_fmt) { - case AV_SAMPLE_FMT_U8: + case FB_SAMPLE_FMT_U8: return SampleFormat::U8; - case AV_SAMPLE_FMT_S16: + case FB_SAMPLE_FMT_S16: return SampleFormat::S16; - case AV_SAMPLE_FMT_S32: + case FB_SAMPLE_FMT_S32: return SampleFormat::S32; - case AV_SAMPLE_FMT_S64: + case FB_SAMPLE_FMT_S64: return SampleFormat::S64; - case AV_SAMPLE_FMT_FLT: + case FB_SAMPLE_FMT_FLT: return SampleFormat::F32; - case AV_SAMPLE_FMT_DBL: + case FB_SAMPLE_FMT_DBL: return SampleFormat::F64; - case AV_SAMPLE_FMT_U8P: + case FB_SAMPLE_FMT_U8P: return SampleFormat::U8P; - case AV_SAMPLE_FMT_S16P: + case FB_SAMPLE_FMT_S16P: return SampleFormat::S16P; - case AV_SAMPLE_FMT_S32P: + case FB_SAMPLE_FMT_S32P: return SampleFormat::S32P; - case AV_SAMPLE_FMT_S64P: + case FB_SAMPLE_FMT_S64P: return SampleFormat::S64P; - case AV_SAMPLE_FMT_FLTP: + case FB_SAMPLE_FMT_FLTP: return SampleFormat::F32P; - case AV_SAMPLE_FMT_DBLP: + case FB_SAMPLE_FMT_DBLP: return SampleFormat::F64P; - case AV_SAMPLE_FMT_NONE: - case AV_SAMPLE_FMT_NB: + default: break; } return SampleFormat::INVALID; } -AVSampleFormat FFmpegUtils::GetFFmpegSampleFormat(const SampleFormat &smp_fmt) +int FFmpegUtils::GetFFmpegSampleFormat(const SampleFormat &smp_fmt) { switch (smp_fmt) { case SampleFormat::U8: - return AV_SAMPLE_FMT_U8; + return FB_SAMPLE_FMT_U8; case SampleFormat::S16: - return AV_SAMPLE_FMT_S16; + return FB_SAMPLE_FMT_S16; case SampleFormat::S32: - return AV_SAMPLE_FMT_S32; + return FB_SAMPLE_FMT_S32; case SampleFormat::S64: - return AV_SAMPLE_FMT_S64; + return FB_SAMPLE_FMT_S64; case SampleFormat::F32: - return AV_SAMPLE_FMT_FLT; + return FB_SAMPLE_FMT_FLT; case SampleFormat::F64: - return AV_SAMPLE_FMT_DBL; + return FB_SAMPLE_FMT_DBL; case SampleFormat::U8P: - return AV_SAMPLE_FMT_U8P; + return FB_SAMPLE_FMT_U8P; case SampleFormat::S16P: - return AV_SAMPLE_FMT_S16P; + return FB_SAMPLE_FMT_S16P; case SampleFormat::S32P: - return AV_SAMPLE_FMT_S32P; + return FB_SAMPLE_FMT_S32P; case SampleFormat::S64P: - return AV_SAMPLE_FMT_S64P; + return FB_SAMPLE_FMT_S64P; case SampleFormat::F32P: - return AV_SAMPLE_FMT_FLTP; + return FB_SAMPLE_FMT_FLTP; case SampleFormat::F64P: - return AV_SAMPLE_FMT_DBLP; + return FB_SAMPLE_FMT_DBLP; case SampleFormat::INVALID: case SampleFormat::COUNT: break; } - return AV_SAMPLE_FMT_NONE; + return FB_SAMPLE_FMT_NONE; } -int FFmpegUtils::GetSwsColorspaceFromAVColorSpace(AVColorSpace cs) -{ - switch (cs) { - case AVCOL_SPC_BT709: - return SWS_CS_ITU709; - case AVCOL_SPC_FCC: - return SWS_CS_FCC; - case AVCOL_SPC_BT470BG: - return SWS_CS_ITU624; - case AVCOL_SPC_SMPTE170M: - return SWS_CS_SMPTE170M; - case AVCOL_SPC_SMPTE240M: - return SWS_CS_SMPTE240M; - case AVCOL_SPC_BT2020_NCL: - return SWS_CS_BT2020; - default: - break; - } - - return SWS_CS_DEFAULT; -} - -AVPixelFormat FFmpegUtils::ConvertJPEGSpaceToRegularSpace(AVPixelFormat f) +int FFmpegUtils::ConvertJPEGSpaceToRegularSpace(int f) { switch (f) { - case AV_PIX_FMT_YUVJ420P: - return AV_PIX_FMT_YUV420P; - case AV_PIX_FMT_YUVJ422P: - return AV_PIX_FMT_YUV422P; - case AV_PIX_FMT_YUVJ444P: - return AV_PIX_FMT_YUV444P; - case AV_PIX_FMT_YUVJ440P: - return AV_PIX_FMT_YUV440P; - case AV_PIX_FMT_YUVJ411P: - return AV_PIX_FMT_YUV411P; + case FB_PIX_FMT_YUVJ420P: + return FB_PIX_FMT_YUV420P; + case FB_PIX_FMT_YUVJ422P: + return FB_PIX_FMT_YUV422P; + case FB_PIX_FMT_YUVJ444P: + return FB_PIX_FMT_YUV444P; + case FB_PIX_FMT_YUVJ440P: + return FB_PIX_FMT_YUV440P; + case FB_PIX_FMT_YUVJ411P: + return FB_PIX_FMT_YUV411P; default: break; } @@ -160,25 +135,21 @@ AVPixelFormat FFmpegUtils::ConvertJPEGSpaceToRegularSpace(AVPixelFormat f) return f; } -AVPixelFormat FFmpegUtils::GetFFmpegPixelFormat(const PixelFormat &pix_fmt, - int channel_layout) +int FFmpegUtils::GetFFmpegPixelFormat(const PixelFormat &pix_fmt, + int channel_layout) { if (channel_layout == VideoParams::kRGBChannelCount) { switch (pix_fmt) { case PixelFormat::U8: - return AV_PIX_FMT_RGB24; + return FB_PIX_FMT_RGB24; case PixelFormat::U10: - return AV_PIX_FMT_NONE; + return FB_PIX_FMT_NONE; case PixelFormat::U16: - return AV_PIX_FMT_RGB48; + return FB_PIX_FMT_RGB48LE; case PixelFormat::F16: -#ifdef HAVE_AV_PIX_FMT_RGBF16 - return AV_PIX_FMT_RGBF16; -#else - return AV_PIX_FMT_RGB48; -#endif + return FB_PIX_FMT_RGBF16LE; case PixelFormat::F32: - return AV_PIX_FMT_RGBF32; + return FB_PIX_FMT_RGBF32LE; case PixelFormat::INVALID: case PixelFormat::COUNT: break; @@ -186,26 +157,22 @@ AVPixelFormat FFmpegUtils::GetFFmpegPixelFormat(const PixelFormat &pix_fmt, } else if (channel_layout == VideoParams::kRGBAChannelCount) { switch (pix_fmt) { case PixelFormat::U8: - return AV_PIX_FMT_RGBA; + return FB_PIX_FMT_RGBA; case PixelFormat::U10: - return AV_PIX_FMT_NONE; + return FB_PIX_FMT_NONE; case PixelFormat::U16: - return AV_PIX_FMT_RGBA64; + return FB_PIX_FMT_RGBA64LE; case PixelFormat::F16: -#ifdef HAVE_AV_PIX_FMT_RGBAF16 - return AV_PIX_FMT_RGBAF16; -#else - return AV_PIX_FMT_RGBA64; -#endif + return FB_PIX_FMT_RGBAF16LE; case PixelFormat::F32: - return AV_PIX_FMT_RGBAF32; + return FB_PIX_FMT_RGBAF32LE; case PixelFormat::INVALID: case PixelFormat::COUNT: break; } } - return AV_PIX_FMT_NONE; + return FB_PIX_FMT_NONE; } PixelFormat FFmpegUtils::GetCompatiblePixelFormat(const PixelFormat &pix_fmt) diff --git a/app/common/ffmpegutils.h b/app/common/ffmpegutils.h index 0f38dc1ee..5d823feb3 100644 --- a/app/common/ffmpegutils.h +++ b/app/common/ffmpegutils.h @@ -22,15 +22,10 @@ #ifndef FFMPEGABSTRACTION_H #define FFMPEGABSTRACTION_H -extern "C" { -#include -#include -#include -} +#include #include -#include "common/avframeptr.h" #include "render/videoparams.h" namespace olive @@ -38,43 +33,46 @@ namespace olive using namespace core; +/** + * @brief C++ adapter mapping Olive's native enums to bridge pixel/sample + * formats + * + * All "FFmpeg" formats here are actually the opaque FBPixelFormat / + * FBSampleFormat constants of the ffmpeg_bridge library; no FFmpeg header + * or structure is ever seen by the editor. + */ class FFmpegUtils { public: /** - * @brief Returns an AVPixelFormat that can be used to convert a frame to a data type Olive supports with minimal data loss + * @brief Returns a bridge pixel format that can be used to convert a frame to a data type Olive supports with minimal data loss + * + * Named distinctly from the native PixelFormat overload below: with both + * taking a single argument, an unscoped enum argument would silently + * prefer an int overload over the PixelFormat one. */ - static AVPixelFormat - GetCompatiblePixelFormat(const AVPixelFormat &pix_fmt, - PixelFormat maximum = PixelFormat::INVALID); + static int GetCompatibleBridgePixelFormat( + int pix_fmt, PixelFormat maximum = PixelFormat::INVALID); /** - * @brief Returns a native pixel format that can be used to convert from a native frame to an AVFrame with minimal data loss + * @brief Returns a native pixel format that can be used to convert from a native frame to a bridge frame with minimal data loss */ static PixelFormat GetCompatiblePixelFormat(const PixelFormat &pix_fmt); /** - * @brief Returns an FFmpeg pixel format for a given native pixel format + * @brief Returns a bridge pixel format for a given native pixel format */ - static AVPixelFormat GetFFmpegPixelFormat(const PixelFormat &pix_fmt, - int channel_layout); + static int GetFFmpegPixelFormat(const PixelFormat &pix_fmt, + int channel_layout); /** - * @brief Returns a native sample format type for a given AVSampleFormat + * @brief Returns a native sample format type for a given bridge sample format */ - static SampleFormat GetNativeSampleFormat(const AVSampleFormat &smp_fmt); + static SampleFormat GetNativeSampleFormat(int smp_fmt); /** - * @brief Returns an FFmpeg sample format type for a given native type + * @brief Returns a bridge sample format type for a given native type */ - static AVSampleFormat GetFFmpegSampleFormat(const SampleFormat &smp_fmt); - - /** - * @brief Returns an SWS_CS_* macro from an AVColorSpace enum member - * - * Why aren't these the same thing anyway? And for that matter, why doesn't FFmpeg provide a - * convenience function to do this conversion for us? Who knows, but here we are. - */ - static int GetSwsColorspaceFromAVColorSpace(AVColorSpace cs); + static int GetFFmpegSampleFormat(const SampleFormat &smp_fmt); /** * @brief Convert "JPEG"/full-range colorspace to its regular counterpart @@ -83,7 +81,7 @@ public: * time being, FFmpeg still uses these JPEG spaces, so for simplicity (since we *are* color_range * aware), we use this function. */ - static AVPixelFormat ConvertJPEGSpaceToRegularSpace(AVPixelFormat f); + static int ConvertJPEGSpaceToRegularSpace(int f); }; } diff --git a/app/config/config.cpp b/app/config/config.cpp index 8b248de11..796676e69 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -204,7 +204,8 @@ void Config::SetDefaults() SetEntryInternal(QStringLiteral("AudioOutputSampleRate"), NodeValue::kInt, 48000); SetEntryInternal(QStringLiteral("AudioOutputChannelLayout"), - NodeValue::kInt, AV_CH_LAYOUT_STEREO); + NodeValue::kInt, + QVariant::fromValue(static_cast(kChannelLayoutStereo))); SetEntryInternal( QStringLiteral("AudioOutputSampleFormat"), NodeValue::kText, QString::fromStdString(SampleFormat(SampleFormat::S16).to_string())); @@ -216,7 +217,8 @@ void Config::SetDefaults() SetEntryInternal(QStringLiteral("AudioRecordingSampleRate"), NodeValue::kInt, 48000); SetEntryInternal(QStringLiteral("AudioRecordingChannelLayout"), - NodeValue::kInt, AV_CH_LAYOUT_STEREO); + NodeValue::kInt, + QVariant::fromValue(static_cast(kChannelLayoutStereo))); SetEntryInternal( QStringLiteral("AudioRecordingSampleFormat"), NodeValue::kText, QString::fromStdString(SampleFormat(SampleFormat::S16).to_string())); @@ -251,7 +253,7 @@ void Config::SetDefaults() NodeValue::kInt, 48000); SetEntryInternal( QStringLiteral("DefaultSequenceAudioLayout"), NodeValue::kInt, - QVariant::fromValue(static_cast(AV_CH_LAYOUT_STEREO))); + QVariant::fromValue(static_cast(kChannelLayoutStereo))); // Online/offline settings SetEntryInternal(QStringLiteral("OnlinePixelFormat"), NodeValue::kInt, diff --git a/app/dialog/sequence/sequencedialogparametertab.cpp b/app/dialog/sequence/sequencedialogparametertab.cpp index 420a111de..5954b5187 100644 --- a/app/dialog/sequence/sequencedialogparametertab.cpp +++ b/app/dialog/sequence/sequencedialogparametertab.cpp @@ -145,14 +145,12 @@ void SequenceDialogParameterTab::PresetChanged(const SequencePreset &preset) void SequenceDialogParameterTab::SavePresetClicked() { - AVChannelLayout layout = GetSelectedAudioChannelLayout(); emit SaveParametersAsPreset(SequencePreset( QString(), GetSelectedVideoWidth(), GetSelectedVideoHeight(), GetSelectedVideoFrameRate(), GetSelectedVideoPixelAspect(), - GetSelectedVideoInterlacingMode(), GetSelectedAudioSampleRate(), layout, - GetSelectedPreviewResolution(), GetSelectedPreviewFormat(), - GetSelectedPreviewAutoCache())); - av_channel_layout_uninit(&layout); + GetSelectedVideoInterlacingMode(), GetSelectedAudioSampleRate(), + GetSelectedAudioChannelLayout(), GetSelectedPreviewResolution(), + GetSelectedPreviewFormat(), GetSelectedPreviewAutoCache())); } void SequenceDialogParameterTab::UpdatePreviewResolutionLabel() diff --git a/app/dialog/sequence/sequencedialogparametertab.h b/app/dialog/sequence/sequencedialogparametertab.h index a11e1abbd..8dc124902 100644 --- a/app/dialog/sequence/sequencedialogparametertab.h +++ b/app/dialog/sequence/sequencedialogparametertab.h @@ -67,7 +67,7 @@ public: return audio_sample_rate_field_->GetSampleRate(); } - [[nodiscard]] AVChannelLayout GetSelectedAudioChannelLayout() const + [[nodiscard]] uint64_t GetSelectedAudioChannelLayout() const { return audio_channels_field_->GetChannelLayout(); } diff --git a/app/dialog/sequence/sequencedialogpresettab.cpp b/app/dialog/sequence/sequencedialogpresettab.cpp index 8400f0a16..5f888a872 100644 --- a/app/dialog/sequence/sequencedialogpresettab.cpp +++ b/app/dialog/sequence/sequencedialogpresettab.cpp @@ -114,8 +114,7 @@ SequenceDialogPresetTab::CreateHDPresetFolder(const QString &name, int width, OLIVE_CONFIG("OfflinePixelFormat").toInt()); const bool default_autocache = false; QTreeWidgetItem *parent = CreateFolder(name); - AVChannelLayout layout; - av_channel_layout_from_mask(&layout, AV_CH_LAYOUT_STEREO); + const uint64_t layout = kChannelLayoutStereo; AddStandardItem(parent, std::make_shared( tr("%1 23.976 FPS").arg(name), width, height, @@ -146,7 +145,6 @@ SequenceDialogPresetTab::CreateHDPresetFolder(const QString &name, int width, rational(60000, 1001), VideoParams::kPixelAspectSquare, VideoParams::kInterlaceNone, 48000, layout, divider, default_format, default_autocache)); - av_channel_layout_uninit(&layout); return parent; } @@ -159,8 +157,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateSDPresetFolder( const bool default_autocache = false; QTreeWidgetItem *parent = CreateFolder(name); preset_tree_->addTopLevelItem(parent); - AVChannelLayout layout; - av_channel_layout_from_mask(&layout, AV_CH_LAYOUT_STEREO); + const uint64_t layout = kChannelLayoutStereo; AddStandardItem( parent, std::make_shared( tr("%1 Standard").arg(name), width, height, frame_rate, @@ -171,7 +168,6 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateSDPresetFolder( tr("%1 Widescreen").arg(name), width, height, frame_rate, wide_par, VideoParams::kInterlacedBottomFirst, 48000, layout, divider, default_format, default_autocache)); - av_channel_layout_uninit(&layout); return parent; } diff --git a/app/dialog/sequence/sequencepreset.h b/app/dialog/sequence/sequencepreset.h index f6c1b6352..f36bd5a54 100644 --- a/app/dialog/sequence/sequencepreset.h +++ b/app/dialog/sequence/sequencepreset.h @@ -39,7 +39,7 @@ public: SequencePreset(const QString &name, int width, int height, const rational &frame_rate, const rational &pixel_aspect, VideoParams::Interlacing interlacing, int sample_rate, - AVChannelLayout &channel_layout, int preview_divider, + uint64_t channel_layout, int preview_divider, PixelFormat preview_format, bool preview_autocache) : width_(width) , height_(height) @@ -76,8 +76,7 @@ public: } else if (reader->name() == QStringLiteral("samplerate")) { sample_rate_ = reader->readElementText().toInt(); } else if (reader->name() == QStringLiteral("chlayout")) { - channel_layout_.u.mask = - reader->readElementText().toULongLong(); + channel_layout_ = reader->readElementText().toULongLong(); } else if (reader->name() == QStringLiteral("divider")) { preview_divider_ = reader->readElementText().toInt(); } else if (reader->name() == QStringLiteral("format")) { @@ -109,7 +108,7 @@ public: writer->writeTextElement(QStringLiteral("samplerate"), QString::number(sample_rate_)); writer->writeTextElement(QStringLiteral("chlayout"), - QString::number(channel_layout_.u.mask)); + QString::number(channel_layout_)); writer->writeTextElement(QStringLiteral("divider"), QString::number(preview_divider_)); writer->writeTextElement(QStringLiteral("format"), @@ -148,7 +147,7 @@ public: return sample_rate_; } - AVChannelLayout channel_layout() const + uint64_t channel_layout() const { return channel_layout_; } @@ -175,7 +174,7 @@ private: rational pixel_aspect_; VideoParams::Interlacing interlacing_; int sample_rate_; - AVChannelLayout channel_layout_; + uint64_t channel_layout_; int preview_divider_; PixelFormat preview_format_; bool preview_autocache_; diff --git a/app/main.cpp b/app/main.cpp index 3f7dba5d0..396bc8345 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -28,10 +28,6 @@ */ #include "OliveHost.h" -extern "C" { -#include -#include -} #include @@ -406,14 +402,6 @@ int main(int argc, char *argv[]) } #endif - // Register FFmpeg codecs and filters (deprecated in 4.0+) -#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100) - av_register_all(); -#endif -#if LIBAVFILTER_VERSION_INT < AV_VERSION_INT(7, 14, 100) - avfilter_register_all(); -#endif - // Enable Google Crashpad if compiled with it #ifdef USE_CRASHPAD if (!InitializeCrashpad()) { diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 1041df493..21ef97f76 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -236,12 +236,10 @@ void ViewerOutput::set_default_parameters() OLIVE_CONFIG("DefaultSequenceInterlacing") .value(), 1)); - AVChannelLayout layout; - av_channel_layout_from_mask( - &layout, OLIVE_CONFIG("DefaultSequenceAudioLayout").toULongLong()); SetAudioParams( AudioParams(OLIVE_CONFIG("DefaultSequenceAudioFrequency").toInt(), - layout, kDefaultSampleFormat)); + OLIVE_CONFIG("DefaultSequenceAudioLayout").toULongLong(), + kDefaultSampleFormat)); } void ViewerOutput::InvalidateCache(const TimeRange &range, const QString &from, diff --git a/app/node/project/serializer/typeserializer.cpp b/app/node/project/serializer/typeserializer.cpp index c4d1e7a02..9d58f0030 100644 --- a/app/node/project/serializer/typeserializer.cpp +++ b/app/node/project/serializer/typeserializer.cpp @@ -59,7 +59,7 @@ void TypeSerializer::SaveAudioParams(QXmlStreamWriter *writer, writer->writeTextElement(QStringLiteral("samplerate"), QString::number(a.sample_rate())); writer->writeTextElement(QStringLiteral("channellayout"), - QString::number(a.channel_layout().u.mask)); + QString::number(a.channel_layout())); writer->writeTextElement(QStringLiteral("format"), QString::fromStdString(a.format().to_string())); writer->writeTextElement(QStringLiteral("enabled"), diff --git a/app/pluginSupport/OliveClip.cpp b/app/pluginSupport/OliveClip.cpp index f34bc5ed6..8d01d28de 100644 --- a/app/pluginSupport/OliveClip.cpp +++ b/app/pluginSupport/OliveClip.cpp @@ -37,12 +37,16 @@ #endif #include "common/ffmpegutils.h" #include "render/renderer.h" -extern "C" { -#include -#include -} +#include namespace { +// The bridge header only defines the little-endian pixel formats. FFmpeg +// numbers each big-endian variant immediately before its little-endian +// counterpart (BE == LE - 1), so derive the BE constants used below. +constexpr int FB_PIX_FMT_GRAYF32BE = FB_PIX_FMT_GRAYF32LE - 1; +constexpr int FB_PIX_FMT_RGBF32BE = FB_PIX_FMT_RGBF32LE - 1; +constexpr int FB_PIX_FMT_RGBAF32BE = FB_PIX_FMT_RGBAF32LE - 1; + const std::string kBitDepthNoneStr(kOfxBitDepthNone); const std::string kBitDepthByteStr(kOfxBitDepthByte); const std::string kBitDepthShortStr(kOfxBitDepthShort); @@ -68,48 +72,48 @@ static int BytesToPixels(int byte_linesize, const olive::VideoParams ¶ms) return byte_linesize / bytes_per_pixel; } -static int PackedFloatChannels(AVPixelFormat fmt) +static int PackedFloatChannels(int fmt) { switch (fmt) { - case AV_PIX_FMT_GRAYF32LE: - case AV_PIX_FMT_GRAYF32BE: + case FB_PIX_FMT_GRAYF32LE: + case FB_PIX_FMT_GRAYF32BE: return 1; - case AV_PIX_FMT_RGBF32LE: - case AV_PIX_FMT_RGBF32BE: + case FB_PIX_FMT_RGBF32LE: + case FB_PIX_FMT_RGBF32BE: return 3; - case AV_PIX_FMT_RGBAF32LE: - case AV_PIX_FMT_RGBAF32BE: + case FB_PIX_FMT_RGBAF32LE: + case FB_PIX_FMT_RGBAF32BE: return 4; default: return 0; } } -static bool PackedDstInfo(AVPixelFormat fmt, int *channels, +static bool PackedDstInfo(int fmt, int *channels, int *bytes_per_component) { switch (fmt) { - case AV_PIX_FMT_GRAY8: + case FB_PIX_FMT_GRAY8: *channels = 1; *bytes_per_component = 1; return true; - case AV_PIX_FMT_RGB24: + case FB_PIX_FMT_RGB24: *channels = 3; *bytes_per_component = 1; return true; - case AV_PIX_FMT_RGBA: + case FB_PIX_FMT_RGBA: *channels = 4; *bytes_per_component = 1; return true; - case AV_PIX_FMT_GRAY16LE: + case FB_PIX_FMT_GRAY16LE: *channels = 1; *bytes_per_component = 2; return true; - case AV_PIX_FMT_RGB48LE: + case FB_PIX_FMT_RGB48LE: *channels = 3; *bytes_per_component = 2; return true; - case AV_PIX_FMT_RGBA64LE: + case FB_PIX_FMT_RGBA64LE: *channels = 4; *bytes_per_component = 2; return true; @@ -126,28 +130,23 @@ ReadbackTextureToFrame(olive::TexturePtr texture, return nullptr; } - AVPixelFormat pix_fmt = olive::FFmpegUtils::GetFFmpegPixelFormat( + int pix_fmt = olive::FFmpegUtils::GetFFmpegPixelFormat( params.format(), params.channel_count()); - if (pix_fmt == AV_PIX_FMT_NONE) { + if (pix_fmt == FB_PIX_FMT_NONE) { return nullptr; } - const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt); - if (!desc) { - return nullptr; - } - - if (!(desc->flags & AV_PIX_FMT_FLAG_PLANAR)) { + if (!fb_pix_fmt_is_planar(pix_fmt)) { olive::AVFramePtr frame = olive::CreateAVFramePtr(); - frame->format = pix_fmt; - frame->width = params.width(); - frame->height = params.height(); - if (av_frame_get_buffer(frame.get(), 0) < 0) { + frame->set_format(pix_fmt); + frame->set_width(params.width()); + frame->set_height(params.height()); + if (frame->get_buffer(0) < 0) { return nullptr; } - const int linesize_pixels = BytesToPixels(frame->linesize[0], params); + const int linesize_pixels = BytesToPixels(frame->linesize(0), params); texture->renderer()->DownloadFromTexture( - texture->id(), params, frame->data[0], linesize_pixels); + texture->id(), params, frame->data(0), linesize_pixels); return frame; } @@ -157,48 +156,57 @@ ReadbackTextureToFrame(olive::TexturePtr texture, params.interlacing(), params.divider()); olive::AVFramePtr rgba_frame = olive::CreateAVFramePtr(); - rgba_frame->format = AV_PIX_FMT_RGBA; - rgba_frame->width = params.width(); - rgba_frame->height = params.height(); - if (av_frame_get_buffer(rgba_frame.get(), 0) < 0) { + rgba_frame->set_format(FB_PIX_FMT_RGBA); + rgba_frame->set_width(params.width()); + rgba_frame->set_height(params.height()); + if (rgba_frame->get_buffer(0) < 0) { return nullptr; } const int linesize_pixels = - BytesToPixels(rgba_frame->linesize[0], rgba_params); + BytesToPixels(rgba_frame->linesize(0), rgba_params); texture->renderer()->DownloadFromTexture( - texture->id(), rgba_params, rgba_frame->data[0], linesize_pixels); + texture->id(), rgba_params, rgba_frame->data(0), linesize_pixels); olive::AVFramePtr dst = olive::CreateAVFramePtr(); - dst->format = pix_fmt; - dst->width = params.width(); - dst->height = params.height(); - if (av_frame_get_buffer(dst.get(), 0) < 0) { + dst->set_format(pix_fmt); + dst->set_width(params.width()); + dst->set_height(params.height()); + if (dst->get_buffer(0) < 0) { return rgba_frame; } - SwsContext *sws_ctx = sws_getContext( - rgba_frame->width, rgba_frame->height, - static_cast(rgba_frame->format), dst->width, dst->height, - pix_fmt, SWS_POINT, nullptr, nullptr, nullptr); - if (!sws_ctx) { + FBScaler *scaler = fb_scaler_create( + rgba_frame->width(), rgba_frame->height(), rgba_frame->format(), + dst->width(), dst->height(), pix_fmt, FB_SCALER_POINT); + if (!scaler) { return rgba_frame; } - sws_scale(sws_ctx, rgba_frame->data, rgba_frame->linesize, 0, - rgba_frame->height, dst->data, dst->linesize); - sws_freeContext(sws_ctx); + uint8_t *src_data[4]; + int src_linesize[4]; + uint8_t *dst_data[4]; + int dst_linesize[4]; + for (int i = 0; i < 4; ++i) { + src_data[i] = rgba_frame->data(i); + src_linesize[i] = rgba_frame->linesize(i); + dst_data[i] = dst->data(i); + dst_linesize[i] = dst->linesize(i); + } + fb_scaler_scale_slices(scaler, src_data, src_linesize, + rgba_frame->height(), dst_data, dst_linesize); + fb_scaler_free(&scaler); return dst; } static olive::AVFramePtr ConvertPackedFloatFrame(olive::AVFramePtr src, - AVPixelFormat dst_fmt) + int dst_fmt) { - if (!src || !src->data[0]) { + if (!src || !src->data(0)) { return nullptr; } const int src_channels = - PackedFloatChannels(static_cast(src->format)); + PackedFloatChannels(src->format()); if (src_channels == 0) { return nullptr; } @@ -210,23 +218,23 @@ static olive::AVFramePtr ConvertPackedFloatFrame(olive::AVFramePtr src, } olive::AVFramePtr dst = olive::CreateAVFramePtr(); - dst->format = dst_fmt; - dst->width = src->width; - dst->height = src->height; - if (av_frame_get_buffer(dst.get(), 0) < 0) { + dst->set_format(dst_fmt); + dst->set_width(src->width()); + dst->set_height(src->height()); + if (dst->get_buffer(0) < 0) { return nullptr; } auto clamp01 = [](float v) -> float { return std::clamp(v, 0.0f, 1.0f); }; - for (int y = 0; y < src->height; ++y) { + for (int y = 0; y < src->height(); ++y) { const float *src_row = reinterpret_cast( - src->data[0] + y * src->linesize[0]); - uint8_t *dst_row = dst->data[0] + y * dst->linesize[0]; + src->data(0) + y * src->linesize(0)); + uint8_t *dst_row = dst->data(0) + y * dst->linesize(0); if (bytes_per_component == 2) { auto *dst_row_u16 = reinterpret_cast(dst_row); - for (int x = 0; x < src->width; ++x) { + for (int x = 0; x < src->width(); ++x) { const float *pix = src_row + x * src_channels; float r = pix[0]; float g = (src_channels > 1) ? pix[1] : r; @@ -250,7 +258,7 @@ static olive::AVFramePtr ConvertPackedFloatFrame(olive::AVFramePtr src, } } } else { - for (int x = 0; x < src->width; ++x) { + for (int x = 0; x < src->width(); ++x) { const float *pix = src_row + x * src_channels; float r = pix[0]; float g = (src_channels > 1) ? pix[1] : r; @@ -613,12 +621,12 @@ void olive::plugin::OliveClipInstance::setInputTexture(TexturePtr texture, } AVFramePtr frame = texture->frame(); - if (!frame || !frame->data[0]) { + if (!frame || !frame->data(0)) { frame = ReadbackTextureToFrame(texture, params_); } - AVPixelFormat expected_fmt = FFmpegUtils::GetFFmpegPixelFormat( + int expected_fmt = FFmpegUtils::GetFFmpegPixelFormat( params_.format(), params_.channel_count()); - if (expected_fmt == AV_PIX_FMT_NONE) { + if (expected_fmt == FB_PIX_FMT_NONE) { return; } OfxRectI bounds = { 0, 0, params_.width(), params_.height() }; @@ -646,7 +654,7 @@ void olive::plugin::OliveClipInstance::setInputTexture(TexturePtr texture, return; } - if (!frame || !frame->data[0]) { + if (!frame || !frame->data(0)) { std::memset(dst, 0, image->row_bytes() * image->height()); return; } @@ -657,8 +665,8 @@ void olive::plugin::OliveClipInstance::setInputTexture(TexturePtr texture, // and SIGSEGV on Apple Silicon (where (int)NaN often evaluates to 0 or // INT_MIN, causing huge offsets into bgrid._data). if (params_.format() == core::PixelFormat::F32) { - const float *fptr = reinterpret_cast(frame->data[0]); - int row_floats = frame->linesize[0] / static_cast(sizeof(float)); + const float *fptr = reinterpret_cast(frame->data(0)); + int row_floats = frame->linesize(0) / static_cast(sizeof(float)); bool has_nan = false; for (int y = 0; y < params_.height() && !has_nan; ++y) { for (int x = 0; x < params_.width() * params_.channel_count(); @@ -684,9 +692,9 @@ void olive::plugin::OliveClipInstance::setInputTexture(TexturePtr texture, } AVFramePtr src_frame = frame; - if (frame->format != expected_fmt || frame->width != params_.width() || - frame->height != params_.height()) { - if (PackedFloatChannels(static_cast(frame->format)) > + if (frame->format() != expected_fmt || frame->width() != params_.width() || + frame->height() != params_.height()) { + if (PackedFloatChannels(frame->format()) > 0) { AVFramePtr converted = ConvertPackedFloatFrame(frame, expected_fmt); if (converted) { @@ -695,25 +703,34 @@ void olive::plugin::OliveClipInstance::setInputTexture(TexturePtr texture, } } AVFramePtr converted = CreateAVFramePtr(); - converted->format = expected_fmt; - converted->width = params_.width(); - converted->height = params_.height(); - if (av_frame_get_buffer(converted.get(), 0) < 0) { + converted->set_format(expected_fmt); + converted->set_width(params_.width()); + converted->set_height(params_.height()); + if (converted->get_buffer(0) < 0) { return; } - SwsContext *sws_ctx = sws_getContext( - frame->width, frame->height, - static_cast(frame->format), converted->width, - converted->height, static_cast(converted->format), - SWS_POINT, nullptr, nullptr, nullptr); - if (!sws_ctx) { + FBScaler *scaler = fb_scaler_create( + frame->width(), frame->height(), frame->format(), + converted->width(), converted->height(), converted->format(), + FB_SCALER_POINT); + if (!scaler) { return; } - sws_scale(sws_ctx, frame->data, frame->linesize, 0, frame->height, - converted->data, converted->linesize); - sws_freeContext(sws_ctx); + uint8_t *src_data[4]; + int src_linesize[4]; + uint8_t *dst_data[4]; + int dst_linesize[4]; + for (int i = 0; i < 4; ++i) { + src_data[i] = frame->data(i); + src_linesize[i] = frame->linesize(i); + dst_data[i] = converted->data(i); + dst_linesize[i] = converted->linesize(i); + } + fb_scaler_scale_slices(scaler, src_data, src_linesize, frame->height(), + dst_data, dst_linesize); + fb_scaler_free(&scaler); src_frame = converted; } @@ -722,13 +739,13 @@ copy_pixels: int bytes_per_component = params_.format().byte_count(); int bytes_per_row = params_.width() * params_.channel_count() * bytes_per_component; - int src_row_bytes = src_frame->linesize[0]; + int src_row_bytes = src_frame->linesize(0); int dst_row_bytes = image->row_bytes(); int copy_bytes = std::min(bytes_per_row, std::min(src_row_bytes, dst_row_bytes)); - int copy_height = std::min(image->height(), src_frame->height); + int copy_height = std::min(image->height(), src_frame->height()); - const uint8_t *src = src_frame->data[0]; + const uint8_t *src = src_frame->data(0); if (params_.format() == core::PixelFormat::F32) { const float *src_f = reinterpret_cast(src); float *dst_f = reinterpret_cast(dst); diff --git a/app/render/plugin/pluginrenderer.cpp b/app/render/plugin/pluginrenderer.cpp index 03decea34..66cd52bf0 100644 --- a/app/render/plugin/pluginrenderer.cpp +++ b/app/render/plugin/pluginrenderer.cpp @@ -51,15 +51,21 @@ #include "ofxhUtilities.h" #include "ofxGPURender.h" #include "olive/core/util/color.h" -extern "C" { -#include -#include -#include -} +#include + +// The bridge header only defines the little-endian pixel formats. FFmpeg +// numbers each big-endian variant immediately before its little-endian +// counterpart (BE == LE - 1), so derive the BE constants used below. +constexpr int FB_PIX_FMT_GRAY16BE = FB_PIX_FMT_GRAY16LE - 1; +constexpr int FB_PIX_FMT_RGB48BE = FB_PIX_FMT_RGB48LE - 1; +constexpr int FB_PIX_FMT_RGBA64BE = FB_PIX_FMT_RGBA64LE - 1; +constexpr int FB_PIX_FMT_GRAYF32BE = FB_PIX_FMT_GRAYF32LE - 1; +constexpr int FB_PIX_FMT_RGBF32BE = FB_PIX_FMT_RGBF32LE - 1; +constexpr int FB_PIX_FMT_RGBAF32BE = FB_PIX_FMT_RGBAF32LE - 1; // 作用:从 OFX Image 属性推导 FFmpeg 像素格式,并返回每像素字节数。 // Purpose: Infer FFmpeg pixel format from OFX image properties and return bytes-per-pixel. -static AVPixelFormat +static int GetOfxAVPixelFormat(const OFX::Host::ImageEffect::Image &image, int *bytes_per_pixel) { @@ -88,36 +94,29 @@ GetOfxAVPixelFormat(const OFX::Host::ImageEffect::Image &image, channel_count = 1; } - AVPixelFormat pix_fmt = + int pix_fmt = olive::FFmpegUtils::GetFFmpegPixelFormat(pixel_format, channel_count); - if (pix_fmt == AV_PIX_FMT_NONE && channel_count == 1) { + if (pix_fmt == FB_PIX_FMT_NONE && channel_count == 1) { if (pixel_format == olive::core::PixelFormat::U8) { - pix_fmt = AV_PIX_FMT_GRAY8; + pix_fmt = FB_PIX_FMT_GRAY8; } else if (pixel_format == olive::core::PixelFormat::U16) { - pix_fmt = AV_PIX_FMT_GRAY16LE; + pix_fmt = FB_PIX_FMT_GRAY16LE; } else if (pixel_format == olive::core::PixelFormat::F16) { -#ifdef HAVE_AV_PIX_FMT_GRAYF16 - pix_fmt = AV_PIX_FMT_GRAYF16; -#else - pix_fmt = AV_PIX_FMT_GRAY16LE; -#endif + pix_fmt = FB_PIX_FMT_GRAYF16LE; } else if (pixel_format == olive::core::PixelFormat::F32) { - pix_fmt = AV_PIX_FMT_GRAYF32; + pix_fmt = FB_PIX_FMT_GRAYF32LE; } } - if (pix_fmt == AV_PIX_FMT_NONE) { - return AV_PIX_FMT_NONE; + if (pix_fmt == FB_PIX_FMT_NONE) { + return FB_PIX_FMT_NONE; } - const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt); - if (!desc) { - return AV_PIX_FMT_NONE; - } - - int bits_per_pixel = av_get_bits_per_pixel(desc); + // fb_pix_fmt_bits_per_pixel returns 0 for unknown formats, which also + // covers the old "av_pix_fmt_desc_get returned nullptr" case. + int bits_per_pixel = fb_pix_fmt_bits_per_pixel(pix_fmt); if (bits_per_pixel <= 0 || bits_per_pixel % 8 != 0) { - return AV_PIX_FMT_NONE; + return FB_PIX_FMT_NONE; } *bytes_per_pixel = bits_per_pixel / 8; @@ -285,7 +284,7 @@ static void ApplyParamOverrides(OFX::Host::ImageEffect::Instance &instance, } } -static AVPixelFormat +static int GetDestinationAVPixelFormat(const olive::VideoParams ¶ms); // 作用:读取 clip 偏好(像素深度与分量)并更新 VideoParams。 @@ -456,7 +455,7 @@ static int ConversionCost(const olive::VideoParams &src, // Purpose: Check if params map to a valid AVPixelFormat. static bool ParamsConvertible(const olive::VideoParams ¶ms) { - return GetDestinationAVPixelFormat(params) != AV_PIX_FMT_NONE; + return GetDestinationAVPixelFormat(params) != FB_PIX_FMT_NONE; } // 作用:在 clip 偏好无效时,选择一个插件支持的输出格式。 @@ -692,8 +691,8 @@ create_avframe_from_ofx_image(OFX::Host::ImageEffect::Image &image) } int bytes_per_pixel = 0; - AVPixelFormat pix_fmt = GetOfxAVPixelFormat(image, &bytes_per_pixel); - if (pix_fmt == AV_PIX_FMT_NONE || bytes_per_pixel <= 0) { + int pix_fmt = GetOfxAVPixelFormat(image, &bytes_per_pixel); + if (pix_fmt == FB_PIX_FMT_NONE || bytes_per_pixel <= 0) { qWarning().noquote() << "OFX output image has unsupported pixel format depth=" << QString::fromStdString( @@ -713,20 +712,20 @@ create_avframe_from_ofx_image(OFX::Host::ImageEffect::Image &image) src += bounds[1] * row_bytes + bounds[0] * bytes_per_pixel; olive::AVFramePtr frame = olive::CreateAVFramePtr(); - frame->width = width; - frame->height = height; - frame->format = pix_fmt; + frame->set_width(width); + frame->set_height(height); + frame->set_format(pix_fmt); - if (av_frame_get_buffer(frame.get(), 0) < 0) { + if (frame->get_buffer(0) < 0) { return nullptr; } const int copy_bytes = width * bytes_per_pixel; - if (frame->linesize[0] == row_bytes && row_bytes == copy_bytes) { - std::memcpy(frame->data[0], src, copy_bytes * height); + if (frame->linesize(0) == row_bytes && row_bytes == copy_bytes) { + std::memcpy(frame->data(0), src, copy_bytes * height); } else { for (int y = 0; y < height; ++y) { - std::memcpy(frame->data[0] + y * frame->linesize[0], + std::memcpy(frame->data(0) + y * frame->linesize(0), src + y * row_bytes, copy_bytes); } } @@ -797,45 +796,45 @@ create_avframe_from_ofx_image_with_params(OFX::Host::ImageEffect::Image &image, if (needs_conversion) { // Create source frame with actual format - AVPixelFormat src_fmt = AV_PIX_FMT_NONE; + int src_fmt = FB_PIX_FMT_NONE; if (src_channel_count == 4) { if (src_bytes_per_component == 1) - src_fmt = AV_PIX_FMT_RGBA; + src_fmt = FB_PIX_FMT_RGBA; else if (src_bytes_per_component == 2) - src_fmt = AV_PIX_FMT_RGBA64LE; + src_fmt = FB_PIX_FMT_RGBA64LE; else if (src_bytes_per_component == 4) - src_fmt = AV_PIX_FMT_RGBAF32LE; + src_fmt = FB_PIX_FMT_RGBAF32LE; } else if (src_channel_count == 3) { if (src_bytes_per_component == 1) - src_fmt = AV_PIX_FMT_RGB24; + src_fmt = FB_PIX_FMT_RGB24; else if (src_bytes_per_component == 2) - src_fmt = AV_PIX_FMT_RGB48LE; + src_fmt = FB_PIX_FMT_RGB48LE; else if (src_bytes_per_component == 4) - src_fmt = AV_PIX_FMT_RGBF32LE; + src_fmt = FB_PIX_FMT_RGBF32LE; } else if (src_channel_count == 1) { if (src_bytes_per_component == 1) - src_fmt = AV_PIX_FMT_GRAY8; + src_fmt = FB_PIX_FMT_GRAY8; else if (src_bytes_per_component == 2) - src_fmt = AV_PIX_FMT_GRAY16LE; + src_fmt = FB_PIX_FMT_GRAY16LE; else if (src_bytes_per_component == 4) - src_fmt = AV_PIX_FMT_GRAYF32LE; + src_fmt = FB_PIX_FMT_GRAYF32LE; } - if (src_fmt != AV_PIX_FMT_NONE) { + if (src_fmt != FB_PIX_FMT_NONE) { olive::AVFramePtr src_frame = olive::CreateAVFramePtr(); - src_frame->width = width; - src_frame->height = height; - src_frame->format = src_fmt; - if (av_frame_get_buffer(src_frame.get(), 0) >= 0) { + src_frame->set_width(width); + src_frame->set_height(height); + src_frame->set_format(src_fmt); + if (src_frame->get_buffer(0) >= 0) { // Copy source data row by row (or as a single block if strides match) const int copy_bytes = width * src_bytes_per_pixel; - if (src_frame->linesize[0] == row_bytes && + if (src_frame->linesize(0) == row_bytes && row_bytes == copy_bytes) { - memcpy(src_frame->data[0], src, copy_bytes * height); + memcpy(src_frame->data(0), src, copy_bytes * height); } else { for (int y = 0; y < height; ++y) { - memcpy(src_frame->data[0] + y * src_frame->linesize[0], + memcpy(src_frame->data(0) + y * src_frame->linesize(0), src + y * row_bytes, copy_bytes); } } @@ -853,24 +852,24 @@ create_avframe_from_ofx_image_with_params(OFX::Host::ImageEffect::Image &image, } // Same format - direct copy - AVPixelFormat pix_fmt = GetDestinationAVPixelFormat(params); - if (pix_fmt == AV_PIX_FMT_NONE) { + int pix_fmt = GetDestinationAVPixelFormat(params); + if (pix_fmt == FB_PIX_FMT_NONE) { return nullptr; } olive::AVFramePtr frame = olive::CreateAVFramePtr(); - frame->width = width; - frame->height = height; - frame->format = pix_fmt; + frame->set_width(width); + frame->set_height(height); + frame->set_format(pix_fmt); - if (av_frame_get_buffer(frame.get(), 0) < 0) { + if (frame->get_buffer(0) < 0) { return nullptr; } const int copy_bytes = width * std::min(src_bytes_per_pixel, dst_bytes_per_pixel); for (int y = 0; y < height; ++y) { - memcpy(frame->data[0] + y * frame->linesize[0], src + y * row_bytes, + memcpy(frame->data(0) + y * frame->linesize(0), src + y * row_bytes, copy_bytes); } @@ -879,24 +878,20 @@ create_avframe_from_ofx_image_with_params(OFX::Host::ImageEffect::Image &image, // 作用:将 VideoParams 映射为最终输出的 AVPixelFormat。 // Purpose: Map VideoParams to the final AVPixelFormat. -static AVPixelFormat +static int GetDestinationAVPixelFormat(const olive::VideoParams ¶ms) { - AVPixelFormat pix_fmt = olive::FFmpegUtils::GetFFmpegPixelFormat( + int pix_fmt = olive::FFmpegUtils::GetFFmpegPixelFormat( params.format(), params.channel_count()); - if (pix_fmt == AV_PIX_FMT_NONE && params.channel_count() == 1) { + if (pix_fmt == FB_PIX_FMT_NONE && params.channel_count() == 1) { if (params.format() == olive::core::PixelFormat::U8) { - pix_fmt = AV_PIX_FMT_GRAY8; + pix_fmt = FB_PIX_FMT_GRAY8; } else if (params.format() == olive::core::PixelFormat::U16) { - pix_fmt = AV_PIX_FMT_GRAY16LE; + pix_fmt = FB_PIX_FMT_GRAY16LE; } else if (params.format() == olive::core::PixelFormat::F16) { -#ifdef HAVE_AV_PIX_FMT_GRAYF16 - pix_fmt = AV_PIX_FMT_GRAYF16; -#else - pix_fmt = AV_PIX_FMT_GRAY16LE; -#endif + pix_fmt = FB_PIX_FMT_GRAYF16LE; } else if (params.format() == olive::core::PixelFormat::F32) { - pix_fmt = AV_PIX_FMT_GRAYF32; + pix_fmt = FB_PIX_FMT_GRAYF32LE; } } return pix_fmt; @@ -926,30 +921,25 @@ ReadbackTextureToFrame(olive::TexturePtr texture, return nullptr; } - AVPixelFormat pix_fmt = GetDestinationAVPixelFormat(params); - if (pix_fmt == AV_PIX_FMT_NONE) { + int pix_fmt = GetDestinationAVPixelFormat(params); + if (pix_fmt == FB_PIX_FMT_NONE) { return nullptr; } - const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt); - if (!desc) { - return nullptr; - } - - if (!(desc->flags & AV_PIX_FMT_FLAG_PLANAR)) { + if (!fb_pix_fmt_is_planar(pix_fmt)) { olive::AVFramePtr frame = olive::CreateAVFramePtr(); - frame->format = pix_fmt; - frame->width = params.width(); - frame->height = params.height(); - if (av_frame_get_buffer(frame.get(), 0) < 0) { + frame->set_format(pix_fmt); + frame->set_width(params.width()); + frame->set_height(params.height()); + if (frame->get_buffer(0) < 0) { return nullptr; } if (texture->renderer()) { const int linesize_pixels = olive::plugin::detail::BytesToPixels( - frame->linesize[0], params); + frame->linesize(0), params); texture->renderer()->DownloadFromTexture( - texture->id(), params, frame->data[0], linesize_pixels); + texture->id(), params, frame->data(0), linesize_pixels); } return frame; } @@ -961,39 +951,48 @@ ReadbackTextureToFrame(olive::TexturePtr texture, params.interlacing(), params.divider()); olive::AVFramePtr rgba_frame = olive::CreateAVFramePtr(); - rgba_frame->format = AV_PIX_FMT_RGBA; - rgba_frame->width = params.width(); - rgba_frame->height = params.height(); - if (av_frame_get_buffer(rgba_frame.get(), 0) < 0) { + rgba_frame->set_format(FB_PIX_FMT_RGBA); + rgba_frame->set_width(params.width()); + rgba_frame->set_height(params.height()); + if (rgba_frame->get_buffer(0) < 0) { return nullptr; } if (texture->renderer()) { const int linesize_pixels = olive::plugin::detail::BytesToPixels( - rgba_frame->linesize[0], rgba_params); + rgba_frame->linesize(0), rgba_params); texture->renderer()->DownloadFromTexture( - texture->id(), rgba_params, rgba_frame->data[0], linesize_pixels); + texture->id(), rgba_params, rgba_frame->data(0), linesize_pixels); } olive::AVFramePtr dst = olive::CreateAVFramePtr(); - dst->format = pix_fmt; - dst->width = params.width(); - dst->height = params.height(); - if (av_frame_get_buffer(dst.get(), 0) < 0) { + dst->set_format(pix_fmt); + dst->set_width(params.width()); + dst->set_height(params.height()); + if (dst->get_buffer(0) < 0) { return rgba_frame; } - SwsContext *sws_ctx = sws_getContext( - rgba_frame->width, rgba_frame->height, - static_cast(rgba_frame->format), dst->width, dst->height, - pix_fmt, SWS_POINT, nullptr, nullptr, nullptr); - if (!sws_ctx) { + FBScaler *scaler = fb_scaler_create( + rgba_frame->width(), rgba_frame->height(), rgba_frame->format(), + dst->width(), dst->height(), pix_fmt, FB_SCALER_POINT); + if (!scaler) { return rgba_frame; } - sws_scale(sws_ctx, rgba_frame->data, rgba_frame->linesize, 0, - rgba_frame->height, dst->data, dst->linesize); - sws_freeContext(sws_ctx); + uint8_t *src_data[4]; + int src_linesize[4]; + uint8_t *dst_data[4]; + int dst_linesize[4]; + for (int i = 0; i < 4; ++i) { + src_data[i] = rgba_frame->data(i); + src_linesize[i] = rgba_frame->linesize(i); + dst_data[i] = dst->data(i); + dst_linesize[i] = dst->linesize(i); + } + fb_scaler_scale_slices(scaler, src_data, src_linesize, + rgba_frame->height(), dst_data, dst_linesize); + fb_scaler_free(&scaler); return dst; } @@ -1012,49 +1011,49 @@ int olive::plugin::detail::BytesToPixels(int byte_linesize, } // 作用:将 AVPixelFormat 映射为 Olive 的 PixelFormat 和通道数(仅常见 packed 格式)。 -static void GetOliveFormatFromAV(AVPixelFormat fmt, +static void GetOliveFormatFromAV(int fmt, olive::core::PixelFormat *out_fmt, int *out_ch) { switch (fmt) { - case AV_PIX_FMT_GRAY8: + case FB_PIX_FMT_GRAY8: *out_fmt = olive::core::PixelFormat::U8; *out_ch = 1; return; - case AV_PIX_FMT_RGB24: + case FB_PIX_FMT_RGB24: *out_fmt = olive::core::PixelFormat::U8; *out_ch = 3; return; - case AV_PIX_FMT_RGBA: + case FB_PIX_FMT_RGBA: *out_fmt = olive::core::PixelFormat::U8; *out_ch = 4; return; - case AV_PIX_FMT_GRAY16LE: - case AV_PIX_FMT_GRAY16BE: + case FB_PIX_FMT_GRAY16LE: + case FB_PIX_FMT_GRAY16BE: *out_fmt = olive::core::PixelFormat::U16; *out_ch = 1; return; - case AV_PIX_FMT_RGB48LE: - case AV_PIX_FMT_RGB48BE: + case FB_PIX_FMT_RGB48LE: + case FB_PIX_FMT_RGB48BE: *out_fmt = olive::core::PixelFormat::U16; *out_ch = 3; return; - case AV_PIX_FMT_RGBA64LE: - case AV_PIX_FMT_RGBA64BE: + case FB_PIX_FMT_RGBA64LE: + case FB_PIX_FMT_RGBA64BE: *out_fmt = olive::core::PixelFormat::U16; *out_ch = 4; return; - case AV_PIX_FMT_GRAYF32LE: - case AV_PIX_FMT_GRAYF32BE: + case FB_PIX_FMT_GRAYF32LE: + case FB_PIX_FMT_GRAYF32BE: *out_fmt = olive::core::PixelFormat::F32; *out_ch = 1; return; - case AV_PIX_FMT_RGBF32LE: - case AV_PIX_FMT_RGBF32BE: + case FB_PIX_FMT_RGBF32LE: + case FB_PIX_FMT_RGBF32BE: *out_fmt = olive::core::PixelFormat::F32; *out_ch = 3; return; - case AV_PIX_FMT_RGBAF32LE: - case AV_PIX_FMT_RGBAF32BE: + case FB_PIX_FMT_RGBAF32LE: + case FB_PIX_FMT_RGBAF32BE: *out_fmt = olive::core::PixelFormat::F32; *out_ch = 4; return; @@ -1077,45 +1076,55 @@ ConvertFrameIfNeeded(olive::AVFramePtr src, return nullptr; } - AVPixelFormat dst_fmt = GetDestinationAVPixelFormat(dst_params); - if (dst_fmt == AV_PIX_FMT_NONE) { + int dst_fmt = GetDestinationAVPixelFormat(dst_params); + if (dst_fmt == FB_PIX_FMT_NONE) { return src; } // Same format & size, no conversion needed - if (src->format == dst_fmt && src->width == dst_params.width() && - src->height == dst_params.height()) { + if (src->format() == dst_fmt && src->width() == dst_params.width() && + src->height() == dst_params.height()) { return src; } olive::AVFramePtr dst = olive::CreateAVFramePtr(); - dst->format = dst_fmt; - dst->width = dst_params.width(); - dst->height = dst_params.height(); - if (av_frame_get_buffer(dst.get(), 0) < 0) { + dst->set_format(dst_fmt); + dst->set_width(dst_params.width()); + dst->set_height(dst_params.height()); + if (dst->get_buffer(0) < 0) { qWarning().noquote() << "[WARN] av_frame_get_buffer failed for dst_fmt=" << dst_fmt; return src; } // Try FFmpeg sws_scale first - SwsContext *sws_ctx = sws_getContext( - src->width, src->height, static_cast(src->format), - dst->width, dst->height, dst_fmt, SWS_POINT, nullptr, nullptr, nullptr); - if (sws_ctx) { - int ret = sws_scale(sws_ctx, src->data, src->linesize, 0, src->height, - dst->data, dst->linesize); - sws_freeContext(sws_ctx); + FBScaler *scaler = fb_scaler_create( + src->width(), src->height(), src->format(), + dst->width(), dst->height(), dst_fmt, FB_SCALER_POINT); + if (scaler) { + uint8_t *src_data[4]; + int src_linesize[4]; + uint8_t *dst_data[4]; + int dst_linesize[4]; + for (int i = 0; i < 4; ++i) { + src_data[i] = src->data(i); + src_linesize[i] = src->linesize(i); + dst_data[i] = dst->data(i); + dst_linesize[i] = dst->linesize(i); + } + int ret = fb_scaler_scale_slices(scaler, src_data, src_linesize, + src->height(), dst_data, dst_linesize); + fb_scaler_free(&scaler); if (ret > 0) { return dst; } } // sws_scale failed (e.g. RGBAF32 not supported). Use GPU if renderer available. - if (renderer && src->data[0]) { + if (renderer && src->data(0)) { olive::core::PixelFormat src_fmt; int src_ch; - GetOliveFormatFromAV(static_cast(src->format), &src_fmt, + GetOliveFormatFromAV(src->format(), &src_fmt, &src_ch); if (src_fmt != olive::core::PixelFormat::INVALID && src_ch > 0) { // Ensure renderer's OpenGL context is current before GPU operations. @@ -1125,13 +1134,13 @@ ConvertFrameIfNeeded(olive::AVFramePtr src, gl_renderer->EnsureContextCurrent(__FUNCTION__); } - olive::VideoParams src_vp(src->width, src->height, src_fmt, src_ch); + olive::VideoParams src_vp(src->width(), src->height(), src_fmt, src_ch); int src_bpp = olive::VideoParams::GetBytesPerPixel(src_fmt, src_ch); int src_linesize_pixels = - (src_bpp > 0) ? src->linesize[0] / src_bpp : src->width; + (src_bpp > 0) ? src->linesize(0) / src_bpp : src->width(); olive::TexturePtr src_tex = renderer->CreateTexture( - src_vp, src->data[0], src_linesize_pixels); + src_vp, src->data(0), src_linesize_pixels); if (src_tex) { olive::TexturePtr dst_tex = renderer->CreateTexture(dst_params); if (dst_tex) { @@ -1145,8 +1154,8 @@ ConvertFrameIfNeeded(olive::AVFramePtr src, // Download result back to AVFrame int dst_bpp = dst_params.GetBytesPerPixel(); int dst_linesize_pixels = - (dst_bpp > 0) ? dst->linesize[0] / dst_bpp : dst->width; - dst_tex->Download(dst->data[0], dst_linesize_pixels); + (dst_bpp > 0) ? dst->linesize(0) / dst_bpp : dst->width(); + dst_tex->Download(dst->data(0), dst_linesize_pixels); return dst; } } @@ -1155,7 +1164,7 @@ ConvertFrameIfNeeded(olive::AVFramePtr src, qWarning().noquote() << "[WARN] ConvertFrameIfNeeded failed (sws_scale + GPU both unavailable). " - << "Returning unconverted source. src_fmt=" << src->format + << "Returning unconverted source. src_fmt=" << src->format() << " dst_fmt=" << dst_fmt; return src; } @@ -1208,39 +1217,39 @@ ConvertTextureForParams(olive::TexturePtr src, // CPU fallback: readback, sws_scale, re-upload olive::AVFramePtr frame = src->frame(); - if (!frame || !frame->data[0]) { + if (!frame || !frame->data(0)) { frame = ReadbackTextureToFrame(src, src_params); } - if (!frame || !frame->data[0]) { + if (!frame || !frame->data(0)) { return nullptr; } olive::AVFramePtr converted = ConvertFrameIfNeeded(frame, dst_params, nullptr); - if (!converted || !converted->data[0]) { + if (!converted || !converted->data(0)) { return nullptr; } - if (converted->linesize[0] <= 0) { + if (converted->linesize(0) <= 0) { return nullptr; } olive::TexturePtr dst; if (auto *renderer = src->renderer()) { int linesize_pixels = - LinesizeToPixels(dst_params, converted->linesize[0]); + LinesizeToPixels(dst_params, converted->linesize(0)); if (linesize_pixels <= 0) { linesize_pixels = dst_params.effective_width(); } - dst = renderer->CreateTexture(dst_params, converted->data[0], + dst = renderer->CreateTexture(dst_params, converted->data(0), linesize_pixels); } else { dst = std::make_shared(dst_params); int linesize_pixels = - LinesizeToPixels(dst_params, converted->linesize[0]); + LinesizeToPixels(dst_params, converted->linesize(0)); if (linesize_pixels <= 0) { linesize_pixels = dst_params.effective_width(); } - dst->Upload(converted->data[0], linesize_pixels); + dst->Upload(converted->data(0), linesize_pixels); } if (dst) { dst->handleFrame(converted); @@ -1533,7 +1542,7 @@ void olive::plugin::PluginRenderer::RenderPlugin( return true; } AVFramePtr frame = tex->frame(); - return frame && frame->data[0]; + return frame && frame->data(0); }; std::map input_textures; std::map input_clips; @@ -1845,19 +1854,19 @@ void olive::plugin::PluginRenderer::RenderPlugin( } AVFramePtr converted = ConvertFrameIfNeeded(frame_ptr, destination_params, renderer_); - const AVPixelFormat expected_fmt = + const int expected_fmt = GetDestinationAVPixelFormat(destination_params); destination->handleFrame(converted); - if (destination->renderer() && converted && converted->data[0] && - (expected_fmt == AV_PIX_FMT_NONE || - converted->format == expected_fmt)) { + if (destination->renderer() && converted && converted->data(0) && + (expected_fmt == FB_PIX_FMT_NONE || + converted->format() == expected_fmt)) { int linesize_pixels = - LinesizeToPixels(destination_params, converted->linesize[0]); + LinesizeToPixels(destination_params, converted->linesize(0)); if (linesize_pixels <= 0) { linesize_pixels = destination_params.effective_width(); } - destination->Upload(converted->data[0], linesize_pixels); - } else if (destination->renderer() && converted && converted->data[0]) { + destination->Upload(converted->data(0), linesize_pixels); + } else if (destination->renderer() && converted && converted->data(0)) { qWarning().noquote() << "OFX output pixel format mismatch for plugin=" << PluginIdForInstance(instance); diff --git a/app/render/plugin/pluginrenderer.cpp.orig b/app/render/plugin/pluginrenderer.cpp.orig deleted file mode 100644 index bafd5f627..000000000 --- a/app/render/plugin/pluginrenderer.cpp.orig +++ /dev/null @@ -1,1807 +0,0 @@ -/* - * Oak Video Editor - Non-Linear Video Editor - * Copyright (C) 2025 Olive CE Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -// -// Created by mikesolar on 25-10-19. -// -#include "ofxCore.h" -#include "ofxhPropertySuite.h" -#include "olive/core/render/pixelformat.h" -#include "render/texture.h" -#include "render/opengl/openglrenderer.h" -#include "node/value.h" -#include "render/videoparams.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#define GL_PREAMBLE //QMutexLocker __l(&global_opengl_mutex); -#include "pluginrenderer.h" -#include "pluginSupport/OliveClip.h" -#include "pluginSupport/OlivePluginInstance.h" -#include "common/ffmpegutils.h" -#include "ofxhParam.h" -#include "ofxImageEffect.h" -#include "ofxhUtilities.h" -#include "ofxGPURender.h" -#include "olive/core/util/color.h" -extern "C"{ -#include -#include -#include -} - - -// 作用:从 OFX Image 属性推导 FFmpeg 像素格式,并返回每像素字节数。 -// Purpose: Infer FFmpeg pixel format from OFX image properties and return bytes-per-pixel. -static AVPixelFormat GetOfxAVPixelFormat(const OFX::Host::ImageEffect::Image &image, - int *bytes_per_pixel) -{ - const std::string &depth = image.getStringProperty(kOfxImageEffectPropPixelDepth); - const std::string &components = image.getStringProperty(kOfxImageEffectPropComponents); - - olive::core::PixelFormat pixel_format = olive::core::PixelFormat::INVALID; - if (depth == kOfxBitDepthByte) { - pixel_format = olive::core::PixelFormat::U8; - } else if (depth == kOfxBitDepthShort) { - pixel_format = olive::core::PixelFormat::U16; - } else if (depth == kOfxBitDepthHalf) { - pixel_format = olive::core::PixelFormat::F16; - } else if (depth == kOfxBitDepthFloat) { - pixel_format = olive::core::PixelFormat::F32; - } - - int channel_count = 0; - if (components == kOfxImageComponentRGBA) { - channel_count = 4; - } else if (components == kOfxImageComponentRGB) { - channel_count = 3; - } else if (components == kOfxImageComponentAlpha) { - channel_count = 1; - } - - AVPixelFormat pix_fmt = olive::FFmpegUtils::GetFFmpegPixelFormat(pixel_format, channel_count); - if (pix_fmt == AV_PIX_FMT_NONE && channel_count == 1) { - if (pixel_format == olive::core::PixelFormat::U8) { - pix_fmt = AV_PIX_FMT_GRAY8; - } else if (pixel_format == olive::core::PixelFormat::U16) { - pix_fmt = AV_PIX_FMT_GRAY16LE; - } else if (pixel_format == olive::core::PixelFormat::F16) { - pix_fmt = AV_PIX_FMT_GRAYF16; - } else if (pixel_format == olive::core::PixelFormat::F32) { - pix_fmt = AV_PIX_FMT_GRAYF32; - } - } - - if (pix_fmt == AV_PIX_FMT_NONE) { - return AV_PIX_FMT_NONE; - } - - const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt); - if (!desc) { - return AV_PIX_FMT_NONE; - } - - int bits_per_pixel = av_get_bits_per_pixel(desc); - if (bits_per_pixel <= 0 || bits_per_pixel % 8 != 0) { - return AV_PIX_FMT_NONE; - } - - *bytes_per_pixel = bits_per_pixel / 8; - return pix_fmt; -} - -// 作用:为插件实例注入当前帧的参数值,避免依赖节点实时回读。 -static void ApplyParamOverrides(OFX::Host::ImageEffect::Instance &instance, - const olive::NodeValueRow &values, - OfxTime time) -{ - const auto ¶ms = instance.getParams(); - for (const auto &entry : params) { - if (!entry.second) { - continue; - } - const QString key = QString::fromStdString(entry.first); - if (!values.contains(key)) { - continue; - } - const olive::NodeValue &value = values.value(key); - if (value.type() == olive::NodeValue::kNone || - value.type() == olive::NodeValue::kTexture || - value.type() == olive::NodeValue::kSamples) { - continue; - } - const std::string &type = entry.second->getType(); - - if (type == kOfxParamTypeInteger) { - if (auto *param = - dynamic_cast( - entry.second)) { - param->set(time, value.data().toInt()); - } - continue; - } - if (type == kOfxParamTypeDouble) { - if (auto *param = - dynamic_cast( - entry.second)) { - param->set(time, value.data().toDouble()); - } - continue; - } - if (type == kOfxParamTypeBoolean) { - if (auto *param = - dynamic_cast( - entry.second)) { - param->set(time, value.data().toBool()); - } - continue; - } - if (type == kOfxParamTypeChoice) { - if (auto *param = - dynamic_cast( - entry.second)) { - param->set(time, value.data().toInt()); - } - continue; - } - if (type == kOfxParamTypeString || type == kOfxParamTypeCustom || - type == kOfxParamTypeBytes || type == kOfxParamTypeStrChoice) { - if (auto *param = - dynamic_cast( - entry.second)) { - const QByteArray utf8 = value.data().toString().toUtf8(); - param->set(time, utf8.constData()); - } - continue; - } - if (type == kOfxParamTypeRGBA) { - if (auto *param = - dynamic_cast( - entry.second)) { - if (value.data().canConvert()) { - const auto c = value.data().value(); - param->set(time, c.red(), c.green(), c.blue(), c.alpha()); - } else if (value.data().canConvert()) { - const QVector4D v = value.data().value(); - param->set(time, v.x(), v.y(), v.z(), v.w()); - } else if (value.data().canConvert()) { - const QVector3D v = value.data().value(); - param->set(time, v.x(), v.y(), v.z(), 1.0); - } - } - continue; - } - if (type == kOfxParamTypeRGB) { - if (auto *param = - dynamic_cast( - entry.second)) { - if (value.data().canConvert()) { - const auto c = value.data().value(); - param->set(time, c.red(), c.green(), c.blue()); - } else if (value.data().canConvert()) { - const QVector4D v = value.data().value(); - param->set(time, v.x(), v.y(), v.z()); - } else if (value.data().canConvert()) { - const QVector3D v = value.data().value(); - param->set(time, v.x(), v.y(), v.z()); - } - } - continue; - } - if (type == kOfxParamTypeDouble2D) { - if (auto *param = - dynamic_cast( - entry.second)) { - if (value.data().canConvert()) { - const QVector2D v = value.data().value(); - param->set(time, v.x(), v.y()); - } - } - continue; - } - if (type == kOfxParamTypeInteger2D) { - if (auto *param = - dynamic_cast( - entry.second)) { - if (value.data().canConvert()) { - const QVector2D v = value.data().value(); - param->set(time, static_cast(v.x()), - static_cast(v.y())); - } - } - continue; - } - if (type == kOfxParamTypeDouble3D) { - if (auto *param = - dynamic_cast( - entry.second)) { - if (value.data().canConvert()) { - const QVector3D v = value.data().value(); - param->set(time, v.x(), v.y(), v.z()); - } - } - continue; - } - if (type == kOfxParamTypeInteger3D) { - if (auto *param = - dynamic_cast( - entry.second)) { - if (value.data().canConvert()) { - const QVector3D v = value.data().value(); - param->set(time, static_cast(v.x()), - static_cast(v.y()), - static_cast(v.z())); - } - } - continue; - } - } -} - -static AVPixelFormat GetDestinationAVPixelFormat(const olive::VideoParams ¶ms); - -// 作用:读取 clip 偏好(像素深度与分量)并更新 VideoParams。 -// Purpose: Apply clip preferences (depth/components) into VideoParams. -static bool ApplyClipPreferencesToParams( - const OFX::Host::ImageEffect::ClipInstance &clip, - olive::VideoParams *params) -{ - if (!params) { - return false; - } - - olive::core::PixelFormat format = olive::core::PixelFormat::INVALID; - const std::string &depth = clip.getPixelDepth(); - if (depth == kOfxBitDepthByte) { - format = olive::core::PixelFormat::U8; - } else if (depth == kOfxBitDepthShort) { - format = olive::core::PixelFormat::U16; - } else if (depth == kOfxBitDepthHalf) { - format = olive::core::PixelFormat::F16; - } else if (depth == kOfxBitDepthFloat) { - format = olive::core::PixelFormat::F32; - } - - int channels = 0; - const std::string &components = clip.getComponents(); - if (components == kOfxImageComponentRGBA) { - channels = 4; - } else if (components == kOfxImageComponentRGB) { - channels = 3; - } else if (components == kOfxImageComponentAlpha) { - channels = 1; - } - - if (format == olive::core::PixelFormat::INVALID || channels == 0) { - return false; - } - - params->set_format(format); - params->set_channel_count(channels); - return true; -} - -// 作用:将 OFX bit depth 字符串映射为内部 PixelFormat。 -// Purpose: Map OFX bit depth string to internal PixelFormat. -static olive::core::PixelFormat PixelFormatFromOfxDepth( - const std::string &depth) -{ - if (depth == kOfxBitDepthByte) { - return olive::core::PixelFormat::U8; - } - if (depth == kOfxBitDepthShort) { - return olive::core::PixelFormat::U16; - } - if (depth == kOfxBitDepthHalf) { - return olive::core::PixelFormat::F16; - } - if (depth == kOfxBitDepthFloat) { - return olive::core::PixelFormat::F32; - } - return olive::core::PixelFormat::INVALID; -} - -// 作用:将内部 PixelFormat 转为 OFX bit depth 字符串。 -// Purpose: Map internal PixelFormat to OFX bit depth string. -static const char *OfxDepthFromPixelFormat(olive::core::PixelFormat format) -{ - switch (format) { - case olive::core::PixelFormat::U8: - return kOfxBitDepthByte; - case olive::core::PixelFormat::U16: - return kOfxBitDepthShort; - case olive::core::PixelFormat::F16: - return kOfxBitDepthHalf; - case olive::core::PixelFormat::F32: - return kOfxBitDepthFloat; - case olive::core::PixelFormat::INVALID: - case olive::core::PixelFormat::COUNT: - break; - } - return kOfxBitDepthNone; -} - -// 作用:将 OFX components 字符串映射为通道数。 -// Purpose: Map OFX components string to channel count. -static int ChannelCountFromOfxComponent(const std::string &components) -{ - if (components == kOfxImageComponentRGBA) { - return 4; - } - if (components == kOfxImageComponentRGB) { - return 3; - } - if (components == kOfxImageComponentAlpha) { - return 1; - } - return 0; -} - -// 作用:将通道数映射为 OFX components 字符串。 -// Purpose: Map channel count to OFX components string. -static const char *OfxComponentsFromChannels(int channel_count) -{ - switch (channel_count) { - case 1: - return kOfxImageComponentAlpha; - case 3: - return kOfxImageComponentRGB; - case 4: - return kOfxImageComponentRGBA; - default: - break; - } - return kOfxImageComponentNone; -} - -// 作用:判断插件是否支持指定像素深度。 -// Purpose: Check whether effect supports a given pixel depth. -static bool EffectSupportsPixelDepth( - const OFX::Host::ImageEffect::Instance &instance, - const std::string &depth) -{ - const auto &effect_props = instance.getDescriptor().getProps(); - const int depth_count = - effect_props.getDimension(kOfxImageEffectPropSupportedPixelDepths); - for (int i = 0; i < depth_count; ++i) { - if (effect_props.getStringProperty( - kOfxImageEffectPropSupportedPixelDepths, i) == depth) { - return true; - } - } - return false; -} - -// 作用:判断 clip 是否支持指定组件格式。 -// Purpose: Check whether clip supports a given components string. -static bool ClipSupportsComponents( - const OFX::Host::ImageEffect::ClipInstance &clip, - const std::string &components) -{ - const auto &supported_components = clip.getSupportedComponents(); - for (const auto &comp : supported_components) { - if (comp == components) { - return true; - } - } - return false; -} - -// 作用:估算从源参数到目标参数的转换代价,用于排序选择。 -// Purpose: Estimate conversion cost from source to target params for ranking. -static int ConversionCost(const olive::VideoParams &src, - const olive::VideoParams &dst) -{ - const int src_bpp = src.channel_count() * src.format().byte_count(); - const int dst_bpp = dst.channel_count() * dst.format().byte_count(); - int cost = std::abs(dst_bpp - src_bpp); - if (src.format() != dst.format()) { - cost += 4; - } - if (src.channel_count() != dst.channel_count()) { - cost += 2; - } - return cost; -} - -// 作用:判断目标参数能否转换为可用的 AVPixelFormat。 -// Purpose: Check if params map to a valid AVPixelFormat. -static bool ParamsConvertible(const olive::VideoParams ¶ms) -{ - return GetDestinationAVPixelFormat(params) != AV_PIX_FMT_NONE; -} - -// 作用:在 clip 偏好无效时,选择一个插件支持的输出格式。 -// Purpose: Pick a supported output format when clip preferences are invalid. -static void ChooseSupportedOutputParams( - const OFX::Host::ImageEffect::Instance &instance, - const OFX::Host::ImageEffect::ClipInstance &clip, - const olive::VideoParams &preferred, - olive::VideoParams *out) -{ - if (!out) { - return; - } - - *out = preferred; - - const char *preferred_components = - OfxComponentsFromChannels(preferred.channel_count()); - if (std::strcmp(preferred_components, kOfxImageComponentNone) != 0 && - ClipSupportsComponents(clip, preferred_components)) { - out->set_channel_count(preferred.channel_count()); - } else if (ClipSupportsComponents(clip, kOfxImageComponentRGBA)) { - out->set_channel_count(4); - } else if (ClipSupportsComponents(clip, kOfxImageComponentRGB)) { - out->set_channel_count(3); - } else if (ClipSupportsComponents(clip, kOfxImageComponentAlpha)) { - out->set_channel_count(1); - } - - const olive::core::PixelFormat preferred_format = preferred.format(); - const std::array candidates = { - preferred_format, - olive::core::PixelFormat::F16, - olive::core::PixelFormat::F32, - olive::core::PixelFormat::U16, - olive::core::PixelFormat::U8, - }; - for (const auto &candidate : candidates) { - if (candidate == olive::core::PixelFormat::INVALID) { - continue; - } - if (!EffectSupportsPixelDepth( - instance, OfxDepthFromPixelFormat(candidate))) { - continue; - } - olive::VideoParams test_params = *out; - test_params.set_format(candidate); - if (!ParamsConvertible(test_params)) { - continue; - } - out->set_format(candidate); - return; - } -} - -static olive::TexturePtr ConvertTextureForParams( - olive::TexturePtr src, - const olive::VideoParams &dst_params); - -// 作用:根据插件能力与偏好选择输入格式并执行转换。 -// Purpose: Select a supported input format and convert texture for the clip. -static olive::TexturePtr ConvertTextureForClip( - const OFX::Host::ImageEffect::Instance &instance, - const OFX::Host::ImageEffect::ClipInstance &clip, - olive::TexturePtr src, - const olive::VideoParams &preferred_params, - bool force_preferred, - olive::VideoParams *out_params) -{ - if (!src || !out_params) { - return nullptr; - } - - const olive::VideoParams &src_params = src->params(); - auto add_candidate = [](std::vector &list, - const olive::VideoParams ¶ms) { - for (const auto &existing : list) { - if (existing.format() == params.format() && - existing.channel_count() == params.channel_count()) { - return; - } - } - list.push_back(params); - }; - - std::vector channel_candidates; - const auto &supported_components = clip.getSupportedComponents(); - for (const auto &comp : supported_components) { - int channels = ChannelCountFromOfxComponent(comp); - if (channels > 0 && - std::find(channel_candidates.begin(), - channel_candidates.end(), - channels) == channel_candidates.end()) { - channel_candidates.push_back(channels); - } - } - if (channel_candidates.empty() && preferred_params.channel_count() > 0) { - channel_candidates.push_back(preferred_params.channel_count()); - } - - std::vector format_candidates; - const auto &effect_props = instance.getDescriptor().getProps(); - const int depth_count = - effect_props.getDimension(kOfxImageEffectPropSupportedPixelDepths); - for (int i = 0; i < depth_count; ++i) { - olive::core::PixelFormat fmt = - PixelFormatFromOfxDepth(effect_props.getStringProperty( - kOfxImageEffectPropSupportedPixelDepths, i)); - if (fmt != olive::core::PixelFormat::INVALID && - std::find(format_candidates.begin(), - format_candidates.end(), - fmt) == format_candidates.end()) { - format_candidates.push_back(fmt); - } - } - if (format_candidates.empty() && - preferred_params.format() != olive::core::PixelFormat::INVALID) { - format_candidates.push_back(preferred_params.format()); - } - - std::vector candidates; - add_candidate(candidates, preferred_params); - - const bool prefer_rgba8 = - (preferred_params.format() == olive::core::PixelFormat::U8 || - preferred_params.format() == olive::core::PixelFormat::INVALID) && - ClipSupportsComponents(clip, kOfxImageComponentRGBA) && - EffectSupportsPixelDepth(instance, kOfxBitDepthByte); - if (prefer_rgba8) { - olive::VideoParams rgba_candidate = src_params; - rgba_candidate.set_format(olive::core::PixelFormat::U8); - rgba_candidate.set_channel_count(4); - if (ParamsConvertible(rgba_candidate)) { - add_candidate(candidates, rgba_candidate); - } - } - - for (olive::core::PixelFormat fmt : format_candidates) { - for (int channels : channel_candidates) { - if (fmt == olive::core::PixelFormat::INVALID || channels <= 0) { - continue; - } - olive::VideoParams candidate = src_params; - candidate.set_format(fmt); - candidate.set_channel_count(channels); - if (!ParamsConvertible(candidate)) { - continue; - } - add_candidate(candidates, candidate); - } - } - - if (candidates.empty()) { - return nullptr; - } - - std::stable_sort(candidates.begin(), candidates.end(), - [&src_params, &preferred_params, prefer_rgba8, force_preferred](const auto &a, - const auto &b) { - if (force_preferred) { - const bool a_pref = (a.format() == preferred_params.format() && - a.channel_count() == preferred_params.channel_count()); - const bool b_pref = (b.format() == preferred_params.format() && - b.channel_count() == preferred_params.channel_count()); - if (a_pref != b_pref) { - return a_pref; - } - } - if (prefer_rgba8) { - const bool a_rgba8 = - a.format() == olive::core::PixelFormat::U8 && - a.channel_count() == 4; - const bool b_rgba8 = - b.format() == olive::core::PixelFormat::U8 && - b.channel_count() == 4; - if (a_rgba8 != b_rgba8) { - return a_rgba8; - } - } - const int cost_a = ConversionCost(src_params, a); - const int cost_b = ConversionCost(src_params, b); - if (cost_a != cost_b) { - return cost_a < cost_b; - } - if (a.format() == preferred_params.format() && - a.channel_count() == preferred_params.channel_count()) { - return true; - } - return false; - }); - - for (const auto &candidate : candidates) { - if (candidate.format() == src_params.format() && - candidate.channel_count() == src_params.channel_count()) { - *out_params = src_params; - return src; - } - olive::TexturePtr converted = - ConvertTextureForParams(src, candidate); - if (converted) { - *out_params = candidate; - return converted; - } - } - - return nullptr; -} - -// 作用:从 OFX Image 复制数据到 AVFrame(按图像属性推导格式)。 -// Purpose: Copy OFX Image data into an AVFrame with inferred format. -static olive::AVFramePtr create_avframe_from_ofx_image(OFX::Host::ImageEffect::Image &image) -{ - void *data_ptr = image.getPointerProperty(kOfxImagePropData); - if (!data_ptr) { - qWarning().noquote() << "OFX output image missing data pointer"; - return nullptr; - } - - int bounds[4] = {0, 0, 0, 0}; - image.getIntPropertyN(kOfxImagePropBounds, bounds, 4); - int width = bounds[2] - bounds[0]; - int height = bounds[3] - bounds[1]; - if (width <= 0 || height <= 0) { - qWarning().noquote() - << "OFX output image has invalid bounds" - << bounds[0] << bounds[1] << bounds[2] << bounds[3]; - return nullptr; - } - - int bytes_per_pixel = 0; - AVPixelFormat pix_fmt = GetOfxAVPixelFormat(image, &bytes_per_pixel); - if (pix_fmt == AV_PIX_FMT_NONE || bytes_per_pixel <= 0) { - qWarning().noquote() - << "OFX output image has unsupported pixel format depth=" - << QString::fromStdString(image.getStringProperty( - kOfxImageEffectPropPixelDepth)) - << "components=" - << QString::fromStdString( - image.getStringProperty(kOfxImageEffectPropComponents)); - return nullptr; - } - - int row_bytes = image.getIntProperty(kOfxImagePropRowBytes); - if (row_bytes <= 0) { - row_bytes = width * bytes_per_pixel; - } - - uint8_t *src = static_cast(data_ptr); - src += bounds[1] * row_bytes + bounds[0] * bytes_per_pixel; - - olive::AVFramePtr frame = olive::CreateAVFramePtr(); - frame->width = width; - frame->height = height; - frame->format = pix_fmt; - - if (av_frame_get_buffer(frame.get(), 0) < 0) { - return nullptr; - } - - const int copy_bytes = width * bytes_per_pixel; - for (int y = 0; y < height; ++y) { - std::memcpy(frame->data[0] + y * frame->linesize[0], - src + y * row_bytes, - copy_bytes); - } - - return frame; -} - -// Forward declaration -static olive::AVFramePtr ConvertFrameIfNeeded(olive::AVFramePtr src, - const olive::VideoParams &dst_params); - -// 作用:按指定 VideoParams 复制 OFX Image 到 AVFrame。 -// Purpose: Copy OFX Image data into an AVFrame using target VideoParams. -static olive::AVFramePtr create_avframe_from_ofx_image_with_params( - OFX::Host::ImageEffect::Image &image, - const olive::VideoParams ¶ms) -{ - void *data_ptr = image.getPointerProperty(kOfxImagePropData); - if (!data_ptr) { - return nullptr; - } - - int bounds[4] = {0, 0, 0, 0}; - image.getIntPropertyN(kOfxImagePropBounds, bounds, 4); - int width = bounds[2] - bounds[0]; - int height = bounds[3] - bounds[1]; - if (width <= 0 || height <= 0) { - return nullptr; - } - - AVPixelFormat pix_fmt = GetDestinationAVPixelFormat(params); - if (pix_fmt == AV_PIX_FMT_NONE) { - return nullptr; - } - - // Get the ACTUAL format from the image properties (not from params) - // The image may have a different format than params (e.g., plugin - // requested U16 but params is U8) - std::string image_depth = image.getStringProperty(kOfxImageEffectPropPixelDepth); - std::string image_comp = image.getStringProperty(kOfxImageEffectPropComponents); - - int image_channel_count = 4; // default to RGBA - if (image_comp == kOfxImageComponentRGB) { - image_channel_count = 3; - } else if (image_comp == kOfxImageComponentAlpha) { - image_channel_count = 1; - } - - int bytes_per_component = 1; // default to 8-bit - if (image_depth == kOfxBitDepthShort) { - bytes_per_component = 2; - } else if (image_depth == kOfxBitDepthHalf || image_depth == kOfxBitDepthFloat) { - bytes_per_component = 4; - } - - const int src_bytes_per_pixel = image_channel_count * bytes_per_component; - if (src_bytes_per_pixel <= 0) { - return nullptr; - } - - int row_bytes = image.getIntProperty(kOfxImagePropRowBytes); - if (row_bytes <= 0) { - row_bytes = width * src_bytes_per_pixel; - } - - uint8_t *src = static_cast(data_ptr); - src += bounds[1] * row_bytes + bounds[0] * src_bytes_per_pixel; - - olive::AVFramePtr frame = olive::CreateAVFramePtr(); - frame->width = width; - frame->height = height; - frame->format = pix_fmt; - - if (av_frame_get_buffer(frame.get(), 0) < 0) { - return nullptr; - } - - // Debug output - qDebug() << "OFX output image:" - << "depth=" << QString::fromStdString(image_depth) - << "comp=" << QString::fromStdString(image_comp) - << "ch=" << image_channel_count - << "bpc=" << bytes_per_component - << "src_bpp=" << src_bytes_per_pixel - << "row_bytes=" << row_bytes - << "bounds=" << bounds[0] << bounds[1] << bounds[2] << bounds[3] - << "width=" << width << "height=" << height - << "dst_params_fmt=" << static_cast(params.format()) - << "dst_params_ch=" << params.channel_count() - << "dst_pix_fmt=" << pix_fmt - << "dst_linesize=" << frame->linesize[0]; - - // Check if we need format conversion - bool needs_conversion = false; - if (params.format().byte_count() != bytes_per_component || - params.channel_count() != image_channel_count) { - needs_conversion = true; - } - - if (needs_conversion) { - // Create a temporary AVFrame with the source format - AVPixelFormat src_pix_fmt = AV_PIX_FMT_NONE; - if (image_channel_count == 4) { - if (bytes_per_component == 1) src_pix_fmt = AV_PIX_FMT_RGBA; - else if (bytes_per_component == 2) src_pix_fmt = AV_PIX_FMT_RGBA64; - else if (bytes_per_component == 4) src_pix_fmt = AV_PIX_FMT_RGBAF32; - } else if (image_channel_count == 3) { - if (bytes_per_component == 1) src_pix_fmt = AV_PIX_FMT_RGB24; - else if (bytes_per_component == 2) src_pix_fmt = AV_PIX_FMT_RGB48; - } - - if (src_pix_fmt != AV_PIX_FMT_NONE) { - olive::AVFramePtr src_frame = olive::CreateAVFramePtr(); - src_frame->width = width; - src_frame->height = height; - src_frame->format = src_pix_fmt; - - // Allocate buffer for source frame - if (av_frame_get_buffer(src_frame.get(), 0) < 0) { - return nullptr; - } - - // Copy data to source frame - for (int y = 0; y < height; ++y) { - std::memcpy(src_frame->data[0] + y * src_frame->linesize[0], - src + y * row_bytes, - width * src_bytes_per_pixel); - } - - // Use ConvertFrameIfNeeded for format conversion - return ConvertFrameIfNeeded(src_frame, params); - } - } - - // Same format - direct copy - const int copy_bytes = width * src_bytes_per_pixel; - for (int y = 0; y < height; ++y) { - std::memcpy(frame->data[0] + y * frame->linesize[0], - src + y * row_bytes, - copy_bytes); - } - - return frame; -} - -// 作用:将 VideoParams 映射为最终输出的 AVPixelFormat。 -// Purpose: Map VideoParams to the final AVPixelFormat. -static AVPixelFormat GetDestinationAVPixelFormat(const olive::VideoParams ¶ms) -{ - AVPixelFormat pix_fmt = - olive::FFmpegUtils::GetFFmpegPixelFormat(params.format(), - params.channel_count()); - if (pix_fmt == AV_PIX_FMT_NONE && params.channel_count() == 1) { - if (params.format() == olive::core::PixelFormat::U8) { - pix_fmt = AV_PIX_FMT_GRAY8; - } else if (params.format() == olive::core::PixelFormat::U16) { - pix_fmt = AV_PIX_FMT_GRAY16LE; - } else if (params.format() == olive::core::PixelFormat::F16) { - pix_fmt = AV_PIX_FMT_GRAYF16; - } else if (params.format() == olive::core::PixelFormat::F32) { - pix_fmt = AV_PIX_FMT_GRAYF32; - } - } - return pix_fmt; -} - -// 作用:根据交错设置返回 OFX render field 字符串。 -// Purpose: Return OFX render field string based on interlacing. -static const char *GetRenderFieldForParams(const olive::VideoParams ¶ms) -{ - switch (params.interlacing()) { - case olive::VideoParams::kInterlaceNone: - return kOfxImageFieldNone; - case olive::VideoParams::kInterlacedTopFirst: - case olive::VideoParams::kInterlacedBottomFirst: - return kOfxImageFieldBoth; - } - return kOfxImageFieldNone; -} - -// 作用:从 GPU 纹理回读到 AVFrame(必要时做格式转换)。 -// Purpose: Read back GPU texture into AVFrame with format conversion if needed. -static olive::AVFramePtr ReadbackTextureToFrame(olive::TexturePtr texture, - const olive::VideoParams ¶ms) -{ - if (!texture || texture->IsDummy()) { - return nullptr; - } - - AVPixelFormat pix_fmt = GetDestinationAVPixelFormat(params); - if (pix_fmt == AV_PIX_FMT_NONE) { - return nullptr; - } - - const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt); - if (!desc) { - return nullptr; - } - - if (!(desc->flags & AV_PIX_FMT_FLAG_PLANAR)) { - olive::AVFramePtr frame = olive::CreateAVFramePtr(); - frame->format = pix_fmt; - frame->width = params.width(); - frame->height = params.height(); - if (av_frame_get_buffer(frame.get(), 0) < 0) { - return nullptr; - } - - if (texture->renderer()) { - const int linesize_pixels = - olive::plugin::detail::BytesToPixels(frame->linesize[0], - params); - texture->renderer()->DownloadFromTexture( - texture->id(), params, frame->data[0], linesize_pixels); - } - return frame; - } - - // Planar formats: read back as RGBA and convert. - olive::VideoParams rgba_params( - params.width(), params.height(), olive::core::PixelFormat::U8, 4, - params.pixel_aspect_ratio(), params.interlacing(), params.divider()); - - olive::AVFramePtr rgba_frame = olive::CreateAVFramePtr(); - rgba_frame->format = AV_PIX_FMT_RGBA; - rgba_frame->width = params.width(); - rgba_frame->height = params.height(); - if (av_frame_get_buffer(rgba_frame.get(), 0) < 0) { - return nullptr; - } - - if (texture->renderer()) { - const int linesize_pixels = - olive::plugin::detail::BytesToPixels(rgba_frame->linesize[0], - rgba_params); - texture->renderer()->DownloadFromTexture( - texture->id(), rgba_params, rgba_frame->data[0], linesize_pixels); - } - - olive::AVFramePtr dst = olive::CreateAVFramePtr(); - dst->format = pix_fmt; - dst->width = params.width(); - dst->height = params.height(); - if (av_frame_get_buffer(dst.get(), 0) < 0) { - return rgba_frame; - } - - SwsContext *sws_ctx = sws_getContext( - rgba_frame->width, rgba_frame->height, - static_cast(rgba_frame->format), - dst->width, dst->height, pix_fmt, SWS_POINT, - nullptr, nullptr, nullptr); - if (!sws_ctx) { - return rgba_frame; - } - - sws_scale(sws_ctx, rgba_frame->data, rgba_frame->linesize, 0, - rgba_frame->height, dst->data, dst->linesize); - sws_freeContext(sws_ctx); - - return dst; -} - -// 作用:将字节行跨度转换为像素行跨度。 -// Purpose: Convert byte stride to pixel stride. -int olive::plugin::detail::BytesToPixels(int byte_linesize, - const olive::VideoParams ¶ms) -{ - const int bytes_per_pixel = - olive::VideoParams::GetBytesPerPixel(params.format(), - params.channel_count()); - if (byte_linesize <= 0 || bytes_per_pixel <= 0) { - return 0; - } - return byte_linesize / bytes_per_pixel; -} - -// 作用:必要时将 AVFrame 转换为目标 VideoParams 对应格式。 -// Purpose: Convert AVFrame to match destination VideoParams when needed. -static olive::AVFramePtr ConvertFrameIfNeeded(olive::AVFramePtr src, - const olive::VideoParams &dst_params) -{ - if (!src) { - return nullptr; - } - - AVPixelFormat dst_fmt = GetDestinationAVPixelFormat(dst_params); - if (dst_fmt == AV_PIX_FMT_NONE) { - return src; - } - - if (src->format == dst_fmt && - src->width == dst_params.width() && - src->height == dst_params.height()) { - return src; - } - - olive::AVFramePtr dst = olive::CreateAVFramePtr(); - dst->format = dst_fmt; - dst->width = dst_params.width(); - dst->height = dst_params.height(); - if (av_frame_get_buffer(dst.get(), 0) < 0) { - return src; - } - - auto float_channels = [](AVPixelFormat fmt) -> int { - switch (fmt) { - case AV_PIX_FMT_GRAYF32LE: - case AV_PIX_FMT_GRAYF32BE: - return 1; - case AV_PIX_FMT_RGBF32LE: - case AV_PIX_FMT_RGBF32BE: - return 3; - case AV_PIX_FMT_RGBAF32LE: - case AV_PIX_FMT_RGBAF32BE: - return 4; - default: - return 0; - } - }; - - auto dst_packed_info = [](AVPixelFormat fmt, int *channels, - int *bytes_per_component) -> bool { - switch (fmt) { - case AV_PIX_FMT_GRAY8: - *channels = 1; - *bytes_per_component = 1; - return true; - case AV_PIX_FMT_RGB24: - *channels = 3; - *bytes_per_component = 1; - return true; - case AV_PIX_FMT_RGBA: - *channels = 4; - *bytes_per_component = 1; - return true; - case AV_PIX_FMT_GRAY16LE: - *channels = 1; - *bytes_per_component = 2; - return true; - case AV_PIX_FMT_RGB48LE: - *channels = 3; - *bytes_per_component = 2; - return true; - case AV_PIX_FMT_RGBA64LE: - *channels = 4; - *bytes_per_component = 2; - return true; - default: - return false; - } - }; - - auto float_dst_from_packed = [&](const olive::AVFramePtr &packed_src, - AVPixelFormat float_fmt, - const olive::AVFramePtr &float_dst) -> bool { - if (!packed_src || !float_dst || !packed_src->data[0] || - !float_dst->data[0]) { - return false; - } - const int dst_channels = float_channels(float_fmt); - if (dst_channels == 0) { - return false; - } - int src_channels = 0; - int bytes_per_component = 0; - if (!dst_packed_info(static_cast(packed_src->format), - &src_channels, &bytes_per_component)) { - return false; - } - const float inv_scale = - (bytes_per_component == 2) ? (1.0f / 65535.0f) - : (1.0f / 255.0f); - for (int y = 0; y < packed_src->height; ++y) { - const uint8_t *src_row = - packed_src->data[0] + y * packed_src->linesize[0]; - float *dst_row = reinterpret_cast( - float_dst->data[0] + y * float_dst->linesize[0]); - if (bytes_per_component == 2) { - const uint16_t *src_u16 = - reinterpret_cast(src_row); - for (int x = 0; x < packed_src->width; ++x) { - const uint16_t *pix = src_u16 + x * src_channels; - float r = pix[0] * inv_scale; - float g = (src_channels > 1) ? pix[1] * inv_scale : r; - float b = (src_channels > 2) ? pix[2] * inv_scale : r; - float a = (src_channels > 3) ? pix[3] * inv_scale : 1.0f; - dst_row[x * dst_channels + 0] = r; - if (dst_channels > 1) { - dst_row[x * dst_channels + 1] = g; - } - if (dst_channels > 2) { - dst_row[x * dst_channels + 2] = b; - } - if (dst_channels > 3) { - dst_row[x * dst_channels + 3] = a; - } - } - } else { - for (int x = 0; x < packed_src->width; ++x) { - const uint8_t *pix = src_row + x * src_channels; - float r = pix[0] * inv_scale; - float g = (src_channels > 1) ? pix[1] * inv_scale : r; - float b = (src_channels > 2) ? pix[2] * inv_scale : r; - float a = (src_channels > 3) ? pix[3] * inv_scale : 1.0f; - dst_row[x * dst_channels + 0] = r; - if (dst_channels > 1) { - dst_row[x * dst_channels + 1] = g; - } - if (dst_channels > 2) { - dst_row[x * dst_channels + 2] = b; - } - if (dst_channels > 3) { - dst_row[x * dst_channels + 3] = a; - } - } - } - } - return true; - }; - - const int dst_float_channels = float_channels(dst_fmt); - if (dst_float_channels > 0) { - int src_channels = 0; - int bytes_per_component = 0; - if (dst_packed_info(static_cast(src->format), - &src_channels, &bytes_per_component)) { - if (float_dst_from_packed(src, dst_fmt, dst)) { - return dst; - } - } else { - olive::AVFramePtr packed = olive::CreateAVFramePtr(); - AVPixelFormat packed_fmt = (dst_float_channels == 4) - ? AV_PIX_FMT_RGBA - : (dst_float_channels == 3) - ? AV_PIX_FMT_RGB24 - : AV_PIX_FMT_GRAY8; - packed->format = packed_fmt; - packed->width = dst->width; - packed->height = dst->height; - if (av_frame_get_buffer(packed.get(), 0) >= 0) { - SwsContext *pre_ctx = sws_getContext( - src->width, src->height, - static_cast(src->format), - packed->width, packed->height, packed_fmt, SWS_POINT, - nullptr, nullptr, nullptr); - if (pre_ctx) { - sws_scale(pre_ctx, src->data, src->linesize, 0, src->height, - packed->data, packed->linesize); - sws_freeContext(pre_ctx); - if (float_dst_from_packed(packed, dst_fmt, dst)) { - return dst; - } - } - } - } - } - - auto clamp01 = [](float v) -> float { - return std::clamp(v, 0.0f, 1.0f); - }; - - const int src_float_channels = float_channels( - static_cast(src->format)); - if (src_float_channels > 0) { - int dst_channels = 0; - int bytes_per_component = 0; - if (dst_packed_info(dst_fmt, &dst_channels, &bytes_per_component) && - src->data[0] && dst->data[0]) { - for (int y = 0; y < src->height; ++y) { - const float *src_row = reinterpret_cast( - src->data[0] + y * src->linesize[0]); - uint8_t *dst_row = dst->data[0] + y * dst->linesize[0]; - if (bytes_per_component == 2) { - auto *dst_row_u16 = - reinterpret_cast(dst_row); - for (int x = 0; x < src->width; ++x) { - const float *pix = - src_row + x * src_float_channels; - float r = pix[0]; - float g = (src_float_channels > 1) ? pix[1] : r; - float b = (src_float_channels > 2) ? pix[2] : r; - float a = (src_float_channels > 3) ? pix[3] : 1.0f; - if (dst_channels == 1) { - float luma = 0.2126f * r + 0.7152f * g + 0.0722f * b; - dst_row_u16[x] = static_cast( - std::lround(clamp01(luma) * 65535.0f)); - continue; - } - dst_row_u16[x * dst_channels + 0] = - static_cast( - std::lround(clamp01(r) * 65535.0f)); - dst_row_u16[x * dst_channels + 1] = - static_cast( - std::lround(clamp01(g) * 65535.0f)); - dst_row_u16[x * dst_channels + 2] = - static_cast( - std::lround(clamp01(b) * 65535.0f)); - if (dst_channels == 4) { - dst_row_u16[x * dst_channels + 3] = - static_cast( - std::lround(clamp01(a) * 65535.0f)); - } - } - } else { - for (int x = 0; x < src->width; ++x) { - const float *pix = - src_row + x * src_float_channels; - float r = pix[0]; - float g = (src_float_channels > 1) ? pix[1] : r; - float b = (src_float_channels > 2) ? pix[2] : r; - float a = (src_float_channels > 3) ? pix[3] : 1.0f; - if (dst_channels == 1) { - float luma = 0.2126f * r + 0.7152f * g + 0.0722f * b; - dst_row[x] = static_cast( - std::lround(clamp01(luma) * 255.0f)); - continue; - } - dst_row[x * dst_channels + 0] = - static_cast( - std::lround(clamp01(r) * 255.0f)); - dst_row[x * dst_channels + 1] = - static_cast( - std::lround(clamp01(g) * 255.0f)); - dst_row[x * dst_channels + 2] = - static_cast( - std::lround(clamp01(b) * 255.0f)); - if (dst_channels == 4) { - dst_row[x * dst_channels + 3] = - static_cast( - std::lround(clamp01(a) * 255.0f)); - } - } - } - } - return dst; - } - } - - SwsContext *sws_ctx = sws_getContext( - src->width, src->height, static_cast(src->format), - dst->width, dst->height, dst_fmt, SWS_POINT, - nullptr, nullptr, nullptr); - if (!sws_ctx) { - return src; - } - - sws_scale(sws_ctx, src->data, src->linesize, 0, src->height, - dst->data, dst->linesize); - sws_freeContext(sws_ctx); - - return dst; -} - -// 作用:从字节行跨度换算像素行跨度。 -// Purpose: Convert byte line size to pixel line size. -static int LinesizeToPixels(const olive::VideoParams ¶ms, int linesize_bytes) -{ - const int bytes_per_pixel = - params.channel_count() * params.format().byte_count(); - if (bytes_per_pixel <= 0) { - return 0; - } - return linesize_bytes / bytes_per_pixel; -} - -// 作用:将纹理转换为指定 VideoParams(CPU 路径,必要时回读)。 -// Purpose: Convert texture to target VideoParams (CPU path with readback). -static olive::TexturePtr ConvertTextureForParams(olive::TexturePtr src, - const olive::VideoParams &dst_params) -{ - if (!src) { - return nullptr; - } - const olive::VideoParams &src_params = src->params(); - if (src_params.format() == dst_params.format() && - src_params.channel_count() == dst_params.channel_count() && - src_params.width() == dst_params.width() && - src_params.height() == dst_params.height()) { - return src; - } - - olive::AVFramePtr frame = src->frame(); - if (!frame || !frame->data[0]) { - frame = ReadbackTextureToFrame(src, src_params); - } - if (!frame || !frame->data[0]) { - return nullptr; - } - - olive::AVFramePtr converted = ConvertFrameIfNeeded(frame, dst_params); - if (!converted || !converted->data[0]) { - return nullptr; - } - if (converted->linesize[0] <= 0) { - return nullptr; - } - - olive::TexturePtr dst; - if (auto *renderer = src->renderer()) { - int linesize_pixels = - LinesizeToPixels(dst_params, converted->linesize[0]); - if (linesize_pixels <= 0) { - linesize_pixels = dst_params.effective_width(); - } - dst = renderer->CreateTexture(dst_params, converted->data[0], - linesize_pixels); - } else { - dst = std::make_shared(dst_params); - int linesize_pixels = - LinesizeToPixels(dst_params, converted->linesize[0]); - if (linesize_pixels <= 0) { - linesize_pixels = dst_params.effective_width(); - } - dst->Upload(converted->data[0], linesize_pixels); - } - if (dst) { - dst->handleFrame(converted); - } - return dst; -} - -// 作用:安全获取插件标识符,便于日志输出。 -// Purpose: Safely fetch plugin identifier for logging. -static QString PluginIdForInstance(const OFX::Host::ImageEffect::Instance *instance) -{ - if (!instance) { - return QStringLiteral(""); - } - auto *plugin = instance->getPlugin(); - if (!plugin) { - return QStringLiteral(""); - } - return QString::fromStdString(plugin->getIdentifier()); -} - -// 作用:统一 OFX 调用失败日志输出。 -// Purpose: Centralized logging for OFX action failures. -static void LogOfxFailure(const char *action, OfxStatus stat, - const OFX::Host::ImageEffect::Instance *instance) -{ - if (stat == kOfxStatOK || stat == kOfxStatReplyDefault) { - return; - } - qWarning().noquote() - << "OFX action failed:" << action - << "plugin=" << PluginIdForInstance(instance) - << "status=" << OFX::StatStr(stat) - << "(" << stat << ")"; -} - -// 作用:输出 clip 的声明属性与关联 VideoParams,辅助定位格式不一致。 -// Purpose: Log clip declared properties and VideoParams for debugging. -static void LogClipState(const char *label, - const OFX::Host::ImageEffect::ClipInstance *clip, - const olive::VideoParams *params) -{ - if (!clip) { - qWarning().noquote() << "OFX clip state" << label << ""; - return; - } - qWarning().noquote() - << "OFX clip state" << label - << "name=" << QString::fromStdString(clip->getName()) - << "pixelDepth=" << QString::fromStdString(clip->getPixelDepth()) - << "components=" << QString::fromStdString(clip->getComponents()); - if (params) { - qWarning().noquote() - << "OFX clip params" << label - << "width=" << params->width() - << "height=" << params->height() - << "format=" << static_cast(params->format()) - << "channels=" << params->channel_count(); - } -} - -// 作用:输出 OFX Image 的属性(深度/组件/行跨度/边界)。 -// Purpose: Log OFX image properties (depth/components/stride/bounds). -static void LogImageProps(const char *label, - OFX::Host::ImageEffect::Image *image) -{ - if (!image) { - qWarning().noquote() << "OFX image props" << label << ""; - return; - } - int bounds[4] = {0, 0, 0, 0}; - int rod[4] = {0, 0, 0, 0}; - image->getIntPropertyN(kOfxImagePropBounds, bounds, 4); - image->getIntPropertyN(kOfxImagePropRegionOfDefinition, rod, 4); - const int row_bytes = image->getIntProperty(kOfxImagePropRowBytes); - const std::string &depth = - image->getStringProperty(kOfxImageEffectPropPixelDepth); - const std::string &components = - image->getStringProperty(kOfxImageEffectPropComponents); - qWarning().noquote() - << "OFX image props" << label - << "pixelDepth=" << QString::fromStdString(depth) - << "components=" << QString::fromStdString(components) - << "rowBytes=" << row_bytes - << "bounds=" << bounds[0] << bounds[1] << bounds[2] << bounds[3] - << "rod=" << rod[0] << rod[1] << rod[2] << rod[3]; -} - -// 作用:渲染失败时标记目标画面(紫色)提示错误。 -// Purpose: Mark render failure on destination (magenta). -static void MarkRenderFailure(olive::TexturePtr destination) -{ - if (destination && destination->renderer()) { - destination->renderer()->ClearDestination(destination.get(), 1.0, 0.0, 1.0, 1.0); - } -} -static olive::AVFramePtr DownloadTextureToFrame(const olive::TexturePtr &tex) -{ - if (!tex || tex->IsDummy() || !tex->renderer()) { - return nullptr; - } - const olive::VideoParams ¶ms = tex->params(); - return ReadbackTextureToFrame(tex, params); -} - -// 作用:执行 OFX 插件渲染全流程(准备输入、调用动作、处理输出)。 -// Purpose: Run full OFX plugin render flow (inputs, actions, outputs). -void olive::plugin::PluginRenderer::RenderPlugin(TexturePtr src, olive::plugin::PluginJob& job, - olive::TexturePtr destination, - olive::VideoParams destination_params, - bool clear_destination, bool interactive) -{ - auto instance=job.pluginInstance(); - if (!instance) { - return; - } - bool supports_opengl = false; -#ifdef OFX_SUPPORTS_OPENGLRENDER - const std::string &gl_supported = - instance->getDescriptor().getProps().getStringProperty( - kOfxImageEffectPropOpenGLRenderSupported); - supports_opengl = (gl_supported == "true" || gl_supported == "1"); -#endif - auto *olive_instance = - dynamic_cast(instance); - const bool use_opengl = - supports_opengl && destination && destination->renderer() && - destination->id().isValid(); - if (olive_instance) { - olive_instance->setVideoParam(destination_params); - } - - // current render scale of 1 - OfxPointD renderScale; - renderScale.x = renderScale.y = 1.0; - - - int numFramesToRender=1; - - // Output Clip - OliveClipInstance *output_clip=dynamic_cast(instance->getClip("Output")); - if (!output_clip) { - return; - } - - // ensure the instance was created - OfxStatus stat = kOfxStatOK; - if (olive_instance && !olive_instance->isCreated()) { - stat = instance->createInstanceAction(); - if(stat != kOfxStatOK && stat != kOfxStatReplyDefault) { - LogOfxFailure("createInstance", stat, instance); - MarkRenderFailure(destination); - return; - } - } - - - OfxTime frame = job.time_seconds(); - - const auto &clips = olive_instance->getDescriptor().getClips(); - QString effect_input_id; - if (const auto *node = job.node()) { - effect_input_id = node->GetEffectInputID(); - } - auto is_usable_input = [](const TexturePtr &tex) { - if (!tex) { - return false; - } - if (!tex->IsDummy() && tex->renderer()) { - return true; - } - AVFramePtr frame = tex->frame(); - return frame && frame->data[0]; - }; - std::map input_textures; - std::map input_clips; - std::map input_params; - auto values = job.GetValues(); - for (const auto &entry : clips) { - if (entry.first == kOfxImageEffectOutputClipName) { - continue; - } - OliveClipInstance *input_clip = - dynamic_cast(instance->getClip(entry.first)); - if (!input_clip) { - continue; - } - const QString clip_key = QString::fromStdString(entry.first); - TexturePtr input_tex = nullptr; - if (!effect_input_id.isEmpty() && clip_key == effect_input_id && - is_usable_input(src)) { - input_tex = src; - } else { - input_tex = values.value(clip_key).toTexture(); - if (!input_tex && - entry.first == kOfxImageEffectSimpleSourceClipName) { - input_tex = values.value(kTextureInput).toTexture(); - } - } - if (!is_usable_input(input_tex) && - entry.first == kOfxImageEffectSimpleSourceClipName && - is_usable_input(src)) { - input_tex = src; - } - if (is_usable_input(input_tex)) { - input_textures[entry.first] = input_tex; - olive::VideoParams params = input_tex->params(); - input_clip->setInputTexture(input_tex, frame); - input_clips[entry.first] = input_clip; - } - } - - // call getClipPreferences to know which format plugin requires - OFX::Host::Property::Set args; - args.setDoubleProperty(kOfxPropTime, frame); - double render_scale_array[] = { - renderScale.x, renderScale.y - }; - args.setDoublePropertyN(kOfxImageEffectPropRenderScale, render_scale_array, 2); - instance->setupClipPreferencesArgs(args); - // now we need to call getClipPreferences on the instance so that it does - // the clip component/depth logic and caches away the components and depth. - bool ok = instance->getClipPreferences(); - if (!ok) { - qWarning().noquote() << "OFX getClipPreferences failed for plugin=" - << PluginIdForInstance(instance); - MarkRenderFailure(destination); - return; - } - /// RoI is in canonical coords. - OfxRectD regionOfInterest; - regionOfInterest.x1 = 0.0; - regionOfInterest.y1 = 0.0; - regionOfInterest.x2 = destination_params.width() * destination_params.pixel_aspect_ratio().toDouble(); - regionOfInterest.y2 = destination_params.height(); - - OfxRectD regionOfDefinition = regionOfInterest; - - output_clip->setRegionOfDefinition(regionOfDefinition, frame); - output_clip->setOutputTexture(destination, frame); - - // get the RoI for each input clip - // the regions of interest for each input clip are returned in a std::map - // on a real host, these will be the regions of each input clip that the - // effect needs to render a given frame (clipped to the RoD). - // - // In our example we are doing full frame fetches regardless. - - - // set correct format for input - for (const auto &entry : input_clips) { - if (entry.first == kOfxImageEffectOutputClipName) { - continue; - } - OliveClipInstance *input_clip = entry.second; - if (!input_clip) { - continue; - } - const QString clip_key = QString::fromStdString(entry.first); - TexturePtr input_tex = input_textures[entry.first]; - if (!use_opengl) { - AVFramePtr ptr = - ReadbackTextureToFrame(input_tex, input_tex->params()); - input_tex->handleFrame(ptr); - } - if (is_usable_input(input_tex)) { - input_textures[entry.first] = input_tex; - // First set the input texture (updates params_) - input_clip->setInputTexture(input_tex, frame); - // Then get the bitdepth/component from the instance - std::string bitdepth = input_clip->getUnmappedBitDepth(); - std::string component = input_clip->getUnmappedComponents(); - VideoParams params = input_tex->params(); - PixelFormat plugin_format = PixelFormat::from_ofx(bitdepth); - if (plugin_format != PixelFormat::INVALID) { - params.set_format(plugin_format); - } - if (!component.empty() && component != kOfxImageComponentNone) { - params.set_channel_count(component); - } - TexturePtr converted_tex = ConvertTextureForParams(input_tex, params); - if (converted_tex) { - input_tex = converted_tex; - input_textures[entry.first] = converted_tex; - } - OfxRectD rod; - rod.x1 = 0; - rod.y1 = 0; - rod.x2 = params.width() * params.pixel_aspect_ratio().toDouble(); - rod.y2 = params.height(); - input_clip->setRegionOfDefinition(rod, frame); - input_clips[entry.first] = input_clip; - } - } - std::map rois; - stat = instance->getRegionOfInterestAction(frame, renderScale, - regionOfInterest, rois); - if (stat != kOfxStatOK && stat != kOfxStatReplyDefault) { - LogOfxFailure("getRegionOfInterest", stat, instance); - MarkRenderFailure(destination); - return; - } - // set correct format for output - VideoParams output_params = destination_params; // params for plugin - // First set the destination params on the clip - output_clip->setParams(output_params); - // Get the plugin's preferred bitdepth/component from the instance (not descriptor) - // Use getUnmappedBitDepth/Components which use the instance's params_ - std::string bitdepth = output_clip->getUnmappedBitDepth(); - std::string component = output_clip->getUnmappedComponents(); - // If plugin returns a valid format different from destination, update output_params - PixelFormat plugin_format = PixelFormat::from_ofx(bitdepth); - if (plugin_format != PixelFormat::INVALID) { - output_params.set_format(plugin_format); - } - if (!component.empty() && component != kOfxImageComponentNone) { - output_params.set_channel_count(component); - } - // CRITICAL: Update params_ with the final format that plugin expects - // so getOutputImage creates the image with correct format - output_clip->setParams(output_params); - - // The render window is in pixel coordinates - // ie: render scale and a PAR of not 1 - OfxRectI renderWindow; - renderWindow.x1 = renderWindow.y1 = 0; - renderWindow.x2 = destination_params.width(); - renderWindow.y2 = destination_params.height(); - - - stat = instance->beginRenderAction(frame, numFramesToRender, - 1.0, false, renderScale, true, - interactive); - if (stat != kOfxStatOK && stat != kOfxStatReplyDefault) { - LogOfxFailure("beginRender", stat, instance); - MarkRenderFailure(destination); - return; - } - -#ifdef OFX_SUPPORTS_OPENGLRENDER - if (use_opengl) { - instance->contextAttachedAction(); - AttachOutputTexture(destination); - } -#endif - - - if (!output_params.is_valid()) { - qWarning().noquote() - << "OFX render skipped due to invalid output params for plugin=" - << PluginIdForInstance(instance); - MarkRenderFailure(destination); - instance->endRenderAction(frame, numFramesToRender, 1.0, interactive, - renderScale, true, interactive); - return; - } - - // render a frame - const char *render_field = GetRenderFieldForParams(output_params); - stat = instance->renderAction(frame, render_field, renderWindow, renderScale, - true, interactive, interactive); - if (stat != kOfxStatOK && stat != kOfxStatReplyDefault) { - LogOfxFailure("render", stat, instance); - LogClipState("output", output_clip, &output_params); - for (const auto &entry : input_clips) { - const auto params_it = input_params.find(entry.first); - const olive::VideoParams *params = - (params_it != input_params.end()) ? ¶ms_it->second - : nullptr; - LogClipState("input", entry.second, params); - OFX::Host::ImageEffect::Image *image = - entry.second->getImage(frame, nullptr); - LogImageProps("input", image); - //if (image) { - //image->releaseReference(); - //} - } - OFX::Host::ImageEffect::Image* output_image = - output_clip->getOutputImage(frame); - LogImageProps("output", output_image); - MarkRenderFailure(destination); - instance->endRenderAction(frame, numFramesToRender, 1.0, interactive, - renderScale, true, interactive); - return; - } - - // get the output image buffer (CPU path only) - OFX::Host::ImageEffect::Image* output_image; - if (!use_opengl) { - output_image = output_clip->getOutputImage(frame); - if (!output_image) { - qWarning().noquote() - << "OFX getOutputImage returned null for plugin=" - << PluginIdForInstance(instance); - MarkRenderFailure(destination); - instance->endRenderAction(frame, numFramesToRender, 1.0, interactive, - renderScale, true, interactive); - return; - } - } else { - if (!destination || !destination->id().isValid()) { -#ifdef OFX_SUPPORTS_OPENGLRENDER - DetachOutputTexture(); - instance->contextDetachedAction(); -#endif - instance->endRenderAction(frame, numFramesToRender, 1.0, interactive, - renderScale, true, interactive); - return; - } - } - - qDebug() << "RenderPlugin: destination_params fmt=" << static_cast(destination_params.format()) << "ch=" << destination_params.channel_count() << "dest->params fmt=" << static_cast(destination->params().format()) << "ch=" << destination->params().channel_count(); - if (!use_opengl) { - AVFramePtr frame_ptr = - create_avframe_from_ofx_image_with_params(*output_image, - destination_params); - if (!frame_ptr) { - qWarning().noquote() - << "OFX output image conversion failed for plugin=" - << PluginIdForInstance(instance); - instance->endRenderAction(frame, numFramesToRender, 1.0, interactive, - renderScale, true, interactive); - return; - } - AVFramePtr converted = ConvertFrameIfNeeded(frame_ptr, destination_params); - const AVPixelFormat expected_fmt = - GetDestinationAVPixelFormat(destination_params); - destination->handleFrame(converted); - if (destination->renderer() && converted && converted->data[0] && - (expected_fmt == AV_PIX_FMT_NONE || - converted->format == expected_fmt)) { - int linesize_pixels = - LinesizeToPixels(destination_params, converted->linesize[0]); - if (linesize_pixels <= 0) { - linesize_pixels = destination_params.effective_width(); - } - destination->Upload(converted->data[0], linesize_pixels); - } else if (destination->renderer() && converted && converted->data[0]) { - qWarning().noquote() - << "OFX output pixel format mismatch for plugin=" - << PluginIdForInstance(instance); - } - } else { - AVFramePtr frame_ptr = - ReadbackTextureToFrame(destination, destination_params); -#ifdef OFX_SUPPORTS_OPENGLRENDER - DetachOutputTexture(); - instance->contextDetachedAction(); -#endif - if (frame_ptr && destination) { - AVFramePtr converted = - ConvertFrameIfNeeded(frame_ptr, destination_params); - const AVPixelFormat expected_fmt = - GetDestinationAVPixelFormat(destination_params); - destination->handleFrame(converted); - if (destination->renderer() && converted && converted->data[0] && - (expected_fmt == AV_PIX_FMT_NONE || - converted->format == expected_fmt)) { - int linesize_pixels = LinesizeToPixels(destination_params, - converted->linesize[0]); - if (linesize_pixels <= 0) { - linesize_pixels = destination_params.effective_width(); - } - destination->Upload(converted->data[0], linesize_pixels); - } else if (destination->renderer() && converted && - converted->data[0]) { - qWarning().noquote() - << "OFX output pixel format mismatch for plugin=" - << PluginIdForInstance(instance); - } - } - } - - instance->endRenderAction(frame, numFramesToRender, 1.0, interactive, renderScale, true,interactive - ); - -} - -// 作用:绑定输出纹理到 OFX 的 GL 输出路径。 -// Purpose: Attach output texture for OFX GL rendering. -void olive::plugin::PluginRenderer::AttachOutputTexture(olive::TexturePtr texture) -{ - if (!texture) { - return; - } - AttachTextureAsDestination(texture->id()); -} - -// 作用:解除 OFX 的 GL 输出绑定。 -// Purpose: Detach OFX GL output binding. -void olive::plugin::PluginRenderer::DetachOutputTexture() -{ - DetachTextureAsDestination(); -} diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 246f92cca..0ac87ba24 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -758,7 +758,7 @@ TexturePtr RenderProcessor::ProcessPluginJob(TexturePtr texture, return true; } AVFramePtr frame = tex->frame(); - return frame && frame->data[0]; + return frame && frame->data(0); }; TexturePtr src = nullptr; diff --git a/app/render/videoparams.cpp b/app/render/videoparams.cpp index 6e5b54536..d50e498e7 100644 --- a/app/render/videoparams.cpp +++ b/app/render/videoparams.cpp @@ -21,9 +21,7 @@ #include "videoparams.h" -extern "C" { -#include -} +#include #include #include @@ -365,7 +363,7 @@ int VideoParams::GetScaledDimension(int dim, int divider) int64_t VideoParams::get_time_in_timebase_units(const rational &time) const { if (time_base_.isNull()) { - return AV_NOPTS_VALUE; + return INT64_MIN; // AV_NOPTS_VALUE } return Timecode::time_to_timestamp(time, time_base_) + start_time_; diff --git a/app/ui/humanstrings.cpp b/app/ui/humanstrings.cpp index e8197acad..a4d095558 100644 --- a/app/ui/humanstrings.cpp +++ b/app/ui/humanstrings.cpp @@ -31,15 +31,15 @@ QString HumanStrings::SampleRateToString(const int &sample_rate) QString HumanStrings::ChannelLayoutToString(const uint64_t &layout) { switch (layout) { - case AV_CH_LAYOUT_MONO: + case kChannelLayoutMono: return QCoreApplication::translate("AudioParams", "Mono"); - case AV_CH_LAYOUT_STEREO: + case kChannelLayoutStereo: return QCoreApplication::translate("AudioParams", "Stereo"); - case AV_CH_LAYOUT_2_1: + case kChannelLayout2_1: return QCoreApplication::translate("AudioParams", "2.1"); - case AV_CH_LAYOUT_5POINT1: + case kChannelLayout5Point1: return QCoreApplication::translate("AudioParams", "5.1"); - case AV_CH_LAYOUT_7POINT1: + case kChannelLayout7Point1: return QCoreApplication::translate("AudioParams", "7.1"); default: return QCoreApplication::translate("AudioParams", "Unknown (0x%1)") diff --git a/app/widget/standardcombos/channellayoutcombobox.h b/app/widget/standardcombos/channellayoutcombobox.h index f5ddefefd..6e9fcc1e0 100644 --- a/app/widget/standardcombos/channellayoutcombobox.h +++ b/app/widget/standardcombos/channellayoutcombobox.h @@ -24,9 +24,6 @@ #include #include -extern "C" { -#include -} #include "ui/humanstrings.h" namespace olive @@ -46,12 +43,9 @@ public: QVariant::fromValue(ch_layout)); } } - [[nodiscard]] AVChannelLayout GetChannelLayout() const + [[nodiscard]] uint64_t GetChannelLayout() const { - AVChannelLayout audio_channel_layout_; - av_channel_layout_from_mask(&audio_channel_layout_, - this->currentData().toULongLong()); - return audio_channel_layout_; + return this->currentData().toULongLong(); } void SetChannelLayout(uint64_t ch) { @@ -62,15 +56,6 @@ public: } } } - void SetChannelLayout(const AVChannelLayout &ch) - { - for (int i = 0; i < this->count(); i++) { - if (this->itemData(i).toULongLong() == ch.u.mask) { - this->setCurrentIndex(i); - break; - } - } - } public slots: private: }; diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index cceb1e318..2cbe46c82 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -603,10 +603,10 @@ void ViewerWidget::UpdateAudioProcessor() qDebug() << "ViewerWidget::UpdateAudioProcessor: from sample_rate=" << ap.sample_rate() << "channels=" << ap.channel_count() - << "layout_mask=0x" << Qt::hex << ap.channel_layout().u.mask + << "layout_mask=0x" << Qt::hex << ap.channel_layout() << "to sample_rate=" << packed.sample_rate() << "channels=" << packed.channel_count() - << "layout_mask=0x" << packed.channel_layout().u.mask + << "layout_mask=0x" << packed.channel_layout() << Qt::dec; audio_processor_.Open( diff --git a/ffmpeg_bridge/CMakeLists.txt b/ffmpeg_bridge/CMakeLists.txt index 0badf5afb..dc672bcc2 100644 --- a/ffmpeg_bridge/CMakeLists.txt +++ b/ffmpeg_bridge/CMakeLists.txt @@ -93,10 +93,16 @@ set_target_properties(ffmpeg_bridge PROPERTIES if (WIN32) set_target_properties(ffmpeg_bridge PROPERTIES PREFIX "") + # Windows has no RPATH: the DLL must sit next to the executables + install(TARGETS ffmpeg_bridge + RUNTIME DESTINATION bin + LIBRARY DESTINATION bin + ARCHIVE DESTINATION ffmpeg_bridge/lib + ) +else() + install(TARGETS ffmpeg_bridge + RUNTIME DESTINATION ffmpeg_bridge/bin + LIBRARY DESTINATION ffmpeg_bridge/bin + ARCHIVE DESTINATION ffmpeg_bridge/lib + ) endif () - -install(TARGETS ffmpeg_bridge - RUNTIME DESTINATION ffmpeg_bridge/bin - LIBRARY DESTINATION ffmpeg_bridge/bin - ARCHIVE DESTINATION ffmpeg_bridge/lib -) diff --git a/ffmpeg_bridge/include/ffmpeg_bridge/ffmpeg_bridge.h b/ffmpeg_bridge/include/ffmpeg_bridge/ffmpeg_bridge.h index 36ba2ac2d..dcfa8aaaf 100644 --- a/ffmpeg_bridge/include/ffmpeg_bridge/ffmpeg_bridge.h +++ b/ffmpeg_bridge/include/ffmpeg_bridge/ffmpeg_bridge.h @@ -87,6 +87,7 @@ typedef enum FBPixelFormat { FB_PIX_FMT_YUVJ444P = 14, FB_PIX_FMT_RGBA = 26, FB_PIX_FMT_GRAY16LE = 30, + FB_PIX_FMT_YUV440P = 31, FB_PIX_FMT_YUVJ440P = 32, FB_PIX_FMT_RGB48LE = 35, FB_PIX_FMT_YUV420P10LE = 62, @@ -254,6 +255,8 @@ FB_API void fb_frame_free(FBFrame **frame); FB_API void fb_frame_unref(FBFrame *frame); /** Allocate the frame's buffer(s) from its width/height/format fields. */ FB_API int fb_frame_get_buffer(FBFrame *frame, int align); +/** Ensure the frame's data is writable (mirrors av_frame_make_writable). */ +FB_API int fb_frame_make_writable(FBFrame *frame); FB_API int fb_frame_copy_props(FBFrame *dst, const FBFrame *src); /** Transfer data between a hardware frame and a software frame. */ FB_API int fb_frame_hw_transfer_data(FBFrame *dst, const FBFrame *src); @@ -351,6 +354,7 @@ FB_API void fb_decoder_seek(FBDecoder *decoder, int64_t timestamp); FB_API int fb_decoder_get_stream_info(const FBDecoder *decoder, FBStreamInfo *out); FB_API int64_t fb_decoder_get_format_start_time(const FBDecoder *decoder); +FB_API int64_t fb_decoder_get_format_duration(const FBDecoder *decoder); FB_API int fb_decoder_guess_sample_aspect_ratio(const FBDecoder *decoder, FBFrame *frame, int *num, @@ -467,6 +471,9 @@ FB_API int fb_resampler_get_out_samples(FBResampler *resampler, FB_API int fb_resampler_convert(FBResampler *resampler, uint8_t **out, int out_count, const uint8_t **in, int in_count); +/** Same as fb_resampler_convert but takes the input directly from a frame. */ +FB_API int fb_resampler_convert_frame(FBResampler *resampler, uint8_t **out, + int out_count, const FBFrame *in_frame); /* ------------------------------------------------------------------------- */ /* Audio filter graph (abuffer/aformat/atempo/abuffersink wrapper) */ diff --git a/ffmpeg_bridge/src/decoder.cpp b/ffmpeg_bridge/src/decoder.cpp index 4217fe54e..851999425 100644 --- a/ffmpeg_bridge/src/decoder.cpp +++ b/ffmpeg_bridge/src/decoder.cpp @@ -456,6 +456,14 @@ int64_t fb_decoder_get_format_start_time(const FBDecoder *decoder) return decoder->fmt_ctx->start_time; } +int64_t fb_decoder_get_format_duration(const FBDecoder *decoder) +{ + if (!decoder || !decoder->fmt_ctx) { + return FB_NOPTS_VALUE; + } + return decoder->fmt_ctx->duration; +} + int fb_decoder_guess_sample_aspect_ratio(const FBDecoder *decoder, FBFrame *frame, int *num, int *den) { diff --git a/ffmpeg_bridge/src/encoder.cpp b/ffmpeg_bridge/src/encoder.cpp index 89aa0e7e6..8b5d9bd09 100644 --- a/ffmpeg_bridge/src/encoder.cpp +++ b/ffmpeg_bridge/src/encoder.cpp @@ -566,6 +566,8 @@ int fb_encoder_write_audio(FBEncoder *e, const uint8_t *const *channel_data, int bytes_per_sample = av_get_bytes_per_sample(static_cast(sample_format)); + int planar = + av_sample_fmt_is_planar(static_cast(sample_format)); bool result = true; @@ -588,10 +590,16 @@ int fb_encoder_write_audio(FBEncoder *e, const uint8_t *const *channel_data, e->SetError("Failed to allocate sample array", r); return r; } else { - for (int i = 0; i < channels; i++) { - memcpy(input_data[i], - channel_data[i] + start * size_t(bytes_per_sample), - input_sample_count * size_t(bytes_per_sample)); + if (planar) { + for (int i = 0; i < channels; i++) { + memcpy(input_data[i], + channel_data[i] + start * size_t(bytes_per_sample), + input_sample_count * size_t(bytes_per_sample)); + } + } else { + size_t stride = size_t(bytes_per_sample) * size_t(channels); + memcpy(input_data[0], channel_data[0] + start * stride, + input_sample_count * stride); } start += input_sample_count; diff --git a/ffmpeg_bridge/src/frame.cpp b/ffmpeg_bridge/src/frame.cpp index 9df99c26c..212830392 100644 --- a/ffmpeg_bridge/src/frame.cpp +++ b/ffmpeg_bridge/src/frame.cpp @@ -59,6 +59,14 @@ int fb_frame_get_buffer(FBFrame *frame, int align) return av_frame_get_buffer(frame->frame, align); } +int fb_frame_make_writable(FBFrame *frame) +{ + if (!frame || !frame->frame) { + return AVERROR(EINVAL); + } + return av_frame_make_writable(frame->frame); +} + int fb_frame_copy_props(FBFrame *dst, const FBFrame *src) { if (!dst || !src) { diff --git a/ffmpeg_bridge/src/internal.h b/ffmpeg_bridge/src/internal.h index 2fd259c20..004729a70 100644 --- a/ffmpeg_bridge/src/internal.h +++ b/ffmpeg_bridge/src/internal.h @@ -68,6 +68,7 @@ static_assert(FB_PIX_FMT_YUVJ422P == AV_PIX_FMT_YUVJ422P, "pixfmt mismatch"); static_assert(FB_PIX_FMT_YUVJ444P == AV_PIX_FMT_YUVJ444P, "pixfmt mismatch"); static_assert(FB_PIX_FMT_RGBA == AV_PIX_FMT_RGBA, "pixfmt mismatch"); static_assert(FB_PIX_FMT_GRAY16LE == AV_PIX_FMT_GRAY16LE, "pixfmt mismatch"); +static_assert(FB_PIX_FMT_YUV440P == AV_PIX_FMT_YUV440P, "pixfmt mismatch"); static_assert(FB_PIX_FMT_YUVJ440P == AV_PIX_FMT_YUVJ440P, "pixfmt mismatch"); static_assert(FB_PIX_FMT_RGB48LE == AV_PIX_FMT_RGB48LE, "pixfmt mismatch"); static_assert(FB_PIX_FMT_YUV420P10LE == AV_PIX_FMT_YUV420P10LE, "pixfmt mismatch"); diff --git a/ffmpeg_bridge/src/probe.cpp b/ffmpeg_bridge/src/probe.cpp index 65ff9b639..6a02a0b59 100644 --- a/ffmpeg_bridge/src/probe.cpp +++ b/ffmpeg_bridge/src/probe.cpp @@ -309,6 +309,14 @@ int fb_probe_read_subtitle_stream(const char *filename, int stream_index, return AVERROR_EXTERNAL; } + // Limit to SRT for now (mirrors the editor's historical behavior) + FBStreamInfo stream_info; + if (fb_decoder_get_stream_info(decoder, &stream_info) < 0 || + stream_info.codec_id != (int)AV_CODEC_ID_SUBRIP) { + fb_decoder_free(&decoder); + return AVERROR(EINVAL); + } + FBPacket *pkt = fb_packet_alloc(); while (fb_decoder_get_packet(decoder, pkt) >= 0) { diff --git a/ffmpeg_bridge/src/swr.cpp b/ffmpeg_bridge/src/swr.cpp index 9e854fd80..57d0fdaa5 100644 --- a/ffmpeg_bridge/src/swr.cpp +++ b/ffmpeg_bridge/src/swr.cpp @@ -81,3 +81,14 @@ int fb_resampler_convert(FBResampler *resampler, uint8_t **out, int out_count, } return swr_convert(resampler->ctx, out, out_count, in, in_count); } + +int fb_resampler_convert_frame(FBResampler *resampler, uint8_t **out, + int out_count, const FBFrame *in_frame) +{ + if (!resampler || !in_frame || !in_frame->frame) { + return AVERROR(EINVAL); + } + return swr_convert(resampler->ctx, out, out_count, + (const uint8_t **)in_frame->frame->extended_data, + in_frame->frame->nb_samples); +} diff --git a/ffmpeg_bridge/src/utils.cpp b/ffmpeg_bridge/src/utils.cpp index e885ddf0e..44bc0fba8 100644 --- a/ffmpeg_bridge/src/utils.cpp +++ b/ffmpeg_bridge/src/utils.cpp @@ -172,7 +172,8 @@ int fb_pix_fmt_component_size(int pix_fmt) if (!desc || desc->nb_components == 0) { return 0; } - return desc->comp[0].step; + // Bytes used to store one component: 1 for 8-bit formats, 2 for 9-16bit + return (desc->comp[0].depth + 7) / 8; } int fb_find_best_pix_fmt_of_list(const int *list, int pix_fmt)