diff mbox series

[FFmpeg-devel,03/25] avfilter: add negotiation API for color space/range

Message ID 20231109122534.124157-4-ffmpeg@haasn.xyz
State New
Headers show
Series YUVJ removal + filter negotiation | expand

Checks

Context Check Description
yinshiyou/make_loongarch64 success Make finished
yinshiyou/make_fate_loongarch64 success Make fate finished
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished

Commit Message

Niklas Haas Nov. 9, 2023, 12:19 p.m. UTC
From: Niklas Haas <git@haasn.dev>

Motivated by YUVJ removal. This change will allow full negotiation
between color ranges and matrices as needed. By default, all ranges and
matrices are marked as supported.

Because grayscale formats are currently handled very inconsistently (and
in particular, assumed as forced full-range by swscale), we exclude them
from negotiation altogether for the time being, to get this API merged.

After filter negotiation is available, we can relax the
grayscale-is-forced-jpeg restriction again, when it will be more
feasible to do so without breaking a million test cases.

Note that this commit updates one FATE test as a consequence of the
sanity fallback for non-YUV formats. In particular, the test case now
writes rgb24(pc, gbr/unspecified/unspecified) to the matroska file,
instead of rgb24(unspecified/unspecified/unspecified) as before.
---
 doc/APIchanges              |   8 ++
 libavfilter/avfilter.c      |  19 ++++-
 libavfilter/avfilter.h      |  28 ++++++
 libavfilter/avfiltergraph.c | 166 +++++++++++++++++++++++++++++++++++-
 libavfilter/formats.c       | 122 +++++++++++++++++++++++++-
 libavfilter/formats.h       |  54 ++++++++++++
 libavfilter/internal.h      |   6 ++
 libavfilter/vaapi_vpp.c     |   4 +
 libavfilter/version.h       |   2 +-
 libavfilter/video.c         |   2 +
 tests/ref/fate/shortest-sub |   4 +-
 11 files changed, 405 insertions(+), 10 deletions(-)

Comments

Anton Khirnov Dec. 6, 2023, 3:03 p.m. UTC | #1
Quoting Niklas Haas (2023-11-09 13:19:35)
> diff --git a/doc/APIchanges b/doc/APIchanges
> index 12383a28d3..ce3f90a674 100644
> --- a/doc/APIchanges
> +++ b/doc/APIchanges
> @@ -2,6 +2,14 @@ The last version increases of all libraries were on 2023-02-09
>  
>  API changes, most recent first:
>  
> +2023-11-xx - xxxxxxxxxx - lavf 58.14.100 - avfilter.h formats.h
> +  Add AVFilterFormatsConfig.color_spaces, AVFilterFormatsConfig.color_ranges,
> +  AVFilterLink.colorspace, AVFilterLink.color_range, ff_all_color_spaces,
> +  ff_all_color_ranges, ff_set_common_color_spaces, ff_set_common_color_ranges,
> +  ff_set_common_color_spaces_from_list, ff_set_common_color_ranges_from_list,
> +  ff_set_common_all_color_spaces, ff_set_common_all_color_ranges,
> +  ff_formats_check_color_spaces, ff_formats_check_color_ranges.

ff* are private and so shouldn't be mentioned in APIchanges

AVFilterFormatsConfig lives in a public header, but seems not to be
usable by API users.

