use super::{pts_to_ns, CodecParser, Frame};
use crate::mux::ts::PesPacket;
const SEQ_HEADER_CODE: u8 = 0xB3;
const SEQ_EXT_CODE: u8 = 0xB5;
const PICTURE_CODE: u8 = 0x00;
const PICTURE_TYPE_I: u8 = 1;
const FRAME_RATES: [(u32, u32); 9] = [
(0, 1), (24000, 1001), (24, 1), (25, 1), (30000, 1001), (30, 1), (50, 1), (60000, 1001), (60, 1), ];
const ASPECT_RATIOS: [(u8, u8); 5] = [
(0, 0), (1, 1), (4, 3), (16, 9), (221, 100), ];
pub struct Mpeg2Parser {
seq_header: Option<Vec<u8>>,
}
impl Default for Mpeg2Parser {
fn default() -> Self {
Self::new()
}
}
impl Mpeg2Parser {
pub fn new() -> Self {
Self { seq_header: None }
}
pub fn resolution(&self) -> Option<(u16, u16)> {
let hdr = self.seq_header.as_ref()?;
parse_resolution(hdr)
}
pub fn frame_rate(&self) -> Option<(u32, u32)> {
let hdr = self.seq_header.as_ref()?;
parse_frame_rate(hdr)
}
pub fn aspect_ratio(&self) -> Option<(u8, u8)> {
let hdr = self.seq_header.as_ref()?;
parse_aspect_ratio(hdr)
}
}
impl CodecParser for Mpeg2Parser {
fn parse(&mut self, pes: &PesPacket) -> Vec<Frame> {
if pes.data.is_empty() {
return Vec::new();
}
let pts_ns = pes.dts.or(pes.pts).map(pts_to_ns).unwrap_or(0);
let data = &pes.data;
let mut keyframe = false;
let mut pos = 0;
while let Some(sc) = find_start_code(data, pos) {
if sc + 3 >= data.len() {
break;
}
let code = data[sc + 3];
match code {
SEQ_HEADER_CODE => {
let hdr_start = sc;
let hdr_end = find_start_code(data, sc + 4).unwrap_or(data.len());
let mut seq_data = data[hdr_start..hdr_end].to_vec();
if hdr_end + 3 < data.len() && data[hdr_end + 3] == SEQ_EXT_CODE {
let ext_end = find_start_code(data, hdr_end + 4).unwrap_or(data.len());
seq_data.extend_from_slice(&data[hdr_end..ext_end]);
}
self.seq_header = Some(seq_data);
keyframe = true;
pos = sc + 4;
}
PICTURE_CODE => {
if sc + 5 < data.len() {
let picture_coding_type = (data[sc + 5] >> 3) & 0x07;
if picture_coding_type == PICTURE_TYPE_I {
keyframe = true;
}
}
pos = sc + 4;
}
_ => {
pos = sc + 4;
}
}
}
vec![Frame {
pts_ns,
keyframe,
data: pes.data.clone(),
}]
}
fn codec_private(&self) -> Option<Vec<u8>> {
self.seq_header.clone()
}
}
fn parse_resolution(hdr: &[u8]) -> Option<(u16, u16)> {
if hdr.len() < 8 {
return None;
}
let h = ((hdr[4] as u16) << 4) | ((hdr[5] as u16) >> 4);
let v = (((hdr[5] & 0x0F) as u16) << 8) | hdr[6] as u16;
Some((h, v))
}
fn parse_frame_rate(hdr: &[u8]) -> Option<(u32, u32)> {
if hdr.len() < 8 {
return None;
}
let frame_rate_code = (hdr[7] & 0x0F) as usize;
if frame_rate_code == 0 || frame_rate_code >= FRAME_RATES.len() {
return None;
}
Some(FRAME_RATES[frame_rate_code])
}
fn parse_aspect_ratio(hdr: &[u8]) -> Option<(u8, u8)> {
if hdr.len() < 8 {
return None;
}
let ar_code = ((hdr[7] >> 4) & 0x0F) as usize;
if ar_code == 0 || ar_code >= ASPECT_RATIOS.len() {
return None;
}
Some(ASPECT_RATIOS[ar_code])
}
fn find_start_code(data: &[u8], from: usize) -> Option<usize> {
if data.len() < from + 3 {
return None;
}
(from..data.len() - 2).find(|&i| data[i] == 0x00 && data[i + 1] == 0x00 && data[i + 2] == 0x01)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mux::ts::PesPacket;
fn make_pes(data: Vec<u8>, pts: Option<i64>) -> PesPacket {
PesPacket {
pid: 0x1011,
pts,
dts: None,
data,
}
}
fn make_seq_header(width: u16, height: u16, aspect: u8, frame_rate: u8) -> Vec<u8> {
let mut hdr = vec![0x00, 0x00, 0x01, SEQ_HEADER_CODE];
hdr.push((width >> 4) as u8);
hdr.push(((width & 0x0F) as u8) << 4 | ((height >> 8) & 0x0F) as u8);
hdr.push((height & 0xFF) as u8);
hdr.push((aspect << 4) | (frame_rate & 0x0F));
hdr.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0x00]);
hdr
}
fn make_picture_header(coding_type: u8) -> Vec<u8> {
let byte5 = (coding_type & 0x07) << 3;
vec![0x00, 0x00, 0x01, PICTURE_CODE, 0x00, byte5, 0x00, 0x00]
}
#[test]
fn parse_sequence_header_resolution() {
let hdr = make_seq_header(720, 480, 2, 4);
let res = parse_resolution(&hdr);
assert_eq!(res, Some((720, 480)));
}
#[test]
fn parse_sequence_header_1920x1080() {
let hdr = make_seq_header(1920, 1080, 3, 4);
let res = parse_resolution(&hdr);
assert_eq!(res, Some((1920, 1080)));
}
#[test]
fn parse_sequence_header_frame_rate() {
let hdr = make_seq_header(720, 480, 2, 4); let fr = parse_frame_rate(&hdr);
assert_eq!(fr, Some((30000, 1001)));
}
#[test]
fn parse_sequence_header_aspect_ratio() {
let hdr = make_seq_header(720, 480, 3, 4); let ar = parse_aspect_ratio(&hdr);
assert_eq!(ar, Some((16, 9)));
}
#[test]
fn parse_sequence_header_too_short() {
let hdr = vec![0x00, 0x00, 0x01, SEQ_HEADER_CODE];
assert!(parse_resolution(&hdr).is_none());
assert!(parse_frame_rate(&hdr).is_none());
assert!(parse_aspect_ratio(&hdr).is_none());
}
#[test]
fn detect_i_frame() {
let mut parser = Mpeg2Parser::new();
let mut data = Vec::new();
data.extend_from_slice(&make_picture_header(PICTURE_TYPE_I));
data.extend_from_slice(&[0xFF; 16]);
let pes = make_pes(data, Some(90000));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert!(frames[0].keyframe, "I-frame should be detected as keyframe");
}
#[test]
fn detect_p_frame_not_keyframe() {
let mut parser = Mpeg2Parser::new();
let mut data = Vec::new();
data.extend_from_slice(&make_picture_header(2)); data.extend_from_slice(&[0xFF; 16]);
let pes = make_pes(data, Some(90000));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert!(!frames[0].keyframe, "P-frame should not be keyframe");
}
#[test]
fn detect_b_frame_not_keyframe() {
let mut parser = Mpeg2Parser::new();
let mut data = Vec::new();
data.extend_from_slice(&make_picture_header(3)); data.extend_from_slice(&[0xFF; 16]);
let pes = make_pes(data, Some(90000));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert!(!frames[0].keyframe, "B-frame should not be keyframe");
}
#[test]
fn codec_private_from_sequence_header() {
let mut parser = Mpeg2Parser::new();
let mut data = Vec::new();
let seq = make_seq_header(720, 480, 3, 4);
data.extend_from_slice(&seq);
data.extend_from_slice(&make_picture_header(PICTURE_TYPE_I));
data.extend_from_slice(&[0xFF; 8]);
let pes = make_pes(data, Some(0));
let _frames = parser.parse(&pes);
let cp = parser.codec_private();
assert!(
cp.is_some(),
"codec_private should be available after sequence header"
);
let cp = cp.unwrap();
assert_eq!(&cp[..4], &[0x00, 0x00, 0x01, SEQ_HEADER_CODE]);
}
#[test]
fn codec_private_none_initially() {
let parser = Mpeg2Parser::new();
assert!(parser.codec_private().is_none());
}
#[test]
fn codec_private_includes_extension() {
let mut parser = Mpeg2Parser::new();
let mut data = Vec::new();
let seq = make_seq_header(1920, 1080, 3, 4);
data.extend_from_slice(&seq);
data.extend_from_slice(&[0x00, 0x00, 0x01, SEQ_EXT_CODE]);
data.extend_from_slice(&[0x14, 0x8A, 0x00, 0x01, 0x00, 0x00]); data.extend_from_slice(&make_picture_header(PICTURE_TYPE_I));
data.extend_from_slice(&[0xFF; 4]);
let pes = make_pes(data, Some(0));
let _frames = parser.parse(&pes);
let cp = parser.codec_private().unwrap();
let has_ext = cp.windows(4).any(|w| w == [0x00, 0x00, 0x01, SEQ_EXT_CODE]);
assert!(has_ext, "codec_private should include sequence extension");
}
#[test]
fn sequence_header_implies_keyframe() {
let mut parser = Mpeg2Parser::new();
let mut data = Vec::new();
data.extend_from_slice(&make_seq_header(720, 480, 3, 4));
data.extend_from_slice(&[0xFF; 16]);
let pes = make_pes(data, Some(0));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert!(frames[0].keyframe);
}
#[test]
fn pts_conversion_to_nanoseconds() {
let mut parser = Mpeg2Parser::new();
let mut data = Vec::new();
data.extend_from_slice(&make_picture_header(PICTURE_TYPE_I));
data.extend_from_slice(&[0xFF; 4]);
let pes = make_pes(data, Some(90000));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].pts_ns, 1_000_000_000);
}
#[test]
fn empty_pes_no_frames() {
let mut parser = Mpeg2Parser::new();
let pes = make_pes(Vec::new(), Some(0));
let frames = parser.parse(&pes);
assert!(frames.is_empty());
}
#[test]
fn parser_resolution_method() {
let mut parser = Mpeg2Parser::new();
let mut data = Vec::new();
data.extend_from_slice(&make_seq_header(720, 576, 2, 3));
data.extend_from_slice(&make_picture_header(PICTURE_TYPE_I));
data.extend_from_slice(&[0xFF; 4]);
let pes = make_pes(data, Some(0));
let _ = parser.parse(&pes);
assert_eq!(parser.resolution(), Some((720, 576)));
assert_eq!(parser.frame_rate(), Some((25, 1))); assert_eq!(parser.aspect_ratio(), Some((4, 3))); }
}