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;
}