PR #25233 opened by ffmpeg-devel
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25233
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25233.patch

**Backport:** https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24974

With `-export_side_data film_grain`, the native AV1 decoder exports `uv_mult` 
and `uv_mult_luma` 128 too high and `uv_offset` 256 too high. libdav1d exports 
the same stream unbiased. This PR subtracts the coding bias in 
`export_film_grain()`, so both decoders export identical film grain side data.

## Why

The bitstream codes `cb_mult`, `cb_luma_mult` and `cb_offset` (and the Cr 
equivalents) as unsigned values with a bias 
(`cbs_av1_syntax_template.c:1300-1309`). `av1dec.c:1129-1134` copied them into 
`AVFilmGrainAOMParams` unchanged. Everywhere else, the struct holds them 
unbiased:

- `libavutil/film_grain_params.h:108-112` documents `uv_offset` as a 9-bit 
value in [-256, 255].
- The AFGS1 parser stores `get_bits(8) - 128` and `get_bits(9) - 256` 
(`aom_film_grain.c:330-332`).
- The synthesizer uses them as signed multipliers and offsets 
(`aom_film_grain_template.c:373-376`).
- `libdav1d.c:546-551` copies dav1d's values, which dav1d parses as `- 128` / 
`- 256` (dav1d 1.5.4 `src/obu.c:1133-1135`).
- `av1dec.c:1121-1126` already unbiases the AR coefficients in the same 
function.

Any consumer that applies the exported grain therefore scaled chroma noise 
wrong. One example is libplacebo's `pl_map_avframe()`, which copies these 
fields into `pl_av1_grain_data` (`int8_t uv_mult`, `int16_t uv_offset`, 
`libplacebo/utils/libav_internal.h:441-443`).

## How it was tested

The native decoder needs a hwaccel, so this cannot be a FATE test. I decoded 
with VAAPI on an AMD Raphael iGPU (radeonsi, Mesa 26.2.4) and compared every 
field of the `showinfo` film grain line, frame by frame, against libdav1d 1.5.4 
on master `7bc3576910`:

```sh
ffmpeg -export_side_data film_grain -c:v libdav1d -i IN.ivf -vf showinfo -f 
null -
ffmpeg -init_hw_device vaapi=va:/dev/dri/renderD129 -hwaccel vaapi 
-hwaccel_device va \
       -export_side_data film_grain -c:v av1 -i IN.ivf -vf showinfo -f null -
```

| sample | before | after |
| --- | --- | --- |
| `av1-test-vectors/av1-1-b8-23-film_grain-50.ivf` | 6 fields differ on all 10 
frames (`uv_mult[0] = 128` vs `0`) | identical |
| `av1-test-vectors/av1-1-b10-23-film_grain-50.ivf` | same | identical |
| `av1/film_grain.ivf` | same | identical |
| aomenc 3.15.1 with a grain table, raw cb 100/200/300, cr 150/170/200 | 
`uv_mult[0] = 100` vs `-28`, `uv_offset[1] = 200` vs `-56` | identical |

The FATE vectors all code the neutral values (128/192/256). The crafted stream 
adds negative results and distinct Cb/Cr values. `make fate-source` passes.

## Notes

- For a plane without scaling points these fields are not coded, and the 
decoder now exports -128/-256 there instead of 0. Those values are unused, as 
are the AR coefficients the function already exports for such a plane.
- release/9.0 has the same code (`av1dec.c:1090`). The patch applies there 
unchanged, so it is a backport candidate.



>From 6dfaba896fddaea098abec8ac84ff67375a7c6f1 Mon Sep 17 00:00:00 2001
From: lusoris <[email protected]>
Date: Fri, 9 Oct 2026 18:19:54 +0200
Subject: [PATCH] avcodec/av1dec: remove the coding bias from exported chroma
 grain params

export_film_grain() copied cb_mult, cb_luma_mult and cb_offset (and the
Cr equivalents) straight from the bitstream, where they are coded with
a bias of 128, 128 and 256. AVFilmGrainAOMParams holds them unbiased:
the uv_offset documentation gives a range of [-256, 255], the AFGS1
parser in aom_film_grain.c subtracts the bias, the film grain
synthesizer uses the values as signed multipliers and offsets, and
libdav1d exports them unbiased. The AR coefficients a few lines above
are already unbiased the same way.

With -export_side_data film_grain and a hwaccel, the native decoder
therefore exported values 128 or 256 higher than libdav1d for the
same stream, and any consumer applying the exported grain scaled
chroma noise wrong.

Signed-off-by: lusoris <[email protected]>
(cherry picked from commit 2a8c6d30b934f9bd8429f96f605e5fc7c5751679)
---
 libavcodec/av1dec.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/libavcodec/av1dec.c b/libavcodec/av1dec.c
index d614c8d68ab..e4255d0745d 100644
--- a/libavcodec/av1dec.c
+++ b/libavcodec/av1dec.c
@@ -1087,12 +1087,12 @@ 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;
-    aom->uv_mult[1] = film_grain->cr_mult;
-    aom->uv_mult_luma[0] = film_grain->cb_luma_mult;
-    aom->uv_mult_luma[1] = film_grain->cr_luma_mult;
-    aom->uv_offset[0] = film_grain->cb_offset;
-    aom->uv_offset[1] = film_grain->cr_offset;
+    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;
 
     return 0;
 }
-- 
2.54.0

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

Reply via email to