PR #25349 opened by Forgejo_Fairy URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25349 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25349.patch
Primaries and transfer characteristics currently travel on frames without participating in link negotiation. Add both properties to the public filter API and negotiation machinery, allowing buffer sinks and format filters to request conversions through automatically inserted scale filters. Forward FFmpeg encoder requests and input colorimetry into the graph, and reinitialize when these input properties change. The series separates public API additions, negotiation, endpoint integration, individual filter adaptations, documentation, and tests. Color-changing filters advertise explicit overrides or leave frame-dependent outputs unspecified. Preserve existing dynamic defaults and independent gainmap input colorimetry. Implements #25342 and addresses the missing command-line propagation discussed in #20736. Validation: - Each of the 27 commits was rebuilt and checked with relevant FATE tests; the first 26 were checked through the final rebase, and the final side-data test commit was checked separately. - All 660 enabled filter FATE targets passed on ARM64 and x86-64 with GPL, libzimg, and lcms2 enabled. The final API test passed on both architectures. - Eight new FATE targets cover API negotiation, automatic versus explicit conversion, defaults, exclusions, extended enums, disabled conversion, filter compatibility, and color-dependent side data. - Targeted ASan/UBSan tests passed with leak detection enabled. - Command-line primaries/transfer requests matched explicit scale conversion byte for byte and retained the requested encoded tags. - QSV/OpenCL/VAAPI compilation checks passed; the changed libplacebo object compiled against libplacebo 7.351.0 and newer Vulkan headers. GPU runtime behavior remains untested. Windows capture, VideoToolbox, AMF, and LCEVC adaptations were source-reviewed but not built or exercised in their required environments. >From db3ce5435750b4e6ad998ce6d9609c931eb88bc1 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 22 Oct 2026 11:09:02 +0000 Subject: [PATCH 01/27] avfilter: add primaries and transfer properties to the public API Assisted-by: Fairy --- doc/APIchanges | 5 +++++ libavfilter/avfilter.c | 2 ++ libavfilter/avfilter.h | 6 ++++++ libavfilter/buffersink.c | 2 ++ libavfilter/buffersink.h | 2 ++ libavfilter/buffersrc.c | 2 ++ libavfilter/buffersrc.h | 5 +++++ libavfilter/version.h | 2 +- 8 files changed, 25 insertions(+), 1 deletion(-) diff --git a/doc/APIchanges b/doc/APIchanges index 323b88af8f9..4ba81d2d15c 100644 --- a/doc/APIchanges +++ b/doc/APIchanges @@ -2,6 +2,11 @@ The last version increases of all libraries were on 2026-06-23. API changes, most recent first: +2026-10-22 - xxxxxxxxxx - lavfi 12.6.100 - avfilter.h buffersrc.h buffersink.h + Add AVFilterLink.color_primaries and color_trc, + AVBufferSrcParameters.color_primaries and color_trc, and + av_buffersink_get_color_primaries() and av_buffersink_get_color_trc(). + 2026-10-xx - xxxxxxxxxx - lavu 61.16.100 - hwcontext.h Add av_hwframe_get_transfer_buffer(). diff --git a/libavfilter/avfilter.c b/libavfilter/avfilter.c index d078e404ff7..75c7ad7591b 100644 --- a/libavfilter/avfilter.c +++ b/libavfilter/avfilter.c @@ -190,6 +190,8 @@ int avfilter_link(AVFilterContext *src, unsigned srcpad, av_assert0(AV_PIX_FMT_NONE == -1 && AV_SAMPLE_FMT_NONE == -1); link->format = -1; link->colorspace = AVCOL_SPC_UNSPECIFIED; + link->color_primaries = AVCOL_PRI_UNSPECIFIED; + link->color_trc = AVCOL_TRC_UNSPECIFIED; ff_framequeue_init(&li->fifo, &fffiltergraph(src->graph)->frame_queues); return 0; diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h index a6b875c37ae..e8c6ebb2836 100644 --- a/libavfilter/avfilter.h +++ b/libavfilter/avfilter.h @@ -418,6 +418,12 @@ struct AVFilterLink { */ enum AVChromaLocation chroma_location; + /** + * Agreed upon color primaries and transfer characteristics, for video. + */ + enum AVColorPrimaries color_primaries; + enum AVColorTransferCharacteristic color_trc; + /***************************************************************** * All fields below this line are not part of the public API. They * may not be used outside of libavfilter and can be changed and diff --git a/libavfilter/buffersink.c b/libavfilter/buffersink.c index 97c8ea5654d..fd0f3a040ce 100644 --- a/libavfilter/buffersink.c +++ b/libavfilter/buffersink.c @@ -253,6 +253,8 @@ MAKE_AVFILTERLINK_ACCESSOR(enum AVColorSpace, colorspace) MAKE_AVFILTERLINK_ACCESSOR(enum AVColorRange, color_range) MAKE_AVFILTERLINK_ACCESSOR(enum AVAlphaMode , alpha_mode) MAKE_AVFILTERLINK_ACCESSOR(enum AVChromaLocation, chroma_location) +MAKE_AVFILTERLINK_ACCESSOR(enum AVColorPrimaries, color_primaries) +MAKE_AVFILTERLINK_ACCESSOR(enum AVColorTransferCharacteristic, color_trc) MAKE_AVFILTERLINK_ACCESSOR(int , sample_rate ) diff --git a/libavfilter/buffersink.h b/libavfilter/buffersink.h index 0601aefff88..36fb70c1cad 100644 --- a/libavfilter/buffersink.h +++ b/libavfilter/buffersink.h @@ -115,6 +115,8 @@ AVRational av_buffersink_get_frame_rate (const AVFilterContext *c int av_buffersink_get_w (const AVFilterContext *ctx); int av_buffersink_get_h (const AVFilterContext *ctx); AVRational av_buffersink_get_sample_aspect_ratio (const AVFilterContext *ctx); +enum AVColorPrimaries av_buffersink_get_color_primaries(const AVFilterContext *ctx); +enum AVColorTransferCharacteristic av_buffersink_get_color_trc(const AVFilterContext *ctx); enum AVColorSpace av_buffersink_get_colorspace (const AVFilterContext *ctx); enum AVColorRange av_buffersink_get_color_range (const AVFilterContext *ctx); enum AVAlphaMode av_buffersink_get_alpha_mode (const AVFilterContext *ctx); diff --git a/libavfilter/buffersrc.c b/libavfilter/buffersrc.c index 8e6097df084..85d4793651f 100644 --- a/libavfilter/buffersrc.c +++ b/libavfilter/buffersrc.c @@ -119,6 +119,8 @@ AVBufferSrcParameters *av_buffersrc_parameters_alloc(void) if (!par) return NULL; + par->color_primaries = AVCOL_PRI_UNSPECIFIED; + par->color_trc = AVCOL_TRC_UNSPECIFIED; par->format = -1; par->color_range = AVCOL_RANGE_UNSPECIFIED; par->color_space = AVCOL_SPC_UNSPECIFIED; diff --git a/libavfilter/buffersrc.h b/libavfilter/buffersrc.h index fb6dfa34877..5452e31845d 100644 --- a/libavfilter/buffersrc.h +++ b/libavfilter/buffersrc.h @@ -133,6 +133,11 @@ typedef struct AVBufferSrcParameters { * Video only, the chroma sample location. */ enum AVChromaLocation chroma_location; + /** + * Video only, the color primaries and transfer characteristics. + */ + enum AVColorPrimaries color_primaries; + enum AVColorTransferCharacteristic color_trc; } AVBufferSrcParameters; /** diff --git a/libavfilter/version.h b/libavfilter/version.h index 523a7fe0a6e..f191d98883f 100644 --- a/libavfilter/version.h +++ b/libavfilter/version.h @@ -31,7 +31,7 @@ #include "version_major.h" -#define LIBAVFILTER_VERSION_MINOR 5 +#define LIBAVFILTER_VERSION_MINOR 6 #define LIBAVFILTER_VERSION_MICRO 100 -- 2.54.0 >From 092e74fc7198eb925cce944b44f5deaded200814 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 22 Oct 2026 11:11:13 +0000 Subject: [PATCH 02/27] avfilter: negotiate color primaries and transfer characteristics Prefer unspecified colorimetry unless constrained. Resolve incompatible lists through the existing scale insertion path. Assisted-by: Fairy --- libavfilter/avfilter.c | 10 +++ libavfilter/avfilter.h | 3 + libavfilter/avfiltergraph.c | 46 ++++++++++- libavfilter/formats.c | 150 ++++++++++++++++++++++++++++++++++++ libavfilter/formats.h | 94 ++++++++++++++++++++++ libavfilter/video.c | 2 + 6 files changed, 302 insertions(+), 3 deletions(-) diff --git a/libavfilter/avfilter.c b/libavfilter/avfilter.c index 75c7ad7591b..3d4d846ad1f 100644 --- a/libavfilter/avfilter.c +++ b/libavfilter/avfilter.c @@ -320,6 +320,12 @@ int avfilter_insert_filter(AVFilterLink *link, AVFilterContext *filt, if (link->outcfg.chroma_locations) ff_formats_changeref(&link->outcfg.chroma_locations, &filt->outputs[filt_dstpad_idx]->outcfg.chroma_locations); + if (link->outcfg.color_primaries) + ff_formats_changeref(&link->outcfg.color_primaries, + &filt->outputs[filt_dstpad_idx]->outcfg.color_primaries); + if (link->outcfg.color_trcs) + ff_formats_changeref(&link->outcfg.color_trcs, + &filt->outputs[filt_dstpad_idx]->outcfg.color_trcs); if (link->outcfg.samplerates) ff_formats_changeref(&link->outcfg.samplerates, &filt->outputs[filt_dstpad_idx]->outcfg.samplerates); @@ -799,7 +805,11 @@ static void free_link(AVFilterLink *link) ff_formats_unref(&link->incfg.alpha_modes); ff_formats_unref(&link->outcfg.alpha_modes); ff_formats_unref(&link->incfg.chroma_locations); + ff_formats_unref(&link->incfg.color_primaries); + ff_formats_unref(&link->incfg.color_trcs); ff_formats_unref(&link->outcfg.chroma_locations); + ff_formats_unref(&link->outcfg.color_primaries); + ff_formats_unref(&link->outcfg.color_trcs); ff_formats_unref(&link->incfg.samplerates); ff_formats_unref(&link->outcfg.samplerates); ff_channel_layouts_unref(&link->incfg.channel_layouts); diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h index e8c6ebb2836..5d12569e7c8 100644 --- a/libavfilter/avfilter.h +++ b/libavfilter/avfilter.h @@ -151,6 +151,9 @@ typedef struct AVFilterFormatsConfig { */ AVFilterFormats *chroma_locations; ///< AVChromaLocation + AVFilterFormats *color_primaries; ///< AVColorPrimaries + AVFilterFormats *color_trcs; ///< AVColorTransferCharacteristic + } AVFilterFormatsConfig; /** diff --git a/libavfilter/avfiltergraph.c b/libavfilter/avfiltergraph.c index 13c9930295f..42d8713dcf3 100644 --- a/libavfilter/avfiltergraph.c +++ b/libavfilter/avfiltergraph.c @@ -305,7 +305,9 @@ static int filter_link_check_formats(void *log, AVFilterLink *link, AVFilterForm (ret = ff_formats_check_color_spaces(log, cfg->color_spaces)) < 0 || (ret = ff_formats_check_color_ranges(log, cfg->color_ranges)) < 0 || (ret = ff_formats_check_alpha_modes(log, cfg->alpha_modes)) < 0 || - (ret = ff_formats_check_chroma_locations(log, cfg->chroma_locations)) < 0) + (ret = ff_formats_check_chroma_locations(log, cfg->chroma_locations)) < 0 || + (ret = ff_formats_check_color_primaries(log, cfg->color_primaries)) < 0 || + (ret = ff_formats_check_color_trcs(log, cfg->color_trcs)) < 0) return ret; break; @@ -422,7 +424,9 @@ static int formats_declared(AVFilterContext *f) !(f->inputs[i]->outcfg.color_ranges && f->inputs[i]->outcfg.color_spaces && f->inputs[i]->outcfg.alpha_modes && - f->inputs[i]->outcfg.chroma_locations)) + f->inputs[i]->outcfg.chroma_locations && + f->inputs[i]->outcfg.color_primaries && + f->inputs[i]->outcfg.color_trcs)) return 0; if (f->inputs[i]->type == AVMEDIA_TYPE_AUDIO && !(f->inputs[i]->outcfg.samplerates && @@ -436,7 +440,9 @@ static int formats_declared(AVFilterContext *f) !(f->outputs[i]->incfg.color_ranges && f->outputs[i]->incfg.color_spaces && f->outputs[i]->incfg.alpha_modes && - f->outputs[i]->incfg.chroma_locations)) + f->outputs[i]->incfg.chroma_locations && + f->outputs[i]->incfg.color_primaries && + f->outputs[i]->incfg.color_trcs)) return 0; if (f->outputs[i]->type == AVMEDIA_TYPE_AUDIO && !(f->outputs[i]->incfg.samplerates && @@ -670,9 +676,17 @@ retry: av_assert0(outlink-> incfg.alpha_modes->refcount > 0); av_assert0(outlink->outcfg.alpha_modes->refcount > 0); av_assert0( inlink-> incfg.chroma_locations->refcount > 0); + av_assert0( inlink-> incfg.color_primaries->refcount > 0); + av_assert0( inlink-> incfg.color_trcs->refcount > 0); av_assert0( inlink->outcfg.chroma_locations->refcount > 0); + av_assert0( inlink->outcfg.color_primaries->refcount > 0); + av_assert0( inlink->outcfg.color_trcs->refcount > 0); av_assert0(outlink-> incfg.chroma_locations->refcount > 0); + av_assert0(outlink-> incfg.color_primaries->refcount > 0); + av_assert0(outlink-> incfg.color_trcs->refcount > 0); av_assert0(outlink->outcfg.chroma_locations->refcount > 0); + av_assert0(outlink->outcfg.color_primaries->refcount > 0); + av_assert0(outlink->outcfg.color_trcs->refcount > 0); } else if (outlink->type == AVMEDIA_TYPE_AUDIO) { av_assert0( inlink-> incfg.samplerates->refcount > 0); av_assert0( inlink->outcfg.samplerates->refcount > 0); @@ -904,6 +918,24 @@ static int pick_format(AVFilterLink *link, AVFilterLink *ref) link->alpha_mode = AVALPHA_MODE_UNSPECIFIED; } + if (!link->incfg.color_primaries->nb_formats) { + av_log(link->src, AV_LOG_ERROR, "Cannot select color primaries" + " for the link between filters %s and %s.\n", link->src->name, + link->dst->name); + return AVERROR(EINVAL); + } + link->incfg.color_primaries->nb_formats = 1; + link->color_primaries = link->incfg.color_primaries->formats[0]; + + if (!link->incfg.color_trcs->nb_formats) { + av_log(link->src, AV_LOG_ERROR, "Cannot select color transfer characteristics" + " for the link between filters %s and %s.\n", link->src->name, + link->dst->name); + return AVERROR(EINVAL); + } + link->incfg.color_trcs->nb_formats = 1; + link->color_trc = link->incfg.color_trcs->formats[0]; + if (desc->log2_chroma_w || desc->log2_chroma_h) { if (!link->incfg.chroma_locations->nb_formats) { av_log(link->src, AV_LOG_ERROR, "Cannot select chroma location" @@ -958,7 +990,11 @@ static int pick_format(AVFilterLink *link, AVFilterLink *ref) ff_formats_unref(&link->incfg.alpha_modes); ff_formats_unref(&link->outcfg.alpha_modes); ff_formats_unref(&link->incfg.chroma_locations); + ff_formats_unref(&link->incfg.color_primaries); + ff_formats_unref(&link->incfg.color_trcs); ff_formats_unref(&link->outcfg.chroma_locations); + ff_formats_unref(&link->outcfg.color_primaries); + ff_formats_unref(&link->outcfg.color_trcs); return 0; } @@ -1016,6 +1052,10 @@ static int reduce_formats_on_filter(AVFilterContext *filter) nb_formats, ff_add_format); REDUCE_FORMATS(int, AVFilterFormats, chroma_locations, formats, nb_formats, ff_add_format); + REDUCE_FORMATS(int, AVFilterFormats, color_primaries, formats, + nb_formats, ff_add_format); + REDUCE_FORMATS(int, AVFilterFormats, color_trcs, formats, + nb_formats, ff_add_format); /* reduce channel layouts */ for (i = 0; i < filter->nb_inputs; i++) { diff --git a/libavfilter/formats.c b/libavfilter/formats.c index 2994cdaa897..19f964962ab 100644 --- a/libavfilter/formats.c +++ b/libavfilter/formats.c @@ -364,6 +364,8 @@ PRINT_NAME(enum AVColorSpace, color_space_name) PRINT_NAME(enum AVColorRange, color_range_name) PRINT_NAME(enum AVAlphaMode, alpha_mode_name) PRINT_NAME(enum AVChromaLocation, chroma_location_name) +PRINT_NAME(enum AVColorPrimaries, color_primaries_name) +PRINT_NAME(enum AVColorTransferCharacteristic, color_transfer_name) static void print_channel_layout_desc(AVBPrint *bp, const void *layoutsp) { @@ -429,6 +431,20 @@ static const AVFilterFormatsMerger mergers_video[] = { .can_merge = can_merge_generic, .print_list = print_chroma_location_name, CONVERSION_FILTER_SWSCALE + }, { + .name = "Color primaries", + .offset = offsetof(AVFilterFormatsConfig, color_primaries), + .merge = merge_generic, + .can_merge = can_merge_generic, + .print_list = print_color_primaries_name, + CONVERSION_FILTER_SWSCALE + }, { + .name = "Color transfer characteristics", + .offset = offsetof(AVFilterFormatsConfig, color_trcs), + .merge = merge_generic, + .can_merge = can_merge_generic, + .print_list = print_color_transfer_name, + CONVERSION_FILTER_SWSCALE }, }; @@ -753,6 +769,38 @@ AVFilterFormats *ff_all_chroma_locations(void) return ret; } +AVFilterFormats *ff_all_color_primaries(void) +{ + AVFilterFormats *ret = NULL; + if (ff_add_format(&ret, AVCOL_PRI_UNSPECIFIED) < 0) + return NULL; + for (int value = 0; value < AVCOL_PRI_EXT_NB; value++) { + if (value == AVCOL_PRI_RESERVED0 || value == AVCOL_PRI_RESERVED || + value == AVCOL_PRI_UNSPECIFIED || !av_color_primaries_name(value)) + continue; + if (ff_add_format(&ret, value) < 0) + return NULL; + } + + return ret; +} + +AVFilterFormats *ff_all_color_trcs(void) +{ + AVFilterFormats *ret = NULL; + if (ff_add_format(&ret, AVCOL_TRC_UNSPECIFIED) < 0) + return NULL; + for (int value = 0; value < AVCOL_TRC_EXT_NB; value++) { + if (value == AVCOL_TRC_RESERVED0 || value == AVCOL_TRC_RESERVED || + value == AVCOL_TRC_UNSPECIFIED || !av_color_transfer_name(value)) + continue; + if (ff_add_format(&ret, value) < 0) + return NULL; + } + + return ret; +} + #define FORMATS_REF(f, ref, unref_fn) \ void *tmp; \ \ @@ -994,6 +1042,42 @@ int ff_set_common_all_chroma_locations(AVFilterContext *ctx) return ff_set_common_chroma_locations(ctx, ff_all_chroma_locations()); } +int ff_set_common_color_primaries(AVFilterContext *ctx, + AVFilterFormats *color_primaries) +{ + SET_COMMON_FORMATS(ctx, color_primaries, AVMEDIA_TYPE_VIDEO, + ff_formats_ref, ff_formats_unref); +} + +int ff_set_common_color_primaries_from_list(AVFilterContext *ctx, + const int *color_primaries) +{ + return ff_set_common_color_primaries(ctx, ff_make_format_list(color_primaries)); +} + +int ff_set_common_all_color_primaries(AVFilterContext *ctx) +{ + return ff_set_common_color_primaries(ctx, ff_all_color_primaries()); +} + +int ff_set_common_color_trcs(AVFilterContext *ctx, + AVFilterFormats *color_trcs) +{ + SET_COMMON_FORMATS(ctx, color_trcs, AVMEDIA_TYPE_VIDEO, + ff_formats_ref, ff_formats_unref); +} + +int ff_set_common_color_trcs_from_list(AVFilterContext *ctx, + const int *color_trcs) +{ + return ff_set_common_color_trcs(ctx, ff_make_format_list(color_trcs)); +} + +int ff_set_common_all_color_trcs(AVFilterContext *ctx) +{ + return ff_set_common_color_trcs(ctx, ff_all_color_trcs()); +} + /** * A helper for query_formats() which sets all links to the same list of * formats. If there are no links hooked to this filter, the list of formats is @@ -1198,6 +1282,56 @@ int ff_set_common_all_chroma_locations2(const AVFilterContext *ctx, return ff_set_common_chroma_locations2(ctx, cfg_in, cfg_out, ff_all_chroma_locations()); } +int ff_set_common_color_primaries2(const AVFilterContext *ctx, + AVFilterFormatsConfig **cfg_in, + AVFilterFormatsConfig **cfg_out, + AVFilterFormats *color_primaries) +{ + SET_COMMON_FORMATS2(ctx, cfg_in, cfg_out, color_primaries, AVMEDIA_TYPE_VIDEO, + ff_formats_ref, ff_formats_unref); +} + +int ff_set_common_color_primaries_from_list2(const AVFilterContext *ctx, + AVFilterFormatsConfig **cfg_in, + AVFilterFormatsConfig **cfg_out, + const int *color_primaries) +{ + return ff_set_common_color_primaries2(ctx, cfg_in, cfg_out, + ff_make_format_list(color_primaries)); +} + +int ff_set_common_all_color_primaries2(const AVFilterContext *ctx, + AVFilterFormatsConfig **cfg_in, + AVFilterFormatsConfig **cfg_out) +{ + return ff_set_common_color_primaries2(ctx, cfg_in, cfg_out, ff_all_color_primaries()); +} + +int ff_set_common_color_trcs2(const AVFilterContext *ctx, + AVFilterFormatsConfig **cfg_in, + AVFilterFormatsConfig **cfg_out, + AVFilterFormats *color_trcs) +{ + SET_COMMON_FORMATS2(ctx, cfg_in, cfg_out, color_trcs, AVMEDIA_TYPE_VIDEO, + ff_formats_ref, ff_formats_unref); +} + +int ff_set_common_color_trcs_from_list2(const AVFilterContext *ctx, + AVFilterFormatsConfig **cfg_in, + AVFilterFormatsConfig **cfg_out, + const int *color_trcs) +{ + return ff_set_common_color_trcs2(ctx, cfg_in, cfg_out, + ff_make_format_list(color_trcs)); +} + +int ff_set_common_all_color_trcs2(const AVFilterContext *ctx, + AVFilterFormatsConfig **cfg_in, + AVFilterFormatsConfig **cfg_out) +{ + return ff_set_common_color_trcs2(ctx, cfg_in, cfg_out, ff_all_color_trcs()); +} + int ff_set_common_formats2(const AVFilterContext *ctx, AVFilterFormatsConfig **cfg_in, AVFilterFormatsConfig **cfg_out, @@ -1284,6 +1418,12 @@ int ff_default_query_formats(AVFilterContext *ctx) ret = ff_set_common_all_chroma_locations(ctx); if (ret < 0) return ret; + ret = ff_set_common_all_color_primaries(ctx); + if (ret < 0) + return ret; + ret = ff_set_common_all_color_trcs(ctx); + if (ret < 0) + return ret; } if (type != AVMEDIA_TYPE_VIDEO) { ret = ff_set_common_all_channel_counts(ctx); @@ -1361,6 +1501,16 @@ int ff_formats_check_chroma_locations(void *log, const AVFilterFormats *fmts) return check_list(log, "chroma location", fmts); } +int ff_formats_check_color_primaries(void *log, const AVFilterFormats *fmts) +{ + return check_list(log, "color primaries", fmts); +} + +int ff_formats_check_color_trcs(void *log, const AVFilterFormats *fmts) +{ + return check_list(log, "color transfer characteristics", fmts); +} + static int layouts_compatible(const AVChannelLayout *a, const AVChannelLayout *b) { return !av_channel_layout_compare(a, b) || diff --git a/libavfilter/formats.h b/libavfilter/formats.h index 488a09b50bf..6909a6cf9a1 100644 --- a/libavfilter/formats.h +++ b/libavfilter/formats.h @@ -156,6 +156,18 @@ AVFilterFormats *ff_all_alpha_modes(void); av_warn_unused_result AVFilterFormats *ff_all_chroma_locations(void); +/** + * Construct an AVFilterFormats representing all possible color primaries. + */ +av_warn_unused_result +AVFilterFormats *ff_all_color_primaries(void); + +/** + * Construct an AVFilterFormats representing all possible color transfer characteristics. + */ +av_warn_unused_result +AVFilterFormats *ff_all_color_trcs(void); + /** * Helpers for query_formats() which set all free audio links to the same list * of channel layouts/sample rates. If there are no links hooked to this list, @@ -256,6 +268,40 @@ int ff_set_common_chroma_locations_from_list(AVFilterContext *ctx, av_warn_unused_result int ff_set_common_all_chroma_locations(AVFilterContext *ctx); +av_warn_unused_result +int ff_set_common_color_primaries(AVFilterContext *ctx, + AVFilterFormats *color_primaries); + +/** + * Equivalent to ff_set_common_color_primaries(ctx, ff_make_format_list(color_primaries)) + */ +av_warn_unused_result +int ff_set_common_color_primaries_from_list(AVFilterContext *ctx, + const int *color_primaries); + +/** + * Equivalent to ff_set_common_color_primaries(ctx, ff_all_color_primaries()) + */ +av_warn_unused_result +int ff_set_common_all_color_primaries(AVFilterContext *ctx); + +av_warn_unused_result +int ff_set_common_color_trcs(AVFilterContext *ctx, + AVFilterFormats *color_trcs); + +/** + * Equivalent to ff_set_common_color_trcs(ctx, ff_make_format_list(color_trcs)) + */ +av_warn_unused_result +int ff_set_common_color_trcs_from_list(AVFilterContext *ctx, + const int *color_trcs); + +/** + * Equivalent to ff_set_common_color_trcs(ctx, ff_all_color_trcs()) + */ +av_warn_unused_result +int ff_set_common_all_color_trcs(AVFilterContext *ctx); + /** * A helper for query_formats() which sets all links to the same list of @@ -389,6 +435,40 @@ int ff_set_common_all_chroma_locations2(const AVFilterContext *ctx, AVFilterFormatsConfig **cfg_in, AVFilterFormatsConfig **cfg_out); +av_warn_unused_result +int ff_set_common_color_primaries2(const AVFilterContext *ctx, + AVFilterFormatsConfig **cfg_in, + AVFilterFormatsConfig **cfg_out, + AVFilterFormats *color_primaries); + +av_warn_unused_result +int ff_set_common_color_primaries_from_list2(const AVFilterContext *ctx, + AVFilterFormatsConfig **cfg_in, + AVFilterFormatsConfig **cfg_out, + const int *color_primaries); + +av_warn_unused_result +int ff_set_common_all_color_primaries2(const AVFilterContext *ctx, + AVFilterFormatsConfig **cfg_in, + AVFilterFormatsConfig **cfg_out); + +av_warn_unused_result +int ff_set_common_color_trcs2(const AVFilterContext *ctx, + AVFilterFormatsConfig **cfg_in, + AVFilterFormatsConfig **cfg_out, + AVFilterFormats *color_trcs); + +av_warn_unused_result +int ff_set_common_color_trcs_from_list2(const AVFilterContext *ctx, + AVFilterFormatsConfig **cfg_in, + AVFilterFormatsConfig **cfg_out, + const int *color_trcs); + +av_warn_unused_result +int ff_set_common_all_color_trcs2(const AVFilterContext *ctx, + AVFilterFormatsConfig **cfg_in, + AVFilterFormatsConfig **cfg_out); + av_warn_unused_result int ff_set_common_formats2(const AVFilterContext *ctx, AVFilterFormatsConfig **cfg_in, @@ -604,6 +684,20 @@ int ff_formats_check_alpha_modes(void *log, const AVFilterFormats *fmts); */ int ff_formats_check_chroma_locations(void *log, const AVFilterFormats *fmts); +/** + * Check that fmts is a valid formats list for color primaries. + * + * In particular, check for duplicates. + */ +int ff_formats_check_color_primaries(void *log, const AVFilterFormats *fmts); + +/** + * Check that fmts is a valid formats list for color transfer characteristics. + * + * In particular, check for duplicates. + */ +int ff_formats_check_color_trcs(void *log, const AVFilterFormats *fmts); + struct AVBPrint; typedef struct AVFilterFormatMerger { diff --git a/libavfilter/video.c b/libavfilter/video.c index 6e20794d43a..db979c81d22 100644 --- a/libavfilter/video.c +++ b/libavfilter/video.c @@ -53,6 +53,8 @@ static void set_link_props(AVFrame *frame, const AVFilterLink *link) frame->color_range = link->color_range; frame->alpha_mode = link->alpha_mode; frame->chroma_location = link->chroma_location; + frame->color_primaries = link->color_primaries; + frame->color_trc = link->color_trc; } AVFrame *ff_default_get_video_buffer2(AVFilterLink *link, int w, int h, int align) -- 2.54.0 >From 1f696ffc9f66a82a88d129d28faf82379b60d46f Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 22 Oct 2026 11:17:53 +0000 Subject: [PATCH 03/27] avfilter/gainmap: negotiate independent input colorimetry Gain maps compare images in different color spaces and have no output primaries or transfer characteristic. Assisted-by: Fairy --- libavfilter/vf_gainmap.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/libavfilter/vf_gainmap.c b/libavfilter/vf_gainmap.c index b8f6c89bee7..3383f05df31 100644 --- a/libavfilter/vf_gainmap.c +++ b/libavfilter/vf_gainmap.c @@ -239,6 +239,17 @@ static int query_formats(const AVFilterContext *ctx, enum AVPixelFormat in_fmt, out_fmt; int ret; + for (int i = 0; i < ctx->nb_inputs; i++) { + if ((ret = ff_formats_ref(ff_all_color_primaries(), &cfg_in[i]->color_primaries)) < 0 || + (ret = ff_formats_ref(ff_all_color_trcs(), &cfg_in[i]->color_trcs)) < 0) + return ret; + } + if ((ret = ff_formats_ref(ff_make_formats_list_singleton(AVCOL_PRI_UNSPECIFIED), + &cfg_out[0]->color_primaries)) < 0 || + (ret = ff_formats_ref(ff_make_formats_list_singleton(AVCOL_TRC_UNSPECIFIED), + &cfg_out[0]->color_trcs)) < 0) + return ret; + in_fmt = AV_PIX_FMT_GBRPF32; out_fmt = s->nb_channels == 1 ? AV_PIX_FMT_GRAYF32 : AV_PIX_FMT_GBRPF32; -- 2.54.0 >From b67578cd91dd1a94a10e6545b73666cc0b99f2d3 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 22 Oct 2026 11:12:07 +0000 Subject: [PATCH 04/27] avfilter/setparams: advertise output primaries and transfer characteristics Assisted-by: Fairy --- libavfilter/vf_setparams.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libavfilter/vf_setparams.c b/libavfilter/vf_setparams.c index 62e7a43fed3..348acdf430d 100644 --- a/libavfilter/vf_setparams.c +++ b/libavfilter/vf_setparams.c @@ -167,6 +167,20 @@ static int query_formats(const AVFilterContext *ctx, return ret; } + if (s->color_primaries >= 0) { + ret = ff_formats_ref(ff_make_formats_list_singleton(s->color_primaries), + &cfg_out[0]->color_primaries); + if (ret < 0) + return ret; + } + + if (s->color_trc >= 0) { + ret = ff_formats_ref(ff_make_formats_list_singleton(s->color_trc), + &cfg_out[0]->color_trcs); + if (ret < 0) + return ret; + } + if (s->chroma_location >= 0) { ret = ff_formats_ref(ff_make_formats_list_singleton(s->chroma_location), &cfg_out[0]->chroma_locations); -- 2.54.0 >From 2775ce0f49090314ceae710f2cd1f4d317398adf Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 22 Oct 2026 11:12:21 +0000 Subject: [PATCH 05/27] avfilter/buffer: negotiate source and sink primaries and transfer Assisted-by: Fairy --- libavfilter/buffersink.c | 22 +++++++++++++ libavfilter/buffersink.h | 2 ++ libavfilter/buffersrc.c | 70 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 87 insertions(+), 7 deletions(-) diff --git a/libavfilter/buffersink.c b/libavfilter/buffersink.c index fd0f3a040ce..ec243bffa3b 100644 --- a/libavfilter/buffersink.c +++ b/libavfilter/buffersink.c @@ -61,6 +61,12 @@ typedef struct BufferSinkContext { int *chromalocations; unsigned nb_chromalocations; + int *color_primaries; + unsigned nb_color_primaries; + + int *color_trcs; + unsigned nb_color_trcs; + /* only used for audio */ enum AVSampleFormat *sample_formats; unsigned nb_sample_formats; @@ -172,6 +178,8 @@ static int init_video(AVFilterContext *ctx) TERMINATE_ARRAY(colorranges, -1); TERMINATE_ARRAY(colorspaces, -1); TERMINATE_ARRAY(alphamodes, -1); + TERMINATE_ARRAY(color_primaries, -1); + TERMINATE_ARRAY(color_trcs, -1); TERMINATE_ARRAY(chromalocations, -1); return common_init(ctx); @@ -321,6 +329,16 @@ static int vsink_query_formats(const AVFilterContext *ctx, if (ret < 0) return ret; } + if (buf->nb_color_primaries) { + ret = ff_set_common_color_primaries_from_list2(ctx, cfg_in, cfg_out, buf->color_primaries); + if (ret < 0) + return ret; + } + if (buf->nb_color_trcs) { + ret = ff_set_common_color_trcs_from_list2(ctx, cfg_in, cfg_out, buf->color_trcs); + if (ret < 0) + return ret; + } if (buf->nb_chromalocations) { ret = ff_set_common_chroma_locations_from_list2(ctx, cfg_in, cfg_out, buf->chromalocations); if (ret < 0) @@ -370,6 +388,10 @@ static const AVOption buffersink_options[] = { AV_OPT_TYPE_INT | AV_OPT_TYPE_FLAG_ARRAY, .max = INT_MAX, .flags = FLAGS }, { "colorranges", "array of supported color ranges", OFFSET(colorranges), AV_OPT_TYPE_INT | AV_OPT_TYPE_FLAG_ARRAY, .max = INT_MAX, .flags = FLAGS }, + { "color_primaries", "array of supported color primaries", OFFSET(color_primaries), + AV_OPT_TYPE_INT | AV_OPT_TYPE_FLAG_ARRAY, .max = INT_MAX, .flags = FLAGS }, + { "color_trcs", "array of supported color transfer characteristics", OFFSET(color_trcs), + AV_OPT_TYPE_INT | AV_OPT_TYPE_FLAG_ARRAY, .max = INT_MAX, .flags = FLAGS }, { "chromalocations", "array of supported chroma locations", OFFSET(chromalocations), AV_OPT_TYPE_INT | AV_OPT_TYPE_FLAG_ARRAY, .max = INT_MAX, .flags = FLAGS }, { "alphamodes", "array of supported color ranges", OFFSET(alphamodes), diff --git a/libavfilter/buffersink.h b/libavfilter/buffersink.h index 36fb70c1cad..b9429d75871 100644 --- a/libavfilter/buffersink.h +++ b/libavfilter/buffersink.h @@ -56,6 +56,8 @@ * The format can be constrained by setting options, using av_opt_set() and * related functions with the AV_OPT_SEARCH_CHILDREN flag. * - pixel_formats (array of pixel formats), + * - color_primaries (array of int), + * - color_trcs (array of int), * - colorspaces (array of int), * - colorranges (array of int), * - alphamodes (array of int), diff --git a/libavfilter/buffersrc.c b/libavfilter/buffersrc.c index 85d4793651f..10573a01f9d 100644 --- a/libavfilter/buffersrc.c +++ b/libavfilter/buffersrc.c @@ -55,6 +55,8 @@ typedef struct BufferSourceContext { enum AVColorRange color_range, prev_color_range; enum AVAlphaMode alpha_mode, prev_alpha_mode; enum AVChromaLocation chroma_location, prev_chroma_location; + enum AVColorPrimaries color_primaries, prev_color_primaries; + enum AVColorTransferCharacteristic color_trc, prev_color_trc; AVRational pixel_aspect; AVBufferRef *hw_frames_ctx; @@ -74,21 +76,21 @@ typedef struct BufferSourceContext { int chroma_loc_warned; } BufferSourceContext; -#define CHECK_VIDEO_PARAM_CHANGE(s, c, width, height, format, csp, range, alpha, chromaloc, pts)\ +#define CHECK_VIDEO_PARAM_CHANGE(s, c, width, height, format, csp, range, alpha, chromaloc, primaries, trc, pts)\ c->link_delta = c->w != width || c->h != height || c->pix_fmt != format ||\ c->color_space != csp || c->color_range != range || c->alpha_mode != alpha ||\ - c->chroma_location != chromaloc;\ + c->chroma_location != chromaloc || c->color_primaries != primaries || c->color_trc != trc;\ c->prev_delta = c->prev_w != width || c->prev_h != height || c->prev_pix_fmt != format ||\ c->prev_color_space != csp || c->prev_color_range != range || c->prev_alpha_mode != alpha ||\ - c->prev_chroma_location != chromaloc;\ + c->prev_chroma_location != chromaloc || c->prev_color_primaries != primaries || c->prev_color_trc != trc;\ if (c->link_delta) {\ int loglevel = c->prev_delta ? AV_LOG_WARNING : AV_LOG_DEBUG;\ av_log(s, loglevel, "Changing video frame properties on the fly is not supported by all filters.\n");\ - av_log(s, loglevel, "filter context - w: %d h: %d fmt: %d csp: %s range: %s alpha: %s chroma_loc: %s, incoming frame - w: %d h: %d fmt: %d csp: %s range: %s alpha: %s chroma_loc: %s pts_time: %s\n",\ + av_log(s, loglevel, "filter context - w: %d h: %d fmt: %d csp: %s range: %s alpha: %s chroma_loc: %s primaries: %s trc: %s, incoming frame - w: %d h: %d fmt: %d csp: %s range: %s alpha: %s chroma_loc: %s primaries: %s trc: %s pts_time: %s\n",\ c->w, c->h, c->pix_fmt, av_color_space_name(c->color_space), av_color_range_name(c->color_range), av_alpha_mode_name(c->alpha_mode),\ - av_chroma_location_name(c->chroma_location),\ + av_chroma_location_name(c->chroma_location), av_color_primaries_name(c->color_primaries), av_color_transfer_name(c->color_trc),\ width, height, format, av_color_space_name(csp), av_color_range_name(range), av_alpha_mode_name(alpha),\ - av_chroma_location_name(chromaloc),\ + av_chroma_location_name(chromaloc), av_color_primaries_name(primaries), av_color_transfer_name(trc),\ av_ts2timestr(pts, &s->outputs[0]->time_base));\ }\ if (c->prev_delta) {\ @@ -101,6 +103,8 @@ typedef struct BufferSourceContext { c->prev_color_range = range;\ c->prev_alpha_mode = alpha;\ c->prev_chroma_location = chromaloc;\ + c->prev_color_primaries = primaries;\ + c->prev_color_trc = trc;\ } #define CHECK_AUDIO_PARAM_CHANGE(s, c, srate, layout, format, pts)\ @@ -162,6 +166,10 @@ int av_buffersrc_parameters_set(AVFilterContext *ctx, AVBufferSrcParameters *par s->color_range = s->prev_color_range = param->color_range; if (param->alpha_mode != AVALPHA_MODE_UNSPECIFIED) s->alpha_mode = s->prev_alpha_mode = param->alpha_mode; + if (param->color_primaries != AVCOL_PRI_UNSPECIFIED) + s->color_primaries = s->prev_color_primaries = param->color_primaries; + if (param->color_trc != AVCOL_TRC_UNSPECIFIED) + s->color_trc = s->prev_color_trc = param->color_trc; if (param->chroma_location != AVCHROMA_LOC_UNSPECIFIED) s->chroma_location = s->prev_chroma_location = param->chroma_location; break; @@ -244,7 +252,7 @@ int attribute_align_arg av_buffersrc_add_frame_flags(AVFilterContext *ctx, AVFra CHECK_VIDEO_PARAM_CHANGE(ctx, s, frame->width, frame->height, frame->format, frame->colorspace, frame->color_range, frame->alpha_mode, - frame->chroma_location, frame->pts); + frame->chroma_location, frame->color_primaries, frame->color_trc, frame->pts); break; case AVMEDIA_TYPE_AUDIO: /* For layouts unknown on input but known on link after negotiation. */ @@ -276,6 +284,10 @@ int attribute_align_arg av_buffersrc_add_frame_flags(AVFilterContext *ctx, AVFra copy->colorspace = ctx->outputs[0]->colorspace; if (copy->color_range == AVCOL_RANGE_UNSPECIFIED) copy->color_range = ctx->outputs[0]->color_range; + if (copy->color_primaries == AVCOL_PRI_UNSPECIFIED) + copy->color_primaries = ctx->outputs[0]->color_primaries; + if (copy->color_trc == AVCOL_TRC_UNSPECIFIED) + copy->color_trc = ctx->outputs[0]->color_trc; if (copy->alpha_mode == AVALPHA_MODE_UNSPECIFIED) copy->alpha_mode = ctx->outputs[0]->alpha_mode; if (copy->chroma_location == AVCHROMA_LOC_UNSPECIFIED) { @@ -408,6 +420,42 @@ static const AVOption buffer_options[] = { { "chroma-derived-c", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_CHROMA_DERIVED_CL}, INT_MIN, INT_MAX, V, .unit = "colorspace"}, { "ictcp", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_ICTCP}, INT_MIN, INT_MAX, V, .unit = "colorspace"}, { "ipt-c2", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_IPT_C2}, INT_MIN, INT_MAX, V, .unit = "colorspace"}, + {"color_primaries", "select color primaries", OFFSET(color_primaries), AV_OPT_TYPE_INT, {.i64=AVCOL_PRI_UNSPECIFIED}, 0, AVCOL_PRI_EXT_NB-1, V, .unit = "color_primaries"}, + {"bt709", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_BT709}, 0, 0, V, .unit = "color_primaries"}, + {"unknown", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_UNSPECIFIED}, 0, 0, V, .unit = "color_primaries"}, + {"bt470m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_BT470M}, 0, 0, V, .unit = "color_primaries"}, + {"bt470bg", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_BT470BG}, 0, 0, V, .unit = "color_primaries"}, + {"smpte170m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE170M}, 0, 0, V, .unit = "color_primaries"}, + {"smpte240m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE240M}, 0, 0, V, .unit = "color_primaries"}, + {"film", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_FILM}, 0, 0, V, .unit = "color_primaries"}, + {"bt2020", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_BT2020}, 0, 0, V, .unit = "color_primaries"}, + {"smpte428", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE428}, 0, 0, V, .unit = "color_primaries"}, + {"smpte431", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE431}, 0, 0, V, .unit = "color_primaries"}, + {"smpte432", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE432}, 0, 0, V, .unit = "color_primaries"}, + {"jedec-p22", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_JEDEC_P22}, 0, 0, V, .unit = "color_primaries"}, + {"ebu3213", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_EBU3213}, 0, 0, V, .unit = "color_primaries"}, + {"vgamut", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_V_GAMUT}, 0, 0, V, .unit = "color_primaries"}, + + {"color_trc", "select color transfer", OFFSET(color_trc), AV_OPT_TYPE_INT, {.i64=AVCOL_TRC_UNSPECIFIED}, 0, AVCOL_TRC_EXT_NB-1, V, .unit = "color_trc"}, + {"bt709", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_BT709}, 0, 0, V, .unit = "color_trc"}, + {"unknown", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_UNSPECIFIED}, 0, 0, V, .unit = "color_trc"}, + {"bt470m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_GAMMA22}, 0, 0, V, .unit = "color_trc"}, + {"bt470bg", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_GAMMA28}, 0, 0, V, .unit = "color_trc"}, + {"smpte170m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_SMPTE170M}, 0, 0, V, .unit = "color_trc"}, + {"smpte240m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_SMPTE240M}, 0, 0, V, .unit = "color_trc"}, + {"linear", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_LINEAR}, 0, 0, V, .unit = "color_trc"}, + {"log100", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_LOG}, 0, 0, V, .unit = "color_trc"}, + {"log316", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_LOG_SQRT}, 0, 0, V, .unit = "color_trc"}, + {"iec61966-2-4", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_IEC61966_2_4}, 0, 0, V, .unit = "color_trc"}, + {"bt1361e", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_BT1361_ECG}, 0, 0, V, .unit = "color_trc"}, + {"iec61966-2-1", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_IEC61966_2_1}, 0, 0, V, .unit = "color_trc"}, + {"bt2020-10", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_BT2020_10}, 0, 0, V, .unit = "color_trc"}, + {"bt2020-12", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_BT2020_12}, 0, 0, V, .unit = "color_trc"}, + {"smpte2084", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_SMPTE2084}, 0, 0, V, .unit = "color_trc"}, + {"smpte428", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_SMPTE428}, 0, 0, V, .unit = "color_trc"}, + {"arib-std-b67", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_ARIB_STD_B67}, 0, 0, V, .unit = "color_trc"}, + {"vlog", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_V_LOG}, 0, 0, V, .unit = "color_trc"}, + { "range", "select color range", OFFSET(color_range), AV_OPT_TYPE_INT, {.i64=AVCOL_RANGE_UNSPECIFIED}, 0, AVCOL_RANGE_NB-1, V, .unit = "range"}, { "unspecified", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_UNSPECIFIED}, 0, 0, V, .unit = "range"}, { "unknown", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_UNSPECIFIED}, 0, 0, V, .unit = "range"}, @@ -527,6 +575,14 @@ static int query_formats(const AVFilterContext *ctx, if ((ret = ff_add_format (&formats, c->pix_fmt)) < 0 || (ret = ff_set_common_formats2(ctx, cfg_in, cfg_out, formats)) < 0) return ret; + ret = ff_formats_ref(ff_make_formats_list_singleton(c->color_primaries), + &cfg_out[0]->color_primaries); + if (ret < 0) + return ret; + ret = ff_formats_ref(ff_make_formats_list_singleton(c->color_trc), + &cfg_out[0]->color_trcs); + if (ret < 0) + return ret; /* force specific colorspace/range downstream only for ordinary YUV */ if (ff_fmt_is_regular_yuv(swfmt)) { if ((ret = ff_add_format(&color_spaces, c->color_space)) < 0 || -- 2.54.0 >From 24d3f4323db9044d4a23bf3bf8b221c21b84779e Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 22 Oct 2026 11:13:35 +0000 Subject: [PATCH 06/27] avfilter/scale: convert negotiated primaries and transfer characteristics Advertise swscale capabilities and output overrides, and apply negotiated values to output frames and color-dependent side data. Assisted-by: Fairy --- libavfilter/vf_scale.c | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/libavfilter/vf_scale.c b/libavfilter/vf_scale.c index 69e69bb8ea9..b74bfcdded6 100644 --- a/libavfilter/vf_scale.c +++ b/libavfilter/vf_scale.c @@ -448,6 +448,22 @@ static av_cold void uninit(AVFilterContext *ctx) sws_free_context(&scale->sws); } +static AVFilterFormats *supported_color_primaries(int value, int output) +{ + AVFilterFormats *formats = value == -1 ? ff_all_color_primaries() + : ff_make_formats_list_singleton(value); + if (!formats) + return NULL; + for (int i = 0; i < formats->nb_formats; i++) { + if (!sws_test_primaries(formats->formats[i], output)) { + for (int j = i--; j + 1 < formats->nb_formats; j++) + formats->formats[j] = formats->formats[j + 1]; + formats->nb_formats--; + } + } + return formats; +} + static int query_formats(const AVFilterContext *ctx, AVFilterFormatsConfig **cfg_in, AVFilterFormatsConfig **cfg_out) @@ -530,6 +546,21 @@ static int query_formats(const AVFilterContext *ctx, if ((ret = ff_formats_ref(formats, &cfg_out[0]->chroma_locations)) < 0) return ret; + if ((ret = ff_formats_ref(supported_color_primaries(-1, 0), + &cfg_in[0]->color_primaries)) < 0) + return ret; + if ((ret = ff_formats_ref(supported_color_primaries(scale->out_primaries, 1), + &cfg_out[0]->color_primaries)) < 0) + return ret; + + /* Transfer functions unsupported for conversion can still pass through. */ + if ((ret = ff_formats_ref(ff_all_color_trcs(), &cfg_in[0]->color_trcs)) < 0) + return ret; + formats = scale->out_transfer == -1 ? ff_all_color_trcs() + : ff_make_formats_list_singleton(scale->out_transfer); + if ((ret = ff_formats_ref(formats, &cfg_out[0]->color_trcs)) < 0) + return ret; + if (scale->sws->alpha_blend) { if ((ret = ff_formats_ref(ff_make_formats_list_singleton(AVALPHA_MODE_STRAIGHT), &cfg_in[0]->alpha_modes)) < 0) @@ -689,7 +720,8 @@ static int config_props(AVFilterLink *outlink) AV_SIDE_DATA_PROP_SIZE_DEPENDENT); } - if (scale->in_primaries != scale->out_primaries || scale->in_transfer != scale->out_transfer) { + if ((scale->in_primaries == -1 ? inlink0->color_primaries : scale->in_primaries) != outlink->color_primaries || + (scale->in_transfer == -1 ? inlink0->color_trc : scale->in_transfer) != outlink->color_trc) { av_frame_side_data_remove_by_props(&outlink->side_data, &outlink->nb_side_data, AV_SIDE_DATA_PROP_COLOR_DEPENDENT); } @@ -858,6 +890,10 @@ scale: out->chroma_location = outlink->chroma_location; if (scale->out_chroma_loc != AVCHROMA_LOC_UNSPECIFIED) out->chroma_location = scale->out_chroma_loc; + if (outlink->color_primaries != AVCOL_PRI_UNSPECIFIED) + out->color_primaries = outlink->color_primaries; + if (outlink->color_trc != AVCOL_TRC_UNSPECIFIED) + out->color_trc = outlink->color_trc; if (scale->out_primaries != -1) out->color_primaries = scale->out_primaries; if (scale->out_transfer != -1) -- 2.54.0 >From 6ffeb19df8be52edc94072b1338fa88ef053ccce Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 22 Oct 2026 11:14:00 +0000 Subject: [PATCH 07/27] avfilter/format: constrain primaries and transfer characteristics Assisted-by: Fairy --- doc/filters.texi | 6 ++++++ libavfilter/vf_format.c | 28 ++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/doc/filters.texi b/doc/filters.texi index be3058b1a10..b00f9ab66b1 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -14776,6 +14776,12 @@ A '|'-separated list of color range names, such as A '|'-separated list of color range names, such as "alpha_modes=straight|premultiplied". +@item color_primaries +A '|'-separated list of color primaries. + +@item color_trcs +A '|'-separated list of color transfer characteristics. + @item chroma_locations A '|'-separated list of chroma sample location names, such as "chroma_locations=left|topleft". diff --git a/libavfilter/vf_format.c b/libavfilter/vf_format.c index 7152922158c..b095447cdb0 100644 --- a/libavfilter/vf_format.c +++ b/libavfilter/vf_format.c @@ -43,12 +43,16 @@ typedef struct FormatContext { char *csps; char *ranges; char *alphamodes; + char *primaries; + char *trcs; char *chromalocations; AVFilterFormats *formats; ///< parsed from `pix_fmts` AVFilterFormats *color_spaces; ///< parsed from `csps` AVFilterFormats *color_ranges; ///< parsed from `ranges` AVFilterFormats *alpha_modes; ///< parsed from `alphamodes` + AVFilterFormats *color_primaries; ///< parsed from `primaries` + AVFilterFormats *color_trcs; ///< parsed from `trcs` AVFilterFormats *chroma_locations; ///< parsed from `chromalocations` } FormatContext; @@ -59,6 +63,8 @@ static av_cold void uninit(AVFilterContext *ctx) ff_formats_unref(&s->color_spaces); ff_formats_unref(&s->color_ranges); ff_formats_unref(&s->alpha_modes); + ff_formats_unref(&s->color_primaries); + ff_formats_unref(&s->color_trcs); ff_formats_unref(&s->chroma_locations); } @@ -113,6 +119,10 @@ DEFINE_PARSE(color_range, AVColorRange, av_color_range_from_name, "color range", DEFINE_PARSE(alpha_mode, AVAlphaMode, av_alpha_mode_from_name, "alpha mode", 0) DEFINE_PARSE(chroma_location, AVChromaLocation, av_chroma_location_from_name, "chroma location", 0) +DEFINE_PARSE(color_primaries, AVColorPrimaries, av_color_primaries_from_name, "color primaries", val < 0 || val >= AVCOL_PRI_EXT_NB) + +DEFINE_PARSE(color_trcs, AVColorTransferCharacteristic, av_color_transfer_from_name, "color transfer characteristics", val < 0 || val >= AVCOL_TRC_EXT_NB) + static av_cold int init(AVFilterContext *ctx) { FormatContext *s = ctx->priv; @@ -120,6 +130,8 @@ static av_cold int init(AVFilterContext *ctx) enum AVColorSpace csp; enum AVColorRange crg; enum AVAlphaMode alm; + enum AVColorPrimaries primaries; + enum AVColorTransferCharacteristic trcs; enum AVChromaLocation cloc; int ret; @@ -137,9 +149,17 @@ static av_cold int init(AVFilterContext *ctx) PARSE_LIST(csps, color_spaces, csp, parse_color_space) PARSE_LIST(ranges, color_ranges, crg, parse_color_range) PARSE_LIST(alphamodes, alpha_modes, alm, parse_alpha_mode) + PARSE_LIST(primaries, color_primaries, primaries, parse_color_primaries) + PARSE_LIST(trcs, color_trcs, trcs, parse_color_trcs) PARSE_LIST(chromalocations, chroma_locations, cloc, parse_chroma_location) if (!strcmp(ctx->filter->name, "noformat")) { + if (s->color_primaries && + (ret = ff_add_format(&s->color_primaries, AVCOL_PRI_UNSPECIFIED)) < 0) + return ret; + if (s->color_trcs && + (ret = ff_add_format(&s->color_trcs, AVCOL_TRC_UNSPECIFIED)) < 0) + return ret; if (s->chroma_locations && (ret = ff_add_format(&s->chroma_locations, AVCHROMA_LOC_UNSPECIFIED)) < 0) return ret; @@ -147,6 +167,8 @@ static av_cold int init(AVFilterContext *ctx) (ret = invert_formats(&s->color_spaces, ff_all_color_spaces())) < 0 || (ret = invert_formats(&s->color_ranges, ff_all_color_ranges())) < 0 || (ret = invert_formats(&s->alpha_modes, ff_all_alpha_modes())) < 0 || + (ret = invert_formats(&s->color_primaries, ff_all_color_primaries())) < 0 || + (ret = invert_formats(&s->color_trcs, ff_all_color_trcs())) < 0 || (ret = invert_formats(&s->chroma_locations, ff_all_chroma_locations())) < 0) return ret; } @@ -156,6 +178,8 @@ static av_cold int init(AVFilterContext *ctx) s->color_spaces && (ret = ff_formats_ref(s->color_spaces, &s->color_spaces)) < 0 || s->color_ranges && (ret = ff_formats_ref(s->color_ranges, &s->color_ranges)) < 0 || s->alpha_modes && (ret = ff_formats_ref(s->alpha_modes, &s->alpha_modes)) < 0 || + s->color_primaries && (ret = ff_formats_ref(s->color_primaries, &s->color_primaries)) < 0 || + s->color_trcs && (ret = ff_formats_ref(s->color_trcs, &s->color_trcs)) < 0 || s->chroma_locations && (ret = ff_formats_ref(s->chroma_locations, &s->chroma_locations)) < 0) return ret; @@ -173,6 +197,8 @@ static int query_formats(const AVFilterContext *ctx, s->color_spaces && (ret = ff_set_common_color_spaces2(ctx, cfg_in, cfg_out, s->color_spaces)) < 0 || s->color_ranges && (ret = ff_set_common_color_ranges2(ctx, cfg_in, cfg_out, s->color_ranges)) < 0 || s->alpha_modes && (ret = ff_set_common_alpha_modes2(ctx, cfg_in, cfg_out, s->alpha_modes)) < 0 || + s->color_primaries && (ret = ff_set_common_color_primaries2(ctx, cfg_in, cfg_out, s->color_primaries)) < 0 || + s->color_trcs && (ret = ff_set_common_color_trcs2(ctx, cfg_in, cfg_out, s->color_trcs)) < 0 || s->chroma_locations && (ret = ff_set_common_chroma_locations2(ctx, cfg_in, cfg_out, s->chroma_locations)) < 0) return ret; @@ -186,6 +212,8 @@ static const AVOption options[] = { { "color_spaces", "A '|'-separated list of color spaces", OFFSET(csps), AV_OPT_TYPE_STRING, .flags = AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM }, { "color_ranges", "A '|'-separated list of color ranges", OFFSET(ranges), AV_OPT_TYPE_STRING, .flags = AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM }, { "alpha_modes", "A '|'-separated list of alpha modes", OFFSET(alphamodes), AV_OPT_TYPE_STRING, .flags = AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM }, + { "color_primaries", "A '|'-separated list of color primaries", OFFSET(primaries), AV_OPT_TYPE_STRING, .flags = AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM }, + { "color_trcs", "A '|'-separated list of color transfer characteristics", OFFSET(trcs), AV_OPT_TYPE_STRING, .flags = AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM }, { "chroma_locations", "A '|'-separated list of chroma sample locations", OFFSET(chromalocations), AV_OPT_TYPE_STRING, .flags = AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM }, { NULL } }; -- 2.54.0 >From 0180671bec218bb7a8c950251403844fe81dac21 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 22 Oct 2026 11:14:49 +0000 Subject: [PATCH 08/27] fftools/ffmpeg_filter: negotiate primaries and transfer characteristics Forward input colorimetry and encoder requests to the graph, and reinitialize when input primaries or transfer characteristics change. Assisted-by: Fairy --- fftools/ffmpeg.h | 2 ++ fftools/ffmpeg_filter.c | 30 ++++++++++++++++++++++++++++++ fftools/ffmpeg_mux_init.c | 2 ++ 3 files changed, 34 insertions(+) diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h index f51867435da..0cd03200a95 100644 --- a/fftools/ffmpeg.h +++ b/fftools/ffmpeg.h @@ -323,6 +323,8 @@ typedef struct OutputFilterOptions { enum AVColorRange color_range; enum AVAlphaMode alpha_mode; enum AVChromaLocation chroma_location; + enum AVColorPrimaries color_primaries; + enum AVColorTransferCharacteristic color_trc; unsigned crop_top; unsigned crop_bottom; diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c index 1b9befe0463..231a396bcf3 100644 --- a/fftools/ffmpeg_filter.c +++ b/fftools/ffmpeg_filter.c @@ -126,6 +126,8 @@ typedef struct InputFilterPriv { enum AVColorRange color_range; enum AVAlphaMode alpha_mode; enum AVChromaLocation chroma_location; + enum AVColorPrimaries color_primaries; + enum AVColorTransferCharacteristic color_trc; int sample_rate; AVChannelLayout ch_layout; @@ -210,6 +212,8 @@ typedef struct OutputFilterPriv { enum AVColorRange color_range; enum AVAlphaMode alpha_mode; enum AVChromaLocation chroma_location; + enum AVColorPrimaries color_primaries; + enum AVColorTransferCharacteristic color_trc; unsigned crop_top; unsigned crop_bottom; @@ -298,6 +302,8 @@ static int sub2video_get_blank_frame(InputFilterPriv *ifp) frame->colorspace = ifp->color_space; frame->color_range = ifp->color_range; frame->alpha_mode = ifp->alpha_mode; + frame->color_primaries = ifp->color_primaries; + frame->color_trc = ifp->color_trc; frame->chroma_location = ifp->chroma_location; ret = av_frame_get_buffer(frame, 0); @@ -669,6 +675,8 @@ static const AVOption ofilter_options[] = { {"colorspace", "color space", offsetof(OutputFilterPriv, color_space), AV_OPT_TYPE_INT, {.i64 = AVCOL_SPC_UNSPECIFIED }, 0, INT_MAX }, {"color_range", "color range", offsetof(OutputFilterPriv, color_range), AV_OPT_TYPE_INT, {.i64 = AVCOL_RANGE_UNSPECIFIED }, 0, INT_MAX }, {"alpha_mode", "color range", offsetof(OutputFilterPriv, alpha_mode), AV_OPT_TYPE_INT, {.i64 = AVALPHA_MODE_UNSPECIFIED }, 0, INT_MAX }, + {"color_primaries", "color primaries", offsetof(OutputFilterPriv, color_primaries), AV_OPT_TYPE_INT, {.i64 = AVCOL_PRI_UNSPECIFIED }, 0, INT_MAX }, + {"color_trc", "color transfer characteristics", offsetof(OutputFilterPriv, color_trc), AV_OPT_TYPE_INT, {.i64 = AVCOL_TRC_UNSPECIFIED }, 0, INT_MAX }, {"chroma_location", "chroma sample location", offsetof(OutputFilterPriv, chroma_location), AV_OPT_TYPE_INT, {.i64 = AVCHROMA_LOC_UNSPECIFIED }, 0, INT_MAX }, {"ar", "set audio sampling rate (in Hz)", offsetof(OutputFilterPriv, sample_rate), AV_OPT_TYPE_INT, {.i64 = 0 }, 0, INT_MAX }, {"ch_layout", NULL, offsetof(OutputFilterPriv, ch_layout), AV_OPT_TYPE_CHLAYOUT, {.str = NULL }, 0, 0 }, @@ -984,6 +992,12 @@ int ofilter_bind_enc(OutputFilter *ofilter, unsigned sched_idx_enc, else ofp->alpha_modes = opts->alpha_modes; + if (opts->color_primaries != AVCOL_PRI_UNSPECIFIED) + ofp->color_primaries = opts->color_primaries; + + if (opts->color_trc != AVCOL_TRC_UNSPECIFIED) + ofp->color_trc = opts->color_trc; + if (opts->chroma_location != AVCHROMA_LOC_UNSPECIFIED) ofp->chroma_location = opts->chroma_location; else @@ -1115,6 +1129,8 @@ static InputFilter *ifilter_alloc(FilterGraph *fg) ifp->color_space = AVCOL_SPC_UNSPECIFIED; ifp->color_range = AVCOL_RANGE_UNSPECIFIED; ifp->alpha_mode = AVALPHA_MODE_UNSPECIFIED; + ifp->color_primaries = AVCOL_PRI_UNSPECIFIED; + ifp->color_trc = AVCOL_TRC_UNSPECIFIED; ifp->chroma_location = AVCHROMA_LOC_UNSPECIFIED; ifp->frame_queue = av_fifo_alloc2(8, sizeof(AVFrame*), AV_FIFO_FLAG_AUTO_GROW); @@ -1824,6 +1840,10 @@ static int configure_output_video_filter(FilterGraphPriv *fgp, AVFilterGraph *gr choose_color_ranges(ofp, &bprint); choose_alpha_modes(ofp, &bprint); choose_chroma_locations(ofp, &bprint); + if (ofp->color_primaries != AVCOL_PRI_UNSPECIFIED) + av_bprintf(&bprint, "color_primaries=%d:", ofp->color_primaries); + if (ofp->color_trc != AVCOL_TRC_UNSPECIFIED) + av_bprintf(&bprint, "color_trcs=%d:", ofp->color_trc); if (!av_bprint_is_complete(&bprint)) return AVERROR(ENOMEM); @@ -1993,6 +2013,8 @@ static int configure_input_video_filter(FilterGraph *fg, AVFilterGraph *graph, par->color_space = ifp->color_space; par->color_range = ifp->color_range; par->alpha_mode = ifp->alpha_mode; + par->color_primaries = ifp->color_primaries; + par->color_trc = ifp->color_trc; par->chroma_location = ifp->chroma_location; par->hw_frames_ctx = ifp->hw_frames_ctx; par->side_data = ifp->side_data; @@ -2272,6 +2294,8 @@ static int configure_filtergraph(FilterGraph *fg, FilterGraphThread *fgt) ofp->color_space = av_buffersink_get_colorspace(sink); ofp->color_range = av_buffersink_get_color_range(sink); ofp->alpha_mode = av_buffersink_get_alpha_mode(sink); + ofp->color_primaries = av_buffersink_get_color_primaries(sink); + ofp->color_trc = av_buffersink_get_color_trc(sink); ofp->chroma_location = av_buffersink_get_chroma_location(sink); // If the timing parameters are not locked yet, get the tentative values @@ -2369,6 +2393,8 @@ static int ifilter_parameters_from_frame(InputFilter *ifilter, const AVFrame *fr ifp->color_space = frame->colorspace; ifp->color_range = frame->color_range; ifp->alpha_mode = frame->alpha_mode; + ifp->color_primaries = frame->color_primaries; + ifp->color_trc = frame->color_trc; ifp->chroma_location = frame->chroma_location; ifp->sample_rate = frame->sample_rate; @@ -3165,6 +3191,8 @@ static int send_eof(FilterGraphThread *fgt, InputFilter *ifilter, ifp->color_space = ifp->opts.fallback->colorspace; ifp->color_range = ifp->opts.fallback->color_range; ifp->alpha_mode = ifp->opts.fallback->alpha_mode; + ifp->color_primaries = ifp->opts.fallback->color_primaries; + ifp->color_trc = ifp->opts.fallback->color_trc; ifp->chroma_location = ifp->opts.fallback->chroma_location; ifp->time_base = ifp->opts.fallback->time_base; @@ -3237,6 +3265,8 @@ static int send_frame(FilterGraph *fg, FilterGraphThread *fgt, ifp->color_space != frame->colorspace || ifp->color_range != frame->color_range || ifp->alpha_mode != frame->alpha_mode || + ifp->color_primaries != frame->color_primaries || + ifp->color_trc != frame->color_trc || ifp->chroma_location != frame->chroma_location) need_reinit |= VIDEO_CHANGED; break; diff --git a/fftools/ffmpeg_mux_init.c b/fftools/ffmpeg_mux_init.c index 27d64923888..1e3a208a184 100644 --- a/fftools/ffmpeg_mux_init.c +++ b/fftools/ffmpeg_mux_init.c @@ -973,6 +973,8 @@ ost_bind_filter(const Muxer *mux, MuxStream *ms, OutputFilter *ofilter, .color_range = enc_ctx->color_range, .alpha_mode = enc_ctx->alpha_mode, .chroma_location = enc_ctx->chroma_sample_location, + .color_primaries = enc_ctx->color_primaries, + .color_trc = enc_ctx->color_trc, .vsync_method = vsync_method, .frame_rate = ms->frame_rate, .max_frame_rate = ms->max_frame_rate, -- 2.54.0 >From 57f804b6fe4bed88d88599c5620641954d62e6c0 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 22 Oct 2026 11:19:08 +0000 Subject: [PATCH 09/27] avfilter/colorspace: advertise output primaries and transfer Preserve format-dependent BT.2020 transfer defaults by leaving the link transfer unspecified until an explicit output format is requested. Assisted-by: Fairy --- libavfilter/vf_colorspace.c | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/libavfilter/vf_colorspace.c b/libavfilter/vf_colorspace.c index 5e788680646..9d0b8c8fc92 100644 --- a/libavfilter/vf_colorspace.c +++ b/libavfilter/vf_colorspace.c @@ -756,8 +756,7 @@ static int filter_frame(AVFilterLink *link, AVFrame *in) out->colorspace = s->out_csp; out->color_range = s->user_rng == AVCOL_RANGE_UNSPECIFIED ? in->color_range : s->user_rng; - out->color_primaries = s->user_prm == AVCOL_PRI_UNSPECIFIED ? - default_prm[FFMIN(s->user_all, CS_NB)] : s->user_prm; + out->color_primaries = outlink->color_primaries; if (s->user_trc == AVCOL_TRC_UNSPECIFIED) { const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(out->format); @@ -768,6 +767,7 @@ static int filter_frame(AVFilterLink *link, AVFrame *in) out->color_trc = s->user_trc; } + if (out->color_primaries != in->color_primaries || out->color_trc != in->color_trc) { av_frame_side_data_remove_by_props(&out->side_data, &out->nb_side_data, AV_SIDE_DATA_PROP_COLOR_DEPENDENT); @@ -877,6 +877,31 @@ static int query_formats(const AVFilterContext *ctx, return res; } + res = ff_formats_ref(ff_make_formats_list_singleton( + s->user_prm == AVCOL_PRI_UNSPECIFIED ? + default_prm[FFMIN(s->user_all, CS_NB)] : s->user_prm), + &cfg_out[0]->color_primaries); + if (res < 0) + return res; + + if (s->user_trc == AVCOL_TRC_UNSPECIFIED) { + int trc = default_trc[FFMIN(s->user_all, CS_NB)]; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(s->user_format); + + if (trc == AVCOL_TRC_BT2020_10) { + if (!desc) + trc = AVCOL_TRC_UNSPECIFIED; + else if (desc->comp[0].depth >= 12) + trc = AVCOL_TRC_BT2020_12; + } + formats = ff_make_formats_list_singleton(trc); + } else { + formats = ff_make_formats_list_singleton(s->user_trc); + } + res = ff_formats_ref(formats, &cfg_out[0]->color_trcs); + if (res < 0) + return res; + formats = ff_make_pixel_format_list(pix_fmts); if (!formats) return AVERROR(ENOMEM); -- 2.54.0 >From 33ddb03cfa36d27e6c59893901b562fa66e86df9 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 22 Oct 2026 11:20:35 +0000 Subject: [PATCH 10/27] avfilter/zscale: advertise output primaries and transfer overrides Assisted-by: Fairy --- libavfilter/vf_zscale.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/libavfilter/vf_zscale.c b/libavfilter/vf_zscale.c index 8f600cbf035..bd7871dbf0a 100644 --- a/libavfilter/vf_zscale.c +++ b/libavfilter/vf_zscale.c @@ -231,6 +231,18 @@ static int query_formats(const AVFilterContext *ctx, if ((ret = ff_formats_ref(formats, &cfg_out[0]->color_ranges)) < 0) return ret; + if (s->primaries != -1) { + ret = ff_formats_ref(ff_make_formats_list_singleton(s->primaries), &cfg_out[0]->color_primaries); + if (ret < 0) + return ret; + } + + if (s->trc != -1) { + ret = ff_formats_ref(ff_make_formats_list_singleton(s->trc), &cfg_out[0]->color_trcs); + if (ret < 0) + return ret; + } + if ((ret = ff_formats_ref(ff_all_chroma_locations(), &cfg_in[0]->chroma_locations)) < 0) return ret; -- 2.54.0 >From b82049711b2dd3550e5e2de2754b48a498e40ca3 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 22 Oct 2026 11:24:33 +0000 Subject: [PATCH 11/27] avfilter/iccdetect: leave detected output colorimetry unspecified during negotiation The output tags depend on each frame ICC profile. Keep them independent of input constraints so requested conversions occur after detection. Assisted-by: Fairy --- libavfilter/vf_iccdetect.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/libavfilter/vf_iccdetect.c b/libavfilter/vf_iccdetect.c index 722c645b24d..09716fcce2a 100644 --- a/libavfilter/vf_iccdetect.c +++ b/libavfilter/vf_iccdetect.c @@ -31,6 +31,7 @@ #include "avfilter.h" #include "fflcms2.h" #include "filters.h" +#include "formats.h" #include "video.h" typedef struct IccDetectContext { @@ -118,6 +119,25 @@ done: return ff_filter_frame(inlink->dst->outputs[0], frame); } +static int query_formats(const AVFilterContext *ctx, + AVFilterFormatsConfig **cfg_in, + AVFilterFormatsConfig **cfg_out) +{ + int ret; + + ret = ff_formats_ref(ff_make_formats_list_singleton(AVCOL_PRI_UNSPECIFIED), + &cfg_out[0]->color_primaries); + if (ret < 0) + return ret; + + ret = ff_formats_ref(ff_make_formats_list_singleton(AVCOL_TRC_UNSPECIFIED), + &cfg_out[0]->color_trcs); + if (ret < 0) + return ret; + + return 0; +} + static const AVFilterPad iccdetect_inputs[] = { { .name = "default", @@ -134,6 +154,7 @@ const FFFilter ff_vf_iccdetect = { .priv_size = sizeof(IccDetectContext), .init = &iccdetect_init, .uninit = &iccdetect_uninit, + FILTER_QUERY_FUNC2(query_formats), FILTER_INPUTS(iccdetect_inputs), FILTER_OUTPUTS(ff_video_default_filterpad), .flags_internal = FF_FILTER_FLAG_DYNAMIC_FRAME_PARAMS, -- 2.54.0 >From 0ef4ebca207a15ef7cf89d6f094f732a0dcb0468 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 22 Oct 2026 11:24:37 +0000 Subject: [PATCH 12/27] avfilter/scale_vaapi: advertise output primaries and transfer overrides Assisted-by: Fairy --- libavfilter/vf_scale_vaapi.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libavfilter/vf_scale_vaapi.c b/libavfilter/vf_scale_vaapi.c index d102ef9ff05..3b35ad6d8b8 100644 --- a/libavfilter/vf_scale_vaapi.c +++ b/libavfilter/vf_scale_vaapi.c @@ -80,6 +80,20 @@ static int scale_vaapi_query_formats(const AVFilterContext *avctx, if (err < 0) return err; + if (ctx->colour_primaries != AVCOL_PRI_UNSPECIFIED) { + err = ff_formats_ref(ff_make_formats_list_singleton(ctx->colour_primaries), + &cfg_out[0]->color_primaries); + if (err < 0) + return err; + } + + if (ctx->colour_transfer != AVCOL_TRC_UNSPECIFIED) { + err = ff_formats_ref(ff_make_formats_list_singleton(ctx->colour_transfer), + &cfg_out[0]->color_trcs); + if (err < 0) + return err; + } + err = ff_formats_ref(ff_all_chroma_locations(), &cfg_in[0]->chroma_locations); if (err < 0) return err; -- 2.54.0 >From ea55b0268dcfd33cf22048452b3cff88b4fe3beb Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 22 Oct 2026 11:24:41 +0000 Subject: [PATCH 13/27] avfilter/libplacebo: negotiate independent input and output colorimetry Declare explicit output overrides. Leave frame-dependent defaults unspecified, including Dolby Vision reshaping. Assisted-by: Fairy --- libavfilter/vf_libplacebo.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/libavfilter/vf_libplacebo.c b/libavfilter/vf_libplacebo.c index ac9a56152f9..143a39bfeda 100644 --- a/libavfilter/vf_libplacebo.c +++ b/libavfilter/vf_libplacebo.c @@ -1352,6 +1352,8 @@ done: RET(ff_add_format(&infmts, cfg_in[0]->formats->formats[n])); } RET(ff_formats_ref(infmts, &cfg_in[i]->formats)); + RET(ff_formats_ref(ff_all_color_primaries(), &cfg_in[i]->color_primaries)); + RET(ff_formats_ref(ff_all_color_trcs(), &cfg_in[i]->color_trcs)); RET(ff_formats_ref(ff_all_color_spaces(), &cfg_in[i]->color_spaces)); RET(ff_formats_ref(ff_all_color_ranges(), &cfg_in[i]->color_ranges)); RET(ff_formats_ref(ff_all_alpha_modes(), &cfg_in[i]->alpha_modes)); @@ -1360,6 +1362,13 @@ done: RET(ff_formats_ref(outfmts, &cfg_out[0]->formats)); + outfmts = ff_make_formats_list_singleton(s->color_primaries >= 0 ? + s->color_primaries : AVCOL_PRI_UNSPECIFIED); + RET(ff_formats_ref(outfmts, &cfg_out[0]->color_primaries)); + outfmts = ff_make_formats_list_singleton(s->color_trc >= 0 ? + s->color_trc : AVCOL_TRC_UNSPECIFIED); + RET(ff_formats_ref(outfmts, &cfg_out[0]->color_trcs)); + outfmts = s->colorspace > 0 ? ff_make_formats_list_singleton(s->colorspace) : ff_all_color_spaces(); RET(ff_formats_ref(outfmts, &cfg_out[0]->color_spaces)); -- 2.54.0 >From a8ab9999fd387990c03ef6b9f6def3b7dbab651f Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 22 Oct 2026 11:26:24 +0000 Subject: [PATCH 14/27] avfilter/tonemap: declare primaries and transfer constraints Assisted-by: Fairy --- libavfilter/vf_tonemap.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/libavfilter/vf_tonemap.c b/libavfilter/vf_tonemap.c index c5be4b15102..ad241acf370 100644 --- a/libavfilter/vf_tonemap.c +++ b/libavfilter/vf_tonemap.c @@ -37,6 +37,7 @@ #include "avfilter.h" #include "colorspace.h" #include "filters.h" +#include "formats.h" #include "video.h" enum TonemapAlgorithm { @@ -299,6 +300,23 @@ static const AVOption tonemap_options[] = { AVFILTER_DEFINE_CLASS(tonemap); +static int query_formats(const AVFilterContext *ctx, + AVFilterFormatsConfig **cfg_in, + AVFilterFormatsConfig **cfg_out) +{ + static const enum AVPixelFormat pix_fmts[] = { + AV_PIX_FMT_GBRPF32, AV_PIX_FMT_GBRAPF32, AV_PIX_FMT_NONE + }; + static const int trcs[] = { AVCOL_TRC_UNSPECIFIED, AVCOL_TRC_LINEAR, -1 }; + int ret; + + if ((ret = ff_formats_ref(ff_make_format_list(trcs), &cfg_in[0]->color_trcs)) < 0 || + (ret = ff_formats_ref(ff_make_formats_list_singleton(AVCOL_TRC_LINEAR), + &cfg_out[0]->color_trcs)) < 0) + return ret; + return ff_set_common_formats2(ctx, cfg_in, cfg_out, ff_make_pixel_format_list(pix_fmts)); +} + static const AVFilterPad tonemap_inputs[] = { { .name = "default", @@ -316,5 +334,5 @@ const FFFilter ff_vf_tonemap = { .priv_size = sizeof(TonemapContext), FILTER_INPUTS(tonemap_inputs), FILTER_OUTPUTS(ff_video_default_filterpad), - FILTER_PIXFMTS(AV_PIX_FMT_GBRPF32, AV_PIX_FMT_GBRAPF32), + FILTER_QUERY_FUNC2(query_formats), }; -- 2.54.0 >From 853262e005c8a2bc1bc129fbe7cabd7f173fa962 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 22 Oct 2026 11:26:28 +0000 Subject: [PATCH 15/27] avfilter/grayworld: declare primaries and transfer constraints Assisted-by: Fairy --- libavfilter/vf_grayworld.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/libavfilter/vf_grayworld.c b/libavfilter/vf_grayworld.c index c1821eb7b85..a34e448cb86 100644 --- a/libavfilter/vf_grayworld.c +++ b/libavfilter/vf_grayworld.c @@ -30,6 +30,7 @@ #include "avfilter.h" #include "filters.h" +#include "formats.h" #include "video.h" typedef struct ThreadData { @@ -288,6 +289,23 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in) return ff_filter_frame(outlink, out); } +static int query_formats(const AVFilterContext *ctx, + AVFilterFormatsConfig **cfg_in, + AVFilterFormatsConfig **cfg_out) +{ + static const enum AVPixelFormat pix_fmts[] = { + AV_PIX_FMT_GBRPF32, AV_PIX_FMT_GBRAPF32, AV_PIX_FMT_NONE + }; + static const int trcs[] = { AVCOL_TRC_UNSPECIFIED, AVCOL_TRC_LINEAR, -1 }; + int ret; + + if ((ret = ff_formats_ref(ff_make_format_list(trcs), &cfg_in[0]->color_trcs)) < 0 || + (ret = ff_formats_ref(ff_make_formats_list_singleton(AVCOL_TRC_LINEAR), + &cfg_out[0]->color_trcs)) < 0) + return ret; + return ff_set_common_formats2(ctx, cfg_in, cfg_out, ff_make_pixel_format_list(pix_fmts)); +} + static const AVFilterPad grayworld_inputs[] = { { .name = "default", @@ -304,6 +322,6 @@ const FFFilter ff_vf_grayworld = { .priv_size = sizeof(GrayWorldContext), FILTER_INPUTS(grayworld_inputs), FILTER_OUTPUTS(ff_video_default_filterpad), - FILTER_PIXFMTS(AV_PIX_FMT_GBRPF32, AV_PIX_FMT_GBRAPF32), + FILTER_QUERY_FUNC2(query_formats), .uninit = uninit, }; -- 2.54.0 >From 1e6427dac7c66759c80f9cb719bcd8ef3a552153 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 22 Oct 2026 11:26:33 +0000 Subject: [PATCH 16/27] avfilter/scale_vt: declare primaries and transfer constraints Assisted-by: Fairy --- libavfilter/vf_scale_vt.c | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/libavfilter/vf_scale_vt.c b/libavfilter/vf_scale_vt.c index 0ff49047ca3..a8d481b260d 100644 --- a/libavfilter/vf_scale_vt.c +++ b/libavfilter/vf_scale_vt.c @@ -26,6 +26,7 @@ #include "libavutil/pixdesc.h" #include "filters.h" +#include "formats.h" #include "scale_eval.h" #include "video.h" @@ -323,6 +324,31 @@ static const AVFilterPad scale_vt_outputs[] = { }, }; +static int query_formats(const AVFilterContext *ctx, + AVFilterFormatsConfig **cfg_in, + AVFilterFormatsConfig **cfg_out) +{ + const ScaleVtContext *s = ctx->priv; + int ret; + + if (s->colour_primaries != AVCOL_PRI_UNSPECIFIED) { + ret = ff_formats_ref(ff_make_formats_list_singleton(s->colour_primaries), + &cfg_out[0]->color_primaries); + if (ret < 0) + return ret; + } + + if (s->colour_transfer != AVCOL_TRC_UNSPECIFIED) { + ret = ff_formats_ref(ff_make_formats_list_singleton(s->colour_transfer), + &cfg_out[0]->color_trcs); + if (ret < 0) + return ret; + } + + return ff_set_common_formats2(ctx, cfg_in, cfg_out, + ff_make_formats_list_singleton(AV_PIX_FMT_VIDEOTOOLBOX)); +} + const FFFilter ff_vf_scale_vt = { .p.name = "scale_vt", .p.description = NULL_IF_CONFIG_SMALL("Scale Videotoolbox frames"), @@ -333,6 +359,6 @@ const FFFilter ff_vf_scale_vt = { .uninit = scale_vt_uninit, FILTER_INPUTS(scale_vt_inputs), FILTER_OUTPUTS(scale_vt_outputs), - FILTER_SINGLE_PIXFMT(AV_PIX_FMT_VIDEOTOOLBOX), + FILTER_QUERY_FUNC2(query_formats), .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE, }; -- 2.54.0 >From a6620ca55c8d330a54247e78aa6f83e32ca1d79d Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 22 Oct 2026 11:26:38 +0000 Subject: [PATCH 17/27] avfilter/tonemap_opencl: declare primaries and transfer constraints Assisted-by: Fairy --- libavfilter/vf_tonemap_opencl.c | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/libavfilter/vf_tonemap_opencl.c b/libavfilter/vf_tonemap_opencl.c index aa8f50b6d67..2061e7167eb 100644 --- a/libavfilter/vf_tonemap_opencl.c +++ b/libavfilter/vf_tonemap_opencl.c @@ -25,6 +25,7 @@ #include "avfilter.h" #include "filters.h" +#include "formats.h" #include "opencl.h" #include "opencl_source.h" #include "video.h" @@ -540,6 +541,31 @@ static const AVFilterPad tonemap_opencl_outputs[] = { }, }; +static int query_formats(const AVFilterContext *ctx, + AVFilterFormatsConfig **cfg_in, + AVFilterFormatsConfig **cfg_out) +{ + const TonemapOpenCLContext *s = ctx->priv; + int ret; + + if (s->primaries != -1) { + ret = ff_formats_ref(ff_make_formats_list_singleton(s->primaries), + &cfg_out[0]->color_primaries); + if (ret < 0) + return ret; + } + + if (s->trc != -1) { + ret = ff_formats_ref(ff_make_formats_list_singleton(s->trc), + &cfg_out[0]->color_trcs); + if (ret < 0) + return ret; + } + + return ff_set_common_formats2(ctx, cfg_in, cfg_out, + ff_make_formats_list_singleton(AV_PIX_FMT_OPENCL)); +} + const FFFilter ff_vf_tonemap_opencl = { .p.name = "tonemap_opencl", .p.description = NULL_IF_CONFIG_SMALL("Perform HDR to SDR conversion with tonemapping."), @@ -550,6 +576,6 @@ const FFFilter ff_vf_tonemap_opencl = { .uninit = &tonemap_opencl_uninit, FILTER_INPUTS(tonemap_opencl_inputs), FILTER_OUTPUTS(tonemap_opencl_outputs), - FILTER_SINGLE_PIXFMT(AV_PIX_FMT_OPENCL), + FILTER_QUERY_FUNC2(query_formats), .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE, }; -- 2.54.0 >From 7444cdae86e753db3fa3d2974531153804f9a2e2 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 22 Oct 2026 11:26:43 +0000 Subject: [PATCH 18/27] avfilter/tonemap_vaapi: declare primaries and transfer constraints Assisted-by: Fairy --- libavfilter/vf_tonemap_vaapi.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/libavfilter/vf_tonemap_vaapi.c b/libavfilter/vf_tonemap_vaapi.c index 9fe82cbb1b6..85359cf24c7 100644 --- a/libavfilter/vf_tonemap_vaapi.c +++ b/libavfilter/vf_tonemap_vaapi.c @@ -23,6 +23,7 @@ #include "avfilter.h" #include "filters.h" +#include "formats.h" #include "vaapi_vpp.h" #include "video.h" @@ -559,6 +560,30 @@ static const AVFilterPad tonemap_vaapi_outputs[] = { }, }; +static int query_formats(const AVFilterContext *avctx, + AVFilterFormatsConfig **cfg_in, + AVFilterFormatsConfig **cfg_out) +{ + const HDRVAAPIContext *ctx = avctx->priv; + int ret; + + if (ctx->color_primaries != AVCOL_PRI_UNSPECIFIED || !ctx->mastering_display) { + ret = ff_formats_ref(ff_make_formats_list_singleton(ctx->color_primaries != AVCOL_PRI_UNSPECIFIED ? ctx->color_primaries : AVCOL_PRI_BT709), + &cfg_out[0]->color_primaries); + if (ret < 0) + return ret; + } + + if (ctx->color_transfer != AVCOL_TRC_UNSPECIFIED || !ctx->mastering_display) { + ret = ff_formats_ref(ff_make_formats_list_singleton(ctx->color_transfer != AVCOL_TRC_UNSPECIFIED ? ctx->color_transfer : AVCOL_TRC_BT709), + &cfg_out[0]->color_trcs); + if (ret < 0) + return ret; + } + + return ff_vaapi_vpp_query_formats(avctx, cfg_in, cfg_out); +} + const FFFilter ff_vf_tonemap_vaapi = { .p.name = "tonemap_vaapi", .p.description = NULL_IF_CONFIG_SMALL("VAAPI VPP for tone-mapping"), @@ -568,6 +593,6 @@ const FFFilter ff_vf_tonemap_vaapi = { .uninit = &ff_vaapi_vpp_ctx_uninit, FILTER_INPUTS(tonemap_vaapi_inputs), FILTER_OUTPUTS(tonemap_vaapi_outputs), - FILTER_QUERY_FUNC2(&ff_vaapi_vpp_query_formats), + FILTER_QUERY_FUNC2(query_formats), .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE, }; -- 2.54.0 >From fc3b9d78f497b54dd46cb2d628b7e00e3693d6f3 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 22 Oct 2026 11:26:49 +0000 Subject: [PATCH 19/27] avfilter/vpp_qsv: declare primaries and transfer constraints Assisted-by: Fairy --- libavfilter/vf_vpp_qsv.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libavfilter/vf_vpp_qsv.c b/libavfilter/vf_vpp_qsv.c index 4de99d6490d..48c610d9bbd 100644 --- a/libavfilter/vf_vpp_qsv.c +++ b/libavfilter/vf_vpp_qsv.c @@ -952,6 +952,20 @@ static int vpp_query_formats(const AVFilterContext *ctx, }; static enum AVPixelFormat out_pix_fmts[4]; + if (vpp->color_primaries != AVCOL_PRI_UNSPECIFIED || vpp->tonemap) { + ret = ff_formats_ref(ff_make_formats_list_singleton(vpp->color_primaries), + &cfg_out[0]->color_primaries); + if (ret < 0) + return ret; + } + + if (vpp->color_transfer != AVCOL_TRC_UNSPECIFIED || vpp->tonemap) { + ret = ff_formats_ref(ff_make_formats_list_singleton(vpp->color_transfer), + &cfg_out[0]->color_trcs); + if (ret < 0) + return ret; + } + ret = ff_formats_ref(ff_make_pixel_format_list(in_pix_fmts), &cfg_in[0]->formats); if (ret < 0) -- 2.54.0 >From 7b8e85d52536e3740a6119bb56eacc876bf11753 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 22 Oct 2026 11:26:54 +0000 Subject: [PATCH 20/27] avfilter/amf_common: declare primaries and transfer constraints Assisted-by: Fairy --- libavfilter/vf_amf_common.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/libavfilter/vf_amf_common.c b/libavfilter/vf_amf_common.c index e1d0b42ee37..52e5087af7a 100644 --- a/libavfilter/vf_amf_common.c +++ b/libavfilter/vf_amf_common.c @@ -384,6 +384,7 @@ int amf_filter_activate(AVFilterContext *avctx) int amf_setup_input_output_formats(AVFilterContext *avctx, const enum AVPixelFormat *input_pix_fmts) { + const AMFFilterContext *ctx = avctx->priv; int err; AVFilterFormats *input_formats; AVFilterFormats *output_formats; @@ -392,6 +393,20 @@ int amf_setup_input_output_formats(AVFilterContext *avctx, AV_PIX_FMT_NONE, }; + if (ctx->out_primaries != AMF_COLOR_PRIMARIES_UNDEFINED) { + err = ff_formats_ref(ff_make_formats_list_singleton(ctx->out_primaries), + &avctx->outputs[0]->incfg.color_primaries); + if (err < 0) + return err; + } + + if (ctx->out_trc != AMF_COLOR_TRANSFER_CHARACTERISTIC_UNDEFINED) { + err = ff_formats_ref(ff_make_formats_list_singleton(ctx->out_trc), + &avctx->outputs[0]->incfg.color_trcs); + if (err < 0) + return err; + } + //in case if hw_device_ctx is set to DXVA2 we change order of pixel formats to set DXVA2 be chosen by default //The order is ignored if hw_frames_ctx is not NULL on the config_output stage if (avctx->hw_device_ctx) { -- 2.54.0 >From 32dba4fd91c2943fe60cc8eb31c6ee1896aa8b84 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 22 Oct 2026 11:26:59 +0000 Subject: [PATCH 21/27] avfilter/lcevc: declare primaries and transfer constraints Assisted-by: Fairy --- libavfilter/vf_lcevc.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/libavfilter/vf_lcevc.c b/libavfilter/vf_lcevc.c index 01e5ded9006..c60cb6d9e14 100644 --- a/libavfilter/vf_lcevc.c +++ b/libavfilter/vf_lcevc.c @@ -25,6 +25,7 @@ #include "libavutil/internal.h" #include "libavutil/opt.h" #include "filters.h" +#include "formats.h" #include "video.h" typedef struct LCEVCContext { @@ -448,13 +449,28 @@ static const enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_NONE }; +static int query_formats(const AVFilterContext *ctx, + AVFilterFormatsConfig **cfg_in, + AVFilterFormatsConfig **cfg_out) +{ + int ret; + + if ((ret = ff_formats_ref(ff_make_formats_list_singleton(AVCOL_PRI_UNSPECIFIED), + &cfg_out[0]->color_primaries)) < 0 || + (ret = ff_formats_ref(ff_make_formats_list_singleton(AVCOL_TRC_UNSPECIFIED), + &cfg_out[0]->color_trcs)) < 0) + return ret; + return ff_set_common_formats2(ctx, cfg_in, cfg_out, + ff_make_pixel_format_list(pix_fmts)); +} + const FFFilter ff_vf_lcevc = { .p.name = "lcevc", .p.description = NULL_IF_CONFIG_SMALL("LCEVC"), .activate = activate, FILTER_INPUTS(ff_video_default_filterpad), FILTER_OUTPUTS(lcevc_outputs), - FILTER_PIXFMTS_ARRAY(pix_fmts), + FILTER_QUERY_FUNC2(query_formats), .priv_size = sizeof(LCEVCContext), .init = init, .uninit = uninit, -- 2.54.0 >From c7874f631beae24793814cee11c776130c46af05 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 22 Oct 2026 11:27:04 +0000 Subject: [PATCH 22/27] avfilter/ddagrab: leave frame-dependent colorimetry unspecified Assisted-by: Fairy --- libavfilter/vsrc_ddagrab.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/libavfilter/vsrc_ddagrab.c b/libavfilter/vsrc_ddagrab.c index e035ac9bc45..a14c97ae93e 100644 --- a/libavfilter/vsrc_ddagrab.c +++ b/libavfilter/vsrc_ddagrab.c @@ -44,6 +44,7 @@ #include "compat/w32dlfcn.h" #include "avfilter.h" #include "filters.h" +#include "formats.h" #include "video.h" #include "vsrc_ddagrab_shaders.h" @@ -1254,6 +1255,21 @@ static const AVFilterPad ddagrab_outputs[] = { }, }; +static int query_formats(const AVFilterContext *ctx, + AVFilterFormatsConfig **cfg_in, + AVFilterFormatsConfig **cfg_out) +{ + int ret; + + if ((ret = ff_formats_ref(ff_make_formats_list_singleton(AVCOL_PRI_UNSPECIFIED), + &cfg_out[0]->color_primaries)) < 0 || + (ret = ff_formats_ref(ff_make_formats_list_singleton(AVCOL_TRC_UNSPECIFIED), + &cfg_out[0]->color_trcs)) < 0) + return ret; + return ff_set_common_formats2(ctx, cfg_in, cfg_out, + ff_make_formats_list_singleton(AV_PIX_FMT_D3D11)); +} + const FFFilter ff_vsrc_ddagrab = { .p.name = "ddagrab", .p.description = NULL_IF_CONFIG_SMALL("Grab Windows Desktop images using Desktop Duplication API"), @@ -1264,6 +1280,6 @@ const FFFilter ff_vsrc_ddagrab = { .init = ddagrab_init, .uninit = ddagrab_uninit, FILTER_OUTPUTS(ddagrab_outputs), - FILTER_SINGLE_PIXFMT(AV_PIX_FMT_D3D11), + FILTER_QUERY_FUNC2(query_formats), .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE, }; -- 2.54.0 >From 15a4b734a10b11e08daccd659541a6517fb5eec4 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 22 Oct 2026 11:27:09 +0000 Subject: [PATCH 23/27] avfilter/gfxcapture: leave frame-dependent colorimetry unspecified Assisted-by: Fairy --- libavfilter/vsrc_gfxcapture.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/libavfilter/vsrc_gfxcapture.c b/libavfilter/vsrc_gfxcapture.c index 19d8a8a52c6..46aef3670c1 100644 --- a/libavfilter/vsrc_gfxcapture.c +++ b/libavfilter/vsrc_gfxcapture.c @@ -22,6 +22,7 @@ #include "libavutil/opt.h" #include "avfilter.h" #include "filters.h" +#include "formats.h" #include "vsrc_gfxcapture.h" @@ -86,6 +87,21 @@ static const AVFilterPad gfxcapture_outputs[] = { }, }; +static int query_formats(const AVFilterContext *ctx, + AVFilterFormatsConfig **cfg_in, + AVFilterFormatsConfig **cfg_out) +{ + int ret; + + if ((ret = ff_formats_ref(ff_make_formats_list_singleton(AVCOL_PRI_UNSPECIFIED), + &cfg_out[0]->color_primaries)) < 0 || + (ret = ff_formats_ref(ff_make_formats_list_singleton(AVCOL_TRC_UNSPECIFIED), + &cfg_out[0]->color_trcs)) < 0) + return ret; + return ff_set_common_formats2(ctx, cfg_in, cfg_out, + ff_make_formats_list_singleton(AV_PIX_FMT_D3D11)); +} + const FFFilter ff_vsrc_gfxcapture = { .p.name = "gfxcapture", .p.description = NULL_IF_CONFIG_SMALL("Capture graphics/screen content as a video source"), @@ -96,7 +112,7 @@ const FFFilter ff_vsrc_gfxcapture = { .init = ff_gfxcapture_init, .uninit = ff_gfxcapture_uninit, FILTER_OUTPUTS(gfxcapture_outputs), - FILTER_SINGLE_PIXFMT(AV_PIX_FMT_D3D11), + FILTER_QUERY_FUNC2(query_formats), .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE, .activate = ff_gfxcapture_activate, }; -- 2.54.0 >From e28f387732b45dbdfb76bb495bcb7de72bc84aad Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 22 Oct 2026 11:28:42 +0000 Subject: [PATCH 24/27] tests/avfilter: cover primaries and transfer negotiation through the buffer API Compare automatic and explicit conversions, preserve unspecified and extended values, and exercise sink alternatives and disabled conversion. Assisted-by: Fairy --- libavfilter/Makefile | 2 +- libavfilter/tests/.gitignore | 1 + libavfilter/tests/color_negotiation.c | 178 ++++++++++++++++++++++++ tests/fate/filter-video.mak | 4 + tests/ref/fate/filter-color-negotiation | 53 +++++++ 5 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 libavfilter/tests/color_negotiation.c create mode 100644 tests/ref/fate/filter-color-negotiation diff --git a/libavfilter/Makefile b/libavfilter/Makefile index ef2542b98b9..7f2c7775a87 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -688,7 +688,7 @@ SKIPHEADERS-$(CONFIG_VULKAN) += vulkan_filter.h SKIPHEADERS-$(CONFIG_SCALE_CUDA_FILTER) += vf_scale_cuda.h TOOLS = graph2dot -TESTPROGS = drawutils filtfmts formats integral +TESTPROGS = color_negotiation drawutils filtfmts formats integral TESTPROGS-$(CONFIG_DRAWVG_FILTER) += drawvg diff --git a/libavfilter/tests/.gitignore b/libavfilter/tests/.gitignore index 18018055732..5455c87aa5c 100644 --- a/libavfilter/tests/.gitignore +++ b/libavfilter/tests/.gitignore @@ -1,3 +1,4 @@ +/color_negotiation /dnn-layer-conv2d /dnn-layer-depth2space /dnn-layer-maximum diff --git a/libavfilter/tests/color_negotiation.c b/libavfilter/tests/color_negotiation.c new file mode 100644 index 00000000000..ee6c801c37b --- /dev/null +++ b/libavfilter/tests/color_negotiation.c @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2026 FFmpeg contributors + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <stdio.h> +#include <string.h> + +#include "libavutil/adler32.h" +#include "libavutil/mem.h" +#include "libavutil/opt.h" +#include "libavutil/pixdesc.h" +#include "libavfilter/avfilter.h" +#include "libavfilter/buffersink.h" +#include "libavfilter/buffersrc.h" + +static unsigned checksum(const AVFrame *frame) +{ + unsigned sum = 1; + for (int p = 0; p < 3; p++) + for (int y = 0; y < frame->height; y++) + sum = av_adler32_update(sum, frame->data[p] + y * frame->linesize[p], + frame->width); + return sum; +} + +static int run_case(const char *name, int primaries, int trc, + const char *out_primaries, const char *out_trcs, + const char *scale, int disable_auto, int untagged, + unsigned *result) +{ + AVFilterGraph *graph = avfilter_graph_alloc(); + AVBufferSrcParameters *par = av_buffersrc_parameters_alloc(); + AVFilterContext *src, *sink, *filter; + AVFrame *in = av_frame_alloc(), *out = av_frame_alloc(); + unsigned input_sum; + int ret = AVERROR(ENOMEM); + + printf("%s\n", name); + if (!graph || !par || !in || !out) + goto end; + graph->nb_threads = 1; + if (disable_auto) + avfilter_graph_set_auto_convert(graph, AVFILTER_AUTO_CONVERT_NONE); + src = avfilter_graph_alloc_filter(graph, avfilter_get_by_name("buffer"), "src"); + if (!src) + goto end; + par->format = AV_PIX_FMT_GBRP; + par->width = par->height = 16; + par->time_base = (AVRational) { 1, 25 }; + par->color_primaries = primaries; + par->color_trc = trc; + if ((ret = av_buffersrc_parameters_set(src, par)) < 0 || + (ret = avfilter_init_str(src, NULL)) < 0) + goto end; + sink = avfilter_graph_alloc_filter(graph, avfilter_get_by_name("buffersink"), "sink"); + if (!sink) { + ret = AVERROR(ENOMEM); + goto end; + } + if (out_primaries && + (ret = av_opt_set(sink, "color_primaries", out_primaries, AV_OPT_SEARCH_CHILDREN)) < 0) + goto end; + if (out_trcs && + (ret = av_opt_set(sink, "color_trcs", out_trcs, AV_OPT_SEARCH_CHILDREN)) < 0) + goto end; + if ((ret = avfilter_init_str(sink, NULL)) < 0 || + (ret = avfilter_graph_create_filter(&filter, avfilter_get_by_name(scale ? "scale" : "null"), + "filter", scale, NULL, graph)) < 0 || + (ret = avfilter_link(src, 0, filter, 0)) < 0 || + (ret = avfilter_link(filter, 0, sink, 0)) < 0) + goto end; + ret = avfilter_graph_config(graph, NULL); + printf("configuration: %s\n", ret < 0 ? "failed" : "ok"); + if (ret < 0) { + ret = 0; + goto end; + } + printf("link: %s / %s\n", + av_color_primaries_name(av_buffersink_get_color_primaries(sink)), + av_color_transfer_name(av_buffersink_get_color_trc(sink))); + + in->format = par->format; + in->width = par->width; + in->height = par->height; + in->colorspace = AVCOL_SPC_RGB; + in->color_range = AVCOL_RANGE_JPEG; + in->color_primaries = untagged ? AVCOL_PRI_UNSPECIFIED : primaries; + in->color_trc = untagged ? AVCOL_TRC_UNSPECIFIED : trc; + if ((ret = av_frame_get_buffer(in, 0)) < 0) + goto end; + for (int p = 0; p < 3; p++) + for (int y = 0; y < in->height; y++) + for (int x = 0; x < in->width; x++) + in->data[p][y * in->linesize[p] + x] = 32 + 4 * x + 2 * y + 16 * p; + input_sum = checksum(in); + if ((ret = av_buffersrc_add_frame(src, in)) < 0 || + (ret = av_buffersink_get_frame(sink, out)) < 0) + goto end; + printf("frame: %s / %s\n", av_color_primaries_name(out->color_primaries), + av_color_transfer_name(out->color_trc)); + *result = checksum(out); + printf("pixels changed: %d\n", *result != input_sum); + +end: + av_frame_free(&in); + av_frame_free(&out); + av_free(par); + avfilter_graph_free(&graph); + return ret; +} + +int main(void) +{ + unsigned implicit = 0, explicit = 0, sum = 0; + int ret; + av_log_set_level(AV_LOG_QUIET); + + ret = run_case("unspecified", AVCOL_PRI_UNSPECIFIED, AVCOL_TRC_UNSPECIFIED, + NULL, NULL, NULL, 0, 0, &sum); + if (ret < 0) + return 1; + ret = run_case("preserve", AVCOL_PRI_BT709, AVCOL_TRC_BT709, + NULL, NULL, NULL, 0, 0, &sum); + if (ret < 0) + return 1; + ret = run_case("fill missing frame tags", AVCOL_PRI_BT709, AVCOL_TRC_BT709, + NULL, NULL, NULL, 0, 1, &sum); + if (ret < 0) + return 1; + ret = run_case("preserve supported alternative", AVCOL_PRI_BT709, AVCOL_TRC_BT709, + "9,1", "8,1", NULL, 0, 0, &sum); + if (ret < 0) + return 1; + ret = run_case("extended enums", AVCOL_PRI_V_GAMUT, AVCOL_TRC_V_LOG, + NULL, NULL, NULL, 0, 0, &sum); + if (ret < 0) + return 1; + ret = run_case("unsupported transfer passthrough", AVCOL_PRI_BT709, AVCOL_TRC_LOG, + NULL, NULL, "", 0, 0, &sum); + if (ret < 0) + return 1; + ret = run_case("automatic primaries", AVCOL_PRI_BT709, AVCOL_TRC_BT709, + "9", NULL, NULL, 0, 0, &sum); + if (ret < 0) + return 1; + ret = run_case("automatic transfer", AVCOL_PRI_BT709, AVCOL_TRC_BT709, + NULL, "8", NULL, 0, 0, &sum); + if (ret < 0) + return 1; + ret = run_case("automatic conversion", AVCOL_PRI_BT709, AVCOL_TRC_BT709, + "9", "8", NULL, 0, 0, &implicit); + if (ret < 0) + return 1; + ret = run_case("explicit conversion", AVCOL_PRI_BT709, AVCOL_TRC_BT709, + NULL, NULL, "out_primaries=bt2020:out_transfer=linear", 0, 0, &explicit); + if (ret < 0) + return 1; + printf("implicit matches explicit: %d\n", implicit == explicit); + ret = run_case("conversion disabled", AVCOL_PRI_BT709, AVCOL_TRC_BT709, + "9", "8", NULL, 1, 0, &sum); + return ret < 0; +} diff --git a/tests/fate/filter-video.mak b/tests/fate/filter-video.mak index 4a065ec0320..d9cfda8afee 100644 --- a/tests/fate/filter-video.mak +++ b/tests/fate/filter-video.mak @@ -523,6 +523,10 @@ FATE_FILTER-$(call FILTERFRAMECRC, FPS TESTSRC2 SETPTS SETTB) += fate-filter-fps fate-filter-fps-single-cross-start: CMD = framecrc -lavfi testsrc2=s=16x16:r=25/2:d=0.08,settb=1/25,setpts=PTS-1,fps=1:start_time=0 fate-filter-fps-single-cross-start: REF = $(SRC_PATH)/tests/ref/fate/filter-fps-single +FATE_LIBAVFILTER-$(call ALLYES, NULL_FILTER SCALE_FILTER) += fate-filter-color-negotiation +fate-filter-color-negotiation: libavfilter/tests/color_negotiation$(EXESUF) +fate-filter-color-negotiation: CMD = run libavfilter/tests/color_negotiation$(EXESUF) + FATE_LIBAVFILTER-yes += fate-filter-drawutils fate-filter-drawutils: libavfilter/tests/drawutils$(EXESUF) fate-filter-drawutils: CMD = run libavfilter/tests/drawutils$(EXESUF) diff --git a/tests/ref/fate/filter-color-negotiation b/tests/ref/fate/filter-color-negotiation new file mode 100644 index 00000000000..8d04e02100f --- /dev/null +++ b/tests/ref/fate/filter-color-negotiation @@ -0,0 +1,53 @@ +unspecified +configuration: ok +link: unknown / unknown +frame: unknown / unknown +pixels changed: 0 +preserve +configuration: ok +link: bt709 / bt709 +frame: bt709 / bt709 +pixels changed: 0 +fill missing frame tags +configuration: ok +link: bt709 / bt709 +frame: bt709 / bt709 +pixels changed: 0 +preserve supported alternative +configuration: ok +link: bt709 / bt709 +frame: bt709 / bt709 +pixels changed: 0 +extended enums +configuration: ok +link: vgamut / vlog +frame: vgamut / vlog +pixels changed: 0 +unsupported transfer passthrough +configuration: ok +link: bt709 / log100 +frame: bt709 / log100 +pixels changed: 0 +automatic primaries +configuration: ok +link: bt2020 / bt709 +frame: bt2020 / bt709 +pixels changed: 1 +automatic transfer +configuration: ok +link: bt709 / linear +frame: bt709 / linear +pixels changed: 1 +automatic conversion +configuration: ok +link: bt2020 / linear +frame: bt2020 / linear +pixels changed: 1 +explicit conversion +configuration: ok +link: bt2020 / linear +frame: bt2020 / linear +pixels changed: 1 +implicit matches explicit: 1 +conversion disabled +configuration: failed -- 2.54.0 >From 25acea8a1a7db8d7db5d481eae8f31fdc6d21877 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 22 Oct 2026 11:29:05 +0000 Subject: [PATCH 25/27] tests/fate: cover primaries and transfer filter constraints Assisted-by: Fairy --- tests/fate/filter-video.mak | 21 +++++++++++++++++++ tests/ref/fate/filter-color-negotiation-auto | 1 + .../fate/filter-color-negotiation-colorspace | 1 + .../filter-color-negotiation-colorspace12 | 1 + .../ref/fate/filter-color-negotiation-default | 1 + .../ref/fate/filter-color-negotiation-exclude | 1 + .../fate/filter-color-negotiation-iccdetect | 1 + .../ref/fate/filter-color-negotiation-zscale | 1 + 8 files changed, 28 insertions(+) create mode 100644 tests/ref/fate/filter-color-negotiation-auto create mode 100644 tests/ref/fate/filter-color-negotiation-colorspace create mode 100644 tests/ref/fate/filter-color-negotiation-colorspace12 create mode 100644 tests/ref/fate/filter-color-negotiation-default create mode 100644 tests/ref/fate/filter-color-negotiation-exclude create mode 100644 tests/ref/fate/filter-color-negotiation-iccdetect create mode 100644 tests/ref/fate/filter-color-negotiation-zscale diff --git a/tests/fate/filter-video.mak b/tests/fate/filter-video.mak index d9cfda8afee..2faf0b40b5f 100644 --- a/tests/fate/filter-video.mak +++ b/tests/fate/filter-video.mak @@ -642,6 +642,27 @@ FATE_FILTER-$(call ALLYES, SCALE_FILTER COLOR_FILTER LAVFI_INDEV WRAPPED_AVFRAME fate-filter-scale-zero-dim: CMD = ! run $(FFMPEG) -nostdin -hide_banner -f lavfi -i "color=c=red:s=3000x2:d=1" -vf "scale=iw/2:-2,scale=iw/3:-2,scale=iw/2:-2" -f null none fate-filter-scale-zero-dim: CMP = null +FATE_FILTER_FFPROBE-$(call ALLYES, FFPROBE LAVFI_INDEV WRAPPED_AVFRAME_DECODER TESTSRC2_FILTER SCALE_FILTER) += fate-filter-color-negotiation-default +fate-filter-color-negotiation-default: CMD = probe -print_format compact -show_entries frame=color_primaries,color_transfer -f lavfi "testsrc2=s=16x16:r=1:d=1,scale=32:32" + +FATE_FILTER_FFPROBE-$(call ALLYES, FFPROBE LAVFI_INDEV WRAPPED_AVFRAME_DECODER TESTSRC2_FILTER SETPARAMS_FILTER FORMAT_FILTER SCALE_FILTER) += fate-filter-color-negotiation-auto +fate-filter-color-negotiation-auto: CMD = probe -print_format compact -show_entries frame=color_primaries,color_transfer -f lavfi "testsrc2=s=16x16:r=1:d=1,setparams=colorspace=bt709:color_primaries=bt709:color_trc=bt709,format=color_primaries=bt2020:color_trcs=linear" + +FATE_FILTER_FFPROBE-$(call ALLYES, FFPROBE LAVFI_INDEV WRAPPED_AVFRAME_DECODER TESTSRC2_FILTER SETPARAMS_FILTER NOFORMAT_FILTER SCALE_FILTER) += fate-filter-color-negotiation-exclude +fate-filter-color-negotiation-exclude: CMD = probe -print_format compact -show_entries frame=color_primaries,color_transfer -f lavfi "testsrc2=s=16x16:r=1:d=1,setparams=colorspace=bt709:color_primaries=bt709:color_trc=bt709,noformat=color_primaries=bt709:color_trcs=bt709" + +FATE_FILTER_FFPROBE-$(call ALLYES, FFPROBE LAVFI_INDEV WRAPPED_AVFRAME_DECODER TESTSRC2_FILTER SETPARAMS_FILTER COLORSPACE_FILTER SCALE_FILTER) += fate-filter-color-negotiation-colorspace +fate-filter-color-negotiation-colorspace: CMD = probe -print_format compact -show_entries frame=color_primaries,color_transfer -f lavfi "testsrc2=s=16x16:r=1:d=1,setparams=colorspace=bt709:color_primaries=bt709:color_trc=bt709,colorspace=all=bt2020,scale=32:32" + +FATE_FILTER_FFPROBE-$(call ALLYES, FFPROBE LAVFI_INDEV WRAPPED_AVFRAME_DECODER TESTSRC2_FILTER SETPARAMS_FILTER COLORSPACE_FILTER SCALE_FILTER) += fate-filter-color-negotiation-colorspace12 +fate-filter-color-negotiation-colorspace12: CMD = probe -print_format compact -show_entries frame=color_primaries,color_transfer -f lavfi "testsrc2=s=16x16:r=1:d=1,setparams=colorspace=bt709:color_primaries=bt709:color_trc=bt709,colorspace=all=bt2020:format=yuv420p12,scale=32:32" + +FATE_FILTER_FFPROBE-$(call ALLYES, FFPROBE LAVFI_INDEV WRAPPED_AVFRAME_DECODER TESTSRC2_FILTER SETPARAMS_FILTER ZSCALE_FILTER SCALE_FILTER) += fate-filter-color-negotiation-zscale +fate-filter-color-negotiation-zscale: CMD = probe -print_format compact -show_entries frame=color_primaries,color_transfer -f lavfi "testsrc2=s=16x16:r=1:d=1,setparams=colorspace=bt709:color_primaries=bt709:color_trc=bt709,zscale=primaries=bt2020:transfer=linear,scale=32:32" + +FATE_FILTER_FFPROBE-$(call ALLYES, FFPROBE LAVFI_INDEV WRAPPED_AVFRAME_DECODER TESTSRC2_FILTER SETPARAMS_FILTER ICCDETECT_FILTER SCALE_FILTER) += fate-filter-color-negotiation-iccdetect +fate-filter-color-negotiation-iccdetect: CMD = probe -print_format compact -show_entries frame=color_primaries,color_transfer -f lavfi "testsrc2=s=16x16:r=1:d=1,setparams=colorspace=bt709:color_primaries=bt709:color_trc=bt709,iccdetect,scale=32:32" + FATE_FILTER_FFPROBE-$(call ALLYES, FFPROBE LAVFI_INDEV WRAPPED_AVFRAME_DECODER TESTSRC2_FILTER FORMAT_FILTER SETPARAMS_FILTER ZSCALE_FILTER) += fate-filter-zscale-passthrough fate-filter-zscale-passthrough: CMD = probe -print_format compact -show_entries frame=color_primaries,color_transfer -f lavfi "testsrc2=s=64x64:r=2:d=2,format=gbrp,setparams=color_primaries=bt709:color_trc=bt709,zscale=primariesin=bt470bg:primaries=bt470bg:transferin=smpte170m:transfer=smpte170m" diff --git a/tests/ref/fate/filter-color-negotiation-auto b/tests/ref/fate/filter-color-negotiation-auto new file mode 100644 index 00000000000..e0df711e73a --- /dev/null +++ b/tests/ref/fate/filter-color-negotiation-auto @@ -0,0 +1 @@ +frame|color_primaries=bt2020|color_transfer=linear diff --git a/tests/ref/fate/filter-color-negotiation-colorspace b/tests/ref/fate/filter-color-negotiation-colorspace new file mode 100644 index 00000000000..34bc8113650 --- /dev/null +++ b/tests/ref/fate/filter-color-negotiation-colorspace @@ -0,0 +1 @@ +frame|color_primaries=bt2020|color_transfer=bt2020-10 diff --git a/tests/ref/fate/filter-color-negotiation-colorspace12 b/tests/ref/fate/filter-color-negotiation-colorspace12 new file mode 100644 index 00000000000..0ca37cadb39 --- /dev/null +++ b/tests/ref/fate/filter-color-negotiation-colorspace12 @@ -0,0 +1 @@ +frame|color_primaries=bt2020|color_transfer=bt2020-12 diff --git a/tests/ref/fate/filter-color-negotiation-default b/tests/ref/fate/filter-color-negotiation-default new file mode 100644 index 00000000000..8a281e6ed67 --- /dev/null +++ b/tests/ref/fate/filter-color-negotiation-default @@ -0,0 +1 @@ +frame|color_primaries=unknown|color_transfer=unknown diff --git a/tests/ref/fate/filter-color-negotiation-exclude b/tests/ref/fate/filter-color-negotiation-exclude new file mode 100644 index 00000000000..a246a0b46b3 --- /dev/null +++ b/tests/ref/fate/filter-color-negotiation-exclude @@ -0,0 +1 @@ +frame|color_primaries=bt470m|color_transfer=bt470m diff --git a/tests/ref/fate/filter-color-negotiation-iccdetect b/tests/ref/fate/filter-color-negotiation-iccdetect new file mode 100644 index 00000000000..29f5e64ddde --- /dev/null +++ b/tests/ref/fate/filter-color-negotiation-iccdetect @@ -0,0 +1 @@ +frame|color_primaries=bt709|color_transfer=bt709 diff --git a/tests/ref/fate/filter-color-negotiation-zscale b/tests/ref/fate/filter-color-negotiation-zscale new file mode 100644 index 00000000000..e0df711e73a --- /dev/null +++ b/tests/ref/fate/filter-color-negotiation-zscale @@ -0,0 +1 @@ +frame|color_primaries=bt2020|color_transfer=linear -- 2.54.0 >From c37d807d6f0e56ba020ffc5295de8d3e4da7fb19 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 22 Oct 2026 11:35:31 +0000 Subject: [PATCH 26/27] doc/filters: document buffer and noformat colorimetry options Assisted-by: Fairy --- doc/filters.texi | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/doc/filters.texi b/doc/filters.texi index b00f9ab66b1..33a953c0cc2 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -19287,6 +19287,12 @@ It accepts the following parameters: A '|'-separated list of pixel format names, such as pix_fmts=yuv420p|monow|rgb24". +@item color_primaries +A '|'-separated list of color primaries to exclude. + +@item color_trcs +A '|'-separated list of color transfer characteristics to exclude. + @end table @subsection Examples @@ -30337,6 +30343,12 @@ A string representing the color space of the buffered video frames. It may be a number corresponding to a color space, or a color space name. +@item color_primaries +@item color_trc +The color primaries and transfer characteristics of the buffered frames, +specified by name or enum value. Both default to unspecified. Missing frame +tags are filled from the corresponding buffer source parameters. + @item range A string representing the color range of the buffered video frames. It may be a number corresponding to a color range, or a color range -- 2.54.0 >From 01758a20cb6f276d86b3752edb0b25307da09217 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 22 Oct 2026 11:41:45 +0000 Subject: [PATCH 27/27] tests/avfilter: verify color-dependent side data through negotiation Check both link and frame metadata, retaining it on passthrough and removing it when primaries or transfer characteristics change. Assisted-by: Fairy --- libavfilter/tests/color_negotiation.c | 16 ++++++++++++++++ tests/ref/fate/filter-color-negotiation | 20 ++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/libavfilter/tests/color_negotiation.c b/libavfilter/tests/color_negotiation.c index ee6c801c37b..05f8a626c16 100644 --- a/libavfilter/tests/color_negotiation.c +++ b/libavfilter/tests/color_negotiation.c @@ -23,6 +23,7 @@ #include "libavutil/adler32.h" #include "libavutil/mem.h" +#include "libavutil/mastering_display_metadata.h" #include "libavutil/opt.h" #include "libavutil/pixdesc.h" #include "libavfilter/avfilter.h" @@ -48,12 +49,22 @@ static int run_case(const char *name, int primaries, int trc, AVBufferSrcParameters *par = av_buffersrc_parameters_alloc(); AVFilterContext *src, *sink, *filter; AVFrame *in = av_frame_alloc(), *out = av_frame_alloc(); + AVMasteringDisplayMetadata *md; + int nb_side_data; unsigned input_sum; int ret = AVERROR(ENOMEM); printf("%s\n", name); if (!graph || !par || !in || !out) goto end; + md = av_mastering_display_metadata_create_side_data(in); + if (!md) + goto end; + md->has_luminance = 1; + md->min_luminance = (AVRational) { 0, 1 }; + md->max_luminance = (AVRational) { 100, 1 }; + par->side_data = in->side_data; + par->nb_side_data = in->nb_side_data; graph->nb_threads = 1; if (disable_auto) avfilter_graph_set_auto_convert(graph, AVFILTER_AUTO_CONVERT_NONE); @@ -95,6 +106,9 @@ static int run_case(const char *name, int primaries, int trc, av_color_primaries_name(av_buffersink_get_color_primaries(sink)), av_color_transfer_name(av_buffersink_get_color_trc(sink))); + av_buffersink_get_side_data(sink, &nb_side_data); + printf("link side data: %d\n", nb_side_data); + in->format = par->format; in->width = par->width; in->height = par->height; @@ -114,6 +128,8 @@ static int run_case(const char *name, int primaries, int trc, goto end; printf("frame: %s / %s\n", av_color_primaries_name(out->color_primaries), av_color_transfer_name(out->color_trc)); + printf("frame mastering metadata: %d\n", + !!av_frame_get_side_data(out, AV_FRAME_DATA_MASTERING_DISPLAY_METADATA)); *result = checksum(out); printf("pixels changed: %d\n", *result != input_sum); diff --git a/tests/ref/fate/filter-color-negotiation b/tests/ref/fate/filter-color-negotiation index 8d04e02100f..61d56b61b8b 100644 --- a/tests/ref/fate/filter-color-negotiation +++ b/tests/ref/fate/filter-color-negotiation @@ -1,52 +1,72 @@ unspecified configuration: ok link: unknown / unknown +link side data: 1 frame: unknown / unknown +frame mastering metadata: 1 pixels changed: 0 preserve configuration: ok link: bt709 / bt709 +link side data: 1 frame: bt709 / bt709 +frame mastering metadata: 1 pixels changed: 0 fill missing frame tags configuration: ok link: bt709 / bt709 +link side data: 1 frame: bt709 / bt709 +frame mastering metadata: 1 pixels changed: 0 preserve supported alternative configuration: ok link: bt709 / bt709 +link side data: 1 frame: bt709 / bt709 +frame mastering metadata: 1 pixels changed: 0 extended enums configuration: ok link: vgamut / vlog +link side data: 1 frame: vgamut / vlog +frame mastering metadata: 1 pixels changed: 0 unsupported transfer passthrough configuration: ok link: bt709 / log100 +link side data: 1 frame: bt709 / log100 +frame mastering metadata: 1 pixels changed: 0 automatic primaries configuration: ok link: bt2020 / bt709 +link side data: 0 frame: bt2020 / bt709 +frame mastering metadata: 0 pixels changed: 1 automatic transfer configuration: ok link: bt709 / linear +link side data: 0 frame: bt709 / linear +frame mastering metadata: 0 pixels changed: 1 automatic conversion configuration: ok link: bt2020 / linear +link side data: 0 frame: bt2020 / linear +frame mastering metadata: 0 pixels changed: 1 explicit conversion configuration: ok link: bt2020 / linear +link side data: 0 frame: bt2020 / linear +frame mastering metadata: 0 pixels changed: 1 implicit matches explicit: 1 conversion disabled -- 2.54.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
