Commit 08a654a564 for ffmpeg

commit 08a654a5648b4b3332f608a9da94e6a019ab2cdb
Author: Kacper Michajłow <kasper93@gmail.com>
Date:   Wed Sep 9 07:12:40 2026 +0200

    avfilter: add link negotiation for AVChromaLocation

    Chroma sample location is the last of the video colorimetry properties
    without a negotiated link value, so nothing downstream can ask a filter for
    a particular siting and no encoder can constrain the graph. Add it alongside
    the existing color space, color range and alpha mode negotiation.

    Merge conflicts are resolved by inserting a scale filter, the same way as
    for color space and color range.

    Signed-off-by: Kacper Michajłow <kasper93@gmail.com>

diff --git a/doc/APIchanges b/doc/APIchanges
index a4b11e2efc..eb2c0c458b 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -2,6 +2,9 @@ The last version increases of all libraries were on 2026-06-23.

 API changes, most recent first:

+2026-09-xx - xxxxxxxxxx - lavfi 12.4.101 - avfilter.h
+  Add AVFilterLink.chroma_location.
+
 2026-08-xx - xxxxxxxxxx - lavf 63.7.100 - avformat.h
   Add AV_STREAM_GROUP_PARAMS_GAIN_MAP.

diff --git a/libavfilter/avfilter.c b/libavfilter/avfilter.c
index d10836df3b..46068719b1 100644
--- a/libavfilter/avfilter.c
+++ b/libavfilter/avfilter.c
@@ -315,6 +315,9 @@ int avfilter_insert_filter(AVFilterLink *link, AVFilterContext *filt,
     if (link->outcfg.alpha_modes)
         ff_formats_changeref(&link->outcfg.alpha_modes,
                              &filt->outputs[filt_dstpad_idx]->outcfg.alpha_modes);
+    if (link->outcfg.chroma_locations)
+        ff_formats_changeref(&link->outcfg.chroma_locations,
+                             &filt->outputs[filt_dstpad_idx]->outcfg.chroma_locations);
     if (link->outcfg.samplerates)
         ff_formats_changeref(&link->outcfg.samplerates,
                              &filt->outputs[filt_dstpad_idx]->outcfg.samplerates);
@@ -790,6 +793,8 @@ static void free_link(AVFilterLink *link)
     ff_formats_unref(&link->outcfg.color_ranges);
     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->outcfg.chroma_locations);
     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 1363a861a1..a6b875c37a 100644
