mirror of
https://github.com/freemkv/libfreemkv.git
synced 2026-10-07 18:13:10 +00:00
tsmux/ps/videomap: doc and comment fixes; tests for MPEG audio stream ids and display aspect
This commit is contained in:
+2
-1
@@ -41,7 +41,8 @@ pub struct PsPacket {
|
||||
/// PES stream ID (0xE0 for video, 0xC0 for audio, 0xBD for private, etc.).
|
||||
pub stream_id: u8,
|
||||
/// Sub-stream ID for private stream 1 (AC3: 0x80-0x87, DTS: 0x88-0x8F,
|
||||
/// LPCM: 0xA0-0xA7, subtitles: 0x20-0x3F).
|
||||
/// LPCM: 0xA0-0xA7, E-AC-3: 0xC0-0xCF, subtitles: 0x20-0x3F); for extended stream
|
||||
/// id 0xFD, the PES `stream_id_extension`.
|
||||
pub sub_stream_id: Option<u8>,
|
||||
/// Presentation timestamp in 90kHz ticks.
|
||||
pub pts: Option<u64>,
|
||||
|
||||
+41
-14
@@ -107,8 +107,8 @@ pub struct TsMuxer<W: Write> {
|
||||
/// the borrow of the converted bytes does not conflict with the `&mut self`
|
||||
/// the writer needs.
|
||||
annex_b: Vec<u8>,
|
||||
/// Per-track arrival-time copy of `params_written` for the pre-keyframe drop guard,
|
||||
/// which runs before the start-up hold (design §2.3 tick-domain rule 1).
|
||||
/// Per-track: a video frame has arrived past the pre-keyframe drop guard, which runs
|
||||
/// before the start-up hold (design §2.3 tick-domain rule 1).
|
||||
arrival_armed: Vec<bool>,
|
||||
/// Per video track: the online DTS deriver, built at its first frame.
|
||||
dts: Vec<Option<DtsDeriver>>,
|
||||
@@ -506,8 +506,8 @@ impl<W: Write> TsMuxer<W> {
|
||||
self.write_psi_if_due(pts_90k)?;
|
||||
|
||||
// NAL video (HEVC/H.264): convert length-prefixed NALUs to Annex B and prepend
|
||||
// codec_private params on the first keyframe only (arm `params_written` even if
|
||||
// data is empty, else later non-key frames fail the drop guard). Others pass through.
|
||||
// codec_private params on the first keyframe only (`params_written` latches even if
|
||||
// data is empty, so no later keyframe re-prepends them). Others pass through.
|
||||
let mut annex_b = std::mem::take(&mut self.annex_b);
|
||||
let convert = is_video && self.is_nal_video(track);
|
||||
if convert {
|
||||
@@ -715,8 +715,8 @@ impl<W: Write> TsMuxer<W> {
|
||||
c
|
||||
}
|
||||
|
||||
/// Flush the underlying writer. BD-TS needs no stream trailer, so this
|
||||
/// only drains buffering; the muxer remains usable afterwards.
|
||||
/// End the stream: resolve the DTS start-up windows, drain the hold and flush the
|
||||
/// writer. BD-TS needs no stream trailer. Call once, after the last frame.
|
||||
///
|
||||
/// Returns [`Error::MuxEmpty`](crate::error::Error::MuxEmpty) when not a
|
||||
/// single frame was emitted: an `m2ts://` sink that wrote only the FMKV
|
||||
@@ -1047,10 +1047,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_data_keyframe_arms_params_so_later_frames_survive() {
|
||||
// An empty-data keyframe must still arm params_written; otherwise
|
||||
// every subsequent non-key frame would be dropped by the
|
||||
// pre-keyframe guard and the track would emit no real frames.
|
||||
fn empty_data_keyframe_arms_the_guard_so_later_frames_survive() {
|
||||
// An empty-data keyframe must still arm the pre-keyframe guard; otherwise
|
||||
// every subsequent non-key frame would be dropped and the track would
|
||||
// emit no real frames.
|
||||
let mut sink: Vec<u8> = Vec::new();
|
||||
{
|
||||
let mut mux = TsMuxer::new(&mut sink, &[VIDEO_PID]);
|
||||
@@ -1350,11 +1350,11 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
// A non-NAL video track must still arm `params_written`, or every later
|
||||
// non-keyframe fails the drop guard and silently vanishes — same class of
|
||||
// bug `empty_data_keyframe_arms_params_so_later_frames_survive` guards.
|
||||
// A non-NAL video keyframe must still arm the pre-keyframe guard, or every later
|
||||
// non-keyframe silently vanishes — same class of bug
|
||||
// `empty_data_keyframe_arms_the_guard_so_later_frames_survive` guards.
|
||||
#[test]
|
||||
fn non_nal_video_keyframe_arms_params_so_later_frames_survive() {
|
||||
fn non_nal_video_keyframe_arms_the_guard_so_later_frames_survive() {
|
||||
let key: Vec<u8> = vec![0x00, 0x00, 0x01, 0xB3, 0xAA, 0xBB];
|
||||
let non_key: Vec<u8> = vec![0x00, 0x00, 0x01, 0xB6, 0xCC, 0xDD];
|
||||
|
||||
@@ -1438,6 +1438,33 @@ mod tests {
|
||||
assert_eq!(pats, 13);
|
||||
}
|
||||
|
||||
// The PES stream_id follows the declared stream_type: the MPEG audio types (13818-1
|
||||
// Table 2-22) take an audio id, everything else private_stream_1.
|
||||
#[test]
|
||||
fn mpeg_audio_stream_types_get_an_audio_stream_id() {
|
||||
let types = [0x03u8, 0x04, 0x0F, 0x81, 0x06];
|
||||
let pids: Vec<u16> = (0..types.len() as u16).map(|k| AUDIO_PID + k).collect();
|
||||
let mut sink: Vec<u8> = Vec::new();
|
||||
{
|
||||
let mut mux = TsMuxer::new(&mut sink, &pids);
|
||||
mux.set_program(types.to_vec()).unwrap();
|
||||
for t in 0..types.len() {
|
||||
mux.write_frame(t, 0, true, &[0xFF, 0xF1, 0x4C, 0x80]).unwrap();
|
||||
}
|
||||
mux.finish().unwrap();
|
||||
}
|
||||
let packets = parse_bd_ts(&sink);
|
||||
let ids: Vec<u8> = pids
|
||||
.iter()
|
||||
.map(|&pid| {
|
||||
let first = packets.iter().find(|p| p.pid == pid && p.pusi).unwrap();
|
||||
assert_eq!(first.payload[..3], [0, 0, 1]);
|
||||
first.payload[3]
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(ids, [0xC0, 0xC0, 0xC0, 0xBD, 0xBD]);
|
||||
}
|
||||
|
||||
// A PES header's 33-bit PTS and DTS (DTS = PTS when absent), 90 kHz.
|
||||
fn pes_pts_dts(payload: &[u8]) -> (u64, u64) {
|
||||
let ts = |b: &[u8]| {
|
||||
|
||||
@@ -404,6 +404,27 @@ mod tests {
|
||||
t
|
||||
}
|
||||
|
||||
// An explicit aspect wins; SD without one is unknown; HD falls back to the coded size.
|
||||
#[test]
|
||||
fn display_aspect_ratio_falls_back_by_resolution() {
|
||||
let t = video_title(
|
||||
Codec::Hevc,
|
||||
Resolution::R1080p,
|
||||
FrameRate::F24,
|
||||
ColorSpace::Bt709,
|
||||
);
|
||||
let DiscStream::Video(mut v) = t.streams[0].clone() else {
|
||||
unreachable!()
|
||||
};
|
||||
assert_eq!(display_aspect_ratio(&v, 1920, 1080), (1920, 1080));
|
||||
assert_eq!(display_aspect_ratio(&v, 0, 0), (0, 1));
|
||||
v.display_aspect = Some((16, 9));
|
||||
assert_eq!(display_aspect_ratio(&v, 1920, 1080), (16, 9));
|
||||
v.display_aspect = None;
|
||||
v.resolution = Resolution::R576i;
|
||||
assert_eq!(display_aspect_ratio(&v, 720, 576), (0, 1));
|
||||
}
|
||||
|
||||
fn src(medium: Medium, path: &str, title: usize) -> SourceInfo {
|
||||
SourceInfo {
|
||||
medium,
|
||||
|
||||
Reference in New Issue
Block a user