PR #25005 opened by seitzbg
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25005
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25005.patch

# Summary of the bug

When frames carrying `AV_FRAME_DATA_S12M_TIMECODE` reach `hevc_nvenc` (for 
example frames decoded from HEVC with a time_code SEI, payload type 136), the 
default `-s12m_tc 1` inserts a time_code SEI built by `ff_alloc_timecode_sei()` 
in libavcodec/utils.c. That helper reports a fixed 16-byte payload (`*sei_size 
= sizeof(uint32_t) * 4`) in a zeroed buffer and omits the payload termination 
required by H.265 Annex D.2.1. Because it writes no extension data, it should 
append a one bit and zero alignment bits whenever its content is not byte 
aligned. For one timestamp, it writes 43 syntax bits, zero-pads the buffer, and 
reports 16 bytes without the terminating one bit.

Representative source and encoded payloads (not the same frame; the helper 
rebuilds the SEI from the frame's S12M side data, which also resets 
`counting_type`):
```
source:       payload_type 136, payload_size 6:   70 c0 b6 8a 84 10
hevc_nvenc:   payload_type 136, payload_size 16:  70 40 46 aa 84 00 00 00 00 00 
00 00 00 00 00 00
```

Filters that parse the SEI payload reject it:
```
70          time_offset_length[0]                                   00000 = 0
Failed to read unit 1 (type 39): Invalid data found when processing input.
Error applying bitstream filters to a packet: Invalid data found when 
processing input
```

Packets carrying this SEI are rejected by filters that parse its payload, 
including `trace_headers`, `hevc_metadata`, and `dovi_rpu`. In my samples every 
encoded video packet carries it, so a filter like `dovi_rpu=strip=1` after the 
encoder produces no video packets at all. Plex Media Server (1.43.5, in the 
sessions I observed) appends `-bsf:0 dovi_rpu=strip=1` when transcoding Dolby 
Vision sources with `hevc_nvenc`, so those transcodes produce no video when the 
source carries timecode.

In current master and 9.0.2, `hevc_nvenc` is the only encoder using this helper 
(H.264 uses the SDK timecode path and AV1 its own helper). In 6.1, nvenc sent 
every codec through it. Seen with 9.0.2 and a 6.1-based downstream build. The 
helper is unchanged in current master.

# Steps to reproduce

Requires an NVIDIA GPU.

1. Take an input whose decoded frames carry S12M timecode side data, e.g. HEVC 
with a time_code SEI (many UHD Blu-ray remuxes have one).
2. `ffmpeg -i input.mkv -map 0:v -t 10 -c:v hevc_nvenc out.mkv`
3. `ffmpeg -i out.mkv -map 0:v -c copy -bsf:v trace_headers -f null -` fails on 
every packet that carries the SEI. `-bsf:v dovi_rpu=strip=1` drops each of them.
4. Repeat step 2 with `-s12m_tc 0`. The output parses. In one 10-second 
comparison, all 483 compared non-timecode NAL units were byte-identical to the 
`-s12m_tc 1` output.

# Fix

When the content is not byte aligned, write `payload_bit_equal_to_one` and 
zero-pad to a byte boundary. Then set `*sei_size = put_bytes_output(&pb)` so 
the reported size matches what was written. For 1, 2 and 3 timestamps the 
payload becomes 6, 11 and 16 bytes, so the existing 16-byte buffer still fits. 
Also return no SEI for `num_clock_ts == 0`, which the syntax does not allow 
(CBS accepts 1 to 3), as `ff_alloc_timecode_metadata_av1()` already does.

Testing:
- The patched libavcodec/utils.c compiles without warnings (`./configure 
--disable-everything --disable-programs --disable-doc --disable-x86asm 
--disable-autodetect && make libavcodec/utils.o`, gcc 15.2).
- I manually repaired the existing one-timestamp payloads of an affected 
`hevc_nvenc` stream by truncating them to six bytes and inserting the 
terminating one bit, modeling the patch's framing change. `trace_headers` then 
parses them (`bit_equal_to_one = 1`, no error), and `dovi_rpu=strip=1` passes 
240 of 240 packets, against 0 before. This does not exercise the patched 
function itself, multiple timestamps, or the NVENC integration.
- I have not run a full NVENC-enabled build with the patch.

Related background: trac #8622 (2020), a distinct earlier case in which 
NVENC-produced SEIs with trailing bytes broke CBS filters and CBS gained 
reserved_payload_extension_data parsing.



>From ad5360dadf1c11f50fb70f0bbc93aa766dad21ef Mon Sep 17 00:00:00 2001
From: Bryan Seitz <[email protected]>
Date: Sat, 10 Oct 2026 23:47:16 -0400
Subject: [PATCH] avcodec/utils: terminate the timecode SEI payload and report
 its real size

ff_alloc_timecode_sei() always reported a 16-byte payload and never wrote
the payload_bit_equal_to_one / payload_bit_equal_to_zero termination that
H.265 Annex D.2.1 requires. With one clock timestamp the helper writes 43
syntax bits, zero-pads the buffer and reports 16 bytes with no terminating
one bit. Filters that parse the SEI payload (trace_headers, hevc_metadata,
dovi_rpu) reject every packet carrying it with "Failed to read unit".

The helper writes no extension data, so append a one bit and zero
alignment bits whenever the content is not byte aligned, and return the
number of bytes actually written. Also emit nothing for num_clock_ts == 0,
which the syntax does not allow, as ff_alloc_timecode_metadata_av1()
already does.

Signed-off-by: Bryan Seitz <[email protected]>
---
 libavcodec/utils.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/libavcodec/utils.c b/libavcodec/utils.c
index 306696ecc5..ae2e62bd9c 100644
--- a/libavcodec/utils.c
+++ b/libavcodec/utils.c
@@ -1027,6 +1027,10 @@ int ff_alloc_timecode_sei(const AVFrame *frame, 
AVRational rate, size_t prefix_l
     }
     tc =  (uint32_t*)sd->data;
     m  = tc[0] & 3;
+    if (!m) { // num_clock_ts must be 1..3
+        *data = NULL;
+        return 0;
+    }
 
     *sei_size = sizeof(uint32_t) * 4;
     *data = av_mallocz(*sei_size + prefix_len);
@@ -1043,7 +1047,12 @@ int ff_alloc_timecode_sei(const AVFrame *frame, 
AVRational rate, size_t prefix_l
         put_timecode_fields(&pb, rate, tc[j]);
         put_bits(&pb, 5, 0); // time_offset_length
     }
+    if (put_bits_count(&pb) & 7) {
+        put_bits(&pb, 1, 1); // payload_bit_equal_to_one
+        align_put_bits(&pb); // payload_bit_equal_to_zero
+    }
     flush_put_bits(&pb);
+    *sei_size = put_bytes_output(&pb);
 
     return 0;
 }
-- 
2.52.0

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

Reply via email to