mux: reconstruct display-order PTS for sparse-PTS program streams

HD-DVD EVO (and DVD VOB) program streams timestamp video at GOP
granularity: only one access unit per GOP carries a PES PTS. The H.264 /
HEVC / VC-1 parsers collapsed a missing PTS to 0, so on such a source
every non-anchor frame landed on the same block timestamp and a decoder
reported "non monotonically increasing dts".

Add a shared SparsePtsReorder that rebuilds a display-order PTS per frame
from the coded picture type (I/P/B) plus the sparse anchor PTS, with a
per-frame duration self-calibrated from the spacing between consecutive
GOP anchors (no external frame-rate needed). Display order is derived via
the classic single-anchor-delay rule (an anchor displays only after the
previously-held anchor; a B displays immediately), exact for the
non-hierarchical GOP structures HD-DVD H.264/VC-1 use. It mirrors the
MPEG-2 parser's GOP-buffered origin-locking.

Gated to the program-stream path only: the three parsers enable it via
with_ps_reorder(is_dvd_ps), so the BD/UHD transport path (per-frame PTS)
is byte-identical and untouched.
This commit is contained in:
Matthew Jackson
2026-07-08 21:54:09 -07:00
parent f8bea78db5
commit 5fdff5664f
5 changed files with 497 additions and 12 deletions
+100 -3
View File
@@ -52,6 +52,10 @@ pub struct H264Parser {
// the stale avcC copy after a mid-title redefinition.
cur_sps: Option<Vec<u8>>,
cur_pps: Option<Vec<u8>>,
/// Display-order PTS reconstruction, enabled only on the program-stream
/// (HD-DVD EVO) path where the source stamps a PTS once per GOP. `None` on
/// the BD/UHD transport path, which carries a per-frame PTS.
reorder: Option<super::reorder::SparsePtsReorder>,
}
impl Default for H264Parser {
@@ -68,6 +72,25 @@ impl H264Parser {
pps: None,
cur_sps: None,
cur_pps: None,
reorder: None,
}
}
/// Enable display-order PTS reconstruction for a program-stream source.
/// No-op (leaves timestamps as parsed) for a transport-stream source.
pub(crate) fn with_ps_reorder(mut self, enabled: bool) -> Self {
if enabled {
self.reorder = Some(super::reorder::SparsePtsReorder::new());
}
self
}
/// Route a finished frame through the PTS reorderer when enabled, else emit
/// it directly (unchanged transport-stream behaviour).
fn finish(&mut self, explicit: Option<i64>, frame: Frame) -> Vec<Frame> {
match self.reorder.as_mut() {
Some(r) => r.push(explicit, frame),
None => vec![frame],
}
}
}
@@ -152,7 +175,8 @@ impl CodecParser for H264Parser {
// decode order and the player reorders by timecode. Use PTS, not DTS —
// DTS presents B-frames in decode order (visible judder) and breaks
// PTS-based seeking. Fall back to DTS only if PTS is absent.
let pts_ns = pes.pts.or(pes.dts).map(pts_to_ns).unwrap_or(0);
let explicit_pts = pes.pts.or(pes.dts).map(pts_to_ns);
let pts_ns = explicit_pts.unwrap_or(0);
// Single pass: detect IDR keyframes, seed/strip param sets, and convert
// Annex B (start-code prefixed) NALUs to length-prefixed NALUs (MKV with
@@ -236,7 +260,7 @@ impl CodecParser for H264Parser {
}
}
vec![Frame {
let frame = Frame {
// Coding-type only: H.264 field order is not decoded here, so
// `field_order()` stays `None` — honestly absent, never guessed.
coding: coding_type.map(PictureInfo::coding_type_only),
@@ -248,7 +272,15 @@ impl CodecParser for H264Parser {
discontinuity: pes.discontinuity,
data: frame_data,
duration_ns: None,
}]
};
self.finish(explicit_pts, frame)
}
fn flush(&mut self) -> Vec<Frame> {
match self.reorder.as_mut() {
Some(r) => r.flush(),
None => Vec::new(),
}
}
fn codec_private(&self) -> Option<Vec<u8>> {
@@ -667,6 +699,71 @@ mod tests {
);
}
/// End-to-end sparse-PTS reconstruction through the REAL parser + reorder:
/// a program-stream source (`with_ps_reorder(true)`) that stamps a PTS only
/// on each GOP's I-frame must yield distinct, display-ordered PTS for every
/// frame — the property the mkv muxer needs so a decoder derives monotonic
/// DTS. Without the reorder the non-anchor frames all collapse to one PTS.
#[test]
fn h264_ps_reorder_reconstructs_distinct_display_pts() {
use super::super::coding::CodingType;
// slice bodies: 0x88 → I (IDR), 0x98 → P, 0x9C → B (non-IDR).
// Decode order of a classic single-B GOP: I P B P B.
let gop = |anchor_pts: Option<i64>| {
vec![
(NAL_SLICE_IDR, 0x88u8, anchor_pts),
(NAL_SLICE_NON_IDR, 0x98, None),
(NAL_SLICE_NON_IDR, 0x9C, None),
(NAL_SLICE_NON_IDR, 0x98, None),
(NAL_SLICE_NON_IDR, 0x9C, None),
]
};
let feed = |reorder: bool| -> Vec<super::super::Frame> {
let mut p = H264Parser::new().with_ps_reorder(reorder);
let mut out = Vec::new();
// Two GOPs; the second I carries an anchor 5 frames later (90 kHz:
// 5 * 3750 = 18750 ticks) so the reorder can calibrate a duration.
for (nal, body, pts) in gop(Some(0)).into_iter().chain(gop(Some(18750))) {
out.extend(p.parse(&make_pes(h264_nal(nal, &[body]), pts)));
}
out.extend(p.flush());
out
};
// With reorder ON: all 10 frames emitted, every PTS distinct.
let recon = feed(true);
assert_eq!(recon.len(), 10, "no frame dropped");
let mut pts: Vec<i64> = recon.iter().map(|f| f.pts_ns).collect();
let n = pts.len();
pts.sort_unstable();
pts.dedup();
assert_eq!(
pts.len(),
n,
"reconstructed PTS are all distinct (no DTS collision)"
);
// The GOP's first-displayed frame is the I; the B in decode position 2
// must display BEFORE the P in decode position 1 (classic reorder).
let g1 = &recon[0..5];
assert_eq!(g1[0].coding.unwrap().coding_type(), CodingType::I);
assert!(
g1[2].pts_ns < g1[1].pts_ns,
"B (decode idx 2) displays before its forward-anchor P (decode idx 1)"
);
assert_eq!(g1[0].pts_ns, 0, "GOP anchor locks the I to its true PTS");
// With reorder OFF (transport-stream behaviour): the non-anchor frames
// collapse to a single colliding PTS — the bug this fix removes.
let raw = feed(false);
let collisions = raw.iter().filter(|f| f.pts_ns == 0).count();
assert!(
collisions >= 8,
"without reorder the sparse-PTS frames collide on 0 (got {collisions})"
);
}
/// Regression (Fight Club bug, H.264 variant): PPS id 0 = body A (→ avcC),
/// redefined to B, then switched BACK to A. A streaming decoder is on B; the
/// revert to A == avcC must still be emitted in-band or the A-segment
+35 -3
View File
@@ -166,6 +166,10 @@ pub struct HevcParser {
// colour-volume metadata is ever fabricated.
sei_mastering: Option<MasteringDisplay>,
sei_content_light: Option<ContentLightLevel>,
/// Display-order PTS reconstruction, enabled only on the program-stream
/// path where the source stamps a PTS once per GOP. `None` on the BD/UHD
/// transport path (the common HEVC case), which carries a per-frame PTS.
reorder: Option<super::reorder::SparsePtsReorder>,
}
/// Mastering Display Colour Volume payload (Rec. ITU-T H.265 D.2.28),
@@ -234,6 +238,25 @@ impl HevcParser {
pts_wrap_offset: 0,
sei_mastering: None,
sei_content_light: None,
reorder: None,
}
}
/// Enable display-order PTS reconstruction for a program-stream source.
/// No-op (leaves timestamps as parsed) for a transport-stream source.
pub(crate) fn with_ps_reorder(mut self, enabled: bool) -> Self {
if enabled {
self.reorder = Some(super::reorder::SparsePtsReorder::new());
}
self
}
/// Route a finished frame through the PTS reorderer when enabled, else emit
/// it directly (unchanged transport-stream behaviour).
fn finish(&mut self, explicit: Option<i64>, frame: Frame) -> Vec<Frame> {
match self.reorder.as_mut() {
Some(r) => r.push(explicit, frame),
None => vec![frame],
}
}
@@ -443,7 +466,8 @@ impl CodecParser for HevcParser {
// block timecode monotonic in storage order, which presents B-frames in
// decode order (visible judder / wrong frames) and breaks PTS-based
// seeking. Fall back to DTS only if PTS is somehow absent.
let pts_ns = pes.pts.or(pes.dts).map(pts_to_ns).unwrap_or(0);
let explicit_pts = pes.pts.or(pes.dts).map(pts_to_ns);
let pts_ns = explicit_pts.unwrap_or(0);
// Auto-detect a non-seamless clip boundary from the bitstream. freemkv
// reads a BD title's clips as ONE concatenated sector stream and the
@@ -651,7 +675,7 @@ impl CodecParser for HevcParser {
// from the first coded picture before writing the track header). `None`
// until both SEI present → SDR / no-SEI tracks carry nothing.
let hdr10 = self.hdr10();
vec![Frame {
let frame = Frame {
// Coding-type only: HEVC field order (pic_struct, from a pic_timing
// SEI) is not decoded here, so field_order() stays None — honestly
// absent, never guessed. HDR10 metadata is attached when measured.
@@ -666,7 +690,15 @@ impl CodecParser for HevcParser {
discontinuity: pes.discontinuity,
data: frame_data,
duration_ns: None,
}]
};
self.finish(explicit_pts, frame)
}
fn flush(&mut self) -> Vec<Frame> {
match self.reorder.as_mut() {
Some(r) => r.flush(),
None => Vec::new(),
}
}
fn codec_private(&self) -> Option<Vec<u8>> {
+9 -3
View File
@@ -25,6 +25,8 @@ pub mod lpcm;
pub mod mpeg2;
/// HDMV PGS (Presentation Graphics Stream) subtitle parser.
pub mod pgs;
/// Display-order PTS reconstruction for sparse-PTS program-stream video.
pub(crate) mod reorder;
/// Shared MPEG/Annex-B start-code scanning helpers.
pub(crate) mod startcode;
/// Dolby TrueHD / Atmos elementary-stream parser.
@@ -166,10 +168,14 @@ pub fn parser_for_codec(
is_dvd_ps: bool,
) -> Box<dyn CodecParser> {
match codec {
Codec::H264 => Box::new(h264::H264Parser::new()),
Codec::Hevc => Box::new(hevc::HevcParser::new()),
// `is_dvd_ps` marks a program-stream source (DVD VOB / HD-DVD EVO), whose
// video is timestamped only at GOP granularity. On that path the H.264 /
// HEVC / VC-1 parsers reconstruct a display-order PTS per frame; on the
// BD/UHD transport path (per-frame PTS) they leave timestamps untouched.
Codec::H264 => Box::new(h264::H264Parser::new().with_ps_reorder(is_dvd_ps)),
Codec::Hevc => Box::new(hevc::HevcParser::new().with_ps_reorder(is_dvd_ps)),
Codec::Mpeg2 => Box::new(mpeg2::Mpeg2Parser::new()),
Codec::Vc1 => Box::new(vc1::Vc1Parser::new()),
Codec::Vc1 => Box::new(vc1::Vc1Parser::new().with_ps_reorder(is_dvd_ps)),
Codec::Ac3 | Codec::Ac3Plus => Box::new(ac3::Ac3Parser::new()),
Codec::DtsHdMa | Codec::DtsHdHr | Codec::Dts => Box::new(dts::DtsParser::new()),
Codec::TrueHd => Box::new(truehd::TrueHdParser::new()),
+318
View File
@@ -0,0 +1,318 @@
//! Display-order PTS reconstruction for sparse-PTS program-stream video.
//!
//! MPEG program streams (DVD VOB, HD-DVD EVO) timestamp video at GOP
//! granularity: only one access unit per GOP carries a PES PTS, and the rest
//! arrive with none. The H.264 / HEVC / VC-1 parsers collapse a missing PTS to
//! `0` (`pes.pts.or(dts).unwrap_or(0)`), so on such a source every non-anchor
//! frame lands on the same block timestamp. A decoder then cannot order them and
//! reports "non monotonically increasing dts". (The MPEG-2 parser already avoids
//! this by reconstructing per-picture PTS from `temporal_reference`; these three
//! codecs carry no such field.)
//!
//! [`SparsePtsReorder`] reconstructs a display-order PTS for every frame from two
//! signals the parsers already provide — the coded picture type (I/P/B) and the
//! sparse anchor PTS — plus a per-frame duration self-calibrated from the spacing
//! between consecutive GOP anchors (no external frame-rate needed). It mirrors
//! the MPEG-2 parser's GOP-buffered origin-locking, but derives display order via
//! the classic single-anchor-delay rule instead of `temporal_reference`:
//!
//! - In DECODE order an anchor (I/P) is stored before the B-frames that
//! reference it forward, so decode `I P B P B` displays as `I B P B P`.
//! - The rule that produces that mapping: an anchor is displayed only after the
//! previously-held anchor; a B-frame displays immediately. This is exact for
//! the classic (non-hierarchical) GOP structures HD-DVD H.264/VC-1 use.
//!
//! This reconstruction is applied ONLY on the program-stream path
//! (`ContentFormat::MpegPs`). BD/UHD transport streams carry a per-frame PTS and
//! are never routed through it, so the primary decode path is untouched.
use super::Frame;
use super::coding::CodingType;
/// Fallback per-frame duration (ns) when the anchor spacing cannot calibrate one
/// (a stream with a single GOP, or no anchor PTS at all): 24000/1001 fps film,
/// the dominant HD-DVD cadence. Only affects intra-GOP spacing — each GOP's
/// origin is re-locked to its own anchor PTS, so a wrong fallback cannot drift
/// the timeline across GOPs.
const FALLBACK_FRAME_DUR_NS: i64 = 1_001_000_000 / 24;
/// One buffered coded picture awaiting its GOP's completion.
struct Pending {
/// Explicit PES PTS (ns) for this AU, or `None` when the source omitted it.
explicit: Option<i64>,
/// Coded picture type; `P` (anchor) when the parser could not determine it,
/// so an unknown frame is never mis-placed as a bi-predicted B.
ctype: CodingType,
frame: Frame,
}
/// A completed GOP, buffered until the NEXT GOP's anchor is known so a per-frame
/// duration can be calibrated from the two anchors before its frames are emitted.
struct Gop {
pend: Vec<Pending>,
/// Display index (0-based) of each `pend` entry, in `pend` (decode) order.
dispidx: Vec<i64>,
/// Display-frame count (== `pend.len()`).
count: i64,
/// The anchor: `(explicit_pts, dispidx)` of the first buffered frame that
/// carried an explicit PTS, used to lock the display origin. `None` when the
/// GOP carried no PTS at all (origin then continues from the running base).
anchor: Option<(i64, i64)>,
}
/// Reconstructs display-order PTS for a sparse-PTS video elementary stream.
pub(crate) struct SparsePtsReorder {
/// Frames of the GOP currently accumulating, in decode order.
cur: Vec<Pending>,
/// The previously-completed GOP, held one step so its duration can be
/// calibrated from the next GOP's anchor before it is emitted.
held: Option<Gop>,
/// Self-calibrated per-frame display duration (ns); 0 until two anchors seen.
dur_ns: i64,
/// Display time (ns) at which the next emitted GOP should begin, when its own
/// anchor is absent. Advanced by each emitted GOP.
next_start_ns: i64,
}
impl SparsePtsReorder {
pub(crate) fn new() -> Self {
Self {
cur: Vec::new(),
held: None,
dur_ns: 0,
next_start_ns: 0,
}
}
/// Feed one parsed frame with its explicit PES PTS (or `None`). Returns any
/// frames whose display PTS is now finalized (emitted in decode order).
pub(crate) fn push(&mut self, explicit: Option<i64>, frame: Frame) -> Vec<Frame> {
let ctype = frame
.coding
.map(|c| c.coding_type())
.unwrap_or(CodingType::P);
// A keyframe opens a new GOP: the picture already accumulated in `cur` is
// a complete GOP. Complete it (this frame belongs to the NEW GOP).
let mut out = Vec::new();
if frame.keyframe && !self.cur.is_empty() {
out = self.complete_current_gop();
}
self.cur.push(Pending {
explicit,
ctype,
frame,
});
out
}
/// Flush all buffered frames at end of stream.
pub(crate) fn flush(&mut self) -> Vec<Frame> {
let mut out = self.complete_current_gop();
if let Some(gop) = self.held.take() {
out.extend(self.emit_gop(gop));
}
out
}
/// Move `cur` into a completed [`Gop`]; if a GOP was already held, calibrate
/// the duration from the two anchors and emit the held one.
fn complete_current_gop(&mut self) -> Vec<Frame> {
if self.cur.is_empty() {
return Vec::new();
}
let pend = std::mem::take(&mut self.cur);
let dispidx = display_indices(pend.iter().map(|p| p.ctype));
let count = pend.len() as i64;
let anchor = pend
.iter()
.zip(&dispidx)
.find_map(|(p, &d)| p.explicit.map(|pts| (pts, d)));
let gop = Gop {
pend,
dispidx,
count,
anchor,
};
let mut out = Vec::new();
match self.held.take() {
Some(held) => {
// Calibrate a per-frame duration from the two anchors' spacing,
// spread across the held GOP's display-frame count. Approximate
// (assumes both anchors sit at a similar relative display slot),
// but each GOP re-locks its own origin, so the estimate only sets
// intra-GOP spacing.
if self.dur_ns == 0 {
if let (Some((p_held, _)), Some((p_next, _))) = (held.anchor, gop.anchor) {
let span = p_next - p_held;
if span > 0 && held.count > 0 {
self.dur_ns = (span / held.count).max(1);
}
}
}
out = self.emit_gop(held);
self.held = Some(gop);
}
None => self.held = Some(gop),
}
out
}
/// Assign each frame in `gop` its display PTS and return them in decode order.
fn emit_gop(&mut self, gop: Gop) -> Vec<Frame> {
let dur = if self.dur_ns > 0 {
self.dur_ns
} else {
FALLBACK_FRAME_DUR_NS
};
// Lock the display origin: prefer the GOP's own anchor PTS (back out its
// display offset); otherwise continue from the running base.
let origin = match gop.anchor {
Some((pts, didx)) => pts - didx * dur,
None => self.next_start_ns,
};
let Gop {
pend,
dispidx,
count,
..
} = gop;
let mut out = Vec::with_capacity(pend.len());
for (mut p, didx) in pend.into_iter().zip(dispidx) {
p.frame.pts_ns = origin + didx * dur;
out.push(p.frame);
}
// Next GOP with no anchor continues after this one's last display slot.
self.next_start_ns = origin + count * dur;
out
}
}
/// Display index (0-based, decode order in → decode order out) for a GOP's coded
/// picture types via the classic single-anchor-delay reorder: an anchor (I/P) is
/// displayed only after the previously-held anchor; a B displays immediately.
/// Decode `I P B P B` → display indices `[0, 2, 1, 4, 3]` (display `I B P B P`).
fn display_indices(types: impl Iterator<Item = CodingType>) -> Vec<i64> {
let types: Vec<CodingType> = types.collect();
let mut disp = vec![0i64; types.len()];
let mut held: Option<usize> = None;
let mut cursor = 0i64;
for (i, &c) in types.iter().enumerate() {
match c {
CodingType::I | CodingType::P => {
if let Some(h) = held {
disp[h] = cursor;
cursor += 1;
}
held = Some(i);
}
CodingType::B => {
disp[i] = cursor;
cursor += 1;
}
}
}
if let Some(h) = held {
disp[h] = cursor;
}
disp
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mux::codec::coding::PictureInfo;
fn frame(ctype: CodingType, keyframe: bool) -> Frame {
Frame {
keyframe,
coding: Some(PictureInfo::coding_type_only(ctype)),
..Default::default()
}
}
#[test]
fn display_indices_map_classic_gop() {
use CodingType::*;
// decode I P B P B -> display I B P B P
let d = display_indices([I, P, B, P, B].into_iter());
assert_eq!(d, vec![0, 2, 1, 4, 3]);
}
#[test]
fn display_indices_all_anchors_are_identity() {
use CodingType::*;
let d = display_indices([I, P, P, P].into_iter());
assert_eq!(d, vec![0, 1, 2, 3]);
}
#[test]
fn reconstructs_monotonic_display_pts_from_one_anchor_per_gop() {
use CodingType::*;
// Two GOPs of 5 frames, decode order I P B P B, anchor PTS only on the
// GOP's I (0 ns, then ~5-frames-later). Frame duration should calibrate
// to the spacing/5 and every frame get a distinct increasing display PTS.
let dur = 41_708_333i64;
let mut r = SparsePtsReorder::new();
let mut got: Vec<i64> = Vec::new();
// GOP 1: anchor on the I at t=0.
for (k, (ct, pts)) in [(I, Some(0i64)), (P, None), (B, None), (P, None), (B, None)]
.into_iter()
.enumerate()
{
let out = r.push(pts, frame(ct, k == 0));
got.extend(out.iter().map(|f| f.pts_ns));
}
// GOP 2: anchor on the I at t = 5*dur (its true display time).
for (k, (ct, pts)) in [
(I, Some(5 * dur)),
(P, None),
(B, None),
(P, None),
(B, None),
]
.into_iter()
.enumerate()
{
let out = r.push(pts, frame(ct, k == 0));
got.extend(out.iter().map(|f| f.pts_ns));
}
got.extend(r.flush().iter().map(|f| f.pts_ns));
// Ten frames out, none dropped.
assert_eq!(got.len(), 10, "all frames emitted");
// The calibrated duration is (5*dur)/5 = dur.
// GOP 1 decode order I P B P B -> display indices 0 2 1 4 3 -> PTS:
assert_eq!(
&got[0..5],
&[0, 2 * dur, 1 * dur, 4 * dur, 3 * dur],
"GOP1 display PTS in decode order"
);
// GOP 2 re-locks origin to 5*dur.
assert_eq!(
&got[5..10],
&[5 * dur, 7 * dur, 6 * dur, 9 * dur, 8 * dur],
"GOP2 display PTS continue monotonically per display order"
);
}
#[test]
fn no_pts_collisions_within_a_gop() {
use CodingType::*;
// Every frame distinct in DISPLAY order — the property the mkv muxer
// needs so a decoder can derive monotonic DTS.
let mut r = SparsePtsReorder::new();
let mut all: Vec<i64> = Vec::new();
for gop in 0..3 {
for (k, ct) in [I, P, B, P, B].into_iter().enumerate() {
let pts = (k == 0).then_some(gop as i64 * 5 * 41_708_333);
all.extend(r.push(pts, frame(ct, k == 0)).iter().map(|f| f.pts_ns));
}
}
all.extend(r.flush().iter().map(|f| f.pts_ns));
let mut sorted = all.clone();
sorted.sort_unstable();
sorted.dedup();
assert_eq!(sorted.len(), all.len(), "no two frames share a display PTS");
}
}
+35 -3
View File
@@ -103,6 +103,10 @@ pub struct Vc1Parser {
cur_entry_point: Option<Vec<u8>>,
width: u32,
height: u32,
/// Display-order PTS reconstruction, enabled only on the program-stream
/// (HD-DVD EVO) path where the source stamps a PTS once per GOP. `None` on
/// the BD/UHD transport path, which carries a per-frame PTS.
reorder: Option<super::reorder::SparsePtsReorder>,
}
impl Default for Vc1Parser {
@@ -120,6 +124,25 @@ impl Vc1Parser {
cur_entry_point: None,
width: 1920,
height: 1080,
reorder: None,
}
}
/// Enable display-order PTS reconstruction for a program-stream source.
/// No-op (leaves timestamps as parsed) for a transport-stream source.
pub(crate) fn with_ps_reorder(mut self, enabled: bool) -> Self {
if enabled {
self.reorder = Some(super::reorder::SparsePtsReorder::new());
}
self
}
/// Route a finished frame through the PTS reorderer when enabled, else emit
/// it directly (unchanged transport-stream behaviour).
fn finish(&mut self, explicit: Option<i64>, frame: Frame) -> Vec<Frame> {
match self.reorder.as_mut() {
Some(r) => r.push(explicit, frame),
None => vec![frame],
}
}
}
@@ -169,7 +192,8 @@ impl CodecParser for Vc1Parser {
// decode order and the player reorders by timecode. Use PTS, not DTS —
// DTS presents B-frames in decode order (visible judder) and breaks
// PTS-based seeking. Fall back to DTS only if PTS is absent.
let ts_ns = pes.pts.or(pes.dts).map(pts_to_ns).unwrap_or(0);
let explicit_pts = pes.pts.or(pes.dts).map(pts_to_ns);
let ts_ns = explicit_pts.unwrap_or(0);
let mut has_seq_header = false;
let mut has_entry_point = false;
let mut frame_start: Option<usize> = None;
@@ -321,7 +345,7 @@ impl CodecParser for Vc1Parser {
vc1_frame_coding_type(data.get(fs + 4..)?, self.cur_seq_header.as_deref())
});
vec![Frame {
let frame = Frame {
// Coding-type only: VC-1 field order is not decoded here, so
// field_order() stays None — honestly absent, never guessed.
coding: coding_type.map(PictureInfo::coding_type_only),
@@ -333,7 +357,15 @@ impl CodecParser for Vc1Parser {
discontinuity: pes.discontinuity,
data: frame_data,
duration_ns: None,
}]
};
self.finish(explicit_pts, frame)
}
fn flush(&mut self) -> Vec<Frame> {
match self.reorder.as_mut() {
Some(r) => r.flush(),
None => Vec::new(),
}
}
fn codec_private(&self) -> Option<Vec<u8>> {