diff mbox series

[FFmpeg-devel,4/6] avfilter/vf_scale: add in/out_chroma_loc

Message ID 20240704115202.1235954-4-ffmpeg@haasn.xyz
State New
Headers show
Series [FFmpeg-devel,1/6] swscale: document SWS_FULL_CHR_H_* flags | expand

Checks

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

Commit Message

Niklas Haas July 4, 2024, 11:52 a.m. UTC
From: Niklas Haas <git@haasn.dev>

Currently, this just functions as a more principled and user-friendly
replacement for the (undocumented and hard to use) *_chr_pos fields.

However, the goal is to automatically infer these values from the input
frames' chroma location, and deprecate the manual use of *_chr_pos
altogether. (Indeed, my plans for an swscale replacement will most
likely also end up limiting the set of legal chroma locations to those
permissible by AVFrame properties)
---
 doc/filters.texi       | 15 +++++++++
 libavfilter/vf_scale.c | 75 ++++++++++++++++++++++++++++--------------
 2 files changed, 66 insertions(+), 24 deletions(-)

Comments

Niklas Haas July 4, 2024, 12:52 p.m. UTC | #1
On Thu, 04 Jul 2024 13:52:00 +0200 Niklas Haas <ffmpeg@haasn.xyz> wrote:
> From: Niklas Haas <git@haasn.dev>
> 
> Currently, this just functions as a more principled and user-friendly
> replacement for the (undocumented and hard to use) *_chr_pos fields.
> 
> However, the goal is to automatically infer these values from the input
> frames' chroma location, and deprecate the manual use of *_chr_pos
> altogether. (Indeed, my plans for an swscale replacement will most
> likely also end up limiting the set of legal chroma locations to those
> permissible by AVFrame properties)
> ---
>  doc/filters.texi       | 15 +++++++++
>  libavfilter/vf_scale.c | 75 ++++++++++++++++++++++++++++--------------
>  2 files changed, 66 insertions(+), 24 deletions(-)
> 
> diff --git a/doc/filters.texi b/doc/filters.texi
> index ca8f6e461a..3cff4eec1c 100644
> --- a/doc/filters.texi
> +++ b/doc/filters.texi
> @@ -21137,6 +21137,21 @@ Set full range (0-255 in case of 8-bit luma).
>  Set "MPEG" range (16-235 in case of 8-bit luma).
>  @end table
>  
> +@item in_chroma_loc
> +@item out_chroma_loc
> +Set in/output chroma sample location. If not specified, center-sited chroma
> +is used by default. Possible values:
> +
> +@table @samp
> +@item auto, unknown
> +@item left
> +@item center
> +@item topleft
> +@item top
> +@item bottomleft
> +@item bottom
> +@end table
> +
>  @item force_original_aspect_ratio
>  Enable decreasing or increasing output video width or height if necessary to
>  keep the original aspect ratio. Possible values:
> diff --git a/libavfilter/vf_scale.c b/libavfilter/vf_scale.c
> index 0b6701673f..f0de0261db 100644
> --- a/libavfilter/vf_scale.c
> +++ b/libavfilter/vf_scale.c
> @@ -168,6 +168,8 @@ typedef struct ScaleContext {
>      int in_range;
>      int out_range;
>  
> +    int in_chroma_loc;
> +    int out_chroma_loc;
>      int out_h_chr_pos;
>      int out_v_chr_pos;
>      int in_h_chr_pos;
> @@ -617,6 +619,29 @@ fail:
>      return ret;
>  }
>  
> +static void calc_chroma_pos(int *h_pos, int *v_pos, int chroma_loc,
> +                            int h_pos_default, int v_pos_default,
> +                            int h_sub, int v_sub, int index)
> +{
> +    *h_pos = h_pos_default;
> +    *v_pos = v_pos_default;
> +    if (chroma_loc != AVCHROMA_LOC_UNSPECIFIED)
> +        av_chroma_location_enum_to_pos(h_pos, v_pos, chroma_loc);
> +
> +    if (h_sub && index > 0 /* interlaced fields */) {
> +        if (*v_pos == -513)
> +            *v_pos = 128; /* explicitly default missing info */
> +        *v_pos += 256 * (index == 2); /* offset by one luma row for odd rows */
> +        *v_pos >>= 1; /* double luma row distance */
> +    }

This logic needs to be revisited for 4:1:0 subsampled material (i.e.
h_sub == 2). In this case, the luma grid is 4x4, so we need to adjust
the sample location relative to the 4x4 grid.

I'm not yet entirely sure how it would interact with interlaced content
in this setting. I'll try and find a spec, since MPEG doesn't allow
4:1:0.

> +
> +    /* Avoid offsetting chroma for progressive content */
> +    if (!h_sub)
> +        *h_pos = -513;
> +    if (!v_sub)
> +        *v_pos = -513;
> +}
> +
>  static int config_props(AVFilterLink *outlink)
>  {
>      AVFilterContext *ctx = outlink->src;
> @@ -673,15 +698,16 @@ static int config_props(AVFilterLink *outlink)
>          inlink0->h == outlink->h &&
>          in_range == outlink->color_range &&
>          in_colorspace == outlink->colorspace &&
> -        inlink0->format == outlink->format)
> +        inlink0->format == outlink->format &&
> +        scale->in_chroma_loc == scale->out_chroma_loc)
>          ;
>      else {
>          struct SwsContext **swscs[3] = {&scale->sws, &scale->isws[0], &scale->isws[1]};
>          int i;
>  
>          for (i = 0; i < 3; i++) {
> -            int in_v_chr_pos = scale->in_v_chr_pos, out_v_chr_pos = scale->out_v_chr_pos;
>              int in_full, out_full, brightness, contrast, saturation;
> +            int h_chr_pos, v_chr_pos;
>              const int *inv_table, *table;
>              struct SwsContext *const s = sws_alloc_context();
>              if (!s)
> @@ -705,28 +731,17 @@ static int config_props(AVFilterLink *outlink)
>                  av_opt_set_int(s, "dst_range",
>                                 outlink->color_range == AVCOL_RANGE_JPEG, 0);
>  
> -            /* Override chroma location default settings to have the correct
> -             * chroma positions. MPEG chroma positions are used by convention.
> -             * Note that this works for both MPEG-1/JPEG and MPEG-2/4 chroma
> -             * locations, since they share a vertical alignment */
> -            if (desc->log2_chroma_h == 1) {
> -                if (in_v_chr_pos == -513)
> -                    in_v_chr_pos = 128; /* explicitly default missing info */
> -                in_v_chr_pos += 256 * (i == 2); /* offset by one luma row for odd rows */
> -                in_v_chr_pos >>= i > 0; /* double luma row distance */
> -            }
> -
> -            if (outdesc->log2_chroma_h == 1) {
> -                if (out_v_chr_pos == -513)
> -                    out_v_chr_pos = 128;
> -                out_v_chr_pos += 256 * (i == 2);
> -                out_v_chr_pos >>= i > 0;
> -            }
> -
> -            av_opt_set_int(s, "src_h_chr_pos", scale->in_h_chr_pos, 0);
> -            av_opt_set_int(s, "src_v_chr_pos", in_v_chr_pos, 0);
> -            av_opt_set_int(s, "dst_h_chr_pos", scale->out_h_chr_pos, 0);
> -            av_opt_set_int(s, "dst_v_chr_pos", out_v_chr_pos, 0);
> +            calc_chroma_pos(&h_chr_pos, &v_chr_pos, scale->in_chroma_loc,
> +                            scale->in_h_chr_pos, scale->in_v_chr_pos,
> +                            desc->log2_chroma_w, desc->log2_chroma_h, i);
> +            av_opt_set_int(s, "src_h_chr_pos", h_chr_pos, 0);
> +            av_opt_set_int(s, "src_v_chr_pos", v_chr_pos, 0);
> +
> +            calc_chroma_pos(&h_chr_pos, &v_chr_pos, scale->out_chroma_loc,
> +                            scale->out_h_chr_pos, scale->out_v_chr_pos,
> +                            outdesc->log2_chroma_w, outdesc->log2_chroma_h, i);
> +            av_opt_set_int(s, "dst_h_chr_pos", h_chr_pos, 0);
> +            av_opt_set_int(s, "dst_v_chr_pos", v_chr_pos, 0);
>  
>              if ((ret = sws_init_context(s, NULL, NULL)) < 0)
>                  return ret;
> @@ -987,6 +1002,8 @@ scale:
>      out->height = outlink->h;
>      out->color_range = outlink->color_range;
>      out->colorspace = outlink->colorspace;
> +    if (scale->out_chroma_loc != AVCHROMA_LOC_UNSPECIFIED)
> +        out->chroma_location = scale->out_chroma_loc;
>  
>      if (scale->output_is_pal)
>          avpriv_set_systematic_pal2((uint32_t*)out->data[1], outlink->format == AV_PIX_FMT_PAL8 ? AV_PIX_FMT_BGR8 : outlink->format);
> @@ -1217,6 +1234,16 @@ static const AVOption scale_options[] = {
>      { "mpeg",   NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AVCOL_RANGE_MPEG}, 0, 0, FLAGS, .unit = "range" },
>      { "tv",     NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AVCOL_RANGE_MPEG}, 0, 0, FLAGS, .unit = "range" },
>      { "pc",     NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AVCOL_RANGE_JPEG}, 0, 0, FLAGS, .unit = "range" },
> +    { "in_chroma_loc",  "set input chroma sample location",  OFFSET(in_chroma_loc),  AV_OPT_TYPE_INT, { .i64 = AVCHROMA_LOC_UNSPECIFIED }, 0, AVCHROMA_LOC_NB-1, .flags = FLAGS, .unit = "chroma_loc" },
> +    { "out_chroma_loc", "set output chroma sample location", OFFSET(out_chroma_loc), AV_OPT_TYPE_INT, { .i64 = AVCHROMA_LOC_UNSPECIFIED }, 0, AVCHROMA_LOC_NB-1, .flags = FLAGS, .unit = "chroma_loc" },
> +        {"auto",          NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCHROMA_LOC_UNSPECIFIED}, 0, 0, FLAGS, .unit = "chroma_loc"},
> +        {"unknown",       NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCHROMA_LOC_UNSPECIFIED}, 0, 0, FLAGS, .unit = "chroma_loc"},
> +        {"left",          NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCHROMA_LOC_LEFT},        0, 0, FLAGS, .unit = "chroma_loc"},
> +        {"center",        NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCHROMA_LOC_CENTER},      0, 0, FLAGS, .unit = "chroma_loc"},
> +        {"topleft",       NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCHROMA_LOC_TOPLEFT},     0, 0, FLAGS, .unit = "chroma_loc"},
> +        {"top",           NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCHROMA_LOC_TOP},         0, 0, FLAGS, .unit = "chroma_loc"},
> +        {"bottomleft",    NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCHROMA_LOC_BOTTOMLEFT},  0, 0, FLAGS, .unit = "chroma_loc"},
> +        {"bottom",        NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCHROMA_LOC_BOTTOM},      0, 0, FLAGS, .unit = "chroma_loc"},
>      { "in_v_chr_pos",   "input vertical chroma position in luma grid/256"  ,   OFFSET(in_v_chr_pos),  AV_OPT_TYPE_INT, { .i64 = -513}, -513, 512, FLAGS },
>      { "in_h_chr_pos",   "input horizontal chroma position in luma grid/256",   OFFSET(in_h_chr_pos),  AV_OPT_TYPE_INT, { .i64 = -513}, -513, 512, FLAGS },
>      { "out_v_chr_pos",   "output vertical chroma position in luma grid/256"  , OFFSET(out_v_chr_pos), AV_OPT_TYPE_INT, { .i64 = -513}, -513, 512, FLAGS },
> -- 
> 2.45.2
>
diff mbox series

