mirror of
https://github.com/freemkv/libfreemkv.git
synced 2026-10-07 18:13:10 +00:00
tests: header gate rebase and subtitle, fvi header provenance, hvcC cut arrays, FMKV stream fields, json mvc and cue times
This commit is contained in:
@@ -338,6 +338,24 @@ mod tests {
|
||||
assert_eq!(n, [0, 1]);
|
||||
}
|
||||
|
||||
// Known provenance (playlist, volume id) and picture count are carried into the header.
|
||||
#[test]
|
||||
fn header_carries_playlist_volume_id_and_picture_count_when_known() {
|
||||
let source = SourceInfo {
|
||||
playlist: "00800.mpls".into(),
|
||||
volume_id: "DISC_LABEL".into(),
|
||||
..SourceInfo::default()
|
||||
};
|
||||
let mut header = MapHeader::from_title(&mpeg2_title(), source);
|
||||
header.picture_count = Some(7);
|
||||
let mut out = Vec::new();
|
||||
write_fvi_header(&mut out, &header).unwrap();
|
||||
let v: serde_json::Value = serde_json::from_slice(&out).unwrap();
|
||||
assert_eq!(v["source"]["playlist"], "00800.mpls");
|
||||
assert_eq!(v["source"]["volume_id"], "DISC_LABEL");
|
||||
assert_eq!(v["picture_count"], 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sink_writes_header_and_only_video_records() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
|
||||
@@ -183,6 +183,32 @@ mod tests {
|
||||
assert!(waiting(&g));
|
||||
}
|
||||
|
||||
// After a forward discontinuity the clock rebases: normal PTS past the jump counts again.
|
||||
#[test]
|
||||
fn the_clock_runs_again_after_a_forward_pts_jump() {
|
||||
let mut g = HeaderGate::default();
|
||||
for pts in [0, S, 100 * S, 101 * S, 102 * S, 103 * S, 104 * S] {
|
||||
g.account(0, pts, 0);
|
||||
}
|
||||
assert!(!waiting(&g), "5 s of content on either side of the jump");
|
||||
}
|
||||
|
||||
// A subtitle track has no codec_private and never holds the headers.
|
||||
#[test]
|
||||
fn subtitle_tracks_never_gate() {
|
||||
let mut t = DiscTitle::empty();
|
||||
t.streams
|
||||
.push(Stream::Subtitle(crate::disc::SubtitleStream {
|
||||
pid: 0x1200,
|
||||
codec: Codec::Pgs,
|
||||
language: "eng".into(),
|
||||
forced: false,
|
||||
qualifier: crate::disc::LabelQualifier::None,
|
||||
codec_data: None,
|
||||
}));
|
||||
assert!(HeaderGate::default().ready(&t, |_| None));
|
||||
}
|
||||
|
||||
// Decode-order reordering (B-frames) must not inflate elapsed time.
|
||||
#[test]
|
||||
fn reordered_pts_count_once() {
|
||||
|
||||
@@ -495,6 +495,23 @@ mod tests {
|
||||
assert_eq!(&out[..], &[0x00, 0x00, 0x00, 0x01, 0x40, 0x01]);
|
||||
}
|
||||
|
||||
// An hvcC ending 1-2 bytes into the next array header is truncated, not read past its end.
|
||||
#[test]
|
||||
fn hvcc_cut_inside_an_array_header_is_truncated_not_out_of_bounds() {
|
||||
for tail in [&[32u8][..], &[32, 0]] {
|
||||
let mut hvcc = vec![0u8; 22];
|
||||
hvcc.push(2); // numArrays = 2
|
||||
hvcc.push(32);
|
||||
hvcc.extend_from_slice(&1u16.to_be_bytes());
|
||||
hvcc.extend_from_slice(&2u16.to_be_bytes());
|
||||
hvcc.extend_from_slice(&[0x40, 0x01]);
|
||||
hvcc.extend_from_slice(tail);
|
||||
let (out, truncated) = hvcc_parse(&hvcc);
|
||||
assert!(truncated, "tail {tail:?}");
|
||||
assert_eq!(out.unwrap(), [0x00, 0x00, 0x00, 0x01, 0x40, 0x01]);
|
||||
}
|
||||
}
|
||||
|
||||
// --- length_prefixed_to_annex_b additional branches ---
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -675,6 +675,94 @@ mod tests {
|
||||
assert_eq!(cursor.position() as usize % BD_SOURCE_PACKET_BYTES, 0);
|
||||
}
|
||||
|
||||
// Every stream field the receiver acts on survives an FMKV hop (network://, stdio://, m2ts://).
|
||||
#[test]
|
||||
fn stream_fields_round_trip_through_the_header() {
|
||||
use crate::disc::{
|
||||
AudioChannels, AudioStream, LabelPurpose, LabelQualifier, MVC_DEPENDENT_LABEL,
|
||||
SampleRate, SubtitleStream,
|
||||
};
|
||||
let mut t = video_title(HdrFormat::Hdr10, ColorSpace::Bt2020);
|
||||
if let Stream::Video(v) = &mut t.streams[0] {
|
||||
v.label = MVC_DEPENDENT_LABEL.into();
|
||||
v.secondary = true;
|
||||
}
|
||||
for (i, purpose) in [
|
||||
LabelPurpose::Commentary,
|
||||
LabelPurpose::Descriptive,
|
||||
LabelPurpose::Score,
|
||||
LabelPurpose::Ime,
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
t.streams.push(Stream::Audio(AudioStream {
|
||||
pid: 0x1100 + i as u16,
|
||||
codec: Codec::Dts,
|
||||
channels: AudioChannels::Surround71,
|
||||
language: "fra".into(),
|
||||
sample_rate: SampleRate::S96,
|
||||
secondary: true,
|
||||
purpose,
|
||||
label: format!("audio {i}"),
|
||||
}));
|
||||
}
|
||||
for qualifier in [
|
||||
LabelQualifier::Sdh,
|
||||
LabelQualifier::DescriptiveService,
|
||||
LabelQualifier::Forced,
|
||||
] {
|
||||
t.streams.push(Stream::Subtitle(SubtitleStream {
|
||||
pid: 0x1200,
|
||||
codec: Codec::Pgs,
|
||||
language: "deu".into(),
|
||||
forced: true,
|
||||
qualifier,
|
||||
codec_data: None,
|
||||
}));
|
||||
}
|
||||
let back = M2tsMeta::from_title(&t).to_title();
|
||||
let Stream::Video(v) = &back.streams[0] else {
|
||||
panic!("video first")
|
||||
};
|
||||
assert!(v.is_mvc_dependent());
|
||||
let dbg = |s: &crate::disc::Stream| format!("{s:?}");
|
||||
let (want, got): (Vec<_>, Vec<_>) = t
|
||||
.streams
|
||||
.iter()
|
||||
.zip(&back.streams)
|
||||
.map(|(a, b)| (dbg(a), dbg(b)))
|
||||
.unzip();
|
||||
assert_eq!(got, want);
|
||||
}
|
||||
|
||||
// The wire track is a u8: a header declaring more than 256 streams is refused.
|
||||
#[test]
|
||||
fn a_header_with_more_than_256_streams_is_refused() {
|
||||
let title_of = |n: usize| {
|
||||
let mut t = DiscTitle::empty();
|
||||
for pid in 0..n {
|
||||
t.streams
|
||||
.push(Stream::Subtitle(crate::disc::SubtitleStream {
|
||||
pid: pid as u16,
|
||||
codec: Codec::Pgs,
|
||||
language: String::new(),
|
||||
forced: false,
|
||||
qualifier: crate::disc::LabelQualifier::None,
|
||||
codec_data: None,
|
||||
}));
|
||||
}
|
||||
t
|
||||
};
|
||||
let read = |n| {
|
||||
let mut buf = Vec::new();
|
||||
write_header(&mut buf, &M2tsMeta::from_title(&title_of(n))).unwrap();
|
||||
read_header(&mut io::Cursor::new(buf))
|
||||
};
|
||||
assert!(read(256).unwrap().is_some());
|
||||
assert!(read(257).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audio_and_subtitle_codec_private_round_trip() {
|
||||
use crate::disc::{AudioChannels, AudioStream, LabelPurpose, SampleRate, SubtitleStream};
|
||||
|
||||
@@ -362,6 +362,40 @@ mod tests {
|
||||
assert!(vtt.contains("a & <b>"), "{vtt}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vtt_times_carry_hours_minutes_and_seconds() {
|
||||
assert_eq!(vtt_time(3725.5), "01:02:05.500");
|
||||
assert_eq!(vtt_time(36_000.0), "10:00:00.000");
|
||||
}
|
||||
|
||||
// json:// marks the MVC dependent view and only that.
|
||||
#[test]
|
||||
fn json_marks_the_mvc_dependent_view() {
|
||||
use crate::disc::{
|
||||
Codec, ColorSpace, DiscTitle, FrameRate, HdrFormat, MVC_DEPENDENT_LABEL, Resolution,
|
||||
Stream as DiscStream, VideoStream,
|
||||
};
|
||||
let video = |label: &str| {
|
||||
DiscStream::Video(VideoStream {
|
||||
pid: 0x1011,
|
||||
codec: Codec::H264,
|
||||
resolution: Resolution::R1080p,
|
||||
frame_rate: FrameRate::F23_976,
|
||||
hdr: HdrFormat::Sdr,
|
||||
color_space: ColorSpace::Bt709,
|
||||
display_aspect: None,
|
||||
secondary: false,
|
||||
label: label.into(),
|
||||
measured_cicp: None,
|
||||
})
|
||||
};
|
||||
let mut t = DiscTitle::empty();
|
||||
t.streams = vec![video(""), video(MVC_DEPENDENT_LABEL)];
|
||||
let v = title_json(&t);
|
||||
assert_eq!(v["video"][0]["mvc"], false);
|
||||
assert_eq!(v["video"][1]["mvc"], true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chapters_format_selected_by_extension() {
|
||||
let xml = chapters_content(&chaps(), Some("xml"));
|
||||
|
||||
Reference in New Issue
Block a user