--- a/libavfilter/avfilter.h
+++ b/libavfilter/avfilter.h
@@ -145,6 +145,12 @@ typedef struct AVFilterFormatsConfig {
      */
     AVFilterFormats *alpha_modes;  ///< AVAlphaMode

+    /**
+     * List of supported chroma sample locations, only for video with
+     * subsampled chroma.
+     */
+    AVFilterFormats *chroma_locations; ///< AVChromaLocation
+
 } AVFilterFormatsConfig;

 /**
@@ -407,6 +413,11 @@ struct AVFilterLink {

     enum AVAlphaMode alpha_mode; ///< alpha mode (for videos with an alpha channel)

+    /**
+     * Agreed upon chroma sample location, for video with subsampled chroma.
+     */
+    enum AVChromaLocation chroma_location;
+
     /*****************************************************************
      * 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/avfiltergraph.c b/libavfilter/avfiltergraph.c
index ad81d91bbc..7b58883c65 100644
--- a/libavfilter/avfiltergraph.c
+++ b/libavfilter/avfiltergraph.c
@@ -304,7 +304,8 @@ static int filter_link_check_formats(void *log, AVFilterLink *link, AVFilterForm
         if ((ret = ff_formats_check_pixel_formats(log, cfg->formats)) < 0 ||
             (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_alpha_modes(log, cfg->alpha_modes)) < 0 ||
+            (ret = ff_formats_check_chroma_locations(log, cfg->chroma_locations)) < 0)
             return ret;
         break;

@@ -420,7 +421,8 @@ static int formats_declared(AVFilterContext *f)
         if (f->inputs[i]->type == AVMEDIA_TYPE_VIDEO &&
             !(f->inputs[i]->outcfg.color_ranges &&
               f->inputs[i]->outcfg.color_spaces &&
-              f->inputs[i]->outcfg.alpha_modes))
+              f->inputs[i]->outcfg.alpha_modes &&
+              f->inputs[i]->outcfg.chroma_locations))
             return 0;
         if (f->inputs[i]->type == AVMEDIA_TYPE_AUDIO &&
             !(f->inputs[i]->outcfg.samplerates &&
@@ -433,7 +435,8 @@ static int formats_declared(AVFilterContext *f)
         if (f->outputs[i]->type == AVMEDIA_TYPE_VIDEO &&
             !(f->outputs[i]->incfg.color_ranges &&
               f->outputs[i]->incfg.color_spaces &&
-              f->outputs[i]->incfg.alpha_modes))
+              f->outputs[i]->incfg.alpha_modes &&
+              f->outputs[i]->incfg.chroma_locations))
             return 0;
         if (f->outputs[i]->type == AVMEDIA_TYPE_AUDIO &&
             !(f->outputs[i]->incfg.samplerates &&
@@ -666,6 +669,10 @@ retry:
                     av_assert0( inlink->outcfg.alpha_modes->refcount > 0);
                     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->outcfg.chroma_locations->refcount > 0);
+                    av_assert0(outlink-> incfg.chroma_locations->refcount > 0);
+                    av_assert0(outlink->outcfg.chroma_locations->refcount > 0);
                 } else if (outlink->type == AVMEDIA_TYPE_AUDIO) {
                     av_assert0( inlink-> incfg.samplerates->refcount > 0);
                     av_assert0( inlink->outcfg.samplerates->refcount > 0);
@@ -896,6 +903,20 @@ static int pick_format(AVFilterLink *link, AVFilterLink *ref)
         } else {
             link->alpha_mode = AVALPHA_MODE_UNSPECIFIED;
         }
+
+        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"
+                       " for the link between filters %s and %s.\n", link->src->name,
+                       link->dst->name);
+                return AVERROR(EINVAL);
+            }
+            link->incfg.chroma_locations->nb_formats = 1;
+            link->chroma_location = link->incfg.chroma_locations->formats[0];
+        } else {
+            /* Chroma location is meaningless without subsampled chroma. */
+            link->chroma_location = AVCHROMA_LOC_UNSPECIFIED;
+        }
     } else if (link->type == AVMEDIA_TYPE_AUDIO) {
         int ret;

@@ -936,6 +957,8 @@ static int pick_format(AVFilterLink *link, AVFilterLink *ref)
     ff_formats_unref(&link->outcfg.color_ranges);
     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->outcfg.chroma_locations);

     return 0;
 }
@@ -991,6 +1014,8 @@ static int reduce_formats_on_filter(AVFilterContext *filter)
                    nb_formats, ff_add_format);
     REDUCE_FORMATS(int,      AVFilterFormats,        alpha_modes,     formats,
                    nb_formats, ff_add_format);
+    REDUCE_FORMATS(int,      AVFilterFormats,        chroma_locations, 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 7644fc4c75..2994cdaa89 100644
--- a/libavfilter/formats.c
+++ b/libavfilter/formats.c
@@ -363,6 +363,7 @@ PRINT_NAME(enum AVPixelFormat,  get_pix_fmt_name)
 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)

 static void print_channel_layout_desc(AVBPrint *bp, const void *layoutsp)
 {
@@ -421,6 +422,14 @@ static const AVFilterFormatsMerger mergers_video[] = {
         .print_list = print_alpha_mode_name,
         .conversion_filter = "premultiply_dynamic",
     },
+    {
+        .name       = "Chroma locations",
+        .offset     = offsetof(AVFilterFormatsConfig, chroma_locations),
+        .merge      = merge_generic,
+        .can_merge  = can_merge_generic,
+        .print_list = print_chroma_location_name,
+        CONVERSION_FILTER_SWSCALE
+    },
 };

 static const AVFilterFormatsMerger mergers_audio[] = {
@@ -733,6 +742,17 @@ AVFilterFormats *ff_all_alpha_modes(void)
     return ret;
 }

