mux/vc1: measure coding type from progressive PTYPE; carry source
Honest PictureInfo population for VC-1 — completes the set (MPEG-2, H.264, HEVC, VC-1 all populate now). - Read the advanced-profile sequence header INTERLACE flag (SMPTE 421M §6.1.1, bit 41), de-escaping emulation-prevention bytes as parse_vc1_resolution does. - For a PROGRESSIVE sequence, decode the picture-layer PTYPE VLC (0=P, 10=B, 110=I, 1110=BI→I, 1111=Skipped→P) from the first bits after the frame start code. For INTERLACED (FCM/FPTYPE precede PTYPE) or simple/main/unknown, decline → coding stays None: honestly absent, never read at a wrong bit offset. - Set coding = coding_type_only(...) and source = pes.source; field order is not decoded, so field_order() is honestly None. - Reuses the shared startcode::BitReader (no new primitive). - Tests: progressive I/P/B from real PTYPE VLCs + source carry + field-order absence; interlaced honest-decline.
This commit is contained in:
+145
-2
@@ -6,12 +6,84 @@
|
||||
//! I-frames (keyframes) are signalled by the presence of a Sequence Header
|
||||
//! (0x0F) in the PES, per the BD VC-1 convention (see `parse`).
|
||||
|
||||
use super::coding::{CodingType, PictureInfo};
|
||||
use super::startcode::BitReader;
|
||||
use super::{CodecParser, Frame, PesPacket, pts_to_ns};
|
||||
|
||||
const SC_SEQUENCE_HEADER: u8 = 0x0F;
|
||||
const SC_ENTRY_POINT: u8 = 0x0E;
|
||||
const SC_FRAME: u8 = 0x0D;
|
||||
|
||||
/// Read the advanced-profile sequence header's `INTERLACE` flag (SMPTE 421M
|
||||
/// §6.1.1): bit 41 after the start code — after PROFILE(2) LEVEL(3)
|
||||
/// COLORDIFF_FORMAT(2) FRMRTQ(3) BITRTQ(5) POSTPROCFLAG(1) MAX_CODED_WIDTH(12)
|
||||
/// MAX_CODED_HEIGHT(12) PULLDOWN(1). `None` for simple/main profile or a header
|
||||
/// too short / over-escaped to reach the bit. De-escapes emulation-prevention
|
||||
/// bytes first (as `parse_vc1_resolution` does) so the bit offset is exact.
|
||||
fn parse_vc1_interlace(sh: &[u8]) -> Option<bool> {
|
||||
if sh.len() < 8 || (sh[4] >> 6) & 0x03 != 3 {
|
||||
return None; // need the start code + advanced profile (PROFILE == 3)
|
||||
}
|
||||
// Collect the first 6 de-escaped bytes (48 bits ≥ the 42 we need).
|
||||
let mut deesc = Vec::with_capacity(6);
|
||||
let mut zeros = 0u8;
|
||||
for &b in &sh[4..] {
|
||||
if zeros >= 2 && b == 0x03 {
|
||||
zeros = 0; // drop the emulation-prevention byte
|
||||
continue;
|
||||
}
|
||||
deesc.push(b);
|
||||
if deesc.len() == 6 {
|
||||
break;
|
||||
}
|
||||
zeros = if b == 0x00 { zeros + 1 } else { 0 };
|
||||
}
|
||||
if deesc.len() < 6 {
|
||||
return None;
|
||||
}
|
||||
let mut bits: u64 = 0;
|
||||
for &b in &deesc {
|
||||
bits = (bits << 8) | b as u64;
|
||||
}
|
||||
// 48 bits; INTERLACE is bit index 41 from the MSB → (48 - 1 - 41) = 6 from LSB.
|
||||
Some((bits >> 6) & 1 == 1)
|
||||
}
|
||||
|
||||
/// Decode the advanced-profile **progressive** picture PTYPE VLC (SMPTE 421M
|
||||
/// §7.1.1.4, Table): `0`=P, `10`=B, `110`=I, `1110`=BI (intra → I), `1111`=
|
||||
/// Skipped (predicted, no residual → P). Only valid when the sequence is
|
||||
/// progressive — for interlaced an FCM code (and, for field pictures, a combined
|
||||
/// FPTYPE) precedes/replaces PTYPE, so the caller declines those.
|
||||
fn vc1_progressive_ptype(br: &mut BitReader) -> Option<CodingType> {
|
||||
if br.read_bit()? == 0 {
|
||||
return Some(CodingType::P); // 0
|
||||
}
|
||||
if br.read_bit()? == 0 {
|
||||
return Some(CodingType::B); // 10
|
||||
}
|
||||
if br.read_bit()? == 0 {
|
||||
return Some(CodingType::I); // 110
|
||||
}
|
||||
// 1110 = BI (intra) → I; 1111 = Skipped (predicted) → P.
|
||||
Some(if br.read_bit()? == 0 {
|
||||
CodingType::I
|
||||
} else {
|
||||
CodingType::P
|
||||
})
|
||||
}
|
||||
|
||||
/// Measure the coding type of an advanced-profile frame from its picture header.
|
||||
/// `frame_rbsp` starts immediately after the frame start code (`00 00 01 0D`).
|
||||
/// Decodes PTYPE only for a PROGRESSIVE sequence (where PTYPE is the first
|
||||
/// picture-layer field); declines (`None`) for interlaced/simple-main/unknown
|
||||
/// rather than guess at the wrong bit offset.
|
||||
fn vc1_frame_coding_type(frame_rbsp: &[u8], seq_header: Option<&[u8]>) -> Option<CodingType> {
|
||||
if parse_vc1_interlace(seq_header?)? {
|
||||
return None; // interlaced: FCM/FPTYPE not decoded here
|
||||
}
|
||||
vc1_progressive_ptype(&mut BitReader::new(frame_rbsp))
|
||||
}
|
||||
|
||||
pub struct Vc1Parser {
|
||||
// First-seen seq_header + entry_point seed the MKV codecPrivate
|
||||
// (BITMAPINFOHEADER extra data). These are the only out-of-band copies
|
||||
@@ -242,9 +314,18 @@ impl CodecParser for Vc1Parser {
|
||||
}
|
||||
};
|
||||
|
||||
// Measure the coding type from the picture header (advanced-profile
|
||||
// progressive PTYPE; interlaced/simple-main declined → None). The frame
|
||||
// RBSP begins just past the 4-byte frame start code (00 00 01 0D).
|
||||
let coding_type = frame_start.and_then(|fs| {
|
||||
vc1_frame_coding_type(data.get(fs + 4..)?, self.cur_seq_header.as_deref())
|
||||
});
|
||||
|
||||
vec![Frame {
|
||||
coding: None,
|
||||
source: None,
|
||||
// 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),
|
||||
source: pes.source,
|
||||
pts_ns: ts_ns,
|
||||
keyframe,
|
||||
data: frame_data,
|
||||
@@ -373,6 +454,68 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vc1_populates_measured_coding_type_and_source() {
|
||||
use super::super::coding::CodingType;
|
||||
// Advanced-profile sequence header: 00 00 01 0F, PROFILE=3 (0xC0), then
|
||||
// zeros so INTERLACE (bit 41) = 0 → progressive.
|
||||
let seq_prog = vec![0x00, 0x00, 0x01, SC_SEQUENCE_HEADER, 0xC0, 0, 0, 0, 0, 0];
|
||||
// Frame: 00 00 01 0D then the PTYPE VLC as the first RBSP bits:
|
||||
// 0xC0 = '110' → I; 0x00 = '0' → P; 0x80 = '10' → B.
|
||||
let frame = |ptype: u8| vec![0x00, 0x00, 0x01, SC_FRAME, ptype];
|
||||
let src = crate::pes::SourcePos::at_byte(2048);
|
||||
let mut p = Vc1Parser::new();
|
||||
|
||||
// I-frame carrying the seq header → keyframe, sets the active seq header.
|
||||
let mut pe = make_pes([seq_prog.clone(), frame(0xC0)].concat(), Some(0));
|
||||
pe.source = Some(src);
|
||||
let fi = p.parse(&pe);
|
||||
assert_eq!(fi.len(), 1);
|
||||
assert!(fi[0].keyframe, "seq header present → keyframe");
|
||||
let ci = fi[0].coding.expect("VC-1 frame carries PictureInfo");
|
||||
assert_eq!(ci.coding_type(), CodingType::I, "PTYPE 110 → I");
|
||||
assert!(
|
||||
ci.field_order().is_none(),
|
||||
"VC-1 field order undecoded → None, never faked"
|
||||
);
|
||||
assert_eq!(
|
||||
fi[0].source.unwrap().byte,
|
||||
2048,
|
||||
"source provenance carried"
|
||||
);
|
||||
|
||||
// P / B frames (no seq header; the active progressive seq header
|
||||
// persists) → measured P / B, not keyframes.
|
||||
let fp = p.parse(&make_pes(frame(0x00), Some(0)));
|
||||
assert!(!fp[0].keyframe);
|
||||
assert_eq!(
|
||||
fp[0].coding.unwrap().coding_type(),
|
||||
CodingType::P,
|
||||
"PTYPE 0 → P"
|
||||
);
|
||||
let fb = p.parse(&make_pes(frame(0x80), Some(0)));
|
||||
assert_eq!(
|
||||
fb[0].coding.unwrap().coding_type(),
|
||||
CodingType::B,
|
||||
"PTYPE 10 → B"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vc1_interlaced_declines_coding_type_never_guesses() {
|
||||
// Interlaced sequence (INTERLACE bit 41 = 1): FCM/FPTYPE precede PTYPE
|
||||
// and are NOT decoded here, so the coding type is honestly omitted
|
||||
// rather than read at the wrong bit offset.
|
||||
let seq_int = vec![0x00, 0x00, 0x01, SC_SEQUENCE_HEADER, 0xC0, 0, 0, 0, 0, 0x40];
|
||||
let frame = vec![0x00, 0x00, 0x01, SC_FRAME, 0xC0];
|
||||
let mut p = Vc1Parser::new();
|
||||
let f = p.parse(&make_pes([seq_int, frame].concat(), Some(0)));
|
||||
assert!(
|
||||
f[0].coding.is_none(),
|
||||
"interlaced VC-1 → coding omitted, never a guessed type"
|
||||
);
|
||||
}
|
||||
|
||||
/// Build a VC-1 PES with sequence header + entry point + frame start code.
|
||||
fn build_vc1_iframe_pes() -> Vec<u8> {
|
||||
let mut data = Vec::new();
|
||||
|
||||
Reference in New Issue
Block a user