mirror of
https://github.com/freemkv/libfreemkv.git
synced 2026-10-07 18:13:10 +00:00
Security - Remove the unsafe Vec::set_len over recycled sector buffers — UB reachable from safe code (GHSA-j8ww-f5fg-9pmh, low). Uses Vec::resize; pooling unchanged. CI / tooling - clippy on stable with the MSRV single-sourced from Cargo.toml; cargo-deny dependency-audit gate with first-party git sources allowed (#15); audience-based comment-guard (#17). Docs - README (keydb ownership, BD-J parser count), drive-access transport/reset notes, mux timeline module-doc links.
102 lines
4.9 KiB
Markdown
102 lines
4.9 KiB
Markdown
# MPEG-2 video parser notes
|
||
|
||
## Module overview
|
||
|
||
**One PES is NOT one frame.** On a DVD the video elementary stream is sliced
|
||
into ~2 KB Program-Stream PES packets (one per 2048-byte pack), so a single
|
||
coded picture (~10-100 KB) spans many PES packets and only the first carries
|
||
a PTS. Emitting one MKV block per PES would write frame *fragments* — the
|
||
decoder then sees truncated pictures (`ac-tex damaged`) and picture-coding
|
||
extensions detached from their picture header (`ignoring pic cod ext`). So
|
||
this parser buffers ES bytes across PES packets and emits exactly one Frame
|
||
per coded picture. (Blu-ray aligns one access unit per PES and would not need
|
||
this, but DVD MPEG-2 PS does.)
|
||
|
||
Access-unit model (ISO/IEC 13818-2): an AU is an optional sequence header +
|
||
optional GOP header + one picture header + its coding extension + slices. A
|
||
new AU begins at the next picture / sequence / GOP start code *once the
|
||
current AU already contains a picture* — leading sequence/GOP headers attach
|
||
to the picture that follows them.
|
||
|
||
Start codes:
|
||
- Picture header: 00 00 01 00
|
||
- Slice: 00 00 01 01 .. AF
|
||
- Sequence header: 00 00 01 B3
|
||
- Extension (seq/pic):00 00 01 B5
|
||
- GOP header: 00 00 01 B8
|
||
|
||
## `MAX_PENDING_BYTES`
|
||
|
||
`MAX_PENDING_FRAMES` alone bounds the *count* of frames held awaiting the
|
||
first PES PTS anchor, but 600 full HD/UHD intra pictures can be ~1 GiB. This
|
||
mirrors the AC-3/DTS/PGS byte caps: once the held data exceeds this, release
|
||
on the 0 base instead of accumulating further. 8 MiB is roughly a few large
|
||
I-frames, far more than the ~15 frames a well-formed DVD buffers before its
|
||
first PTS.
|
||
|
||
## `Mpeg2Parser::process_au`
|
||
|
||
Processes one reassembled access unit (from
|
||
[`AuAssembler`](crate::mux::au_assembly::AuAssembler)): decodes its
|
||
per-picture coding info, captures a new sequence header, and buffers the
|
||
picture into the current GOP for display-order timestamping. The AU's
|
||
timing / source / discontinuity were already attributed by the assembler.
|
||
|
||
## `Mpeg2Parser::flush_gop`
|
||
|
||
Emits the buffered GOP. Each frame's PTS is the display-order prefix-sum of
|
||
field durations from the timeline origin; its block duration is its own
|
||
`nb_fields × field_period`. Frames are emitted in DECODE (buffer) order —
|
||
B-frames keep their position with a correctly LOWER PTS, never reordered
|
||
(reordering emitted blocks is what corrupts the picture). The origin is
|
||
(re-)locked to the GOP's PES PTS; because that is a *presentation*
|
||
timestamp, backing out the carrying frame's display-field offset keeps the
|
||
timeline continuous and monotonic across GOP boundaries.
|
||
|
||
## `extract_seq_header`
|
||
|
||
Extracts the sequence header (+ any B5 extensions / user-data, up to the
|
||
first GOP or picture start code) from a fully-assembled access unit —
|
||
exactly the extradata an MPEG-2 decoder expects as codecPrivate. Returns
|
||
None if the access unit carries no sequence header. A NEW header replaces
|
||
the stored one (title boundary / channel change), so its extension is
|
||
always re-captured.
|
||
|
||
## `picture_coding_flags`
|
||
|
||
Extracts the picture-coding-extension field/pulldown flags
|
||
`(top_field_first, repeat_first_field, progressive_frame, frame_picture)`
|
||
from a coded access unit (`00 00 01 B5`, ext-id `1000`), per ISO/IEC
|
||
13818-2 §6.3.10. The four bits feed the codec-agnostic `PictureInfo`.
|
||
Returns a progressive whole-frame default `(false, false, true, true)` when
|
||
no picture coding extension is present (MPEG-1 / no interlace signalling),
|
||
so the muxer omits `FieldOrder` rather than asserting a guess.
|
||
|
||
## `picture_nb_fields`
|
||
|
||
Returns the number of field-display periods a coded picture occupies, from
|
||
its picture coding extension (`00 00 01 B5`, ext-id `1000`), per ISO/IEC
|
||
13818-2 §6.3.10 (`nb_fields = repeat_pict + 2`, the field count the spec's
|
||
repeat rules yield). This is what times soft-telecined (2:3 pulldown) DVD
|
||
video correctly: a `repeat_first_field` frame occupies 3 fields, a normal
|
||
frame 2, so honoring it spreads the ~23.976 coded frames across the 29.97
|
||
display span with no gap (the "play, pause, play" judder). `progressive_sequence`
|
||
comes from the sequence extension. Returns 2 (a normal frame) when no
|
||
picture coding extension is present.
|
||
|
||
## `parse_progressive_sequence`
|
||
|
||
Reads `progressive_sequence` from a captured sequence header's sequence
|
||
extension (`00 00 01 B5`, ext-id `0001`). False when absent (MPEG-1 / no
|
||
extension) — the interlaced default. Bit layout after the start code:
|
||
ext-id(4) profile_and_level(8) **progressive_sequence(1)** … so it is bit 3
|
||
of the second extension byte (`hdr[q+5]`).
|
||
|
||
## Test: `discontinuity_offset_mark_stamps_post_gap_picture_not_previous`
|
||
|
||
B1 hole-2 regression: MPEG-2 buffers a GOP and emits asynchronously, so a
|
||
concealed gap must be associated by OFFSET (like PTS), landing on the
|
||
picture whose own bytes begin after the gap — NOT the previous picture
|
||
that completes when the discontinuity PES arrives. pic1 (I) is pre-gap;
|
||
pic2 (P), carried by a `discontinuity` PES, is the first post-gap AU.
|