Repository navigation
HLG signalled by an alternative_transfer_characteristics SEI (broadcast HLG) loads as SDR #839
Description
Summary
Broadcast-style HLG plays as SDR on an HLG-capable panel. These sources keep a backward-compatible VUI transfer (BT.2020 10-bit, color_trc 14, or BT.709) so an SDR set still shows a picture, and carry HLG in an alternative_transfer_characteristics SEI (payloadType 147, preferred_transfer_characteristics = 18). That is how BBC iPlayer and other UK broadcast HLG is authored.
detectVideoFormat(stream:) decides on codecpar.color_trc alone, so the source resolves to .sdr. From there the display criteria, the route's VIDEO-RANGE and the colr written into init.mp4 all say SDR, and AVPlayer presents it as SDR. Infuse plays the same file, on the same Apple TV and display, as HLG.
Environment
- AetherEngine 7.40.0, native loopback-HLS route
- Apple TV 4K, tvOS 27.0, display reports
hdr=true hdr10=true hlg=true dv=true - Source: MKV, HEVC Main 10, 3840x2160 @ 25 fps, AAC stereo. Parameter sets in band (23-byte hvcC, rebuilt from in-band VPS/SPS/PPS)
Evidence (device log)
Stock 7.40.0:
[AetherEngine] load url=… source-format=sdr effective-format=sdr rate=25.000
[HLSVideoEngine] prepared: codec=hvc1.2.4.L153.00.00.90,mp4a.40.2 resolution=3840x2160 fps=25.000 range=SDR DV=none …
[NativeAVPlayerHost] #8 item.videoTrack codec='hvc1' … primaries=ITU_R_2020 transfer=ITU_R_709_2 matrix=ITU_R_2020 fullRange=false (readyToPlay)
With a check for SEI 147 before format detection (described below), on the same file:
[AetherEngine] nuvio: alternative transfer SEI signals HLG over a declared trc=14; promoted the stream to HLG (videoPackets=1 live=false)
[AetherEngine] load url=… source-format=hlg effective-format=hlg rate=25.000
[NativeAVPlayerHost] #1 item.videoTrack codec='hvc1' … primaries=ITU_R_2020 transfer=ITU_R_2100_HLG matrix=ITU_R_2020 fullRange=false (readyToPlay)
The panel switches to HLG and the picture matches Infuse. The SEI was in the first video packet.
What we did in our fork (for reference, not a PR)
Right after probe.open in the load probe, before detectVideoFormat runs, the fork does three things:
- Gate: it only looks when all of these hold:
- the codec is HEVC or H.264;
color_primariesis BT.2020;color_trcis one an HLG stream would declare for backward compatibility: BT.709, BT.2020-10, BT.2020-12 or unspecified.
- Peek, without consuming: it reads up to 8 video packets with
Demuxer.peekPackets. At the head of the container these are the packetsfind_stream_infoalready queued, so there is no extra I/O in the common case. - Detect and promote: it walks each packet's prefix SEI NALs (HEVC type 39, H.264 type 6) for payloadType 147. If
preferred_transfer_characteristics == 18, it setscodecpar.color_trc = AVCOL_TRC_ARIB_STD_B67.
Everything downstream then follows the HLG path it already has, because it all reads that codecpar: format detection, display criteria, CodecRoutePolicy's video range, and the muxer's colr from the copied parameters.
This only covers the load probe. makeSourceProbe (the static probe(url:) path) still reports such a source as SDR, and we have not looked at the live-reopen path.
Notes
- A related point, in case it helps: FFmpeg's own HEVC decoder already applies SEI 147 to the frame's
color_trcwhen it decodes. Nothing applies it to the stream'scodecparat probe time, and that is the value the engine uses to choose the route. - The README lists HLG as supported, and it is when the VUI or container says 18. This is the other common way HLG is signalled.
Activity