demux: solidify sink — reuse canonical primitives, fix 3 bugs
Delete re-implementations in the demux:// sink and wire to proven helpers; keep only genuinely-new functionality. - AnnexB reframing: delete the sink's local length_prefixed_to_annexb (it break'd on a zero-length NAL, dropping the rest of the access unit) and call the canonical append_length_prefixed_as_annex_b in mux::hevc, which skips just the empty NAL. - HEVC param sets: delete hvcc_param_sets; reuse hvcc_to_annex_b. - avcC param sets: hoist as the new canonical avcc_to_annex_b in mux::hevc, next to hvcc_to_annex_b (the symmetry point); the sink calls it. - PGS .sup: emit a synthetic clear display set (empty PCS + END) at pts + duration_ns so subtitles time out instead of lingering to EOF. - TimelineContinuity: move verbatim into the shared mux::timeline module (with the prev_offset straggler-remap intact) and use it from both the MKV muxer and the demux sink; delete the sink's drifted TimelineRebase copy (which lacked the straggler branch). - VobSub .idx: emit the conventional 'id: <lang2>, index: 0' line mkvmerge reads to assign the subtitle language; palette reuse unchanged. - output(): seed DemuxOptions.base from title.playlist when non-empty. New constants for the PGS clear-segment framing and avcC header cite the public HDMV PGS (BD-ROM Part 3) and ISO/IEC 14496-15 specs. Tests: a zero-length NAL mid-frame no longer truncates the AU; a frame with duration_ns produces a .sup clear segment; existing demux tests stay green.
This commit is contained in:
@@ -248,10 +248,105 @@ fn starts_with_start_code(data: &[u8]) -> bool {
|
||||
data.starts_with(&START_CODE) || data.starts_with(&[0x00, 0x00, 0x01])
|
||||
}
|
||||
|
||||
/// Convert an `AVCDecoderConfigurationRecord` (avcC) into Annex B NAL
|
||||
/// units. Returns `Some(bytes)` if at least one NAL was extracted, else
|
||||
/// `None`.
|
||||
///
|
||||
/// Layout (per ISO/IEC 14496-15 §5.3.3.1.2):
|
||||
/// - 5-byte fixed header
|
||||
/// - byte 5 = `[reserved:3 | numOfSequenceParameterSets:5]`
|
||||
/// - `numOfSPS` × `(sequenceParameterSetLength:u16-BE + SPS bytes)`
|
||||
/// - 1 byte = `numOfPictureParameterSets`
|
||||
/// - `numOfPPS` × `(pictureParameterSetLength:u16-BE + PPS bytes)`
|
||||
///
|
||||
/// The H.264 counterpart to [`hvcc_to_annex_b`]: the single source of
|
||||
/// truth for avcC → Annex B across all muxers (H.264 ES, BD-TS, standard
|
||||
/// MPEG-TS, the `demux://` sink). Do not reimplement it.
|
||||
pub(crate) fn avcc_to_annex_b(avcc: &[u8]) -> Option<Vec<u8>> {
|
||||
// avcC fixed header is 5 bytes; byte 5 carries the SPS count (low 5 bits),
|
||||
// then the SPS array begins at byte 6 (ISO/IEC 14496-15 §5.3.3.1.2).
|
||||
const AVCC_HEADER_LEN: usize = 5;
|
||||
const NUM_SPS_MASK: u8 = 0x1F; // numOfSequenceParameterSets: low 5 bits
|
||||
if avcc.len() < AVCC_HEADER_LEN + 1 {
|
||||
return None;
|
||||
}
|
||||
let num_sps = (avcc[AVCC_HEADER_LEN] & NUM_SPS_MASK) as usize;
|
||||
let mut offset = AVCC_HEADER_LEN + 1;
|
||||
let mut out = Vec::new();
|
||||
|
||||
// Extract `count` length-prefixed NALs starting at `*offset` into `out`.
|
||||
// Returns `false` (truncated) if a length field or NAL body runs past the
|
||||
// end — the caller then stops, so it never reads further length fields out
|
||||
// of mid-NAL bytes (mirrors `hvcc_to_annex_b`).
|
||||
fn take(avcc: &[u8], count: usize, offset: &mut usize, out: &mut Vec<u8>) -> bool {
|
||||
for _ in 0..count {
|
||||
if *offset + 2 > avcc.len() {
|
||||
return false;
|
||||
}
|
||||
let nal_len = u16::from_be_bytes([avcc[*offset], avcc[*offset + 1]]) as usize;
|
||||
*offset += 2;
|
||||
if *offset + nal_len > avcc.len() {
|
||||
return false;
|
||||
}
|
||||
// ISO/IEC 14496-15 disallows zero-length NAL entries; emitting a
|
||||
// bare start code with no RBSP yields an invalid Annex B NAL.
|
||||
if nal_len == 0 {
|
||||
continue;
|
||||
}
|
||||
out.extend_from_slice(&START_CODE);
|
||||
out.extend_from_slice(&avcc[*offset..*offset + nal_len]);
|
||||
*offset += nal_len;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
let sps_ok = take(avcc, num_sps, &mut offset, &mut out);
|
||||
if sps_ok && offset < avcc.len() {
|
||||
let num_pps = avcc[offset] as usize;
|
||||
offset += 1;
|
||||
take(avcc, num_pps, &mut offset, &mut out);
|
||||
}
|
||||
|
||||
if out.is_empty() { None } else { Some(out) }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn avcc_extracts_sps_and_pps() {
|
||||
// header(5) numSPS=1 spsLen=2 SPS=[0x67,0x42] numPPS=1 ppsLen=1
|
||||
// PPS=[0x68].
|
||||
let avcc = [
|
||||
1, 0x42, 0x00, 0x1F, 0xFF, 0xE1, 0, 2, 0x67, 0x42, 1, 0, 1, 0x68,
|
||||
];
|
||||
let out = avcc_to_annex_b(&avcc).expect("SPS+PPS");
|
||||
assert_eq!(out, vec![0, 0, 0, 1, 0x67, 0x42, 0, 0, 0, 1, 0x68]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn avcc_too_short_is_none() {
|
||||
assert!(avcc_to_annex_b(&[]).is_none());
|
||||
assert!(avcc_to_annex_b(&[1, 0x42, 0, 0x1F, 0xFF]).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn avcc_truncated_sps_stops_cleanly() {
|
||||
// numSPS=1, declares spsLen=5 but only 2 bytes follow → drop it, and
|
||||
// do NOT misread the trailing bytes as a PPS count.
|
||||
let avcc = [1, 0x42, 0x00, 0x1F, 0xFF, 0xE1, 0, 5, 0xAA, 0xBB];
|
||||
assert!(avcc_to_annex_b(&avcc).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn avcc_skips_zero_length_nal() {
|
||||
// numSPS=1 spsLen=0 (skipped) numPPS=1 ppsLen=1 PPS=[0x68].
|
||||
let avcc = [1, 0x42, 0x00, 0x1F, 0xFF, 0xE1, 0, 0, 1, 0, 1, 0x68];
|
||||
let out = avcc_to_annex_b(&avcc).expect("just the PPS");
|
||||
assert_eq!(out, vec![0, 0, 0, 1, 0x68]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn length_prefixed_converts_to_annex_b() {
|
||||
// Two NALs: [3-byte payload AA BB CC] and [2-byte payload DD EE].
|
||||
|
||||
Reference in New Issue
Block a user