Commit f3d2f99263 for ffmpeg

commit f3d2f99263e1f19b497f5d3b6280d3588c704495
Author: Kacper Michajłow <kasper93@gmail.com>
Date:   Wed Sep 9 07:13:03 2026 +0200

    avfilter/buffersrc: add chroma_location parameter

    Lets the caller state the chroma siting of the frames it feeds in, which is
    what the rest of the graph negotiates against. A tagged source constrains
    the link to its own value, an untagged one places no constraint at all,
    there being nothing to convert from.

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

diff --git a/doc/APIchanges b/doc/APIchanges
index 103817062c..80683f77e2 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -2,8 +2,8 @@ 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-09-xx - xxxxxxxxxx - lavfi 12.4.102 - avfilter.h buffersrc.h
+  Add AVFilterLink.chroma_location and AVBufferSrcParameters.chroma_location.

 2026-09-xx - xxxxxxxxxx - lavc 63.16.100 - avcodec.h
   Add AV_CODEC_CONFIG_CHROMA_LOCATION.
diff --git a/doc/filters.texi b/doc/filters.texi
index aa3b37490b..7de2855c78 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -30110,6 +30110,13 @@ A string representing the alpha mode of the buffered video frames.
 It may be a number corresponding to an alpha mode, or an alpha mode
 name.

+@item chroma_location
+A string representing the chroma sample location of the buffered video
+frames. It may be a number corresponding to a chroma sample location, or a
+chroma sample location name. When it is not set, the filtergraph chooses the
+chroma sample location of the output. Frames without a chroma sample location
+are tagged with the one negotiated for the output.
+
 @item pixel_aspect, sar
 The sample (pixel) aspect ratio of the input video.

diff --git a/libavfilter/buffersrc.c b/libavfilter/buffersrc.c
index 16efa6e6c8..8e6097df08 100644
--- a/libavfilter/buffersrc.c
+++ b/libavfilter/buffersrc.c
@@ -54,6 +54,7 @@ typedef struct BufferSourceContext {
     enum AVColorSpace color_space, prev_color_space;
     enum AVColorRange color_range, prev_color_range;
     enum AVAlphaMode  alpha_mode, prev_alpha_mode;
+    enum AVChromaLocation chroma_location, prev_chroma_location;
     AVRational        pixel_aspect;

     AVBufferRef *hw_frames_ctx;
@@ -69,19 +70,25 @@ typedef struct BufferSourceContext {
     int eof;
     int64_t last_pts;
     int link_delta, prev_delta;
+
+    int chroma_loc_warned;
 } BufferSourceContext;

-#define CHECK_VIDEO_PARAM_CHANGE(s, c, width, height, format, csp, range, alpha, pts)\
+#define CHECK_VIDEO_PARAM_CHANGE(s, c, width, height, format, csp, range, alpha, chromaloc, 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->color_space != csp || c->color_range != range || c->alpha_mode != alpha ||\
+                    c->chroma_location != chromaloc;\
     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_color_space != csp || c->prev_color_range != range || c->prev_alpha_mode != alpha ||\
+                    c->prev_chroma_location != chromaloc;\
     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, incoming frame - w: %d h: %d fmt: %d csp: %s range: %s alpha: %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, incoming frame - w: %d h: %d fmt: %d csp: %s range: %s alpha: %s chroma_loc: %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),\
                width, height, format, av_color_space_name(csp), av_color_range_name(range), av_alpha_mode_name(alpha),\
+               av_chroma_location_name(chromaloc),\
                av_ts2timestr(pts, &s->outputs[0]->time_base));\
     }\
     if (c->prev_delta) {\
@@ -93,6 +100,7 @@ typedef struct BufferSourceContext {
         c->prev_color_space = csp;\
         c->prev_color_range = range;\
         c->prev_alpha_mode = alpha;\
+        c->prev_chroma_location = chromaloc;\
     }

 #define CHECK_AUDIO_PARAM_CHANGE(s, c, srate, layout, format, pts)\
@@ -115,6 +123,7 @@ AVBufferSrcParameters *av_buffersrc_parameters_alloc(void)
     par->color_range = AVCOL_RANGE_UNSPECIFIED;
     par->color_space = AVCOL_SPC_UNSPECIFIED;
     par->alpha_mode  = AVALPHA_MODE_UNSPECIFIED;
+    par->chroma_location = AVCHROMA_LOC_UNSPECIFIED;

     return par;
 }
