diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 215f61902..b5e3c87f0 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -1187,7 +1187,7 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational &time, AVFramePtr FFmpegDecoder::TransferHardwareFrame(AVFramePtr f) { if (!fb_decoder_hwaccel_enabled(instance_) || - f->format() != fb_decoder_hw_pix_fmt(instance_)) { + !fb_frame_is_hw(f->handle())) { return f; } diff --git a/core/include/olive/core/util/timecodefunctions.h b/core/include/olive/core/util/timecodefunctions.h index 301c6d5eb..0e4b4dde6 100644 --- a/core/include/olive/core/util/timecodefunctions.h +++ b/core/include/olive/core/util/timecodefunctions.h @@ -24,6 +24,8 @@ #include "rational.h" +#include + namespace olive::core { diff --git a/ffmpeg_bridge/include/ffmpeg_bridge/ffmpeg_bridge.h b/ffmpeg_bridge/include/ffmpeg_bridge/ffmpeg_bridge.h index dcfa8aaaf..60511b362 100644 --- a/ffmpeg_bridge/include/ffmpeg_bridge/ffmpeg_bridge.h +++ b/ffmpeg_bridge/include/ffmpeg_bridge/ffmpeg_bridge.h @@ -69,9 +69,12 @@ extern "C" { #define FB_SCALER_POINT 0x10 /** - * Pixel formats. Values deliberately mirror AVPixelFormat so that the library - * can use them directly; every value is static_assert'ed against the real - * FFmpeg headers inside the library. Callers must treat them as opaque. + * Pixel formats. These are fixed identifiers; the library maps them to the + * AVPixelFormat values of whatever FFmpeg build it was compiled against + * (AVPixelFormat enum values shift between FFmpeg releases). Callers must + * treat them as opaque. A format may be unknown to an older FFmpeg build, + * in which case library calls reject it gracefully. Decoded frames whose + * format has no entry here receive an opaque process-local id >= 1000. */ typedef enum FBPixelFormat { FB_PIX_FMT_NONE = -1, @@ -85,6 +88,7 @@ typedef enum FBPixelFormat { FB_PIX_FMT_YUVJ420P = 12, FB_PIX_FMT_YUVJ422P = 13, FB_PIX_FMT_YUVJ444P = 14, + FB_PIX_FMT_NV12 = 23, FB_PIX_FMT_RGBA = 26, FB_PIX_FMT_GRAY16LE = 30, FB_PIX_FMT_YUV440P = 31, @@ -98,6 +102,7 @@ typedef enum FBPixelFormat { FB_PIX_FMT_YUV422P12LE = 127, FB_PIX_FMT_YUV444P12LE = 131, FB_PIX_FMT_YUVJ411P = 138, + FB_PIX_FMT_P010LE = 158, FB_PIX_FMT_GRAYF32LE = 183, FB_PIX_FMT_RGBAF16LE = 207, FB_PIX_FMT_RGBF32LE = 218, diff --git a/ffmpeg_bridge/src/decoder.cpp b/ffmpeg_bridge/src/decoder.cpp index 851999425..055ef669c 100644 --- a/ffmpeg_bridge/src/decoder.cpp +++ b/ffmpeg_bridge/src/decoder.cpp @@ -431,7 +431,7 @@ int fb_decoder_get_stream_info(const FBDecoder *decoder, FBStreamInfo *out) out->has_decoder = 1; // stream is open, so a decoder was found out->width = par->width; out->height = par->height; - out->pixel_format = par->format; + out->pixel_format = fb::PixFmtFromAV(AVPixelFormat(par->format)); out->field_order = decoder->codec_ctx ? decoder->codec_ctx->field_order : AV_FIELD_UNKNOWN; out->color_range = par->color_range; @@ -499,5 +499,8 @@ int fb_decoder_hwaccel_enabled(const FBDecoder *decoder) int fb_decoder_hw_pix_fmt(const FBDecoder *decoder) { - return decoder ? int(decoder->hw_pix_fmt) : FB_PIX_FMT_NONE; + // Hardware pixel formats have no static FB_PIX_FMT_* identifier, so this + // returns a process-local dynamic id for them. Use fb_frame_is_hw() to + // detect hardware frames. + return decoder ? fb::PixFmtFromAV(decoder->hw_pix_fmt) : FB_PIX_FMT_NONE; } diff --git a/ffmpeg_bridge/src/encoder.cpp b/ffmpeg_bridge/src/encoder.cpp index 8b5d9bd09..8b0893620 100644 --- a/ffmpeg_bridge/src/encoder.cpp +++ b/ffmpeg_bridge/src/encoder.cpp @@ -143,6 +143,7 @@ struct FBEncoder { int video_frame_rate_num = 0; int video_frame_rate_den = 1; std::string video_pix_fmt; + // In AVPixelFormat space (translated from the FB config value at create) int video_src_pix_fmt = FB_PIX_FMT_NONE; int video_color_range = FB_COLOR_RANGE_UNSPEC; int video_field_order = FB_FIELD_ORDER_PROGRESSIVE; @@ -239,7 +240,9 @@ FBEncoder *fb_encoder_create(const FBEncoderConfig *config) if (config->video_pix_fmt) { e->video_pix_fmt = config->video_pix_fmt; } - e->video_src_pix_fmt = config->video_src_pix_fmt; + // Stored in AVPixelFormat space; the public config value is an + // FB_PIX_FMT_* identifier. + e->video_src_pix_fmt = fb::PixFmtToAV(config->video_src_pix_fmt); e->video_color_range = config->video_color_range; e->video_field_order = config->video_field_order; e->video_bit_rate = config->video_bit_rate; @@ -429,7 +432,7 @@ int fb_encoder_write_video_frame(FBEncoder *e, int width, int height, input_frame->width = width; input_frame->height = height; - input_frame->format = pix_fmt; + input_frame->format = fb::PixFmtToAV(pix_fmt); input_frame->data[0] = const_cast(data); input_frame->linesize[0] = linesize; diff --git a/ffmpeg_bridge/src/frame.cpp b/ffmpeg_bridge/src/frame.cpp index 212830392..63434cb46 100644 --- a/ffmpeg_bridge/src/frame.cpp +++ b/ffmpeg_bridge/src/frame.cpp @@ -114,13 +114,14 @@ void fb_frame_set_height(FBFrame *frame, int height) int fb_frame_get_format(const FBFrame *frame) { - return frame ? frame->frame->format : FB_PIX_FMT_NONE; + return frame ? fb::PixFmtFromAV(AVPixelFormat(frame->frame->format)) : + FB_PIX_FMT_NONE; } void fb_frame_set_format(FBFrame *frame, int format) { if (frame) { - frame->frame->format = format; + frame->frame->format = fb::PixFmtToAV(format); } } diff --git a/ffmpeg_bridge/src/internal.h b/ffmpeg_bridge/src/internal.h index 004729a70..2cf15dbf0 100644 --- a/ffmpeg_bridge/src/internal.h +++ b/ffmpeg_bridge/src/internal.h @@ -56,35 +56,6 @@ static_assert(FB_TIME_BASE == AV_TIME_BASE, "FB_TIME_BASE mismatch"); static_assert(FB_SCALER_POINT == SWS_POINT, "FB_SCALER_POINT mismatch"); static_assert(FB_PIX_FMT_NONE == AV_PIX_FMT_NONE, "FB_PIX_FMT_NONE mismatch"); -static_assert(FB_PIX_FMT_YUV420P == AV_PIX_FMT_YUV420P, "pixfmt mismatch"); -static_assert(FB_PIX_FMT_RGB24 == AV_PIX_FMT_RGB24, "pixfmt mismatch"); -static_assert(FB_PIX_FMT_YUV422P == AV_PIX_FMT_YUV422P, "pixfmt mismatch"); -static_assert(FB_PIX_FMT_YUV444P == AV_PIX_FMT_YUV444P, "pixfmt mismatch"); -static_assert(FB_PIX_FMT_YUV410P == AV_PIX_FMT_YUV410P, "pixfmt mismatch"); -static_assert(FB_PIX_FMT_YUV411P == AV_PIX_FMT_YUV411P, "pixfmt mismatch"); -static_assert(FB_PIX_FMT_GRAY8 == AV_PIX_FMT_GRAY8, "pixfmt mismatch"); -static_assert(FB_PIX_FMT_YUVJ420P == AV_PIX_FMT_YUVJ420P, "pixfmt mismatch"); -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"); -static_assert(FB_PIX_FMT_YUV422P10LE == AV_PIX_FMT_YUV422P10LE, "pixfmt mismatch"); -static_assert(FB_PIX_FMT_YUV444P10LE == AV_PIX_FMT_YUV444P10LE, "pixfmt mismatch"); -static_assert(FB_PIX_FMT_RGBA64LE == AV_PIX_FMT_RGBA64LE, "pixfmt mismatch"); -static_assert(FB_PIX_FMT_YUV420P12LE == AV_PIX_FMT_YUV420P12LE, "pixfmt mismatch"); -static_assert(FB_PIX_FMT_YUV422P12LE == AV_PIX_FMT_YUV422P12LE, "pixfmt mismatch"); -static_assert(FB_PIX_FMT_YUV444P12LE == AV_PIX_FMT_YUV444P12LE, "pixfmt mismatch"); -static_assert(FB_PIX_FMT_YUVJ411P == AV_PIX_FMT_YUVJ411P, "pixfmt mismatch"); -static_assert(FB_PIX_FMT_GRAYF32LE == AV_PIX_FMT_GRAYF32LE, "pixfmt mismatch"); -static_assert(FB_PIX_FMT_RGBAF16LE == AV_PIX_FMT_RGBAF16LE, "pixfmt mismatch"); -static_assert(FB_PIX_FMT_RGBF32LE == AV_PIX_FMT_RGBF32LE, "pixfmt mismatch"); -static_assert(FB_PIX_FMT_RGBAF32LE == AV_PIX_FMT_RGBAF32LE, "pixfmt mismatch"); -static_assert(FB_PIX_FMT_RGBF16LE == AV_PIX_FMT_RGBF16LE, "pixfmt mismatch"); -static_assert(FB_PIX_FMT_GRAYF16LE == AV_PIX_FMT_GRAYF16LE, "pixfmt mismatch"); static_assert(FB_SAMPLE_FMT_NONE == AV_SAMPLE_FMT_NONE, "samplefmt mismatch"); static_assert(FB_SAMPLE_FMT_U8 == AV_SAMPLE_FMT_U8, "samplefmt mismatch"); @@ -162,6 +133,24 @@ uint64_t ValidateStreamChannelLayoutMask(const AVStream *stream); /** Map an AVColorSpace to the corresponding SWS_CS_* constant. */ int SwsColorspaceFromAVColorSpace(AVColorSpace cs); +/** + * Translate an FB_PIX_FMT_* value to the AVPixelFormat of the FFmpeg build + * this library was compiled against. AVPixelFormat enum values shift between + * FFmpeg releases (new formats are inserted mid-enum), so the public FB + * values are fixed identifiers resolved by pixel format name. Returns + * AV_PIX_FMT_NONE for FB_PIX_FMT_NONE and for static FB formats unknown to + * this FFmpeg build (e.g. rgbf16le/grayf16le on FFmpeg < 7.1). + */ +AVPixelFormat PixFmtToAV(int fb_fmt); + +/** + * Reverse of PixFmtToAV. Returns FB_PIX_FMT_NONE for AV_PIX_FMT_NONE. + * Formats without a static FB_PIX_FMT_* identifier (hardware-download + * formats such as p210le, etc.) receive a process-local dynamic id >= 1000 + * so they can still round-trip through the API. + */ +int PixFmtFromAV(AVPixelFormat fmt); + void SetError(char *error_buffer, size_t error_buffer_size, const char *context, int error_code); diff --git a/ffmpeg_bridge/src/probe.cpp b/ffmpeg_bridge/src/probe.cpp index 6a02a0b59..c6cfef227 100644 --- a/ffmpeg_bridge/src/probe.cpp +++ b/ffmpeg_bridge/src/probe.cpp @@ -45,7 +45,7 @@ void FillStreamInfo(const AVStream *s, int has_decoder, FBStreamInfo *out) out->has_decoder = has_decoder; out->width = par->width; out->height = par->height; - out->pixel_format = par->format; + out->pixel_format = fb::PixFmtFromAV(AVPixelFormat(par->format)); out->field_order = FB_FIELD_ORDER_UNKNOWN; out->color_range = par->color_range; out->sample_rate = par->sample_rate; diff --git a/ffmpeg_bridge/src/sws.cpp b/ffmpeg_bridge/src/sws.cpp index 2c19362b1..0d8276c3f 100644 --- a/ffmpeg_bridge/src/sws.cpp +++ b/ffmpeg_bridge/src/sws.cpp @@ -30,8 +30,8 @@ FBScaler *fb_scaler_create(int src_width, int src_height, int src_format, int flags) { SwsContext *ctx = sws_getContext( - src_width, src_height, static_cast(src_format), - dst_width, dst_height, static_cast(dst_format), flags, + src_width, src_height, fb::PixFmtToAV(src_format), + dst_width, dst_height, fb::PixFmtToAV(dst_format), flags, nullptr, nullptr, nullptr); if (!ctx) { return nullptr; diff --git a/ffmpeg_bridge/src/utils.cpp b/ffmpeg_bridge/src/utils.cpp index 44bc0fba8..49224eb6b 100644 --- a/ffmpeg_bridge/src/utils.cpp +++ b/ffmpeg_bridge/src/utils.cpp @@ -25,9 +25,128 @@ #include #include +#include +#include +#include + namespace fb { +namespace +{ + +/** + * The public FB_PIX_FMT_* values are fixed identifiers (the host app never + * sees AVPixelFormat). AVPixelFormat enum values shift between FFmpeg + * releases because new formats are inserted mid-enum, so the mapping is + * resolved through pixel format names, which are stable. Formats unknown to + * the FFmpeg build this library was compiled against resolve to + * AV_PIX_FMT_NONE. + */ +struct PixFmtName { + int fb_fmt; + const char *av_name; +}; + +constexpr PixFmtName kPixFmtNames[] = { + { FB_PIX_FMT_YUV420P, "yuv420p" }, + { FB_PIX_FMT_RGB24, "rgb24" }, + { FB_PIX_FMT_YUV422P, "yuv422p" }, + { FB_PIX_FMT_YUV444P, "yuv444p" }, + { FB_PIX_FMT_YUV410P, "yuv410p" }, + { FB_PIX_FMT_YUV411P, "yuv411p" }, + { FB_PIX_FMT_GRAY8, "gray8" }, + { FB_PIX_FMT_YUVJ420P, "yuvj420p" }, + { FB_PIX_FMT_YUVJ422P, "yuvj422p" }, + { FB_PIX_FMT_YUVJ444P, "yuvj444p" }, + { FB_PIX_FMT_NV12, "nv12" }, + { FB_PIX_FMT_RGBA, "rgba" }, + { FB_PIX_FMT_GRAY16LE, "gray16le" }, + { FB_PIX_FMT_YUV440P, "yuv440p" }, + { FB_PIX_FMT_YUVJ440P, "yuvj440p" }, + { FB_PIX_FMT_RGB48LE, "rgb48le" }, + { FB_PIX_FMT_YUV420P10LE, "yuv420p10le" }, + { FB_PIX_FMT_YUV422P10LE, "yuv422p10le" }, + { FB_PIX_FMT_YUV444P10LE, "yuv444p10le" }, + { FB_PIX_FMT_RGBA64LE, "rgba64le" }, + { FB_PIX_FMT_YUV420P12LE, "yuv420p12le" }, + { FB_PIX_FMT_YUV422P12LE, "yuv422p12le" }, + { FB_PIX_FMT_YUV444P12LE, "yuv444p12le" }, + { FB_PIX_FMT_YUVJ411P, "yuvj411p" }, + { FB_PIX_FMT_P010LE, "p010le" }, + { FB_PIX_FMT_GRAYF32LE, "grayf32le" }, + { FB_PIX_FMT_RGBAF16LE, "rgbaf16le" }, + { FB_PIX_FMT_RGBF32LE, "rgbf32le" }, + { FB_PIX_FMT_RGBAF32LE, "rgbaf32le" }, + { FB_PIX_FMT_RGBF16LE, "rgbf16le" }, + { FB_PIX_FMT_GRAYF16LE, "grayf16le" }, +}; + +} // namespace + +namespace +{ + +/** + * Formats outside the static table (hardware-download formats like p210le, + * or anything else a decoder produces) receive process-local identifiers so + * they still round-trip through the API (e.g. into the scaler) instead of + * collapsing to FB_PIX_FMT_NONE. Dynamic ids start above every static + * FB_PIX_FMT_* value and are not stable across runs, which is fine because + * callers treat the values as opaque. + */ +constexpr int kDynamicPixFmtBase = 1000; +std::mutex g_dynamic_pix_fmt_mutex; +std::vector> g_dynamic_pix_fmts; + +} // namespace + +AVPixelFormat PixFmtToAV(int fb_fmt) +{ + if (fb_fmt == FB_PIX_FMT_NONE) { + return AV_PIX_FMT_NONE; + } + for (const PixFmtName &entry : kPixFmtNames) { + if (entry.fb_fmt == fb_fmt) { + return av_get_pix_fmt(entry.av_name); + } + } + if (fb_fmt >= kDynamicPixFmtBase) { + std::lock_guard lock(g_dynamic_pix_fmt_mutex); + for (const auto &entry : g_dynamic_pix_fmts) { + if (entry.first == fb_fmt) { + return entry.second; + } + } + } + return AV_PIX_FMT_NONE; +} + +int PixFmtFromAV(AVPixelFormat fmt) +{ + if (fmt == AV_PIX_FMT_NONE) { + return FB_PIX_FMT_NONE; + } + const char *name = av_get_pix_fmt_name(fmt); + if (name) { + for (const PixFmtName &entry : kPixFmtNames) { + if (strcmp(name, entry.av_name) == 0) { + return entry.fb_fmt; + } + } + } + + std::lock_guard lock(g_dynamic_pix_fmt_mutex); + for (const auto &entry : g_dynamic_pix_fmts) { + if (entry.second == fmt) { + return entry.first; + } + } + const int id = kDynamicPixFmtBase + int(g_dynamic_pix_fmts.size()); + g_dynamic_pix_fmts.emplace_back(id, fmt); + return id; +} + void ChannelLayoutFromMask(AVChannelLayout *layout, uint64_t mask, int fallback_channels) { @@ -133,18 +252,18 @@ const char *fb_version_string(void) const char *fb_pix_fmt_name(int pix_fmt) { - return av_get_pix_fmt_name(static_cast(pix_fmt)); + return av_get_pix_fmt_name(fb::PixFmtToAV(pix_fmt)); } int fb_pix_fmt_from_name(const char *name) { - return av_get_pix_fmt(name); + return fb::PixFmtFromAV(av_get_pix_fmt(name)); } int fb_pix_fmt_bits_per_pixel(int pix_fmt) { const AVPixFmtDescriptor *desc = - av_pix_fmt_desc_get(static_cast(pix_fmt)); + av_pix_fmt_desc_get(fb::PixFmtToAV(pix_fmt)); if (!desc) { return 0; } @@ -154,21 +273,21 @@ int fb_pix_fmt_bits_per_pixel(int pix_fmt) int fb_pix_fmt_has_alpha(int pix_fmt) { const AVPixFmtDescriptor *desc = - av_pix_fmt_desc_get(static_cast(pix_fmt)); + av_pix_fmt_desc_get(fb::PixFmtToAV(pix_fmt)); return desc && (desc->flags & AV_PIX_FMT_FLAG_ALPHA); } int fb_pix_fmt_is_planar(int pix_fmt) { const AVPixFmtDescriptor *desc = - av_pix_fmt_desc_get(static_cast(pix_fmt)); + av_pix_fmt_desc_get(fb::PixFmtToAV(pix_fmt)); return desc && (desc->flags & AV_PIX_FMT_FLAG_PLANAR); } int fb_pix_fmt_component_size(int pix_fmt) { const AVPixFmtDescriptor *desc = - av_pix_fmt_desc_get(static_cast(pix_fmt)); + av_pix_fmt_desc_get(fb::PixFmtToAV(pix_fmt)); if (!desc || desc->nb_components == 0) { return 0; } @@ -178,15 +297,29 @@ int fb_pix_fmt_component_size(int pix_fmt) int fb_find_best_pix_fmt_of_list(const int *list, int pix_fmt) { - // Count the list - int count = 0; - while (list[count] != FB_PIX_FMT_NONE) { - count++; + // Translate the FB_PIX_FMT_NONE-terminated list to AVPixelFormat values, + // skipping formats unknown to this FFmpeg build + std::vector av_list; + for (int i = 0; list[i] != FB_PIX_FMT_NONE; i++) { + AVPixelFormat fmt = fb::PixFmtToAV(list[i]); + if (fmt != AV_PIX_FMT_NONE) { + av_list.push_back(fmt); + } + } + if (av_list.empty()) { + return FB_PIX_FMT_NONE; } - return avcodec_find_best_pix_fmt_of_list( - reinterpret_cast(list), - static_cast(pix_fmt), 1, nullptr); + // With an unknown source format there is no loss metric to compare + // against; prefer the first (most desirable) list entry + AVPixelFormat av_src = fb::PixFmtToAV(pix_fmt); + if (av_src == AV_PIX_FMT_NONE) { + return list[0]; + } + + av_list.push_back(AV_PIX_FMT_NONE); + return fb::PixFmtFromAV(avcodec_find_best_pix_fmt_of_list( + av_list.data(), av_src, 1, nullptr)); } int fb_channel_layout_get_channels(uint64_t mask) diff --git a/tests/gtest/ffmpeg_bridge_test.cpp b/tests/gtest/ffmpeg_bridge_test.cpp index 3e309881e..86ebde5a0 100644 --- a/tests/gtest/ffmpeg_bridge_test.cpp +++ b/tests/gtest/ffmpeg_bridge_test.cpp @@ -735,8 +735,10 @@ TEST(FFmpegBridgeEncoder, WritePngVideoAndProbeBack) const int height = 64; const int frame_count = 5; + const QByteArray filename = path.toUtf8(); + FBEncoderConfig config = {}; - config.filename = path.toUtf8().constData(); + config.filename = filename.constData(); config.video_enabled = 1; config.video_codec = FB_CODEC_PNG; config.video_width = width; @@ -799,8 +801,10 @@ TEST(FFmpegBridgeEncoder, WritePcmAudioAndProbeBack) const QString path = TempFilePath(QStringLiteral("fb_bridge_test.wav")); QFile::remove(path); + const QByteArray filename = path.toUtf8(); + FBEncoderConfig config = {}; - config.filename = path.toUtf8().constData(); + config.filename = filename.constData(); config.audio_enabled = 1; config.audio_codec = FB_CODEC_PCM; config.audio_sample_rate = 44100;