mux: codec-agnostic PictureInfo + provenance; measure field order, never guess
Carry per-picture truth and byte-exact source provenance THROUGH the stream so the muxer (and the upcoming video index) read MEASURED facts instead of assuming them. Honest data in, honest data out. - codec/coding.rs: codec-agnostic PictureInfo (CodingType / FieldOrder + the accessors field_order/coding_type/nb_fields/progressive/keyframe). Each codec folds its raw signals in; consumers use only accessors, never branch on codec. - mpeg2: builds PictureInfo from the picture coding extension and carries it + SourcePos (source_marks, parallel to pts_marks) on every emitted frame. - pes / codec::Frame: additive `coding` + `source`, forwarded through the highway; None for audio/subtitle and the network/stdio deserialize hop. - mkvstream: DEFER muxer construction until the first coded picture, set the video track's FieldOrder from the MEASURED value, THEN write the header — right the first time, no guess, no seek-back. An interlaced track that arrives with no measured order is LOGGED loudly and left UNDETERMINED, never faked. - mkv: MkvTrack::video no longer guesses TFF (a bitstream property the scan cannot know is UNDETERMINED at build). Removed VideoStream::top_field_first (the dead scan-time guess) crate-wide. - Tests: parser population (every PictureInfo facet + per-PES source carry) and mux-stream consumption (measured -> correct; missing -> UNDETERMINED, not faked). Two obsolete tests updated only after confirming (their own comments) they existed to enforce the deleted hardcoded-TFF.
This commit is contained in:
+46
-1
@@ -51,6 +51,10 @@ pub struct PsPacket {
|
||||
pub dts: Option<u64>,
|
||||
/// Elementary stream payload data.
|
||||
pub data: Vec<u8>,
|
||||
/// Source position of this PES's first ES byte, stamped at the demux seam
|
||||
/// from the producer's known stream offset. `None` when the demuxer was fed
|
||||
/// without a base offset.
|
||||
pub source: Option<crate::pes::SourcePos>,
|
||||
}
|
||||
|
||||
/// Canonical DVD video PID. DVD-Video carries a single MPEG-2 video
|
||||
@@ -129,6 +133,12 @@ impl PsPacket {
|
||||
/// Handles non-aligned input by buffering leftover bytes between calls.
|
||||
pub struct PsDemuxer {
|
||||
buffer: Vec<u8>,
|
||||
/// Absolute source byte offset of `buffer[0]` — the running base that turns
|
||||
/// an in-buffer unit position into a [`crate::pes::SourcePos`]. Advanced as
|
||||
/// the buffer drains. `has_base` gates stamping so non-provenance callers
|
||||
/// stay byte-identical.
|
||||
buffer_base: u64,
|
||||
has_base: bool,
|
||||
}
|
||||
|
||||
impl Default for PsDemuxer {
|
||||
@@ -142,6 +152,8 @@ impl PsDemuxer {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
buffer: Vec::with_capacity(64 * 1024),
|
||||
buffer_base: 0,
|
||||
has_base: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,6 +163,24 @@ impl PsDemuxer {
|
||||
self.extract_packets(false)
|
||||
}
|
||||
|
||||
/// Like [`feed`](Self::feed) but records the absolute source byte offset of
|
||||
/// `data[0]`, so every PES this call completes is stamped with a
|
||||
/// [`crate::pes::SourcePos`]. The provenance-stamping entry point; the
|
||||
/// highway calls this with each batch's known source offset. The base must
|
||||
/// be the offset of the FIRST byte appended (i.e. of `data[0]`), which lines
|
||||
/// up with the current buffer tail.
|
||||
pub fn feed_at(&mut self, base_offset: u64, data: &[u8]) -> Vec<PsPacket> {
|
||||
if !self.has_base {
|
||||
// First base seen: the offset of data[0] is base_offset, and data[0]
|
||||
// lands at buffer[buffer.len()], so buffer[0] is base_offset minus
|
||||
// the bytes already buffered.
|
||||
self.buffer_base = base_offset.saturating_sub(self.buffer.len() as u64);
|
||||
self.has_base = true;
|
||||
}
|
||||
self.buffer.extend_from_slice(data);
|
||||
self.extract_packets(false)
|
||||
}
|
||||
|
||||
/// Flush remaining buffered data, returning any final PES packets.
|
||||
pub fn flush(&mut self) -> Vec<PsPacket> {
|
||||
// At EOF, an unbounded (length 0) PES with no trailing start code is
|
||||
@@ -255,7 +285,11 @@ impl PsDemuxer {
|
||||
e
|
||||
};
|
||||
|
||||
if let Some(pkt) = parse_pes_packet(&self.buffer[sc..end]) {
|
||||
if let Some(mut pkt) = parse_pes_packet(&self.buffer[sc..end]) {
|
||||
if self.has_base {
|
||||
pkt.source =
|
||||
Some(crate::pes::SourcePos::at_byte(self.buffer_base + sc as u64));
|
||||
}
|
||||
packets.push(pkt);
|
||||
}
|
||||
pos = end;
|
||||
@@ -269,6 +303,11 @@ impl PsDemuxer {
|
||||
|
||||
if pos > 0 {
|
||||
self.buffer.drain(..pos);
|
||||
// Advance the absolute base past the drained bytes so subsequent
|
||||
// units stamp from the correct offset.
|
||||
if self.has_base {
|
||||
self.buffer_base += pos as u64;
|
||||
}
|
||||
}
|
||||
|
||||
packets
|
||||
@@ -339,6 +378,9 @@ fn parse_pes_packet(data: &[u8]) -> Option<PsPacket> {
|
||||
pts: None,
|
||||
dts: None,
|
||||
data: payload.to_vec(),
|
||||
// Stamped by the demuxer (extract_packets) when a source base is
|
||||
// threaded; the free function has no absolute offset of its own.
|
||||
source: None,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -393,6 +435,8 @@ fn parse_pes_packet(data: &[u8]) -> Option<PsPacket> {
|
||||
pts,
|
||||
dts,
|
||||
data: es_data,
|
||||
// Stamped by the demuxer (extract_packets) when a source base is threaded.
|
||||
source: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -788,6 +832,7 @@ mod tests {
|
||||
pts: None,
|
||||
dts: None,
|
||||
data: vec![0xAA],
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user