@@ -151,6 +160,8 @@ 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->chroma_location != AVCHROMA_LOC_UNSPECIFIED)
+            s->chroma_location = s->prev_chroma_location = param->chroma_location;
         break;
     case AVMEDIA_TYPE_AUDIO:
         if (param->format != AV_SAMPLE_FMT_NONE) {
@@ -230,7 +241,8 @@ int attribute_align_arg av_buffersrc_add_frame_flags(AVFilterContext *ctx, AVFra
         case AVMEDIA_TYPE_VIDEO:
             CHECK_VIDEO_PARAM_CHANGE(ctx, s, frame->width, frame->height,
                                      frame->format, frame->colorspace,
-                                     frame->color_range, frame->alpha_mode, frame->pts);
+                                     frame->color_range, frame->alpha_mode,
+                                     frame->chroma_location, frame->pts);
             break;
         case AVMEDIA_TYPE_AUDIO:
             /* For layouts unknown on input but known on link after negotiation. */
@@ -264,6 +276,15 @@ int attribute_align_arg av_buffersrc_add_frame_flags(AVFilterContext *ctx, AVFra
         copy->color_range = ctx->outputs[0]->color_range;
     if (copy->alpha_mode == AVALPHA_MODE_UNSPECIFIED)
         copy->alpha_mode = ctx->outputs[0]->alpha_mode;
+    if (copy->chroma_location == AVCHROMA_LOC_UNSPECIFIED) {
+        copy->chroma_location = ctx->outputs[0]->chroma_location;
+        /* The input never said where its chroma sits, so it is assumed to be
+         * compatible with whatever the rest of the graph settled on. */
+        if (copy->chroma_location != AVCHROMA_LOC_UNSPECIFIED)
+            av_log_once(ctx, AV_LOG_WARNING, AV_LOG_VERBOSE, &s->chroma_loc_warned,
+                        "Input has no chroma sample location, assuming %s\n",
+                        av_chroma_location_name(copy->chroma_location));
+    }

     ret = ff_filter_frame(ctx->outputs[0], copy);
     if (ret < 0)
@@ -339,12 +360,12 @@ static av_cold int init_video(AVFilterContext *ctx)
         return AVERROR(EINVAL);
     }

-    av_log(ctx, AV_LOG_VERBOSE, "w:%d h:%d pixfmt:%s tb:%d/%d fr:%d/%d sar:%d/%d csp:%s range:%s alpha:%s\n",
+    av_log(ctx, AV_LOG_VERBOSE, "w:%d h:%d pixfmt:%s tb:%d/%d fr:%d/%d sar:%d/%d csp:%s range:%s alpha:%s chroma_loc:%s\n",
            c->w, c->h, av_get_pix_fmt_name(c->pix_fmt),
            c->time_base.num, c->time_base.den, c->frame_rate.num, c->frame_rate.den,
            c->pixel_aspect.num, c->pixel_aspect.den,
            av_color_space_name(c->color_space), av_color_range_name(c->color_range),
-           av_alpha_mode_name(c->alpha_mode));
+           av_alpha_mode_name(c->alpha_mode), av_chroma_location_name(c->chroma_location));

     return common_init(ctx);
 }
@@ -399,6 +420,15 @@ static const AVOption buffer_options[] = {
     {   "unknown",     NULL,   0, AV_OPT_TYPE_CONST, {.i64=AVALPHA_MODE_UNSPECIFIED},   0, 0, V, .unit = "alpha"},
     {   "straight",    NULL,   0, AV_OPT_TYPE_CONST, {.i64=AVALPHA_MODE_STRAIGHT},      0, 0, V, .unit = "alpha"},
     {   "premultiplied", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVALPHA_MODE_PREMULTIPLIED}, 0, 0, V, .unit = "alpha"},
