Parse MKV lacing, route by real TrackNumber, honour NAL length size and edit lists
Four conformance defects in the read paths, two of them silent corruption. **Lacing was ignored entirely.** RFC 9559 §10.2 defines Xiph, EBML and fixed-size lacing, where one Block carries several frames; the reader took the Block payload verbatim, so a laced Block became a single "frame" consisting of a lacing header followed by concatenated frames — garbage to the codec parser, no error. Audio tracks from other muxers commonly use lacing, so an ordinary foreign MKV was silently mangled. All three modes are now parsed: Xiph 255-run sizes including the trailing-zero rule for exact multiples of 255, EBML unsigned first size plus SIGNED VINT deltas with the 2^((7*n)-1)-1 bias of §10.3.3, and fixed-size even division, with the last frame's size deduced from the remainder. Laced timestamps follow §10.3.5: the first frame takes the Block timestamp and the rest are spaced by the track's DefaultDuration, else BlockDuration/count, else shared with a warn. Parsing was chosen over refusing because refusal would leave freemkv unable to remux common foreign audio at all, and each mode is about fifteen lines. A malformed lacing header now raises a NEW code, E_MKV_LACING_INVALID = 9052, deliberately NOT MkvInvalid — because is_skippable_title_stub classifies MkvInvalid as a skippable nav stub, so reusing it would have recreated the exact conflation that is still open as a separate finding. A test asserts the new code is not skippable. **TrackNumber was assumed to be 1..N in TrackEntry order.** RFC 9559 §5.1.4.1.1 only requires it to be non-zero and unique, so sparse or unordered numbers are legal. Block routing and codec_private both computed track + 1. A real TrackNumber map is now built, recorded only for TrackEntries that yield a stream so dropped track types no longer shift the mapping. Verified red here independently, and the failure mode is worse than mis-routing: with track + 1 restored, a buttons track's payload was attributed to the AUDIO stream — wrong payload into the wrong codec parser. **The NAL length prefix was hardcoded to 4 bytes.** lengthSizeMinusOne lives in avcC byte 4 and hvcC byte 21 (ISO/IEC 14496-15 §5.3.3.1.2, §8.3.3.1.2) and was never read, so a source declaring 1- or 2-byte prefixes had its raw prefixed bytes emitted verbatim with no start codes. All four conversion sites now derive the width from the track's own configuration record. **Edit lists were ignored.** No edts/elst was parsed, so the presentation timeline an edit list defines (ISO/IEC 14496-12 §8.6.5/§8.6.6) was dropped — which is how encoder delay is normally expressed. Leading empty edits and the first media edit's media_time are now applied to both dts and pts, with the movie vs media timescale distinction respected. A list needing more than a constant shift applies the leading edit and warns rather than presenting the result as faithful. 17 tests. I reproduced the lacing mutant independently: returning the body whole kills five of them, including the exact-payload and malformed-header cases. Still open and deliberately untouched: the MkvInvalid / is_skippable_title_stub conflation across ~20 reader raise sites. It is a cross-cutting error.rs change and E_MKV_LACING_INVALID is the template for it.
This commit is contained in:
@@ -156,6 +156,15 @@ pub const E_MUX_EMPTY: u16 = 9023;
|
||||
/// [`is_skippable_title_stub`] treats as a skippable empty nav/menu stub — a
|
||||
/// cap-overflow is a real title and must never be silently skipped.
|
||||
pub const E_MUX_HEADER_BUFFER_EXCEEDED: u16 = 9051;
|
||||
/// An `mkv://` SOURCE Block declared lacing (RFC 9559 §10.3) whose header does
|
||||
/// not describe its own payload, so the frame boundaries inside the Block are
|
||||
/// unknowable. Deliberately NOT [`E_MKV_INVALID`], which
|
||||
/// [`is_skippable_title_stub`] treats as a skippable empty nav/menu stub: a
|
||||
/// laced Block belongs to a track with real media in it, and mis-reporting the
|
||||
/// rejection as a stub would drop that media from a run that then exits
|
||||
/// successfully — the same conflation [`E_MUX_HEADER_BUFFER_EXCEEDED`] exists to
|
||||
/// avoid.
|
||||
pub const E_MKV_LACING_INVALID: u16 = 9052;
|
||||
pub const E_EXTENT_NOT_UNIT_ALIGNED: u16 = 9030;
|
||||
/// `mp4://` output but the title has no (primary) video track to carry.
|
||||
pub const E_MP4_NO_VIDEO_TRACK: u16 = 9048;
|
||||
@@ -297,6 +306,10 @@ pub enum Error {
|
||||
},
|
||||
IfoParse,
|
||||
MkvInvalid,
|
||||
/// An `mkv://` source Block's lacing header does not describe its payload —
|
||||
/// the frames packed into that Block cannot be separated. NOT
|
||||
/// [`Error::MkvInvalid`]: see [`E_MKV_LACING_INVALID`].
|
||||
MkvLacingInvalid,
|
||||
NoStreams,
|
||||
/// A [`crate::StreamSelection`] listed a PID that does not exist in the
|
||||
/// title's declared streams — a caller bug (e.g. a stale scan), reported
|
||||
@@ -604,6 +617,7 @@ impl Error {
|
||||
Error::DiscTitleRange { .. } => E_DISC_TITLE_RANGE,
|
||||
Error::IfoParse => E_IFO_PARSE,
|
||||
Error::MkvInvalid => E_MKV_INVALID,
|
||||
Error::MkvLacingInvalid => E_MKV_LACING_INVALID,
|
||||
Error::NoStreams => E_NO_STREAMS,
|
||||
Error::SelectionPidUnknown { .. } => E_SELECTION_PID_UNKNOWN,
|
||||
Error::MapfileInvalid { .. } => E_MAPFILE_INVALID,
|
||||
@@ -895,6 +909,9 @@ impl From<Error> for std::io::Error {
|
||||
// 9051 MuxHeaderBufferExceeded: the source kept yielding frames but
|
||||
// never its codec init data — the input is unusable as declared.
|
||||
E_MUX_HEADER_BUFFER_EXCEEDED => std::io::ErrorKind::InvalidData,
|
||||
// 9052 MkvLacingInvalid: a source Block's lacing header does not
|
||||
// describe its own payload — malformed input data.
|
||||
E_MKV_LACING_INVALID => std::io::ErrorKind::InvalidData,
|
||||
// mp4:// demux errors: a malformed/truncated source file
|
||||
// (E_MP4_INVALID), or a source whose tracks the mux can't use — no
|
||||
// video track / missing codec-private config. All are invalid data.
|
||||
@@ -1391,6 +1408,7 @@ mod tests {
|
||||
E_NETWORK_ADDR_BLOCKED,
|
||||
E_MUX_EMPTY,
|
||||
E_MUX_HEADER_BUFFER_EXCEEDED,
|
||||
E_MKV_LACING_INVALID,
|
||||
E_MP4_NO_VIDEO_TRACK,
|
||||
E_MP4_INVALID,
|
||||
E_MP4_MISSING_CODEC_PRIVATE,
|
||||
@@ -1485,6 +1503,7 @@ mod tests {
|
||||
Error::MuxHeaderBufferExceeded { bytes: 0 },
|
||||
E_MUX_HEADER_BUFFER_EXCEEDED,
|
||||
),
|
||||
(Error::MkvLacingInvalid, E_MKV_LACING_INVALID),
|
||||
(Error::Mp4NoVideoTrack, E_MP4_NO_VIDEO_TRACK),
|
||||
(Error::Mp4Invalid, E_MP4_INVALID),
|
||||
(Error::Mp4MissingCodecPrivate, E_MP4_MISSING_CODEC_PRIVATE),
|
||||
|
||||
Reference in New Issue
Block a user