libfreemkv 0.6.1

Open source raw disc access library for optical drives
Documentation
//! 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
}