> +
>  2023-11-08 - xxxxxxxxxx - lavu 58.32.100 - channel_layout.h
>    Add AV_CH_LAYOUT_7POINT2POINT3 and AV_CHANNEL_LAYOUT_7POINT2POINT3.
>    Add AV_CH_LAYOUT_9POINT1POINT4_BACK and AV_CHANNEL_LAYOUT_9POINT1POINT4_BACK.
> diff --git a/libavfilter/avfilter.c b/libavfilter/avfilter.c
> index ab7782862a..77bfec00c5 100644
> --- a/libavfilter/avfilter.c
> +++ b/libavfilter/avfilter.c
> @@ -185,6 +185,7 @@ int avfilter_link(AVFilterContext *src, unsigned srcpad,
>      link->type    = src->output_pads[srcpad].type;
>      av_assert0(AV_PIX_FMT_NONE == -1 && AV_SAMPLE_FMT_NONE == -1);
>      link->format  = -1;
> +    link->colorspace = AVCOL_SPC_UNSPECIFIED;
>      ff_framequeue_init(&link->fifo, &src->graph->internal->frame_queues);
>  
>      return 0;
> @@ -286,6 +287,12 @@ int avfilter_insert_filter(AVFilterLink *link, AVFilterContext *filt,
>      if (link->outcfg.formats)
>          ff_formats_changeref(&link->outcfg.formats,
>                               &filt->outputs[filt_dstpad_idx]->outcfg.formats);
> +    if (link->outcfg.color_spaces)
> +        ff_formats_changeref(&link->outcfg.color_spaces,
> +                             &filt->outputs[filt_dstpad_idx]->outcfg.color_spaces);
> +    if (link->outcfg.color_ranges)
> +        ff_formats_changeref(&link->outcfg.color_ranges,
> +                             &filt->outputs[filt_dstpad_idx]->outcfg.color_ranges);
>      if (link->outcfg.samplerates)
>          ff_formats_changeref(&link->outcfg.samplerates,
>                               &filt->outputs[filt_dstpad_idx]->outcfg.samplerates);
> @@ -730,6 +737,10 @@ static void free_link(AVFilterLink *link)
>  
>      ff_formats_unref(&link->incfg.formats);
>      ff_formats_unref(&link->outcfg.formats);
> +    ff_formats_unref(&link->incfg.color_spaces);
> +    ff_formats_unref(&link->outcfg.color_spaces);
> +    ff_formats_unref(&link->incfg.color_ranges);
> +    ff_formats_unref(&link->outcfg.color_ranges);
>      ff_formats_unref(&link->incfg.samplerates);
>      ff_formats_unref(&link->outcfg.samplerates);
>      ff_channel_layouts_unref(&link->incfg.channel_layouts);
> @@ -987,9 +998,11 @@ int ff_filter_frame(AVFilterLink *link, AVFrame *frame)
>              strcmp(link->dst->filter->name, "idet") &&
>              strcmp(link->dst->filter->name, "null") &&
>              strcmp(link->dst->filter->name, "scale")) {
> -            av_assert1(frame->format                 == link->format);
> -            av_assert1(frame->width               == link->w);
> -            av_assert1(frame->height               == link->h);
> +            av_assert1(frame->format        == link->format);
> +            av_assert1(frame->width         == link->w);
> +            av_assert1(frame->height        == link->h);
> +            av_assert1(frame->colorspace    == link->color_space);
                                                             ^
Should not be there.

Also, these fail a LOT with this patch. Most of them seem fixed by later
patches, so it's probably better to move them to the end of the set?

> @@ -583,6 +602,29 @@ static enum AVSampleFormat find_best_sample_fmt_of_2(enum AVSampleFormat dst_fmt
>      return score1 < score2 ? dst_fmt1 : dst_fmt2;
>  }
>  
> +int ff_fmt_is_regular_yuv(enum AVPixelFormat fmt)
> +{
> +    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt);
> +    if (!desc)
> +        return 0;
> +    if (desc->nb_components < 3)
> +        return 0; /* Grayscale is explicitly full-range in swscale */
> +    if (desc->flags & (AV_PIX_FMT_FLAG_RGB | AV_PIX_FMT_FLAG_PAL |
> +                       AV_PIX_FMT_FLAG_XYZ | AV_PIX_FMT_FLAG_FLOAT))

Should this include AV_PIX_FMT_FLAG_HWACCEL too?

> +        return 0;
> +
> +    switch (fmt) {
> +    case AV_PIX_FMT_YUVJ420P:
> +    case AV_PIX_FMT_YUVJ422P:
> +    case AV_PIX_FMT_YUVJ444P:
> +    case AV_PIX_FMT_YUVJ440P:
> +    case AV_PIX_FMT_YUVJ411P:
> +        return 0;
> +    default:
> +        return 1;
> +    }
> +}
> +
>  static int pick_format(AVFilterLink *link, AVFilterLink *ref)
>  {
>      if (!link || !link->incfg.formats)
> @@ -603,6 +645,7 @@ static int pick_format(AVFilterLink *link, AVFilterLink *ref)
>                     av_get_pix_fmt_name(ref->format), has_alpha);
>              link->incfg.formats->formats[0] = best;
>          }
> +

unrelated
diff mbox series

Patch

diff --git a/doc/APIchanges b/doc/APIchanges
index 12383a28d3..ce3f90a674 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -2,6 +2,14 @@  The last version increases of all libraries were on 2023-02-09
 
 API changes, most recent first:
 
+2023-11-xx - xxxxxxxxxx - lavf 58.14.100 - avfilter.h formats.h
+  Add AVFilterFormatsConfig.color_spaces, AVFilterFormatsConfig.color_ranges,
+  AVFilterLink.colorspace, AVFilterLink.color_range, ff_all_color_spaces,
+  ff_all_color_ranges, ff_set_common_color_spaces, ff_set_common_color_ranges,
+  ff_set_common_color_spaces_from_list, ff_set_common_color_ranges_from_list,
+  ff_set_common_all_color_spaces, ff_set_common_all_color_ranges,
+  ff_formats_check_color_spaces, ff_formats_check_color_ranges.
+
 2023-11-08 - xxxxxxxxxx - lavu 58.32.100 - channel_layout.h
   Add AV_CH_LAYOUT_7POINT2POINT3 and AV_CHANNEL_LAYOUT_7POINT2POINT3.
   Add AV_CH_LAYOUT_9POINT1POINT4_BACK and AV_CHANNEL_LAYOUT_9POINT1POINT4_BACK.
diff --git a/libavfilter/avfilter.c b/libavfilter/avfilter.c
index ab7782862a..77bfec00c5 100644
--- a/libavfilter/avfilter.c
+++ b/libavfilter/avfilter.c
@@ -185,6 +185,7 @@  int avfilter_link(AVFilterContext *src, unsigned srcpad,
     link->type    = src->output_pads[srcpad].type;
     av_assert0(AV_PIX_FMT_NONE == -1 && AV_SAMPLE_FMT_NONE == -1);
     link->format  = -1;
+    link->colorspace = AVCOL_SPC_UNSPECIFIED;
     ff_framequeue_init(&link->fifo, &src->graph->internal->frame_queues);
 
     return 0;
@@ -286,6 +287,12 @@  int avfilter_insert_filter(AVFilterLink *link, AVFilterContext *filt,
     if (link->outcfg.formats)
         ff_formats_changeref(&link->outcfg.formats,
                              &filt->outputs[filt_dstpad_idx]->outcfg.formats);
+    if (link->outcfg.color_spaces)
+        ff_formats_changeref(&link->outcfg.color_spaces,
+                             &filt->outputs[filt_dstpad_idx]->outcfg.color_spaces);
+    if (link->outcfg.color_ranges)
+        ff_formats_changeref(&link->outcfg.color_ranges,
+                             &filt->outputs[filt_dstpad_idx]->outcfg.color_ranges);
     if (link->outcfg.samplerates)
         ff_formats_changeref(&link->outcfg.samplerates,
                              &filt->outputs[filt_dstpad_idx]->outcfg.samplerates);
@@ -730,6 +737,10 @@  static void free_link(AVFilterLink *link)
 
     ff_formats_unref(&link->incfg.formats);
     ff_formats_unref(&link->outcfg.formats);
+    ff_formats_unref(&link->incfg.color_spaces);
+    ff_formats_unref(&link->outcfg.color_spaces);
+    ff_formats_unref(&link->incfg.color_ranges);
+    ff_formats_unref(&link->outcfg.color_ranges);
     ff_formats_unref(&link->incfg.samplerates);
     ff_formats_unref(&link->outcfg.samplerates);
     ff_channel_layouts_unref(&link->incfg.channel_layouts);
@@ -987,9 +998,11 @@  int ff_filter_frame(AVFilterLink *link, AVFrame *frame)
             strcmp(link->dst->filter->name, "idet") &&
             strcmp(link->dst->filter->name, "null") &&
             strcmp(link->dst->filter->name, "scale")) {
-            av_assert1(frame->format                 == link->format);
-            av_assert1(frame->width               == link->w);
-            av_assert1(frame->height               == link->h);
+            av_assert1(frame->format        == link->format);
+            av_assert1(frame->width         == link->w);
+            av_assert1(frame->height        == link->h);
+            av_assert1(frame->colorspace    == link->color_space);
+            av_assert1(frame->color_range   == link->color_range);
         }
 
         frame->sample_aspect_ratio = link->sample_aspect_ratio;
diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h
index d69381aed4..246d000251 100644
--- a/libavfilter/avfilter.h
+++ b/libavfilter/avfilter.h
@@ -301,6 +301,14 @@  typedef struct AVFilter {
          * @ref AVFilterFormatsConfig.formats "incfg.formats"
          * on every output link to a list of pixel/sample formats that the filter
          * supports on that link.
+         * For video links, this filter may also set
+         * @ref AVFilterFormatsConfig.color_spaces "incfg.color_spaces"
+         *  /
+         * @ref AVFilterFormatsConfig.color_spaces "outcfg.color_spaces"
+         * and @ref AVFilterFormatsConfig.color_ranges "incfg.color_ranges"
+         *  /
+         * @ref AVFilterFormatsConfig.color_ranges "outcfg.color_ranges"
+         * analogously.
          * For audio links, this filter must also set
          * @ref AVFilterFormatsConfig.samplerates "incfg.samplerates"
          *  /
@@ -322,6 +330,10 @@  typedef struct AVFilter {
          * to indicate that this filter supports each of these pixel formats,
          * provided that all inputs and outputs use the same pixel format.
          *
+         * In addition to that the generic code will mark all inputs
+         * and all outputs as supporting all color spaces and ranges, as
+         * long as all inputs and outputs use the same color space/range.
+         *
          * This list must never be NULL if the union is in this state.
          * The type of all inputs and outputs of filters using this must
          * be AVMEDIA_TYPE_VIDEO.
@@ -514,6 +526,12 @@  typedef struct AVFilterFormatsConfig {
      */
     AVFilterChannelLayouts  *channel_layouts;
 
+    /**
+     * Lists of supported YUV color metadata, only for YUV video.
+     */
+    AVFilterFormats *color_spaces;  ///< AVColorSpace
+    AVFilterFormats *color_ranges;  ///< AVColorRange
+
 } AVFilterFormatsConfig;
 
 /**
@@ -565,6 +583,16 @@  struct AVFilterLink {
 
     AVChannelLayout ch_layout;  ///< channel layout of current buffer (see libavutil/channel_layout.h)
 
+    /**
+     * For non-YUV links, these are respectively set to fallback values (as
+     * appropriate for that colorspace).
+     *
+     * Note: This includes grayscale formats, as these are currently treated
+     * as forced full range always.
+     */
+    enum AVColorSpace colorspace;   ///< agreed upon YUV color space
+    enum AVColorRange color_range;  ///< agreed upon YUV color range
+
     /*****************************************************************
      * 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 625cbc022e..e7fabe85ea 100644
--- a/libavfilter/avfiltergraph.c
+++ b/libavfilter/avfiltergraph.c
@@ -298,7 +298,9 @@  static int filter_link_check_formats(void *log, AVFilterLink *link, AVFilterForm
     switch (link->type) {
 
     case AVMEDIA_TYPE_VIDEO:
-        if ((ret = ff_formats_check_pixel_formats(log, cfg->formats)) < 0)
+        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)
             return ret;
         break;
 
@@ -365,6 +367,10 @@  static int formats_declared(AVFilterContext *f)
     for (i = 0; i < f->nb_inputs; i++) {
         if (!f->inputs[i]->outcfg.formats)
             return 0;
+        if (f->inputs[i]->type == AVMEDIA_TYPE_VIDEO &&
+            !(f->inputs[i]->outcfg.color_ranges &&
+              f->inputs[i]->outcfg.color_spaces))
+            return 0;
         if (f->inputs[i]->type == AVMEDIA_TYPE_AUDIO &&
             !(f->inputs[i]->outcfg.samplerates &&
               f->inputs[i]->outcfg.channel_layouts))
@@ -373,6 +379,10 @@  static int formats_declared(AVFilterContext *f)
     for (i = 0; i < f->nb_outputs; i++) {
         if (!f->outputs[i]->incfg.formats)
             return 0;
+        if (f->outputs[i]->type == AVMEDIA_TYPE_VIDEO &&
+            !(f->outputs[i]->incfg.color_ranges &&
+              f->outputs[i]->incfg.color_spaces))
+            return 0;
         if (f->outputs[i]->type == AVMEDIA_TYPE_AUDIO &&
             !(f->outputs[i]->incfg.samplerates &&
               f->outputs[i]->incfg.channel_layouts))
@@ -493,7 +503,16 @@  static int query_formats(AVFilterGraph *graph, void *log_ctx)
                 av_assert0( inlink->outcfg.formats->refcount > 0);
                 av_assert0(outlink->incfg.formats->refcount > 0);
                 av_assert0(outlink->outcfg.formats->refcount > 0);
-                if (outlink->type == AVMEDIA_TYPE_AUDIO) {
+                if (outlink->type == AVMEDIA_TYPE_VIDEO) {
+                    av_assert0( inlink-> incfg.color_spaces->refcount > 0);
+                    av_assert0( inlink->outcfg.color_spaces->refcount > 0);
+                    av_assert0(outlink-> incfg.color_spaces->refcount > 0);
+                    av_assert0(outlink->outcfg.color_spaces->refcount > 0);
+                    av_assert0( inlink-> incfg.color_ranges->refcount > 0);
+                    av_assert0( inlink->outcfg.color_ranges->refcount > 0);
+                    av_assert0(outlink-> incfg.color_ranges->refcount > 0);
+                    av_assert0(outlink->outcfg.color_ranges->refcount > 0);
+                } else if (outlink->type == AVMEDIA_TYPE_AUDIO) {
                     av_assert0( inlink-> incfg.samplerates->refcount > 0);
                     av_assert0( inlink->outcfg.samplerates->refcount > 0);
                     av_assert0(outlink-> incfg.samplerates->refcount > 0);
@@ -583,6 +602,29 @@  static enum AVSampleFormat find_best_sample_fmt_of_2(enum AVSampleFormat dst_fmt
     return score1 < score2 ? dst_fmt1 : dst_fmt2;
 }
 
+int ff_fmt_is_regular_yuv(enum AVPixelFormat fmt)
+{
+    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt);
+    if (!desc)
+        return 0;
+    if (desc->nb_components < 3)
+        return 0; /* Grayscale is explicitly full-range in swscale */
+    if (desc->flags & (AV_PIX_FMT_FLAG_RGB | AV_PIX_FMT_FLAG_PAL |
+                       AV_PIX_FMT_FLAG_XYZ | AV_PIX_FMT_FLAG_FLOAT))
+        return 0;
+
+    switch (fmt) {
+    case AV_PIX_FMT_YUVJ420P:
+    case AV_PIX_FMT_YUVJ422P:
+    case AV_PIX_FMT_YUVJ444P:
+    case AV_PIX_FMT_YUVJ440P:
+    case AV_PIX_FMT_YUVJ411P:
+        return 0;
+    default:
+        return 1;
+    }
+}
+
 static int pick_format(AVFilterLink *link, AVFilterLink *ref)
 {
     if (!link || !link->incfg.formats)
@@ -603,6 +645,7 @@  static int pick_format(AVFilterLink *link, AVFilterLink *ref)
                    av_get_pix_fmt_name(ref->format), has_alpha);
             link->incfg.formats->formats[0] = best;
         }
+
     } else if (link->type == AVMEDIA_TYPE_AUDIO) {
         if(ref && ref->type == AVMEDIA_TYPE_AUDIO){
             enum AVSampleFormat best= AV_SAMPLE_FMT_NONE;
@@ -621,7 +664,40 @@  static int pick_format(AVFilterLink *link, AVFilterLink *ref)
     link->incfg.formats->nb_formats = 1;
     link->format = link->incfg.formats->formats[0];
 
-    if (link->type == AVMEDIA_TYPE_AUDIO) {
+    if (link->type == AVMEDIA_TYPE_VIDEO) {
+        if (!ff_fmt_is_regular_yuv(link->format)) {
+            const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(link->format);
+            /* These fields are explicitly documented as affecting YUV only,
+             * so set them to sane values for other formats. */
+            if (desc->flags & AV_PIX_FMT_FLAG_FLOAT)
+                link->color_range = AVCOL_RANGE_UNSPECIFIED;
+            else
+                link->color_range = AVCOL_RANGE_JPEG;
+            if (desc->flags & (AV_PIX_FMT_FLAG_RGB | AV_PIX_FMT_FLAG_XYZ)) {
+                link->colorspace = AVCOL_SPC_RGB;
+            } else {
+                link->colorspace = AVCOL_SPC_UNSPECIFIED;
+            }
+        } else {
+            if (!link->incfg.color_spaces->nb_formats) {
+                av_log(link->src, AV_LOG_ERROR, "Cannot select color space for"
+                       " the link between filters %s and %s.\n", link->src->name,
+                       link->dst->name);
+                return AVERROR(EINVAL);
+            }
+            link->incfg.color_spaces->nb_formats = 1;
+            link->colorspace = link->incfg.color_spaces->formats[0];
+
+            if (!link->incfg.color_ranges->nb_formats) {
+                av_log(link->src, AV_LOG_ERROR, "Cannot select color range for"
+                       " the link between filters %s and %s.\n", link->src->name,
+                       link->dst->name);
+                return AVERROR(EINVAL);
+            }
+            link->incfg.color_ranges->nb_formats = 1;
+            link->color_range = link->incfg.color_ranges->formats[0];
+        }
+    } else if (link->type == AVMEDIA_TYPE_AUDIO) {
         int ret;
 
         if (!link->incfg.samplerates->nb_formats) {
@@ -661,6 +737,10 @@  FF_ENABLE_DEPRECATION_WARNINGS
     ff_formats_unref(&link->outcfg.samplerates);
     ff_channel_layouts_unref(&link->incfg.channel_layouts);
     ff_channel_layouts_unref(&link->outcfg.channel_layouts);
+    ff_formats_unref(&link->incfg.color_spaces);
+    ff_formats_unref(&link->outcfg.color_spaces);
+    ff_formats_unref(&link->incfg.color_ranges);
+    ff_formats_unref(&link->outcfg.color_ranges);
 
     return 0;
 }
@@ -822,6 +902,82 @@  static void swap_samplerates(AVFilterGraph *graph)
         swap_samplerates_on_filter(graph->filters[i]);
 }
 
+static void swap_color_spaces_on_filter(AVFilterContext *filter)
+{
+    AVFilterLink *link = NULL;
+    enum AVColorSpace csp;
+    int i;
+
+    for (i = 0; i < filter->nb_inputs; i++) {
+        link = filter->inputs[i];
+        if (link->type == AVMEDIA_TYPE_VIDEO &&
+            link->outcfg.color_spaces->nb_formats == 1)
+            break;
+    }
+    if (i == filter->nb_inputs)
+        return;
+
+    csp = link->outcfg.color_spaces->formats[0];
+
+    for (i = 0; i < filter->nb_outputs; i++) {
+        AVFilterLink *outlink = filter->outputs[i];
+        if (outlink->type != AVMEDIA_TYPE_VIDEO)
+            continue;
+        /* there is no meaningful 'score' between different yuv matrices,
+         * so just prioritize an exact match if it exists */
+        for (int j = 0; j < outlink->incfg.color_spaces->nb_formats; j++) {
+            if (csp == outlink->incfg.color_spaces->formats[j]) {
+                FFSWAP(int, outlink->incfg.color_spaces->formats[0],
+                       outlink->incfg.color_spaces->formats[j]);
+                break;
+            }
+        }
+    }
+}
+
+static void swap_color_spaces(AVFilterGraph *graph)
+{
+    for (int i = 0; i < graph->nb_filters; i++)
+        swap_color_spaces_on_filter(graph->filters[i]);
+}
+
+static void swap_color_ranges_on_filter(AVFilterContext *filter)
+{
+    AVFilterLink *link = NULL;
+    enum AVColorRange range;
+    int i;
+
+    for (i = 0; i < filter->nb_inputs; i++) {
+        link = filter->inputs[i];
+        if (link->type == AVMEDIA_TYPE_VIDEO &&
+            link->outcfg.color_ranges->nb_formats == 1)
+            break;
+    }
+    if (i == filter->nb_inputs)
+        return;
+
+    range = link->outcfg.color_ranges->formats[0];
+
+    for (i = 0; i < filter->nb_outputs; i++) {
+        AVFilterLink *outlink = filter->outputs[i];
+        if (outlink->type != AVMEDIA_TYPE_VIDEO)
+            continue;
+        for (int j = 0; j < outlink->incfg.color_ranges->nb_formats; j++) {
+            if (range == outlink->incfg.color_ranges->formats[j]) {
+                FFSWAP(int, outlink->incfg.color_ranges->formats[0],
+                       outlink->incfg.color_ranges->formats[j]);
+                break;
+            }
+        }
+    }
+}
+
+static void swap_color_ranges(AVFilterGraph *graph)
+{
+    for (int i = 0; i < graph->nb_filters; i++)
+        swap_color_ranges_on_filter(graph->filters[i]);
+}
+
 #define CH_CENTER_PAIR (AV_CH_FRONT_LEFT_OF_CENTER | AV_CH_FRONT_RIGHT_OF_CENTER)
 #define CH_FRONT_PAIR  (AV_CH_FRONT_LEFT           | AV_CH_FRONT_RIGHT)
 #define CH_STEREO_PAIR (AV_CH_STEREO_LEFT          | AV_CH_STEREO_RIGHT)
@@ -1098,6 +1254,10 @@  static int graph_config_formats(AVFilterGraph *graph, void *log_ctx)
     if ((ret = reduce_formats(graph)) < 0)
         return ret;
 
+    /* for video filters, ensure that the best colorspace metadata is selected */
+    swap_color_spaces(graph);
+    swap_color_ranges(graph);
+
     /* for audio filters, ensure the best format, sample rate and channel layout
      * is selected */
     swap_sample_fmts(graph);
diff --git a/libavfilter/formats.c b/libavfilter/formats.c
index d1c97daf64..20f00d2db4 100644
--- a/libavfilter/formats.c
+++ b/libavfilter/formats.c
@@ -321,12 +321,46 @@  static int merge_channel_layouts(void *a, void *b)
     return merge_channel_layouts_internal(a, b, 0);
 }
 
+static int merge_generic_internal(AVFilterFormats *a,
+                                  AVFilterFormats *b, int check)
+{
+    av_assert2(check || (a->refcount && b->refcount));
+
+    if (a == b)
+        return 1;
+
+    MERGE_FORMATS(a, b, formats, nb_formats, AVFilterFormats, check, 0);
+
+    return 1;
+}
+
+static int can_merge_generic(const void *a, const void *b)
+{
+    return merge_generic_internal((AVFilterFormats *)a,
+                                  (AVFilterFormats *)b, 1);
+}
+
+static int merge_generic(void *a, void *b)
+{
+    return merge_generic_internal(a, b, 0);
+}
+
 static const AVFilterFormatsMerger mergers_video[] = {
     {
         .offset     = offsetof(AVFilterFormatsConfig, formats),
         .merge      = merge_pix_fmts,
         .can_merge  = can_merge_pix_fmts,
     },
+    {
+        .offset     = offsetof(AVFilterFormatsConfig, color_spaces),
+        .merge      = merge_generic,
+        .can_merge  = can_merge_generic,
+    },
+    {
+        .offset     = offsetof(AVFilterFormatsConfig, color_ranges),
+        .merge      = merge_generic,
+        .can_merge  = can_merge_generic,
+    },
 };
 
 static const AVFilterFormatsMerger mergers_audio[] = {
@@ -594,6 +628,33 @@  AVFilterChannelLayouts *ff_all_channel_counts(void)
     return ret;
 }
 
+AVFilterFormats *ff_all_color_spaces(void)
+{
+    AVFilterFormats *ret = NULL;
+    if (ff_add_format(&ret, AVCOL_SPC_UNSPECIFIED) < 0)
+        return NULL;
+    for (int csp = 0; csp < AVCOL_SPC_NB; csp++) {
+        if (csp == AVCOL_SPC_RESERVED ||
+            csp == AVCOL_SPC_UNSPECIFIED)
+            continue;
+        if (ff_add_format(&ret, csp) < 0)
+            return NULL;
+    }
+
+    return ret;
+}
+
+AVFilterFormats *ff_all_color_ranges(void)
+{
+    AVFilterFormats *ret = NULL;
+    for (int range = 0; range < AVCOL_RANGE_NB; range++) {
+        if (ff_add_format(&ret, range) < 0)
+            return NULL;
+    }
+
+    return ret;
+}
+
 #define FORMATS_REF(f, ref, unref_fn)                                           \
     void *tmp;                                                                  \
                                                                                 \
@@ -763,6 +824,42 @@  int ff_set_common_all_samplerates(AVFilterContext *ctx)
     return ff_set_common_samplerates(ctx, ff_all_samplerates());
 }
 
+int ff_set_common_color_spaces(AVFilterContext *ctx,
+                               AVFilterFormats *color_spaces)
+{
+    SET_COMMON_FORMATS(ctx, color_spaces, AVMEDIA_TYPE_VIDEO,
+                       ff_formats_ref, ff_formats_unref);
+}
+
+int ff_set_common_color_spaces_from_list(AVFilterContext *ctx,
+                                         const int *color_ranges)
+{
+    return ff_set_common_color_spaces(ctx, ff_make_format_list(color_ranges));
+}
+
+int ff_set_common_all_color_spaces(AVFilterContext *ctx)
+{
+    return ff_set_common_color_spaces(ctx, ff_all_color_spaces());
+}
+
+int ff_set_common_color_ranges(AVFilterContext *ctx,
+                               AVFilterFormats *color_ranges)
+{
+    SET_COMMON_FORMATS(ctx, color_ranges, AVMEDIA_TYPE_VIDEO,
+                       ff_formats_ref, ff_formats_unref);
+}
+
+int ff_set_common_color_ranges_from_list(AVFilterContext *ctx,
+                                         const int *color_ranges)
+{
+    return ff_set_common_color_ranges(ctx, ff_make_format_list(color_ranges));
+}
+
+int ff_set_common_all_color_ranges(AVFilterContext *ctx)
+{
+    return ff_set_common_color_ranges(ctx, ff_all_color_ranges());
+}
+
 /**
  * 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
@@ -817,7 +914,14 @@  int ff_default_query_formats(AVFilterContext *ctx)
     ret = ff_set_common_formats(ctx, formats);
     if (ret < 0)
         return ret;
-    if (type == AVMEDIA_TYPE_AUDIO) {
+    if (type == AVMEDIA_TYPE_VIDEO) {
+        ret = ff_set_common_all_color_spaces(ctx);
+        if (ret < 0)
+            return ret;
+        ret = ff_set_common_all_color_ranges(ctx);
+        if (ret < 0)
+            return ret;
+    } else if (type == AVMEDIA_TYPE_AUDIO) {
         ret = ff_set_common_all_channel_counts(ctx);
         if (ret < 0)
             return ret;
@@ -935,6 +1039,22 @@  int ff_formats_check_sample_rates(void *log, const AVFilterFormats *fmts)
     return check_list(log, "sample rate", fmts);
 }
 
+int ff_formats_check_color_spaces(void *log, const AVFilterFormats *fmts)
+{
+    for (int i = 0; fmts && i < fmts->nb_formats; i++) {
+        if (fmts->formats[i] == AVCOL_SPC_RESERVED) {
+            av_log(log, AV_LOG_ERROR, "Invalid color range\n");
+            return AVERROR(EINVAL);
+        }
+    }
+    return check_list(log, "color space", fmts);
+}
+
+int ff_formats_check_color_ranges(void *log, const AVFilterFormats *fmts)
+{
+    return check_list(log, "color range", 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 d44890109e..82b3af4be1 100644
--- a/libavfilter/formats.h
+++ b/libavfilter/formats.h
@@ -130,6 +130,20 @@  AVFilterChannelLayouts *ff_all_channel_counts(void);
 av_warn_unused_result
 AVFilterChannelLayouts *ff_make_channel_layout_list(const AVChannelLayout *fmts);
 
+/**
+ * Construct an AVFilterFormats representing all possible color spaces.
+ *
+ * Note: This list does not include AVCOL_SPC_RESERVED.
+ */
+av_warn_unused_result
+AVFilterFormats *ff_all_color_spaces(void);
+
+/**
+ * Construct an AVFilterFormats representing all possible color ranges.
+ */
+av_warn_unused_result
+AVFilterFormats *ff_all_color_ranges(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,
@@ -165,6 +179,38 @@  int ff_set_common_samplerates_from_list(AVFilterContext *ctx,
 av_warn_unused_result
 int ff_set_common_all_samplerates(AVFilterContext *ctx);
 
+av_warn_unused_result
+int ff_set_common_color_spaces(AVFilterContext *ctx,
+                               AVFilterFormats *color_spaces);
+/**
+ * Equivalent to ff_set_common_color_spaces(ctx, ff_make_format_list(color_spaces))
+ */
+av_warn_unused_result
+int ff_set_common_color_spaces_from_list(AVFilterContext *ctx,
+                                         const int *color_spaces);
+
+/**
+ * Equivalent to ff_set_common_color_spaces(ctx, ff_all_color_spaces())
+ */
+av_warn_unused_result
+int ff_set_common_all_color_spaces(AVFilterContext *ctx);
+
+av_warn_unused_result
+int ff_set_common_color_ranges(AVFilterContext *ctx,
+                               AVFilterFormats *color_ranges);
+/**
+ * Equivalent to ff_set_common_color_ranges(ctx, ff_make_format_list(color_ranges))
+ */
+av_warn_unused_result
+int ff_set_common_color_ranges_from_list(AVFilterContext *ctx,
+                                         const int *color_ranges);
+
+/**
+ * Equivalent to ff_set_common_color_ranges(ctx, ff_all_color_ranges())
+ */
+av_warn_unused_result
+int ff_set_common_all_color_ranges(AVFilterContext *ctx);
+
 /**
  * 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
@@ -328,6 +374,14 @@  int ff_formats_check_sample_rates(void *log, const AVFilterFormats *fmts);
  */
 int ff_formats_check_channel_layouts(void *log, const AVFilterChannelLayouts *fmts);
 
+/**
+ * Check that fmts is a valid formats list for YUV colorspace metadata.
+ *
+ * In particular, check for duplicates.
+ */
+int ff_formats_check_color_spaces(void *log, const AVFilterFormats *fmts);
+int ff_formats_check_color_ranges(void *log, const AVFilterFormats *fmts);
+
 typedef struct AVFilterFormatMerger {
     unsigned offset;
     int (*merge)(void *a, void *b);
diff --git a/libavfilter/internal.h b/libavfilter/internal.h
index 2dbc5def0a..a6cdf9994c 100644
--- a/libavfilter/internal.h
+++ b/libavfilter/internal.h
@@ -203,6 +203,12 @@  enum FilterFormatsState {
  */
 int ff_fmt_is_in(int fmt, const int *fmts);
 
+/**
+ * Returns true if a pixel format is "regular YUV", which includes all pixel
+ * formats that are affected by YUV colorspace negotiation.
+ */
+int ff_fmt_is_regular_yuv(enum AVPixelFormat fmt);
+
 /* Functions to parse audio format arguments */
 
 /**
diff --git a/libavfilter/vaapi_vpp.c b/libavfilter/vaapi_vpp.c
index cf2592e068..59961bfa4a 100644
--- a/libavfilter/vaapi_vpp.c
+++ b/libavfilter/vaapi_vpp.c
@@ -38,6 +38,10 @@  int ff_vaapi_vpp_query_formats(AVFilterContext *avctx)
                               &avctx->outputs[0]->incfg.formats)) < 0)
         return err;
 
+    if ((err = ff_set_common_all_color_spaces(avctx)) < 0 ||
+        (err = ff_set_common_all_color_ranges(avctx)) < 0)
+        return err;
+
     return 0;
 }
 
diff --git a/libavfilter/version.h b/libavfilter/version.h
index 537df129cd..7642b670d1 100644
--- a/libavfilter/version.h
+++ b/libavfilter/version.h
@@ -31,7 +31,7 @@ 
 
 #include "version_major.h"
 
-#define LIBAVFILTER_VERSION_MINOR  13
+#define LIBAVFILTER_VERSION_MINOR  14
 #define LIBAVFILTER_VERSION_MICRO 100
 
 
diff --git a/libavfilter/video.c b/libavfilter/video.c
index 42eeb98c28..243762c8fd 100644
--- a/libavfilter/video.c
+++ b/libavfilter/video.c
@@ -96,6 +96,8 @@  AVFrame *ff_default_get_video_buffer2(AVFilterLink *link, int w, int h, int alig
         return NULL;
 
     frame->sample_aspect_ratio = link->sample_aspect_ratio;
+    frame->colorspace  = link->colorspace;
+    frame->color_range = link->color_range;
 
     return frame;
 }
diff --git a/tests/ref/fate/shortest-sub b/tests/ref/fate/shortest-sub
index 9caee587ce..16b1324e83 100644
--- a/tests/ref/fate/shortest-sub
+++ b/tests/ref/fate/shortest-sub
@@ -1,5 +1,5 @@ 
-73d142a80965f9e0884a5863abde0dab *tests/data/fate/shortest-sub.matroska
-139249 tests/data/fate/shortest-sub.matroska
+d6608277c93097383e62388196dc62f0 *tests/data/fate/shortest-sub.matroska
+139260 tests/data/fate/shortest-sub.matroska
 #extradata 1:      167, 0xf7272d5f
 #tb 0: 1/1000
 #media_type 0: video