Add MKV muxer and event system
- Add event.rs: structured event system for progress reporting - Add mux/: MKV muxer pipeline (EBML writer, TS demuxer, stream assembly) - Codec parsers: H.264, HEVC, AC-3, DTS, TrueHD, PGS, VC-1 - Lookahead buffer for codec private data extraction - Direct m2ts-to-MKV streaming without intermediate files
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
//! AC3 (Dolby Digital) / EAC3 (Dolby Digital Plus) frame parser.
|
||||
//!
|
||||
//! AC3 frames are self-contained and always start with syncword 0x0B77.
|
||||
//! Each PES packet typically contains exactly one AC3 frame.
|
||||
//! All AC3 frames are effectively keyframes (no inter-frame dependencies).
|
||||
|
||||
use super::{CodecParser, Frame, PesPacket, pts_to_ns};
|
||||
|
||||
pub struct Ac3Parser;
|
||||
|
||||
impl Ac3Parser {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl CodecParser for Ac3Parser {
|
||||
fn parse(&mut self, pes: &PesPacket) -> Vec<Frame> {
|
||||
if pes.data.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let pts_ns = pes.pts.map(pts_to_ns).unwrap_or(0);
|
||||
|
||||
// AC3: each PES = one frame, always a keyframe
|
||||
vec![Frame {
|
||||
pts_ns,
|
||||
keyframe: true,
|
||||
data: pes.data.clone(),
|
||||
}]
|
||||
}
|
||||
|
||||
fn codec_private(&self) -> Option<Vec<u8>> {
|
||||
// AC3 doesn't need codecPrivate in MKV
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
//! DTS / DTS-HD elementary stream parser.
|
||||
//!
|
||||
//! DTS core syncword: 0x7FFE8001 (32 bits).
|
||||
//! DTS-HD MA/HRA extension follows the core frame.
|
||||
//! All frames are keyframes (no inter-frame dependencies).
|
||||
//! Each PES packet = one frame.
|
||||
|
||||
use super::{CodecParser, Frame, PesPacket, pts_to_ns};
|
||||
|
||||
pub struct DtsParser;
|
||||
|
||||
impl DtsParser {
|
||||
pub fn new() -> Self { Self }
|
||||
}
|
||||
|
||||
impl CodecParser for DtsParser {
|
||||
fn parse(&mut self, pes: &PesPacket) -> Vec<Frame> {
|
||||
if pes.data.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
let pts_ns = pes.pts.map(pts_to_ns).unwrap_or(0);
|
||||
vec![Frame { pts_ns, keyframe: true, data: pes.data.clone() }]
|
||||
}
|
||||
|
||||
fn codec_private(&self) -> Option<Vec<u8>> { None }
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
//! H.264 (AVC) elementary stream parser.
|
||||
//!
|
||||
//! Extracts SPS and PPS NAL units for MKV codecPrivate.
|
||||
//! Detects keyframes (IDR slices).
|
||||
//! Each PES packet = one access unit = one frame.
|
||||
|
||||
use super::{CodecParser, Frame, PesPacket, pts_to_ns};
|
||||
|
||||
/// H.264 NAL unit types we care about.
|
||||
const NAL_SLICE: u8 = 1;
|
||||
const NAL_SLICE_IDR: u8 = 5;
|
||||
const NAL_SEI: u8 = 6;
|
||||
const NAL_SPS: u8 = 7;
|
||||
const NAL_PPS: u8 = 8;
|
||||
const NAL_AUD: u8 = 9;
|
||||
|
||||
pub struct H264Parser {
|
||||
sps: Option<Vec<u8>>,
|
||||
pps: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl H264Parser {
|
||||
pub fn new() -> Self {
|
||||
Self { sps: None, pps: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl CodecParser for H264Parser {
|
||||
fn parse(&mut self, pes: &PesPacket) -> Vec<Frame> {
|
||||
if pes.data.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let pts_ns = pes.pts.map(pts_to_ns).unwrap_or(0);
|
||||
|
||||
// Scan NAL units for SPS, PPS, and IDR detection
|
||||
let mut keyframe = false;
|
||||
let mut frame_data = Vec::new();
|
||||
|
||||
for nal in NalIterator::new(&pes.data) {
|
||||
let nal_type = nal[0] & 0x1F;
|
||||
|
||||
match nal_type {
|
||||
NAL_SPS => {
|
||||
self.sps = Some(nal.to_vec());
|
||||
}
|
||||
NAL_PPS => {
|
||||
self.pps = Some(nal.to_vec());
|
||||
}
|
||||
NAL_SLICE_IDR => {
|
||||
keyframe = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert Annex B (start code prefixed) to length-prefixed NALUs.
|
||||
// MKV with AVCDecoderConfigurationRecord expects 4-byte length prefix per NALU.
|
||||
// Skip SPS/PPS/AUD NALUs — they're in codecPrivate, not in frame data.
|
||||
for nal in NalIterator::new(&pes.data) {
|
||||
let nal_type = nal[0] & 0x1F;
|
||||
// Skip parameter sets and access unit delimiters
|
||||
if nal_type == NAL_SPS || nal_type == NAL_PPS || nal_type == NAL_AUD {
|
||||
continue;
|
||||
}
|
||||
// 4-byte big-endian length prefix
|
||||
let len = nal.len() as u32;
|
||||
frame_data.extend_from_slice(&len.to_be_bytes());
|
||||
frame_data.extend_from_slice(nal);
|
||||
}
|
||||
|
||||
if frame_data.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
vec![Frame {
|
||||
pts_ns,
|
||||
keyframe,
|
||||
data: frame_data,
|
||||
}]
|
||||
}
|
||||
|
||||
fn codec_private(&self) -> Option<Vec<u8>> {
|
||||
// Build AVCDecoderConfigurationRecord from SPS + PPS
|
||||
let sps = self.sps.as_ref()?;
|
||||
let pps = self.pps.as_ref()?;
|
||||
|
||||
// AVCDecoderConfigurationRecord (ISO 14496-15):
|
||||
// configurationVersion = 1
|
||||
// AVCProfileIndication = SPS[1]
|
||||
// profile_compatibility = SPS[2]
|
||||
// AVCLevelIndication = SPS[3]
|
||||
// lengthSizeMinusOne = 3 (4-byte length prefix)
|
||||
// numOfSequenceParameterSets = 1
|
||||
// sequenceParameterSetLength = sps.len()
|
||||
// sequenceParameterSetNALUnit = sps
|
||||
// numOfPictureParameterSets = 1
|
||||
// pictureParameterSetLength = pps.len()
|
||||
// pictureParameterSetNALUnit = pps
|
||||
|
||||
let mut record = Vec::new();
|
||||
record.push(1); // configurationVersion
|
||||
record.push(sps[1]); // profile
|
||||
record.push(sps[2]); // compatibility
|
||||
record.push(sps[3]); // level
|
||||
record.push(0xFF); // 6 bits reserved (111111) + 2 bits lengthSizeMinusOne (11 = 3)
|
||||
record.push(0xE1); // 3 bits reserved (111) + 5 bits numSPS (1)
|
||||
record.push((sps.len() >> 8) as u8);
|
||||
record.push(sps.len() as u8);
|
||||
record.extend_from_slice(sps);
|
||||
record.push(1); // numPPS
|
||||
record.push((pps.len() >> 8) as u8);
|
||||
record.push(pps.len() as u8);
|
||||
record.extend_from_slice(pps);
|
||||
|
||||
Some(record)
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator over NAL units in Annex B byte stream.
|
||||
/// Finds start codes (00 00 01 or 00 00 00 01) and yields the data between them.
|
||||
struct NalIterator<'a> {
|
||||
data: &'a [u8],
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl<'a> NalIterator<'a> {
|
||||
fn new(data: &'a [u8]) -> Self {
|
||||
// Skip to first start code
|
||||
let pos = find_start_code(data, 0).unwrap_or(data.len());
|
||||
Self { data, pos }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for NalIterator<'a> {
|
||||
type Item = &'a [u8];
|
||||
|
||||
fn next(&mut self) -> Option<&'a [u8]> {
|
||||
if self.pos >= self.data.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Skip the start code at current position
|
||||
let nal_start = skip_start_code(self.data, self.pos)?;
|
||||
|
||||
// Find next start code (or end of data)
|
||||
let nal_end = find_start_code(self.data, nal_start).unwrap_or(self.data.len());
|
||||
|
||||
// Remove trailing zeros (part of next start code's zero prefix)
|
||||
let mut end = nal_end;
|
||||
while end > nal_start && self.data[end - 1] == 0x00 {
|
||||
end -= 1;
|
||||
}
|
||||
|
||||
self.pos = nal_end;
|
||||
|
||||
if end > nal_start {
|
||||
Some(&self.data[nal_start..end])
|
||||
} else {
|
||||
self.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Find the position of the next start code (00 00 01) at or after `from`.
|
||||
pub fn find_start_code(data: &[u8], from: usize) -> Option<usize> {
|
||||
if data.len() < from + 3 {
|
||||
return None;
|
||||
}
|
||||
for i in from..data.len() - 2 {
|
||||
if data[i] == 0x00 && data[i + 1] == 0x00 && data[i + 2] == 0x01 {
|
||||
return Some(i);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Skip past the start code at position `pos`, returning the first byte after it.
|
||||
pub fn skip_start_code(data: &[u8], pos: usize) -> Option<usize> {
|
||||
if pos + 2 >= data.len() {
|
||||
return None;
|
||||
}
|
||||
if data[pos] == 0x00 && data[pos + 1] == 0x00 {
|
||||
if pos + 3 < data.len() && data[pos + 2] == 0x00 && data[pos + 3] == 0x01 {
|
||||
return Some(pos + 4); // 4-byte start code
|
||||
}
|
||||
if data[pos + 2] == 0x01 {
|
||||
return Some(pos + 3); // 3-byte start code
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
//! HEVC (H.265) elementary stream parser.
|
||||
//!
|
||||
//! Extracts VPS, SPS, PPS NAL units for MKV codecPrivate.
|
||||
//! Detects keyframes (IRAP pictures: IDR, CRA, BLA).
|
||||
//! Each PES packet = one access unit = one frame.
|
||||
|
||||
use super::{CodecParser, Frame, PesPacket, pts_to_ns};
|
||||
use super::h264::{find_start_code, skip_start_code};
|
||||
|
||||
// HEVC NAL unit types
|
||||
const NAL_VPS: u8 = 32;
|
||||
const NAL_SPS: u8 = 33;
|
||||
const NAL_PPS: u8 = 34;
|
||||
const NAL_AUD: u8 = 35;
|
||||
// IRAP types (keyframes): BLA, IDR, CRA
|
||||
const NAL_BLA_W_LP: u8 = 16;
|
||||
const NAL_RSV_IRAP_VCL23: u8 = 23;
|
||||
|
||||
pub struct HevcParser {
|
||||
vps: Option<Vec<u8>>,
|
||||
sps: Option<Vec<u8>>,
|
||||
pps: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl HevcParser {
|
||||
pub fn new() -> Self {
|
||||
Self { vps: None, sps: None, pps: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl CodecParser for HevcParser {
|
||||
fn parse(&mut self, pes: &PesPacket) -> Vec<Frame> {
|
||||
if pes.data.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let pts_ns = pes.pts.map(pts_to_ns).unwrap_or(0);
|
||||
let data = &pes.data;
|
||||
let mut keyframe = false;
|
||||
|
||||
// Scan NAL units
|
||||
let mut pos = 0;
|
||||
while let Some(sc_pos) = find_start_code(data, pos) {
|
||||
if let Some(nal_start) = skip_start_code(data, sc_pos) {
|
||||
let next = find_start_code(data, nal_start).unwrap_or(data.len());
|
||||
let mut end = next;
|
||||
while end > nal_start && data[end - 1] == 0x00 { end -= 1; }
|
||||
|
||||
if nal_start < data.len() {
|
||||
// HEVC NAL header: 2 bytes. Type is bits 1-6 of first byte.
|
||||
let nal_type = (data[nal_start] >> 1) & 0x3F;
|
||||
|
||||
match nal_type {
|
||||
NAL_VPS => self.vps = Some(data[nal_start..end].to_vec()),
|
||||
NAL_SPS => self.sps = Some(data[nal_start..end].to_vec()),
|
||||
NAL_PPS => self.pps = Some(data[nal_start..end].to_vec()),
|
||||
t if t >= NAL_BLA_W_LP && t <= NAL_RSV_IRAP_VCL23 => {
|
||||
keyframe = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
pos = next;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert Annex B to length-prefixed NALUs.
|
||||
// Skip VPS/SPS/PPS/AUD — they're in codecPrivate.
|
||||
let mut frame_data = Vec::new();
|
||||
let mut pos = 0;
|
||||
while let Some(sc_pos) = find_start_code(&pes.data, pos) {
|
||||
if let Some(nal_start) = skip_start_code(&pes.data, sc_pos) {
|
||||
let next = find_start_code(&pes.data, nal_start).unwrap_or(pes.data.len());
|
||||
let mut end = next;
|
||||
while end > nal_start && pes.data[end - 1] == 0x00 { end -= 1; }
|
||||
|
||||
if nal_start < pes.data.len() {
|
||||
let nal_type = (pes.data[nal_start] >> 1) & 0x3F;
|
||||
// Skip parameter sets and AUD
|
||||
if nal_type != NAL_VPS && nal_type != NAL_SPS && nal_type != NAL_PPS && nal_type != NAL_AUD {
|
||||
let nal = &pes.data[nal_start..end];
|
||||
let len = nal.len() as u32;
|
||||
frame_data.extend_from_slice(&len.to_be_bytes());
|
||||
frame_data.extend_from_slice(nal);
|
||||
}
|
||||
}
|
||||
pos = next;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if frame_data.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
vec![Frame {
|
||||
pts_ns,
|
||||
keyframe,
|
||||
data: frame_data,
|
||||
}]
|
||||
}
|
||||
|
||||
fn codec_private(&self) -> Option<Vec<u8>> {
|
||||
// HEVCDecoderConfigurationRecord (ISO 14496-15)
|
||||
let vps = self.vps.as_ref()?;
|
||||
let sps = self.sps.as_ref()?;
|
||||
let pps = self.pps.as_ref()?;
|
||||
|
||||
// Simplified: store as arrays in Annex B format
|
||||
// Full HEVCDecoderConfigurationRecord is complex — for now, concatenate
|
||||
let mut record = Vec::new();
|
||||
|
||||
// Minimal HEVCDecoderConfigurationRecord header
|
||||
record.push(1); // configurationVersion
|
||||
// General profile space, tier flag, profile IDC from SPS
|
||||
if sps.len() > 3 {
|
||||
record.push(sps[1]); // general_profile_space + general_tier_flag + general_profile_idc
|
||||
} else {
|
||||
record.push(0);
|
||||
}
|
||||
// general_profile_compatibility_flags (4 bytes)
|
||||
record.extend_from_slice(&[0, 0, 0, 0]);
|
||||
// general_constraint_indicator_flags (6 bytes)
|
||||
record.extend_from_slice(&[0, 0, 0, 0, 0, 0]);
|
||||
// general_level_idc
|
||||
record.push(if sps.len() > 12 { sps[12] } else { 0 });
|
||||
// min_spatial_segmentation_idc (4 + 12 bits)
|
||||
record.extend_from_slice(&[0xF0, 0x00]);
|
||||
// parallelismType (6 + 2 bits)
|
||||
record.push(0xFC);
|
||||
// chromaFormat (6 + 2 bits)
|
||||
record.push(0xFC | 1); // 4:2:0
|
||||
// bitDepthLumaMinus8 (5 + 3 bits)
|
||||
record.push(0xF8);
|
||||
// bitDepthChromaMinus8 (5 + 3 bits)
|
||||
record.push(0xF8);
|
||||
// avgFrameRate
|
||||
record.extend_from_slice(&[0, 0]);
|
||||
// constantFrameRate + numTemporalLayers + temporalIdNested + lengthSizeMinusOne
|
||||
record.push(0x03); // lengthSizeMinusOne = 3 (4 bytes)
|
||||
// numOfArrays
|
||||
record.push(3); // VPS, SPS, PPS
|
||||
|
||||
// VPS array
|
||||
record.push(0x20 | (NAL_VPS & 0x3F)); // array_completeness + NAL type
|
||||
record.extend_from_slice(&[0, 1]); // numNalus = 1
|
||||
record.push((vps.len() >> 8) as u8);
|
||||
record.push(vps.len() as u8);
|
||||
record.extend_from_slice(vps);
|
||||
|
||||
// SPS array
|
||||
record.push(0x20 | (NAL_SPS & 0x3F));
|
||||
record.extend_from_slice(&[0, 1]);
|
||||
record.push((sps.len() >> 8) as u8);
|
||||
record.push(sps.len() as u8);
|
||||
record.extend_from_slice(sps);
|
||||
|
||||
// PPS array
|
||||
record.push(0x20 | (NAL_PPS & 0x3F));
|
||||
record.extend_from_slice(&[0, 1]);
|
||||
record.push((pps.len() >> 8) as u8);
|
||||
record.push(pps.len() as u8);
|
||||
record.extend_from_slice(pps);
|
||||
|
||||
Some(record)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
//! Elementary stream codec parsers.
|
||||
//!
|
||||
//! Each parser takes PES packets and produces frames suitable for MKV muxing.
|
||||
//! Responsibilities:
|
||||
//! - Find frame boundaries
|
||||
//! - Extract codec initialization data (SPS/PPS, etc.)
|
||||
//! - Determine keyframe status
|
||||
//! - Convert PTS from 90kHz to nanoseconds
|
||||
|
||||
pub mod ac3;
|
||||
pub mod h264;
|
||||
pub mod hevc;
|
||||
pub mod vc1;
|
||||
pub mod dts;
|
||||
pub mod truehd;
|
||||
pub mod pgs;
|
||||
|
||||
use crate::disc::Codec;
|
||||
use super::ts::PesPacket;
|
||||
|
||||
/// A single frame ready for MKV muxing.
|
||||
pub struct Frame {
|
||||
/// Presentation timestamp in nanoseconds.
|
||||
pub pts_ns: i64,
|
||||
/// Whether this is a keyframe (used for cue points).
|
||||
pub keyframe: bool,
|
||||
/// Frame data (elementary stream bytes).
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Convert 90kHz PTS to nanoseconds.
|
||||
pub fn pts_to_ns(pts: i64) -> i64 {
|
||||
pts * 100_000 / 9
|
||||
}
|
||||
|
||||
/// Trait for codec-specific elementary stream parsers.
|
||||
pub trait CodecParser: Send {
|
||||
/// Parse a PES packet into zero or more frames.
|
||||
/// Most codecs: one PES = one frame.
|
||||
/// Some (TrueHD): multiple access units per PES.
|
||||
fn parse(&mut self, pes: &PesPacket) -> Vec<Frame>;
|
||||
|
||||
/// Get codec initialization data (e.g., SPS+PPS for H.264).
|
||||
/// Returns None until enough data has been seen.
|
||||
fn codec_private(&self) -> Option<Vec<u8>>;
|
||||
}
|
||||
|
||||
/// Passthrough parser — treats each PES as one frame, no parsing.
|
||||
/// Used for codecs where PES = frame (AC3, DTS, PGS).
|
||||
pub struct PassthroughParser {
|
||||
keyframe: bool,
|
||||
}
|
||||
|
||||
impl PassthroughParser {
|
||||
pub fn new(always_keyframe: bool) -> Self {
|
||||
Self { keyframe: always_keyframe }
|
||||
}
|
||||
}
|
||||
|
||||
impl CodecParser for PassthroughParser {
|
||||
fn parse(&mut self, pes: &PesPacket) -> Vec<Frame> {
|
||||
let pts_ns = pes.pts.map(pts_to_ns).unwrap_or(0);
|
||||
vec![Frame {
|
||||
pts_ns,
|
||||
keyframe: self.keyframe,
|
||||
data: pes.data.clone(),
|
||||
}]
|
||||
}
|
||||
|
||||
fn codec_private(&self) -> Option<Vec<u8>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Create the appropriate parser for a codec.
|
||||
pub fn parser_for_codec(codec: Codec) -> Box<dyn CodecParser> {
|
||||
match codec {
|
||||
Codec::H264 => Box::new(h264::H264Parser::new()),
|
||||
Codec::Hevc => Box::new(hevc::HevcParser::new()),
|
||||
Codec::Vc1 => Box::new(vc1::Vc1Parser::new()),
|
||||
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()),
|
||||
Codec::Pgs => Box::new(pgs::PgsParser::new()),
|
||||
Codec::Lpcm => Box::new(PassthroughParser::new(true)),
|
||||
_ => Box::new(PassthroughParser::new(true)),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
//! HDMV PGS (Presentation Graphics Stream) subtitle parser.
|
||||
//!
|
||||
//! PGS segments: PCS, WDS, PDS, ODS, END.
|
||||
//! Each PES packet contains one or more segments.
|
||||
//! All segments are keyframes (no inter-segment dependencies).
|
||||
|
||||
use super::{CodecParser, Frame, PesPacket, pts_to_ns};
|
||||
|
||||
pub struct PgsParser;
|
||||
|
||||
impl PgsParser {
|
||||
pub fn new() -> Self { Self }
|
||||
}
|
||||
|
||||
impl CodecParser for PgsParser {
|
||||
fn parse(&mut self, pes: &PesPacket) -> Vec<Frame> {
|
||||
if pes.data.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
let pts_ns = pes.pts.map(pts_to_ns).unwrap_or(0);
|
||||
vec![Frame { pts_ns, keyframe: true, data: pes.data.clone() }]
|
||||
}
|
||||
|
||||
fn codec_private(&self) -> Option<Vec<u8>> { None }
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
//! Dolby TrueHD / Atmos elementary stream parser.
|
||||
//!
|
||||
//! TrueHD major sync: 0xF8726FBA at a 4-byte aligned position.
|
||||
//! Access units consist of a major sync followed by minor syncs.
|
||||
//! An embedded AC3 core is in substream 0 for backward compatibility.
|
||||
//! All access units are keyframes.
|
||||
//! Each PES packet = one access unit.
|
||||
|
||||
use super::{CodecParser, Frame, PesPacket, pts_to_ns};
|
||||
|
||||
pub struct TrueHdParser;
|
||||
|
||||
impl TrueHdParser {
|
||||
pub fn new() -> Self { Self }
|
||||
}
|
||||
|
||||
impl CodecParser for TrueHdParser {
|
||||
fn parse(&mut self, pes: &PesPacket) -> Vec<Frame> {
|
||||
if pes.data.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
let pts_ns = pes.pts.map(pts_to_ns).unwrap_or(0);
|
||||
vec![Frame { pts_ns, keyframe: true, data: pes.data.clone() }]
|
||||
}
|
||||
|
||||
fn codec_private(&self) -> Option<Vec<u8>> { None }
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
//! VC-1 (SMPTE 421M) elementary stream parser.
|
||||
//!
|
||||
//! VC-1 uses start codes similar to MPEG-2.
|
||||
//! Sequence header (0x0F) contains codec initialization data.
|
||||
//! Frame start = Frame header start code (0x0D).
|
||||
//! I-frames (keyframes) are identified from the frame header.
|
||||
|
||||
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;
|
||||
|
||||
pub struct Vc1Parser {
|
||||
seq_header: Option<Vec<u8>>,
|
||||
entry_point: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl Vc1Parser {
|
||||
pub fn new() -> Self {
|
||||
Self { seq_header: None, entry_point: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl CodecParser for Vc1Parser {
|
||||
fn parse(&mut self, pes: &PesPacket) -> Vec<Frame> {
|
||||
if pes.data.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let pts_ns = pes.pts.map(pts_to_ns).unwrap_or(0);
|
||||
let mut keyframe = false;
|
||||
|
||||
// Scan for start codes (00 00 01 XX)
|
||||
let data = &pes.data;
|
||||
let mut i = 0;
|
||||
while i + 3 < data.len() {
|
||||
if data[i] == 0x00 && data[i + 1] == 0x00 && data[i + 2] == 0x01 {
|
||||
let sc_type = data[i + 3];
|
||||
match sc_type {
|
||||
SC_SEQUENCE_HEADER => {
|
||||
// Capture everything from here to next start code
|
||||
let end = find_next_sc(data, i + 4).unwrap_or(data.len());
|
||||
self.seq_header = Some(data[i..end].to_vec());
|
||||
}
|
||||
SC_ENTRY_POINT => {
|
||||
let end = find_next_sc(data, i + 4).unwrap_or(data.len());
|
||||
self.entry_point = Some(data[i..end].to_vec());
|
||||
}
|
||||
SC_FRAME => {
|
||||
// First frame after sequence header + entry point is a keyframe
|
||||
if self.seq_header.is_some() && self.entry_point.is_some() {
|
||||
keyframe = true;
|
||||
}
|
||||
// Also check frame type from bitstream (bit after start code)
|
||||
if i + 4 < data.len() {
|
||||
// For Advanced profile: first 2 bits of frame data indicate type
|
||||
// But simpler: any frame preceded by seq+entry is I-frame
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
i += 4;
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
vec![Frame {
|
||||
pts_ns,
|
||||
keyframe,
|
||||
data: pes.data.clone(),
|
||||
}]
|
||||
}
|
||||
|
||||
fn codec_private(&self) -> Option<Vec<u8>> {
|
||||
// MKV V_MS/VFW/FOURCC requires BITMAPINFOHEADER (40 bytes) + extra codec data.
|
||||
// The sequence header + entry point go as extra data after the header.
|
||||
let sh = self.seq_header.as_ref()?;
|
||||
let ep = self.entry_point.as_ref()?;
|
||||
|
||||
let extra_len = sh.len() + ep.len();
|
||||
let header_size: u32 = 40 + extra_len as u32;
|
||||
|
||||
let mut cp = Vec::with_capacity(header_size as usize);
|
||||
|
||||
// BITMAPINFOHEADER (40 bytes, little-endian)
|
||||
cp.extend_from_slice(&header_size.to_le_bytes()); // biSize
|
||||
cp.extend_from_slice(&1920u32.to_le_bytes()); // biWidth (updated by player)
|
||||
cp.extend_from_slice(&1080u32.to_le_bytes()); // biHeight
|
||||
cp.extend_from_slice(&1u16.to_le_bytes()); // biPlanes
|
||||
cp.extend_from_slice(&24u16.to_le_bytes()); // biBitCount
|
||||
cp.extend_from_slice(b"WVC1"); // biCompression = "WVC1" FOURCC
|
||||
cp.extend_from_slice(&0u32.to_le_bytes()); // biSizeImage
|
||||
cp.extend_from_slice(&0u32.to_le_bytes()); // biXPelsPerMeter
|
||||
cp.extend_from_slice(&0u32.to_le_bytes()); // biYPelsPerMeter
|
||||
cp.extend_from_slice(&0u32.to_le_bytes()); // biClrUsed
|
||||
cp.extend_from_slice(&0u32.to_le_bytes()); // biClrImportant
|
||||
|
||||
// Extra codec data: sequence header + entry point (Annex B)
|
||||
cp.extend_from_slice(sh);
|
||||
cp.extend_from_slice(ep);
|
||||
|
||||
Some(cp)
|
||||
}
|
||||
}
|
||||
|
||||
fn find_next_sc(data: &[u8], from: usize) -> Option<usize> {
|
||||
for i in from..data.len().saturating_sub(2) {
|
||||
if data[i] == 0x00 && data[i + 1] == 0x00 && data[i + 2] == 0x01 {
|
||||
return Some(i);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
Reference in New Issue
Block a user