PR #25834 opened by luoye-cpu
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25834
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25834.patch

# Summary of changes

The AV1 bitstream only codes `cb_mult`, `cb_luma_mult` and `cb_offset`
when the Cb plane has at least one grain scaling point, and the Cr
counterparts only when the Cr plane has one.
`cbs_av1_syntax_template.c` reads the three Cb fields under
`if (num_cb_points)` and the three Cr fields under
`if (num_cr_points)`, and infers both point counts to zero when
`chroma_scaling_from_luma` is set, or for a 4:2:0 stream
(`subsampling_x == subsampling_y == 1`) without luma grain points.

`export_film_grain()` nevertheless subtracts the coding bias from all
six fields unconditionally:

| assignment | bitstream field | bias |
|---|---|---|
| `aom->uv_mult[0]` | `cb_mult` | 128 |
| `aom->uv_mult_luma[0]` | `cb_luma_mult` | 128 |
| `aom->uv_offset[0]` | `cb_offset` | 256 |
| `aom->uv_mult[1]` / `uv_mult_luma[1]` / `uv_offset[1]` | `cr_mult` / 
`cr_luma_mult` / `cr_offset` | 128 / 128 / 256 |

When a plane has no scaling points these fields were never coded and
stay at zero, so the subtraction exports `uv_mult = -128`,
`uv_mult_luma = -128` and `uv_offset = -256`: placeholder artifacts of
the unconditional conversion, not values the stream ever described.
`AVFilmGrainAOMParams` carries unbiased parameters:
`libavutil/film_grain_params.h` documents `uv_offset` as a 9-bit value
with a range [-256, 255], and the AFGS1 parser in `aom_film_grain.c`
likewise subtracts the coding bias, so for a field that was never
coded, the correct exported value is zero.

The same stream therefore exports different parameters depending on
the decoder: dav1d leaves unparsed fields at zero and
`libavcodec/libdav1d.c` copies `uv_mult`, `uv_mult_luma` and
`uv_offset` out of the dav1d structure as-is, so libdav1d exports 0
while the native decoder exports -128/-128/-256. This patch makes the
native AV1 decoder's side data consistent with the other
implementations for such streams.