Patch

diff --git a/doc/filters.texi b/doc/filters.texi
index ca8f6e461a..3cff4eec1c 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -21137,6 +21137,21 @@  Set full range (0-255 in case of 8-bit luma).
 Set "MPEG" range (16-235 in case of 8-bit luma).
 @end table
 
+@item in_chroma_loc
+@item out_chroma_loc
+Set in/output chroma sample location. If not specified, center-sited chroma
+is used by default. Possible values:
+
+@table @samp
+@item auto, unknown
+@item left
+@item center
+@item topleft
+@item top
+@item bottomleft
+@item bottom
+@end table
+
 @item force_original_aspect_ratio
 Enable decreasing or increasing output video width or height if necessary to
 keep the original aspect ratio. Possible values:
diff --git a/libavfilter/vf_scale.c b/libavfilter/vf_scale.c
index 0b6701673f..f0de0261db 100644
--- a/libavfilter/vf_scale.c
+++ b/libavfilter/vf_scale.c
@@ -168,6 +168,8 @@  typedef struct ScaleContext {
     int in_range;
     int out_range;
 
+    int in_chroma_loc;
+    int out_chroma_loc;
     int out_h_chr_pos;
     int out_v_chr_pos;
     int in_h_chr_pos;
@@ -617,6 +619,29 @@  fail:
     return ret;
 }
 
