CNA-BUG-146: VideoDecoder converts every YUV frame with one full-range BT.601 matrix: limited-range and BT.709 video decode with wrong levels and colours

CNA snapshot 009d40f5  ·  Known Issues › Current bugs  ·  source links pinned to 009d40f5

✓

Evidence basis: source-verified at the pinned commit; executed for this entry (the Evidence section names exactly what was run); tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit 009d40f5; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.

The planar and NV12 converters ignore the frame's colour range and matrix, so ordinary limited-range video keeps black at 16 and white at 235, and BT.709 content is decoded with BT.601 coefficients.

Identifier
CNA-BUG-146
Category
Bug
Subsystem
Audio & media
Status
Open
Verified against
CNA 009d40f5 (009d40f5dd085c4e674d3479675fac84b12b3e0a)
Severity
Medium (a triage suggestion, not a project priority)
Evidence basis
Reproduced: executed for this entry (the Evidence section names exactly what was run)
Tests touching this area
Yes: see Current tests
Affected contract
VideoPlayer::GetTexture() frame contents (CNA::Internal::Media::VideoDecoder::NextFrame RGBA output)

Expected behaviour

YUV-to-RGB conversion honours the frame's declared range and matrix: limited (“tv”) range expands luma 16–235 and chroma 16–240 to full scale, and BT.709 content, the norm for HD video, uses BT.709 coefficients. FFmpeg reports both on every frame (AVFrame::color_range, AVFrame::colorspace).

Actual behaviour at TARGET

yuv_planar8_to_rgba and yuv_planar16_to_rgba in VideoDecoder.cpp compute R = Y + 359·V/256, G = Y − (88·U + 183·V)/256, B = Y + 454·U/256 with U and V centred on 128 and Y used as is: the full-range BT.601 matrix (the code comment says so). ConvertFrameToRGBA routes YUV420P/422P/444P and their YUVJ variants, and the 10- and 12-bit formats after a shift to 8 bits, to these functions; the NV12 branch of generic_to_rgba uses the same coefficients. Neither range nor matrix is read. For a limited-range frame, black (Y=16) decodes to RGB 16,16,16 and white (Y=235) to 235,235,235, so contrast drops by about 14% and blacks are grey; BT.709 content additionally gets BT.601 hue and saturation. Only full-range BT.601 video (the YUVJ formats) comes out right.

Source locations

Evidence

Checked by reading at 009d40f5; the black and white values above are the formula evaluated by hand at U=V=128, not a run (the executed measurement follows below). The fixtures are ffmpeg smptebars patterns whose references (about 190 for a 75% bar) were sampled after a converting decode; with the missing expansion a limited-range 75% white would come out near 180, inside the tests' ±20 window but outside the ±10 the manifest recommends.

Independent re-verification: Reproduced by the audit at 009d40f5. The TARGET VideoDecoder.cpp was built with g++ -O0 against system FFmpeg 7.1 and run on a synthetic limited-range BT.709 clip: black decoded to (16,16,16), white to about (233,236,235), full-scale red to (199,0,17), where a range- and matrix-aware conversion gives 0, 255 and (255,0,0). The TARGET fixture chroma_420.mkv is tagged tv range and bt470bg; its 75% white bar decodes to (180,180,180) and its black bars to 12-15, against about 190 and 0 from ffmpeg's own conversion, which is inside the tests' +/-20 window but outside the +/-10 the manifest recommends. The formula constants are the full-range BT.601 ones. FNA's replaced YUVToRGBA effect subtracts 16/256 and applies the 1.164 factor with fixed BT.709 coefficients, so it expands limited range where CNA does not.

Focused reproduction

No focused reproduction is known. Nothing has been invented here; the evidence above is what exists.

Current tests

VideoDecoderTests.cpp samples one frame of colour bars per pixel format with a ±20 tolerance; there is no full-black or full-white patch, no explicitly tagged range, and no BT.709 fixture.

Regression test

Fixtures encoded with explicit -color_range tv/pc and -colorspace bt601/bt709, each with 0% and 100% patches and primaries; assert within ±3 of the expected RGB.

Blast radius

Every FFmpeg-backed video whose frames are limited range (most H.264, VP9 and AV1 files) or BT.709. The texture pipeline after decoding is unaffected.

Workaround

Encode game videos as full-range BT.601 (for example yuvj420p), which the converter handles correctly.

The same subject is explained at several altitudes. These are the neighbouring pages at each one.

Known issues
Bug index