Upstream context: 2a8c6d30b9 ("avcodec/av1dec: remove the coding bias
from exported chroma grain params", merged 2026-10-20) added the bias
removal on these six fields, and the discussions along that line
already recorded the leftover: the export rework discussion (#24974,
jamrial, 2025) noted that planes without scaling points do not code
these fields, so de-biasing them would export -128/-256 instead of 0
("these values are not used"), and 2a8c6d30b9's own discussion left
the no-points case behind. #25614 applies the same guard on the QSV
path. This patch completes that line with the no-points case on the
native av1dec path.

The patch guards the two chroma assignments independently, mirroring
the syntax conditions one to one: the Cb triplet under
`aom->num_uv_points[0]` and the Cr triplet under
`aom->num_uv_points[1]`. Skipping the assignments keeps the fields at
zero, which is what an unparsed field means and what libdav1d exports
for the same stream.

## Testing

Verified by source inspection against commit
`2ec7bc915e56c4f3cb71bb4f96c7d900965b6c82`, where the three relevant
files were checked directly:

- `libavcodec/cbs_av1_syntax_template.c`: the `num_cb_points` /
  `num_cr_points` parse conditions for the three biased fields, and the
  infer-to-zero conditions for the point counts.
- `libavcodec/av1dec.c`: the unconditional bias subtraction in
  `export_film_grain()`.
- `libavcodec/libdav1d.c`: the `memcpy`-based export of the dav1d film
  grain structure, which keeps unparsed fields at zero.

The patch applies cleanly to a tree at that commit (`git apply --check`
and `git am` both pass; the change is +10/-6 lines in
`libavcodec/av1dec.c`).

A 4:2:0 8-bit film-grain stream (every plane coded: `num_y_points=3`,
`num_uv_points=[2,2]`) was additionally decoded with
`-export_side_data film_grain` through the native decoder (NVDEC
output, shared export path) and with libdav1d: both report identical
`AVFilmGrainAOMParams` frame by frame, including `uv_mult=-27/-26`,
`uv_mult_luma=-24/-23` and `uv_offset=-153/-150`, the real unbiased
values. Streams that code the fields are therefore unaffected; the
patch only changes the no-points case.

The no-points case itself rests on the syntax conditions above and on
the libdav1d comparison; no end-to-end decode of a no-points bitstream
was run. To reproduce the disagreement on any 4:2:0 stream with
`num_y_points == 0` and `chroma_scaling_from_luma == 0`: decode with
`-export_side_data film_grain` once with av1dec and once with libdav1d,
and compare the `AV_FRAME_DATA_FILM_GRAIN` side data: before the patch
av1dec reports `uv_mult`/`uv_mult_luma`/`uv_offset` of -128/-128/-256
where libdav1d reports 0; after this patch both report 0.

## API

No public API change: `AVFilmGrainAOMParams` is unchanged; the exported
side data merely stops reporting placeholder bias artifacts for chroma
planes with no grain points, matching what libdav1d already reports for
the same streams.

No new FATE samples are required.


>From 914eb353ee436b2d9bc9e0e10cedb62d49498b96 Mon Sep 17 00:00:00 2001
From: luoye-cpu <[email protected]>
Date: Sat, 7 Nov 2026 01:08:46 +0800
Subject: [PATCH] avcodec/av1dec: do not subtract the chroma coding bias
 without points

export_film_grain() subtracts the coding bias from cb_mult, cb_luma_mult
and cb_offset (and the Cr equivalents) unconditionally.  The bitstream
only codes these fields when the corresponding plane has at least one
grain point: cbs_av1_syntax_template.c reads cb_mult, cb_luma_mult and
cb_offset under "if (num_cb_points)" and the Cr counterparts under
"if (num_cr_points)", and infers both point counts to zero for a 4:2:0
stream with no luma grain points, or whenever chroma_scaling_from_luma
is set.

When the fields are not coded they stay at zero, so the subtraction
exports uv_mult = -128, uv_mult_luma = -128 and uv_offset = -256 for a
plane the stream never described.  AVFilmGrainAOMParams carries
unbiased values: film_grain_params.h documents uv_offset as a 9-bit
value with a range [-256, 255], and consumers treat the fields as
signed multipliers and offsets - add_noise_uv() in
aom_film_grain_template.c applies them without checking num_uv_points,
so any consumer of the exported side data that does not re-check the
point count scales chroma noise on a plane that has no grain.

The same stream therefore exports different parameters depending on the
decoder: dav1d leaves the unparsed fields at zero and libdav1d copies
them as-is, so libdav1d exports 0 while av1dec exports -128/-128/-256.
2a8c6d30b9 ("avcodec/av1dec: remove the coding bias from exported
chroma grain params") set out to remove exactly this decoder-to-decoder
disagreement and left the no-points case behind.

Guard the two chroma assignments on aom->num_uv_points[0] and
aom->num_uv_points[1]; skipping the assignments keeps the fields at
zero, which is what an unparsed field means and what libdav1d exports.

Signed-off-by: luoye-cpu <[email protected]>
---
 libavcodec/av1dec.c | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/libavcodec/av1dec.c b/libavcodec/av1dec.c
index 315828e18a4..608403314a2 100644
--- a/libavcodec/av1dec.c
+++ b/libavcodec/av1dec.c
@@ -1128,12 +1128,16 @@ static int export_film_grain(AVCodecContext *avctx, 
AVFrame *frame)
         aom->ar_coeffs_uv[1][i] = film_grain->ar_coeffs_cr_plus_128[i] - 128;
     }
 
-    aom->uv_mult[0] = film_grain->cb_mult - 128;
-    aom->uv_mult[1] = film_grain->cr_mult - 128;
-    aom->uv_mult_luma[0] = film_grain->cb_luma_mult - 128;
-    aom->uv_mult_luma[1] = film_grain->cr_luma_mult - 128;
-    aom->uv_offset[0] = film_grain->cb_offset - 256;
-    aom->uv_offset[1] = film_grain->cr_offset - 256;
+    if (aom->num_uv_points[0]) {
+        aom->uv_mult[0] = film_grain->cb_mult - 128;
+        aom->uv_mult_luma[0] = film_grain->cb_luma_mult - 128;
+        aom->uv_offset[0] = film_grain->cb_offset - 256;
+    }
+    if (aom->num_uv_points[1]) {
+        aom->uv_mult[1] = film_grain->cr_mult - 128;
+        aom->uv_mult_luma[1] = film_grain->cr_luma_mult - 128;
+        aom->uv_offset[1] = film_grain->cr_offset - 256;
+    }
 
     return 0;
 }
-- 
2.54.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to