+AVFilterFormats *ff_all_chroma_locations(void)
+{
+    AVFilterFormats *ret = NULL;
+    for (int loc = 0; loc < AVCHROMA_LOC_NB; loc++) {
+        if (ff_add_format(&ret, loc) < 0)
+            return NULL;
+    }
+
+    return ret;
+}
+
 #define FORMATS_REF(f, ref, unref_fn)                                           \
     void *tmp;                                                                  \
                                                                                 \
@@ -956,6 +976,24 @@ int ff_set_common_all_alpha_modes(AVFilterContext *ctx)
     return ff_set_common_alpha_modes(ctx, ff_all_alpha_modes());
 }

+int ff_set_common_chroma_locations(AVFilterContext *ctx,
+                                   AVFilterFormats *chroma_locations)
+{
+    SET_COMMON_FORMATS(ctx, chroma_locations, AVMEDIA_TYPE_VIDEO,
+                       ff_formats_ref, ff_formats_unref);
+}
+
+int ff_set_common_chroma_locations_from_list(AVFilterContext *ctx,
+                                             const int *chroma_locations)
+{
+    return ff_set_common_chroma_locations(ctx, ff_make_format_list(chroma_locations));
+}
+
+int ff_set_common_all_chroma_locations(AVFilterContext *ctx)
+{
+    return ff_set_common_chroma_locations(ctx, ff_all_chroma_locations());
+}
+
 /**
  * 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
@@ -1135,6 +1173,31 @@ int ff_set_common_all_alpha_modes2(const AVFilterContext *ctx,
     return ff_set_common_alpha_modes2(ctx, cfg_in, cfg_out, ff_all_alpha_modes());
 }

+int ff_set_common_chroma_locations2(const AVFilterContext *ctx,
+                                    AVFilterFormatsConfig **cfg_in,
+                                    AVFilterFormatsConfig **cfg_out,
+                                    AVFilterFormats *chroma_locations)
+{
+    SET_COMMON_FORMATS2(ctx, cfg_in, cfg_out, chroma_locations, AVMEDIA_TYPE_VIDEO,
+                        ff_formats_ref, ff_formats_unref);
+}
+
+int ff_set_common_chroma_locations_from_list2(const AVFilterContext *ctx,
+                                              AVFilterFormatsConfig **cfg_in,
+                                              AVFilterFormatsConfig **cfg_out,
+                                              const int *chroma_locations)
+{
+    return ff_set_common_chroma_locations2(ctx, cfg_in, cfg_out,
+                                           ff_make_format_list(chroma_locations));
+}
+
+int ff_set_common_all_chroma_locations2(const AVFilterContext *ctx,
+                                        AVFilterFormatsConfig **cfg_in,
+                                        AVFilterFormatsConfig **cfg_out)
+{
+    return ff_set_common_chroma_locations2(ctx, cfg_in, cfg_out, ff_all_chroma_locations());
+}
+
 int ff_set_common_formats2(const AVFilterContext *ctx,
                            AVFilterFormatsConfig **cfg_in,
                            AVFilterFormatsConfig **cfg_out,
@@ -1218,6 +1281,9 @@ int ff_default_query_formats(AVFilterContext *ctx)
         ret = ff_set_common_all_alpha_modes(ctx);
         if (ret < 0)
             return ret;
+        ret = ff_set_common_all_chroma_locations(ctx);
+        if (ret < 0)
+            return ret;
     }
     if (type != AVMEDIA_TYPE_VIDEO) {
         ret = ff_set_common_all_channel_counts(ctx);
@@ -1290,6 +1356,11 @@ int ff_formats_check_alpha_modes(void *log, const AVFilterFormats *fmts)
     return check_list(log, "alpha mode", fmts);
 }

+int ff_formats_check_chroma_locations(void *log, const AVFilterFormats *fmts)
+{
+    return check_list(log, "chroma location", 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 45864a32db..488a09b50b 100644
--- a/libavfilter/formats.h
+++ b/libavfilter/formats.h
@@ -150,6 +150,12 @@ AVFilterFormats *ff_all_color_ranges(void);
 av_warn_unused_result
 AVFilterFormats *ff_all_alpha_modes(void);

+/**
+ * Construct an AVFilterFormats representing all possible chroma locations.
+ */
+av_warn_unused_result
+AVFilterFormats *ff_all_chroma_locations(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,
@@ -233,6 +239,23 @@ int ff_set_common_alpha_modes_from_list(AVFilterContext *ctx,
 av_warn_unused_result
 int ff_set_common_all_alpha_modes(AVFilterContext *ctx);

+av_warn_unused_result
+int ff_set_common_chroma_locations(AVFilterContext *ctx,
+                                   AVFilterFormats *chroma_locations);
+
+/**
+ * Equivalent to ff_set_common_chroma_locations(ctx, ff_make_format_list(chroma_locations))
+ */
+av_warn_unused_result
+int ff_set_common_chroma_locations_from_list(AVFilterContext *ctx,
+                                             const int *chroma_locations);
+
+/**
+ * Equivalent to ff_set_common_chroma_locations(ctx, ff_all_chroma_locations())
+ */
+av_warn_unused_result
+int ff_set_common_all_chroma_locations(AVFilterContext *ctx);
+

 /**
  * A helper for query_formats() which sets all links to the same list of
@@ -349,6 +372,23 @@ int ff_set_common_all_alpha_modes2(const AVFilterContext *ctx,
                                    AVFilterFormatsConfig **cfg_in,
                                    AVFilterFormatsConfig **cfg_out);

+av_warn_unused_result
+int ff_set_common_chroma_locations2(const AVFilterContext *ctx,
+                                    AVFilterFormatsConfig **cfg_in,
+                                    AVFilterFormatsConfig **cfg_out,
+                                    AVFilterFormats *chroma_locations);
+
+av_warn_unused_result
+int ff_set_common_chroma_locations_from_list2(const AVFilterContext *ctx,
+                                              AVFilterFormatsConfig **cfg_in,
+                                              AVFilterFormatsConfig **cfg_out,
+                                              const int *chroma_locations);
+
+av_warn_unused_result
+int ff_set_common_all_chroma_locations2(const AVFilterContext *ctx,
+                                        AVFilterFormatsConfig **cfg_in,
+                                        AVFilterFormatsConfig **cfg_out);
+
 av_warn_unused_result
 int ff_set_common_formats2(const AVFilterContext *ctx,
                            AVFilterFormatsConfig **cfg_in,
@@ -557,6 +597,13 @@ int ff_formats_check_color_ranges(void *log, const AVFilterFormats *fmts);
  */
 int ff_formats_check_alpha_modes(void *log, const AVFilterFormats *fmts);

+/**
+ * Check that fmts is a valid formats list for chroma locations.
+ *
+ * In particular, check for duplicates.
+ */
+int ff_formats_check_chroma_locations(void *log, const AVFilterFormats *fmts);
+
 struct AVBPrint;

 typedef struct AVFilterFormatMerger {
diff --git a/libavfilter/version.h b/libavfilter/version.h
index 4d8f28e512..cd37ff7921 100644
--- a/libavfilter/version.h
+++ b/libavfilter/version.h
@@ -32,7 +32,7 @@
 #include "version_major.h"

 #define LIBAVFILTER_VERSION_MINOR   4
-#define LIBAVFILTER_VERSION_MICRO 100
+#define LIBAVFILTER_VERSION_MICRO 101


 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
diff --git a/libavfilter/video.c b/libavfilter/video.c
index 9be2f65448..0f4539869e 100644
--- a/libavfilter/video.c
+++ b/libavfilter/video.c
@@ -77,6 +77,7 @@ AVFrame *ff_default_get_video_buffer2(AVFilterLink *link, int w, int h, int alig
     frame->colorspace  = link->colorspace;
     frame->color_range = link->color_range;
     frame->alpha_mode  = link->alpha_mode;
+    frame->chroma_location = link->chroma_location;

     return frame;
 }