+    { "chroma_location", "select chroma sample location", OFFSET(chroma_location), AV_OPT_TYPE_INT, {.i64=AVCHROMA_LOC_UNSPECIFIED}, 0, AVCHROMA_LOC_NB-1, V, .unit = "chroma_loc"},
+    {   "unspecified", NULL,   0, AV_OPT_TYPE_CONST, {.i64=AVCHROMA_LOC_UNSPECIFIED}, 0, 0, V, .unit = "chroma_loc"},
+    {   "unknown",     NULL,   0, AV_OPT_TYPE_CONST, {.i64=AVCHROMA_LOC_UNSPECIFIED}, 0, 0, V, .unit = "chroma_loc"},
+    {   "left",        NULL,   0, AV_OPT_TYPE_CONST, {.i64=AVCHROMA_LOC_LEFT},       0, 0, V, .unit = "chroma_loc"},
+    {   "center",      NULL,   0, AV_OPT_TYPE_CONST, {.i64=AVCHROMA_LOC_CENTER},     0, 0, V, .unit = "chroma_loc"},
+    {   "topleft",     NULL,   0, AV_OPT_TYPE_CONST, {.i64=AVCHROMA_LOC_TOPLEFT},    0, 0, V, .unit = "chroma_loc"},
+    {   "top",         NULL,   0, AV_OPT_TYPE_CONST, {.i64=AVCHROMA_LOC_TOP},        0, 0, V, .unit = "chroma_loc"},
+    {   "bottomleft",  NULL,   0, AV_OPT_TYPE_CONST, {.i64=AVCHROMA_LOC_BOTTOMLEFT}, 0, 0, V, .unit = "chroma_loc"},
+    {   "bottom",      NULL,   0, AV_OPT_TYPE_CONST, {.i64=AVCHROMA_LOC_BOTTOM},     0, 0, V, .unit = "chroma_loc"},
     { NULL },
 };

@@ -484,6 +514,7 @@ static int query_formats(const AVFilterContext *ctx,
     AVFilterFormats *color_spaces = NULL;
     AVFilterFormats *color_ranges = NULL;
     AVFilterFormats *alpha_modes = NULL;
+    AVFilterFormats *chroma_locations = NULL;
     int ret;

     switch (ctx->outputs[0]->type) {
@@ -525,6 +556,18 @@ static int query_formats(const AVFilterContext *ctx,
             if ((ret = ff_set_common_alpha_modes2(ctx, cfg_in, cfg_out, alpha_modes)) < 0)
                 return ret;
         }
+        if (av_pix_fmt_desc_get(swfmt)->log2_chroma_w ||
+            av_pix_fmt_desc_get(swfmt)->log2_chroma_h) {
+            if (c->chroma_location != AVCHROMA_LOC_UNSPECIFIED) {
+                if ((ret = ff_add_format(&chroma_locations, c->chroma_location)) < 0)
+                    return ret;
+                if ((ret = ff_set_common_chroma_locations2(ctx, cfg_in, cfg_out,
+                                                           chroma_locations)) < 0)
+                    return ret;
+            }
+            /* Untagged input places no constraint, there is nothing to convert
+             * from, so whatever the rest of the graph wants is used as is. */
+        }
         break;
     }
     case AVMEDIA_TYPE_AUDIO:
diff --git a/libavfilter/buffersrc.h b/libavfilter/buffersrc.h
index 75824d964d..fb6dfa3487 100644
--- a/libavfilter/buffersrc.h
+++ b/libavfilter/buffersrc.h
@@ -128,6 +128,11 @@ typedef struct AVBufferSrcParameters {
      * Video only, the alpha mode.
      */
     enum AVAlphaMode alpha_mode;
+
+    /**
+     * Video only, the chroma sample location.
+     */
+    enum AVChromaLocation chroma_location;
 } AVBufferSrcParameters;

 /**
diff --git a/libavfilter/version.h b/libavfilter/version.h
index cd37ff7921..19a7c929cc 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 101
+#define LIBAVFILTER_VERSION_MICRO 102


 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \