fix(codecs): in-band parameter sets compare without trailing zero bytes (fixes #1483)
ffmpeg keeps a trailing zero byte after the hvcC PPS that the in-band PPS lacks, so the byte-for-byte comparison read it as a parameter-set change and sent the stream to the software decoder from its first sample. A NAL unit never ends with 0x00 (H.264 7.4.1, H.265 7.4.2): those bytes are trailing_zero_8bits, so both sides are compared without them. The platform fallback test used a trailing zero byte as its changed SPS; it now changes nal_ref_idc.
This commit is contained in:
parent
71438c627b
commit
c1b1e86046
|
|
@ -175,9 +175,10 @@ impl StreamInfo {
|
||||||
/// with (parameter sets, a sequence header, a key frame of another format).
|
/// with (parameter sets, a sequence header, a key frame of another format).
|
||||||
pub fn parameters_changed(&self, sample: &[u8]) -> bool {
|
pub fn parameters_changed(&self, sample: &[u8]) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Nal(i) => {
|
Self::Nal(i) => i.nals(sample).into_iter().any(|n| {
|
||||||
i.nals(sample).into_iter().any(|n| n.first().is_some_and(|&h| i.is_parameter_set(i.nal_type(h))) && !i.parameter_sets.iter().any(|p| p == n))
|
n.first().is_some_and(|&h| i.is_parameter_set(i.nal_type(h)))
|
||||||
}
|
&& !i.parameter_sets.iter().any(|p| without_trailing_zeros(p) == without_trailing_zeros(n))
|
||||||
|
}),
|
||||||
Self::Frame(i) => i.parameters_changed(sample),
|
Self::Frame(i) => i.parameters_changed(sample),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -199,6 +200,14 @@ impl StreamInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A NAL unit without the zero bytes after its last byte: a NAL unit never ends with 0x00
|
||||||
|
/// (H.264 7.4.1, H.265 7.4.2), so they are trailing_zero_8bits some muxers keep (ffmpeg in `hvcC`), not
|
||||||
|
/// part of the parameter set.
|
||||||
|
fn without_trailing_zeros(nal: &[u8]) -> &[u8] {
|
||||||
|
let end = nal.iter().rposition(|&b| b != 0).map_or(0, |i| i + 1);
|
||||||
|
nal.get(..end).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
/// Length-prefixed parameter-set entries (`u16` length + bytes) starting at `pos`.
|
/// Length-prefixed parameter-set entries (`u16` length + bytes) starting at `pos`.
|
||||||
fn read_sets(rec: &[u8], pos: &mut usize, count: usize, out: &mut Vec<Vec<u8>>) -> Result<()> {
|
fn read_sets(rec: &[u8], pos: &mut usize, count: usize, out: &mut Vec<Vec<u8>>) -> Result<()> {
|
||||||
for _ in 0..count {
|
for _ in 0..count {
|
||||||
|
|
@ -402,13 +411,12 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
fn hevc_info(parameter_sets: Vec<Vec<u8>>) -> NalStreamInfo {
|
||||||
fn nal_splitting_is_bounded() {
|
NalStreamInfo {
|
||||||
let info = NalStreamInfo {
|
|
||||||
codec: NalCodec::Hevc,
|
codec: NalCodec::Hevc,
|
||||||
length_size: 4,
|
length_size: 4,
|
||||||
highest_tid: None,
|
highest_tid: None,
|
||||||
parameter_sets: Vec::new(),
|
parameter_sets,
|
||||||
coded: (16, 16),
|
coded: (16, 16),
|
||||||
crop: (0, 0, 16, 16),
|
crop: (0, 0, 16, 16),
|
||||||
chroma_format_idc: 1,
|
chroma_format_idc: 1,
|
||||||
|
|
@ -419,11 +427,33 @@ mod tests {
|
||||||
color: filmcraft_color::ColorInfo::REC709,
|
color: filmcraft_color::ColorInfo::REC709,
|
||||||
par: (1, 1),
|
par: (1, 1),
|
||||||
reorder: 0,
|
reorder: 0,
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nal_splitting_is_bounded() {
|
||||||
|
let info = hevc_info(Vec::new());
|
||||||
// CRA (21) then a length that runs past the end
|
// CRA (21) then a length that runs past the end
|
||||||
let s = [0, 0, 0, 2, 21 << 1, 1, 0xff, 0xff, 0xff, 0xff, 1];
|
let s = [0, 0, 0, 2, 21 << 1, 1, 0xff, 0xff, 0xff, 0xff, 1];
|
||||||
assert_eq!(info.nal_types(&s), vec![21]);
|
assert_eq!(info.nal_types(&s), vec![21]);
|
||||||
assert!(info.is_irap(&s));
|
assert!(info.is_irap(&s));
|
||||||
assert!(!info.is_irap(&[0, 0, 0, 2, 1 << 1, 1]));
|
assert!(!info.is_irap(&[0, 0, 0, 2, 1 << 1, 1]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parameter_sets_compare_without_trailing_zero_bytes() {
|
||||||
|
// the hvcC PPS of an ffmpeg-muxed file and the in-band one of its first sample (#1483)
|
||||||
|
let hvcc_pps = vec![0x44, 0x01, 0xe0, 0xf7, 0xc0, 0x4c, 0x90, 0x00];
|
||||||
|
let info = StreamInfo::Nal(hevc_info(vec![hvcc_pps.clone()]));
|
||||||
|
let sample = |nal: &[u8]| {
|
||||||
|
let mut v = (nal.len() as u32).to_be_bytes().to_vec();
|
||||||
|
v.extend_from_slice(nal);
|
||||||
|
v
|
||||||
|
};
|
||||||
|
assert!(!info.parameters_changed(&sample(&hvcc_pps)), "the same PPS");
|
||||||
|
assert!(!info.parameters_changed(&sample(&hvcc_pps[..7])), "a trailing zero byte is not part of the NAL unit");
|
||||||
|
assert!(!info.parameters_changed(&sample(&[&hvcc_pps[..], &[0, 0]].concat())), "nor are several");
|
||||||
|
assert!(info.parameters_changed(&sample(&hvcc_pps[..6])), "a missing byte that is not zero is a change");
|
||||||
|
assert!(info.parameters_changed(&sample(&[0x44, 0x01, 0xe0, 0xf7, 0xc0, 0x4c, 0x91])), "another PPS is a change");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,9 +97,10 @@ fn changed_in_band_parameter_sets_switch_to_software() {
|
||||||
let out = decode_all(&mut d, &same);
|
let out = decode_all(&mut d, &same);
|
||||||
assert!(d.is_hardware());
|
assert!(d.is_hardware());
|
||||||
assert_same("repeated SPS", &out, &decode_all(filmcraft_codecs::software_video_decoder(&s.entry).unwrap().as_mut(), &same));
|
assert_same("repeated SPS", &out, &decode_all(filmcraft_codecs::software_video_decoder(&s.entry).unwrap().as_mut(), &same));
|
||||||
// a different SPS (same values, trailing zero byte) at the second sync sample: software
|
// a different SPS (same values, another nal_ref_idc: a trailing zero byte is not a change) at
|
||||||
|
// the second sync sample: software
|
||||||
let mut changed_sps = sps.clone();
|
let mut changed_sps = sps.clone();
|
||||||
changed_sps.push(0);
|
changed_sps[0] ^= 0x40;
|
||||||
let k = (1..s.samples.len()).find(|&i| s.sync[i]).unwrap();
|
let k = (1..s.samples.len()).find(|&i| s.sync[i]).unwrap();
|
||||||
let changed: Vec<_> = s.samples.iter().enumerate().map(|(i, (x, p))| (if i == k { with_sps(&changed_sps, x) } else { x.clone() }, *p)).collect();
|
let changed: Vec<_> = s.samples.iter().enumerate().map(|(i, (x, p))| (if i == k { with_sps(&changed_sps, x) } else { x.clone() }, *p)).collect();
|
||||||
let mut d = hybrid(&s, usize::MAX);
|
let mut d = hybrid(&s, usize::MAX);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue