use super::{CodecParser, Frame, PesPacket, pts_to_ns};
const BD_LPCM_HEADER_SIZE: usize = 4;
pub struct LpcmParser {
strip_header: bool,
}
impl Default for LpcmParser {
fn default() -> Self {
Self::new()
}
}
impl LpcmParser {
pub fn new() -> Self {
Self { strip_header: true }
}
pub fn new_dvd() -> Self {
Self {
strip_header: false,
}
}
}
impl CodecParser for LpcmParser {
fn parse(&mut self, pes: &PesPacket) -> Vec<Frame> {
let offset = if self.strip_header {
BD_LPCM_HEADER_SIZE
} else {
0
};
if pes.data.len() <= offset {
return Vec::new();
}
let pts_ns = pes.pts.map(pts_to_ns).unwrap_or(0);
vec![Frame {
pts_ns,
keyframe: true,
data: pes.data[offset..].to_vec(),
duration_ns: None,
}]
}
fn codec_private(&self) -> Option<Vec<u8>> {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mux::ts::PesPacket;
fn make_pes(data: Vec<u8>, pts: Option<i64>) -> PesPacket {
PesPacket {
pid: 0x1100,
pts,
dts: None,
data,
}
}
#[test]
fn header_skip_extracts_pcm_data() {
let mut parser = LpcmParser::new();
let header = vec![0x00, 0x01, 0x00, 0b1001_0001]; let pcm_data = vec![0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE];
let mut pes_data = header;
pes_data.extend_from_slice(&pcm_data);
let pes = make_pes(pes_data, Some(90000));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].data, pcm_data);
assert_eq!(frames[0].pts_ns, 1_000_000_000); }
#[test]
fn bd_lpcm_strips_4_byte_header() {
let mut parser = LpcmParser::new();
let header = vec![0x00, 0x01, 0x00, 0b1001_0001];
let pcm = vec![0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88];
let mut data = header;
data.extend_from_slice(&pcm);
let frames = parser.parse(&make_pes(data, Some(0)));
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].data, pcm, "BD must strip exactly 4 header bytes");
}
#[test]
fn dvd_lpcm_preserves_all_pcm_bytes() {
let mut parser = LpcmParser::new_dvd();
let pcm = vec![0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0x01, 0x02];
let frames = parser.parse(&make_pes(pcm.clone(), Some(90000)));
assert_eq!(frames.len(), 1);
assert_eq!(
frames[0].data, pcm,
"DVD must preserve every PCM byte — no second strip"
);
assert_eq!(frames[0].pts_ns, 1_000_000_000);
}
#[test]
fn dvd_lpcm_emits_short_payload_bd_would_drop() {
let mut bd = LpcmParser::new();
let mut dvd = LpcmParser::new_dvd();
let pcm = vec![0xAA, 0xBB, 0xCC, 0xDD];
assert!(
bd.parse(&make_pes(pcm.clone(), Some(0))).is_empty(),
"BD treats 4 bytes as header-only"
);
let frames = dvd.parse(&make_pes(pcm.clone(), Some(0)));
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].data, pcm);
}
#[test]
fn always_keyframe() {
let mut parser = LpcmParser::new();
for i in 0..5u8 {
let data = vec![0x00, 0x00, 0x00, 0x00, i, i + 1];
let pes = make_pes(data, Some(90000 * i as i64));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert!(frames[0].keyframe, "LPCM frames should always be keyframes");
}
}
#[test]
fn empty_pes_returns_no_frames() {
let mut parser = LpcmParser::new();
let pes = make_pes(Vec::new(), Some(0));
assert!(parser.parse(&pes).is_empty());
}
#[test]
fn header_only_pes_returns_no_frames() {
let mut parser = LpcmParser::new();
let pes = make_pes(vec![0x00, 0x01, 0x00, 0x00], Some(0));
assert!(parser.parse(&pes).is_empty());
}
#[test]
fn codec_private_none() {
let parser = LpcmParser::new();
assert!(parser.codec_private().is_none());
}
#[test]
fn pts_conversion() {
let mut parser = LpcmParser::new();
let pes = make_pes(vec![0; 8], Some(0));
let frames = parser.parse(&pes);
assert_eq!(frames[0].pts_ns, 0);
let pes_no_pts = make_pes(vec![0; 8], None);
let frames = parser.parse(&pes_no_pts);
assert_eq!(frames[0].pts_ns, 0);
}
}