mirror of
https://github.com/freemkv/libfreemkv.git
synced 2026-10-07 18:13:10 +00:00
bdnav: BC compare per libbluray; labels: camelCase split, deluxe master enums and Rc stack names; ifo: warn on multi-PGC titles; h264/hevc: param sets lead the AU in hierarchy order
This commit is contained in:
+7
-6
@@ -226,7 +226,7 @@ fn run(vm: &mut Vm, mut obj_id: usize, is_feature: &dyn Fn(u16) -> bool) -> Opti
|
||||
// CMP — skip the next command when the compare is false.
|
||||
1 => {
|
||||
let truth = match c.cmp_opt {
|
||||
0x01 => (src & !dst) == 0, // BC: every src bit set in dst
|
||||
0x01 => (dst & !src) == 0, // BC: every dst bit set in src (hdmv_vm.c)
|
||||
0x02 => dst == src,
|
||||
0x03 => dst != src,
|
||||
0x04 => dst >= src,
|
||||
@@ -625,12 +625,13 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
// libbluray INSN_BC: true iff every bit of src is set in dst (src & ~dst == 0).
|
||||
// libbluray INSN_BC skips the next command when `dst & ~src` is non-zero:
|
||||
// true iff every bit of dst is set in src.
|
||||
#[test]
|
||||
fn cmp_bc_tests_src_bits_within_dst() {
|
||||
assert!(cmp_picks_true(0x01, 0b1110, 0b0110));
|
||||
assert!(!cmp_picks_true(0x01, 0b0100, 0b0110));
|
||||
assert!(!cmp_picks_true(0x01, 0b0110, 0b1110));
|
||||
fn cmp_bc_tests_dst_bits_within_src() {
|
||||
assert!(!cmp_picks_true(0x01, 0b1110, 0b0110));
|
||||
assert!(cmp_picks_true(0x01, 0b0100, 0b0110));
|
||||
assert!(cmp_picks_true(0x01, 0b0110, 0b1110));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+69
@@ -815,6 +815,43 @@ fn ptt_srpt_pgc_index(data: &[u8], ptt_offset: usize, ttn: u8) -> Option<usize>
|
||||
pgcn.checked_sub(1)
|
||||
}
|
||||
|
||||
// Distinct PGCs title `ttn`'s part-of-title entries point at. A title split across
|
||||
// PGCs (one per chapter) is read from its first PGC only; the caller warns on > 1.
|
||||
fn ptt_srpt_pgc_count(data: &[u8], ptt_offset: usize, ttn: u8) -> usize {
|
||||
let entries = || -> Option<Vec<u16>> {
|
||||
let count = be_u16(data, ptt_offset).ok()? as usize;
|
||||
let last_byte = be_u32(data, ptt_offset + 4).ok()? as usize;
|
||||
if ttn as usize > count {
|
||||
return None;
|
||||
}
|
||||
let at = |t: usize| {
|
||||
be_u32(data, ptt_offset + 8 + (t - 1) * 4)
|
||||
.ok()
|
||||
.map(|r| r as usize)
|
||||
};
|
||||
let start = at(ttn as usize)?;
|
||||
let end = if (ttn as usize) < count {
|
||||
at(ttn as usize + 1)?
|
||||
} else {
|
||||
last_byte.checked_add(1)?
|
||||
};
|
||||
let mut pgcns = Vec::new();
|
||||
let mut rel = start;
|
||||
while rel.checked_add(4)? <= end {
|
||||
pgcns.push(be_u16(data, ptt_offset.checked_add(rel)?).ok()?);
|
||||
rel += 4;
|
||||
}
|
||||
Some(pgcns)
|
||||
};
|
||||
if ptt_offset == 0 || ttn == 0 {
|
||||
return 0;
|
||||
}
|
||||
let mut pgcns = entries().unwrap_or_default();
|
||||
pgcns.sort_unstable();
|
||||
pgcns.dedup();
|
||||
pgcns.len()
|
||||
}
|
||||
|
||||
/// Parse VTS_PGCIT (Program Chain Information Table) to extract titles.
|
||||
/// `ptt_offset` is the VTS_PTT_SRPT byte offset (0 = absent).
|
||||
pub(crate) fn parse_pgcit(
|
||||
@@ -855,6 +892,16 @@ pub(crate) fn parse_pgcit(
|
||||
continue;
|
||||
}
|
||||
|
||||
let pgcs = ptt_srpt_pgc_count(data, ptt_offset, vts_title_num);
|
||||
if pgcs > 1 {
|
||||
tracing::warn!(
|
||||
target: "freemkv::scan",
|
||||
vts_title_num,
|
||||
pgcs,
|
||||
"title spans several PGCs; only its first PGC is read, the rest of the title is missing"
|
||||
);
|
||||
}
|
||||
|
||||
let entry_offset = entries_start + pgc_index * 8;
|
||||
if entry_offset + 8 > data.len() {
|
||||
skipped += 1;
|
||||
@@ -2705,6 +2752,28 @@ mod tests {
|
||||
d
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ptt_srpt_counts_the_pgcs_a_title_spans() {
|
||||
// Title 1: one PTT per PGC (1, 2, 3); title 2: two PTTs in PGC 4.
|
||||
let entries: [(u16, u16); 5] = [(1, 1), (2, 1), (3, 1), (4, 1), (4, 2)];
|
||||
let total = 8 + 2 * 4 + entries.len() * 4;
|
||||
let mut d = vec![0u8; total];
|
||||
d[0..2].copy_from_slice(&2u16.to_be_bytes());
|
||||
d[4..8].copy_from_slice(&((total - 1) as u32).to_be_bytes());
|
||||
d[8..12].copy_from_slice(&16u32.to_be_bytes());
|
||||
d[12..16].copy_from_slice(&28u32.to_be_bytes());
|
||||
for (i, (pgcn, pgn)) in entries.iter().enumerate() {
|
||||
d[16 + i * 4..18 + i * 4].copy_from_slice(&pgcn.to_be_bytes());
|
||||
d[18 + i * 4..20 + i * 4].copy_from_slice(&pgn.to_be_bytes());
|
||||
}
|
||||
assert_eq!(ptt_srpt_pgc_count(&d, 0, 1), 0);
|
||||
let mut buf = vec![0u8; 4];
|
||||
buf.extend_from_slice(&d);
|
||||
assert_eq!(ptt_srpt_pgc_count(&buf, 4, 1), 3);
|
||||
assert_eq!(ptt_srpt_pgc_count(&buf, 4, 2), 1);
|
||||
assert_eq!(ptt_srpt_pgc_count(&buf, 4, 3), 0);
|
||||
}
|
||||
|
||||
/// TTN -> PGC goes through VTS_PTT_SRPT, not "TTN N is PGCIT entry N"; the PGCIT
|
||||
/// sits at a NON-ZERO offset so the pgcit base term is exercised, and the real TTN
|
||||
/// is stamped on each title (disc/dvd.rs joins on it).
|
||||
|
||||
+38
-11
@@ -22,6 +22,7 @@ use super::{LabelPurpose, LabelQualifier, ParseResult, StreamLabel, StreamLabelT
|
||||
use crate::sector::SectorSource;
|
||||
use crate::udf::UdfFs;
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use std::rc::Rc;
|
||||
|
||||
pub fn detect(reader: &mut dyn SectorSource, udf: &UdfFs) -> bool {
|
||||
// Real signal is `com/bydeluxe/` in a jar's central directory: a cheap
|
||||
@@ -548,6 +549,13 @@ fn find_binding_classes(
|
||||
const MIN_GETSTATIC: usize = 4;
|
||||
let mut candidates: Vec<(String, usize)> = Vec::new();
|
||||
jar::for_each_class_budgeted(archive, budget, |class_name, class| {
|
||||
// A master enum's own <clinit> reads every constant into $VALUES; it is not a binding.
|
||||
if class
|
||||
.this_class_name()
|
||||
.is_some_and(|n| master_enum_classes.contains(n))
|
||||
{
|
||||
return;
|
||||
}
|
||||
let count = count_master_enum_getstatic(class, master_enum_classes);
|
||||
if count >= MIN_GETSTATIC {
|
||||
candidates.push((class_name.to_string(), count));
|
||||
@@ -618,10 +626,10 @@ pub(crate) enum StackVal {
|
||||
// Reference to org.bluray.ti.CodingType; field name (e.g.
|
||||
// DOLBY_AC3_AUDIO) is the codec id — NOT a Deluxe-internal enum, read
|
||||
// straight from the binding constructor's getstatic operand.
|
||||
CodingType(String),
|
||||
CodingType(Rc<str>),
|
||||
/// An uninitialized `new` object — popped by the matching
|
||||
/// invokespecial.
|
||||
NewObj(String),
|
||||
NewObj(Rc<str>),
|
||||
/// Anything we can't model — stack effect tracked but content
|
||||
/// opaque. Lets the walker stay in sync past loads/computed
|
||||
/// values it doesn't understand.
|
||||
@@ -898,9 +906,9 @@ impl<'a> BindingDecoder<'a> {
|
||||
let class_name = insn
|
||||
.cp_index()
|
||||
.and_then(|i| self.pool.class_name(i))
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
self.push(StackVal::NewObj(class_name));
|
||||
.unwrap_or("");
|
||||
// Rc: `dup` copies the handle, not a name of up to 64 KiB.
|
||||
self.push(StackVal::NewObj(class_name.into()));
|
||||
}
|
||||
// Word-level stack ops (a long/double is one two-word value).
|
||||
POP => drop(self.take_words(insn, 1)),
|
||||
@@ -943,7 +951,7 @@ impl<'a> BindingDecoder<'a> {
|
||||
return;
|
||||
};
|
||||
let val = if m.class_name == BD_CODING_TYPE_CLASS {
|
||||
StackVal::CodingType(m.name.to_string())
|
||||
StackVal::CodingType(m.name.into())
|
||||
} else if let Some((kind, ordinal)) = self.master.resolve(m.class_name, m.name) {
|
||||
StackVal::EnumRef { kind, ordinal }
|
||||
} else {
|
||||
@@ -988,12 +996,12 @@ impl<'a> BindingDecoder<'a> {
|
||||
// Underneath the args: the object the constructor
|
||||
// operates on. For our pattern it's NewObj(X).
|
||||
match self.stack.pop() {
|
||||
Some(StackVal::NewObj(name)) if is_init && name == member.class_name => {
|
||||
Some(StackVal::NewObj(name)) if is_init && *name == *member.class_name => {
|
||||
// Bounded by MAX_CONSTRUCTIONS: an unbounded push here
|
||||
// is ~1 GiB reachable from a crafted `<clinit>`.
|
||||
if !is_container && self.constructions.len() < MAX_CONSTRUCTIONS {
|
||||
self.constructions.push(Construction {
|
||||
binding_type: name,
|
||||
binding_type: name.to_string(),
|
||||
args,
|
||||
});
|
||||
}
|
||||
@@ -1173,9 +1181,9 @@ fn interpret_streams(constructions: &[Construction], master: &MasterEnumTable) -
|
||||
StackVal::CodingType(name) if slot.is_none() => {
|
||||
slot = coding_slot(name);
|
||||
if slot == Some(CodingSlot::Audio) {
|
||||
coding_type = Some(name.clone());
|
||||
coding_type = Some(name.to_string());
|
||||
} else if slot.is_none() {
|
||||
unknown_coding = Some(name.clone());
|
||||
unknown_coding = Some(name.to_string());
|
||||
}
|
||||
}
|
||||
StackVal::Int(n) => {
|
||||
@@ -1997,6 +2005,25 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
// A master enum's own <clinit> getstatics every constant (old-javac $VALUES):
|
||||
// counting it would set the 40% bar and drop the real binding classes.
|
||||
#[test]
|
||||
fn find_binding_classes_skips_the_master_enum_itself() {
|
||||
let master_classes: HashSet<&str> = ["LanguageEnum"].into_iter().collect();
|
||||
let lang = class_with_getstatic_refs("LanguageEnum", "LanguageEnum", 70);
|
||||
let audio = class_with_getstatic_refs("Audio", "LanguageEnum", 6);
|
||||
let subs = class_with_getstatic_refs("Subs", "LanguageEnum", 8);
|
||||
let zip = build_zip(&[
|
||||
("LanguageEnum.class", lang),
|
||||
("Audio.class", audio),
|
||||
("Subs.class", subs),
|
||||
]);
|
||||
let mut archive = open_jar(zip);
|
||||
let candidates = find_binding_classes(&mut archive, &master_classes, &mut unbounded());
|
||||
let names: Vec<&str> = candidates.iter().map(|(n, _)| n.as_str()).collect();
|
||||
assert_eq!(names, vec!["Subs.class", "Audio.class"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_binding_classes_empty_master_set_yields_no_candidates() {
|
||||
let master_classes: HashSet<&str> = HashSet::new();
|
||||
@@ -2623,7 +2650,7 @@ mod tests {
|
||||
);
|
||||
for v in &decoder.stack {
|
||||
match v {
|
||||
StackVal::NewObj(name) => assert_eq!(name, "AudioSlot"),
|
||||
StackVal::NewObj(name) => assert_eq!(&**name, "AudioSlot"),
|
||||
other => panic!("expected NewObj(\"AudioSlot\") x2, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
+13
-2
@@ -188,11 +188,13 @@ pub(crate) fn name_words(s: &str) -> Vec<String> {
|
||||
let mut words = Vec::new();
|
||||
for part in s.split(|c: char| !c.is_ascii_alphabetic()) {
|
||||
let mut word = String::new();
|
||||
// The boundary test reads the source char: `word` holds it lower-cased.
|
||||
let mut prev_lower = false;
|
||||
for c in part.chars() {
|
||||
if c.is_ascii_uppercase() && word.chars().last().is_some_and(|l| l.is_ascii_lowercase())
|
||||
{
|
||||
if c.is_ascii_uppercase() && prev_lower {
|
||||
words.push(std::mem::take(&mut word));
|
||||
}
|
||||
prev_lower = c.is_ascii_lowercase();
|
||||
word.push(c.to_ascii_lowercase());
|
||||
}
|
||||
if !word.is_empty() {
|
||||
@@ -1259,6 +1261,15 @@ pub(crate) fn read_jar_file(
|
||||
mod registry_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn name_words_splits_camel_case_but_not_all_caps() {
|
||||
assert_eq!(name_words("MAIN_FEATURE"), ["main", "feature"]);
|
||||
assert_eq!(name_words("FEATURE_A"), ["feature", "a"]);
|
||||
assert_eq!(name_words("Feature_TRAILER"), ["feature", "trailer"]);
|
||||
assert_eq!(name_words("MainFeature_A"), ["main", "feature", "a"]);
|
||||
assert_eq!(name_words("FeatureHD"), ["feature", "hd"]);
|
||||
}
|
||||
|
||||
fn dir_entry(
|
||||
name: &str,
|
||||
is_dir: bool,
|
||||
|
||||
+34
-12
@@ -212,9 +212,11 @@ impl CodecParser for H264Parser {
|
||||
let mut all_vcl_intra = true;
|
||||
let mut saw_vcl = false;
|
||||
let mut first_pic_done = false;
|
||||
// Did this access unit already carry each param-set type in-band?
|
||||
let mut emitted_sps = false;
|
||||
let mut emitted_pps = false;
|
||||
// In-band redefinitions, held aside so they lead the AU in SPS, PPS order:
|
||||
// a PPS is parsed against the SPS before it, so a re-asserted PPS must
|
||||
// never precede a redefined SPS.
|
||||
let mut inband_sps = Vec::new();
|
||||
let mut inband_pps = Vec::new();
|
||||
// Pre-sized to input length plus PARAM_REASSERT_HEADROOM so the mux hot
|
||||
// path avoids repeated reallocs and the keyframe param-set re-assert
|
||||
// below can be spliced in front without reallocating (see that site).
|
||||
@@ -232,12 +234,10 @@ impl CodecParser for H264Parser {
|
||||
// in-band on any change. In MVC passthrough these fall through to
|
||||
// the default arm so subset SPS/PPS stay in-band (self-contained AU).
|
||||
NAL_SPS if !mvc => {
|
||||
emitted_sps |=
|
||||
handle_param_set(&mut self.sps, &mut self.cur_sps, nal, &mut frame_data)
|
||||
handle_param_set(&mut self.sps, &mut self.cur_sps, nal, &mut inband_sps);
|
||||
}
|
||||
NAL_PPS if !mvc => {
|
||||
emitted_pps |=
|
||||
handle_param_set(&mut self.pps, &mut self.cur_pps, nal, &mut frame_data)
|
||||
handle_param_set(&mut self.pps, &mut self.cur_pps, nal, &mut inband_pps);
|
||||
}
|
||||
// Access unit delimiters: drop. Matroska H.264 frame data omits
|
||||
// AUDs (the container delimits access units), so keeping them
|
||||
@@ -278,7 +278,7 @@ impl CodecParser for H264Parser {
|
||||
}
|
||||
}
|
||||
|
||||
if frame_data.is_empty() {
|
||||
if frame_data.is_empty() && inband_sps.is_empty() && inband_pps.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
@@ -291,11 +291,17 @@ impl CodecParser for H264Parser {
|
||||
|
||||
// Every keyframe is self-contained: re-assert active SPS/PPS in-band so a
|
||||
// decoder that dropped the set recovers, and a stale avcC re-apply can't
|
||||
// revert it. Skipped per-type only when this AU already carried it.
|
||||
if keyframe && !mvc {
|
||||
// revert it. A type this AU redefined goes in-band instead, in its place.
|
||||
{
|
||||
let reassert = keyframe && !mvc;
|
||||
let mut prefix = Vec::with_capacity(PARAM_REASSERT_HEADROOM);
|
||||
reassert_active(&mut prefix, &self.cur_sps, emitted_sps);
|
||||
reassert_active(&mut prefix, &self.cur_pps, emitted_pps);
|
||||
for (inband, cur) in [(&inband_sps, &self.cur_sps), (&inband_pps, &self.cur_pps)] {
|
||||
if !inband.is_empty() {
|
||||
prefix.extend_from_slice(inband);
|
||||
} else if reassert {
|
||||
reassert_active(&mut prefix, cur, false);
|
||||
}
|
||||
}
|
||||
if !prefix.is_empty() {
|
||||
// Splice prefix into frame_data in place, sized via
|
||||
// PARAM_REASSERT_HEADROOM to avoid the extra whole-frame alloc+copy
|
||||
@@ -1744,6 +1750,22 @@ mod tests {
|
||||
types
|
||||
}
|
||||
|
||||
// An IDR whose SPS changed but whose PPS did not: the re-asserted PPS must follow
|
||||
// the new SPS (§7.3.2.2: a PPS is parsed against its SPS).
|
||||
#[test]
|
||||
fn reasserted_pps_follows_a_redefined_sps() {
|
||||
let mut parser = H264Parser::new();
|
||||
let au = |sps: u8| {
|
||||
let mut d = vec![0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x1E, sps];
|
||||
d.extend_from_slice(&[0x00, 0x00, 0x01, 0x68, 0x11]);
|
||||
d.extend_from_slice(&[0x00, 0x00, 0x01, 0x65, 0x10, 0x20]);
|
||||
d
|
||||
};
|
||||
parser.parse(&make_pes(au(0xAA), Some(0)));
|
||||
let f = parser.parse(&make_pes(au(0xAB), Some(90000)));
|
||||
assert_eq!(frame_nal_types(&f[0].data), vec![7, 8, 5]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keyframes_self_contained_and_redefinition_emitted() {
|
||||
let mut parser = H264Parser::new();
|
||||
|
||||
+65
-17
@@ -449,9 +449,11 @@ impl CodecParser for HevcParser {
|
||||
// Track whether THIS access unit already carried each param-set type
|
||||
// in-band (a redefinition vs codecPrivate). Used after the scan to
|
||||
// re-assert the active set at a keyframe the source left bare.
|
||||
let mut emitted_vps = false;
|
||||
let mut emitted_sps = false;
|
||||
let mut emitted_pps = false;
|
||||
// Redefinitions are held aside so they lead the AU in VPS, SPS, PPS order: a
|
||||
// re-asserted PPS ahead of a redefined SPS is dropped by the decoder.
|
||||
let mut inband_vps = Vec::new();
|
||||
let mut inband_sps = Vec::new();
|
||||
let mut inband_pps = Vec::new();
|
||||
// Pre-sized to input length plus PARAM_REASSERT_HEADROOM: UHD frames are
|
||||
// 150-300 KB and an unsized Vec otherwise reallocs 5-7x per frame; the
|
||||
// headroom also lets the keyframe param-set re-assert splice in without reallocating.
|
||||
@@ -493,20 +495,20 @@ impl CodecParser for HevcParser {
|
||||
|
||||
match nal_type {
|
||||
NAL_VPS => {
|
||||
emitted_vps |= handle_param_set(
|
||||
handle_param_set(
|
||||
&mut self.vps,
|
||||
&mut self.cur_vps,
|
||||
&data[nal_start..end],
|
||||
&mut frame_data,
|
||||
)
|
||||
&mut inband_vps,
|
||||
);
|
||||
}
|
||||
NAL_SPS => {
|
||||
emitted_sps |= handle_param_set(
|
||||
handle_param_set(
|
||||
&mut self.sps,
|
||||
&mut self.cur_sps,
|
||||
&data[nal_start..end],
|
||||
&mut frame_data,
|
||||
)
|
||||
&mut inband_sps,
|
||||
);
|
||||
}
|
||||
NAL_PPS => {
|
||||
// Store the PPS under its own id so a later slice's
|
||||
@@ -519,12 +521,12 @@ impl CodecParser for HevcParser {
|
||||
{
|
||||
*slot = hevc_num_extra_slice_header_bits(pps);
|
||||
}
|
||||
emitted_pps |= handle_param_set(
|
||||
handle_param_set(
|
||||
&mut self.pps,
|
||||
&mut self.cur_pps,
|
||||
&data[nal_start..end],
|
||||
&mut frame_data,
|
||||
)
|
||||
&mut inband_pps,
|
||||
);
|
||||
}
|
||||
// Drop Access Unit Delimiters: Matroska HEVC frame data
|
||||
// omits AUDs (the container delimits access units), so
|
||||
@@ -569,18 +571,31 @@ impl CodecParser for HevcParser {
|
||||
}
|
||||
}
|
||||
|
||||
if frame_data.is_empty() {
|
||||
if frame_data.is_empty()
|
||||
&& inband_vps.is_empty()
|
||||
&& inband_sps.is_empty()
|
||||
&& inband_pps.is_empty()
|
||||
{
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
// A player re-applies hvcC param sets at every keyframe; if the active
|
||||
// set was redefined mid-title and the source stopped repeating it, the
|
||||
// reversion desyncs CABAC. Re-assert in-band at every keyframe (self-heals).
|
||||
if keyframe {
|
||||
// A type this AU redefined goes in-band in its place instead.
|
||||
{
|
||||
let mut prefix = Vec::with_capacity(PARAM_REASSERT_HEADROOM);
|
||||
reassert_active(&mut prefix, &self.cur_vps, emitted_vps);
|
||||
reassert_active(&mut prefix, &self.cur_sps, emitted_sps);
|
||||
reassert_active(&mut prefix, &self.cur_pps, emitted_pps);
|
||||
for (inband, cur) in [
|
||||
(&inband_vps, &self.cur_vps),
|
||||
(&inband_sps, &self.cur_sps),
|
||||
(&inband_pps, &self.cur_pps),
|
||||
] {
|
||||
if !inband.is_empty() {
|
||||
prefix.extend_from_slice(inband);
|
||||
} else if keyframe {
|
||||
reassert_active(&mut prefix, cur, false);
|
||||
}
|
||||
}
|
||||
if !prefix.is_empty() {
|
||||
// Splice prefix into frame_data in place via PARAM_REASSERT_HEADROOM,
|
||||
// avoiding the extra whole-frame alloc+copy per keyframe the old
|
||||
@@ -2657,6 +2672,39 @@ mod tests {
|
||||
|
||||
// --- parameter-set redefinition (mid-title redefinition bug) ---
|
||||
|
||||
// An IRAP whose SPS changed but whose PPS did not: the re-asserted PPS must follow
|
||||
// the new SPS, or the decoder parses it against the old one and drops it.
|
||||
#[test]
|
||||
fn reasserted_pps_follows_a_redefined_sps() {
|
||||
let mut parser = HevcParser::new();
|
||||
let nal = |t: u8, body: u8| {
|
||||
let mut v = vec![0x00, 0x00, 0x01];
|
||||
v.extend_from_slice(&hevc_nal_header(t));
|
||||
v.extend_from_slice(&[body, body]);
|
||||
v
|
||||
};
|
||||
let au = |sps: u8| {
|
||||
let mut d = nal(32, 0x11); // VPS
|
||||
d.extend(nal(33, sps)); // SPS
|
||||
d.extend(nal(34, 0x33)); // PPS
|
||||
d.extend(nal(19, 0x44)); // IDR_W_RADL
|
||||
d
|
||||
};
|
||||
let types = |fd: &[u8]| {
|
||||
let (mut t, mut o) = (Vec::new(), 0usize);
|
||||
while o + 4 <= fd.len() {
|
||||
let len = u32::from_be_bytes([fd[o], fd[o + 1], fd[o + 2], fd[o + 3]]) as usize;
|
||||
o += 4;
|
||||
t.push((fd[o] >> 1) & 0x3F);
|
||||
o += len;
|
||||
}
|
||||
t
|
||||
};
|
||||
parser.parse(&make_pes(au(0x22), Some(0)));
|
||||
let f = parser.parse(&make_pes(au(0x23), Some(1)));
|
||||
assert_eq!(types(&f[0].data), vec![32, 33, 34, 19]);
|
||||
}
|
||||
|
||||
// A parameter set REDEFINED mid-stream must be emitted INLINE so the decoder re-activates
|
||||
// it.
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user