+static void calc_chroma_pos(int *h_pos, int *v_pos, int chroma_loc,
+                            int h_pos_default, int v_pos_default,
+                            int h_sub, int v_sub, int index)
+{
+    *h_pos = h_pos_default;
+    *v_pos = v_pos_default;
+    if (chroma_loc != AVCHROMA_LOC_UNSPECIFIED)
+        av_chroma_location_enum_to_pos(h_pos, v_pos, chroma_loc);
+
+    if (h_sub && index > 0 /* interlaced fields */) {
+        if (*v_pos == -513)
+            *v_pos = 128; /* explicitly default missing info */
+        *v_pos += 256 * (index == 2); /* offset by one luma row for odd rows */
+        *v_pos >>= 1; /* double luma row distance */
+    }
+
+    /* Avoid offsetting chroma for progressive content */
+    if (!h_sub)
+        *h_pos = -513;
+    if (!v_sub)
+        *v_pos = -513;
+}
+
 static int config_props(AVFilterLink *outlink)
 {
     AVFilterContext *ctx = outlink->src;
@@ -673,15 +698,16 @@  static int config_props(AVFilterLink *outlink)
         inlink0->h == outlink->h &&
         in_range == outlink->color_range &&
         in_colorspace == outlink->colorspace &&
-        inlink0->format == outlink->format)
+        inlink0->format == outlink->format &&
+        scale->in_chroma_loc == scale->out_chroma_loc)
         ;
     else {
         struct SwsContext **swscs[3] = {&scale->sws, &scale->isws[0], &scale->isws[1]};
         int i;
 
         for (i = 0; i < 3; i++) {
-            int in_v_chr_pos = scale->in_v_chr_pos, out_v_chr_pos = scale->out_v_chr_pos;
             int in_full, out_full, brightness, contrast, saturation;
+            int h_chr_pos, v_chr_pos;
             const int *inv_table, *table;
             struct SwsContext *const s = sws_alloc_context();
             if (!s)
@@ -705,28 +731,17 @@  static int config_props(AVFilterLink *outlink)
                 av_opt_set_int(s, "dst_range",
                                outlink->color_range == AVCOL_RANGE_JPEG, 0);
 
-            /* Override chroma location default settings to have the correct
-             * chroma positions. MPEG chroma positions are used by convention.
-             * Note that this works for both MPEG-1/JPEG and MPEG-2/4 chroma
-             * locations, since they share a vertical alignment */
-            if (desc->log2_chroma_h == 1) {
-                if (in_v_chr_pos == -513)
-                    in_v_chr_pos = 128; /* explicitly default missing info */
-                in_v_chr_pos += 256 * (i == 2); /* offset by one luma row for odd rows */
-                in_v_chr_pos >>= i > 0; /* double luma row distance */
-            }
-
-            if (outdesc->log2_chroma_h == 1) {
-                if (out_v_chr_pos == -513)
-                    out_v_chr_pos = 128;
-                out_v_chr_pos += 256 * (i == 2);
-                out_v_chr_pos >>= i > 0;
-            }
-
-            av_opt_set_int(s, "src_h_chr_pos", scale->in_h_chr_pos, 0);
-            av_opt_set_int(s, "src_v_chr_pos", in_v_chr_pos, 0);
-            av_opt_set_int(s, "dst_h_chr_pos", scale->out_h_chr_pos, 0);
-            av_opt_set_int(s, "dst_v_chr_pos", out_v_chr_pos, 0);
+            calc_chroma_pos(&h_chr_pos, &v_chr_pos, scale->in_chroma_loc,
+                            scale->in_h_chr_pos, scale->in_v_chr_pos,
+                            desc->log2_chroma_w, desc->log2_chroma_h, i);
+            av_opt_set_int(s, "src_h_chr_pos", h_chr_pos, 0);
+            av_opt_set_int(s, "src_v_chr_pos", v_chr_pos, 0);
+
+            calc_chroma_pos(&h_chr_pos, &v_chr_pos, scale->out_chroma_loc,
+                            scale->out_h_chr_pos, scale->out_v_chr_pos,
+                            outdesc->log2_chroma_w, outdesc->log2_chroma_h, i);
+            av_opt_set_int(s, "dst_h_chr_pos", h_chr_pos, 0);
+            av_opt_set_int(s, "dst_v_chr_pos", v_chr_pos, 0);
 
             if ((ret = sws_init_context(s, NULL, NULL)) < 0)
                 return ret;
@@ -987,6 +1002,8 @@  scale:
     out->height = outlink->h;
     out->color_range = outlink->color_range;
     out->colorspace = outlink->colorspace;
+    if (scale->out_chroma_loc != AVCHROMA_LOC_UNSPECIFIED)
+        out->chroma_location = scale->out_chroma_loc;
 
     if (scale->output_is_pal)
         avpriv_set_systematic_pal2((uint32_t*)out->data[1], outlink->format == AV_PIX_FMT_PAL8 ? AV_PIX_FMT_BGR8 : outlink->format);
@@ -1217,6 +1234,16 @@  static const AVOption scale_options[] = {
     { "mpeg",   NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AVCOL_RANGE_MPEG}, 0, 0, FLAGS, .unit = "range" },
     { "tv",     NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AVCOL_RANGE_MPEG}, 0, 0, FLAGS, .unit = "range" },
     { "pc",     NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AVCOL_RANGE_JPEG}, 0, 0, FLAGS, .unit = "range" },
