use crate::h264::MAX_VIDEO_FRAME_BYTES;
#[derive(Default)] pub(crate) struct Vp8Depacketizer { timestamp: Option, buffer: Vec, keyframe: bool, saw_partition_start: bool, }
impl Vp8Depacketizer { pub(crate) fn push( &mut self, timestamp: u32, marker: bool, payload: &[u8], ) -> Option<(Vec, bool)> { let (descriptor_len, start_of_partition) = parse_vp8_payload_descriptor(payload)?; let frame_payload = &payload[descriptor_len..]; if frame_payload.is_empty() { return None; }
if self.timestamp != Some(timestamp) {
self.timestamp = Some(timestamp);
self.buffer.clear();
self.keyframe = false;
self.saw_partition_start = false;
}
if start_of_partition {
self.saw_partition_start = true;
if let Some(first_byte) = frame_payload.first() {
self.keyframe = (first_byte & 0x01) == 0;
}
} else if !self.saw_partition_start && self.buffer.is_empty() {
return None;
}
if self.buffer.len().saturating_add(frame_payload.len()) > MAX_VIDEO_FRAME_BYTES {
self.timestamp = None;
self.buffer.clear();
self.keyframe = false;
self.saw_partition_start = false;
return None;
}
self.buffer.extend_from_slice(frame_payload);
if marker && !self.buffer.is_empty() {
let frame = std::mem::take(&mut self.buffer);
let keyframe = self.keyframe;
self.timestamp = None;
self.keyframe = false;
self.saw_partition_start = false;
return Some((frame, keyframe));
}
None
}
pub(crate) fn reset(&mut self) {
self.timestamp = None;
self.buffer.clear();
self.keyframe = false;
self.saw_partition_start = false;
}
}
pub(crate) fn parse_vp8_payload_descriptor(payload: &[u8]) -> Option<(usize, bool)> { if payload.is_empty() { return None; } let mut cursor = 1usize; let x = (payload[0] & 0x80) != 0; let s = (payload[0] & 0x10) != 0; let partition_id = payload[0] & 0x0F; if x { if payload.len() < cursor + 1 { return None; } let i = (payload[cursor] & 0x80) != 0; let l = (payload[cursor] & 0x40) != 0; let t = (payload[cursor] & 0x20) != 0; let k = (payload[cursor] & 0x10) != 0; cursor += 1; if i { if payload.len() < cursor + 1 { return None; } let m = (payload[cursor] & 0x80) != 0; cursor += if m { 2 } else { 1 }; } if l { cursor += 1; } if t || k { cursor += 1; } } if payload.len() < cursor { return None; } Some((cursor, s && partition_id == 0)) }
#[cfg(test)] mod tests { use super::*;
#[test]
fn vp8_video_depacketizer_resets_on_sequence_gap() {
let mut depacketizer = Vp8Depacketizer::default();
let timestamp = 45_000u32;
// First packet: start-of-partition, not marker (frame not complete yet)
let start_packet = [0x10, 0x00, 0xAA];
assert_eq!(depacketizer.push(timestamp, false, &start_packet), None);
// Simulate a sequence gap by calling reset (as VideoDepacketizerState
// would do on detecting a sequence number gap), then push a
// continuation packet — it should be dropped because reset cleared state.
depacketizer.reset();
let continuation_packet = [0x00, 0xBB];
assert_eq!(
depacketizer.push(timestamp, true, &continuation_packet),
None
);
// Next frame on a new timestamp should succeed as a single-packet frame.
let next_timestamp = timestamp.wrapping_add(3_000);
let next_frame_packet = [0x10, 0x00, 0xCC];
let (frame, keyframe) = depacketizer
.push(next_timestamp, true, &next_frame_packet)
.expect("single-packet vp8 frame should survive after gap reset");
assert_eq!(frame, vec![0x00, 0xCC]);
assert!(keyframe);
}
}