+    { "in_chroma_loc",  "set input chroma sample location",  OFFSET(in_chroma_loc),  AV_OPT_TYPE_INT, { .i64 = AVCHROMA_LOC_UNSPECIFIED }, 0, AVCHROMA_LOC_NB-1, .flags = FLAGS, .unit = "chroma_loc" },
+    { "out_chroma_loc", "set output chroma sample location", OFFSET(out_chroma_loc), AV_OPT_TYPE_INT, { .i64 = AVCHROMA_LOC_UNSPECIFIED }, 0, AVCHROMA_LOC_NB-1, .flags = FLAGS, .unit = "chroma_loc" },
+        {"auto",          NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCHROMA_LOC_UNSPECIFIED}, 0, 0, FLAGS, .unit = "chroma_loc"},
+        {"unknown",       NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCHROMA_LOC_UNSPECIFIED}, 0, 0, FLAGS, .unit = "chroma_loc"},
+        {"left",          NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCHROMA_LOC_LEFT},        0, 0, FLAGS, .unit = "chroma_loc"},
+        {"center",        NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCHROMA_LOC_CENTER},      0, 0, FLAGS, .unit = "chroma_loc"},
+        {"topleft",       NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCHROMA_LOC_TOPLEFT},     0, 0, FLAGS, .unit = "chroma_loc"},
+        {"top",           NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCHROMA_LOC_TOP},         0, 0, FLAGS, .unit = "chroma_loc"},
+        {"bottomleft",    NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCHROMA_LOC_BOTTOMLEFT},  0, 0, FLAGS, .unit = "chroma_loc"},
+        {"bottom",        NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCHROMA_LOC_BOTTOM},      0, 0, FLAGS, .unit = "chroma_loc"},
     { "in_v_chr_pos",   "input vertical chroma position in luma grid/256"  ,   OFFSET(in_v_chr_pos),  AV_OPT_TYPE_INT, { .i64 = -513}, -513, 512, FLAGS },
     { "in_h_chr_pos",   "input horizontal chroma position in luma grid/256",   OFFSET(in_h_chr_pos),  AV_OPT_TYPE_INT, { .i64 = -513}, -513, 512, FLAGS },
     { "out_v_chr_pos",   "output vertical chroma position in luma grid/256"  , OFFSET(out_v_chr_pos), AV_OPT_TYPE_INT, { .i64 = -513}, -513, 512, FLAGS },