#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FrameRole {
Idr {
display_index: u32,
},
P {
display_index: u32,
},
B {
display_index: u32,
l0_display_index: u32,
l1_display_index: u32,
},
}
impl FrameRole {
pub fn display_index(&self) -> u32 {
match self {
FrameRole::Idr { display_index }
| FrameRole::P { display_index }
| FrameRole::B { display_index, .. } => *display_index,
}
}
pub fn is_b(&self) -> bool {
matches!(self, FrameRole::B { .. })
}
pub fn is_idr(&self) -> bool {
matches!(self, FrameRole::Idr { .. })
}
}
#[derive(Debug)]
pub struct EncodeFrame {
pub yuv: Vec<u8>,
pub role: FrameRole,
}
#[derive(Debug)]
pub struct ReorderBuffer {
pub gop_length: u32,
pub m_factor: u32,
next_display_index: u32,
pending_b: Vec<(u32, Vec<u8>)>,
last_p_display: Option<u32>,
}
impl ReorderBuffer {
pub fn new(gop_length: u32, m_factor: u32) -> Self {
assert!(m_factor >= 1, "m_factor must be >= 1");
assert!(gop_length >= 1, "gop_length must be >= 1");
Self {
gop_length,
m_factor,
next_display_index: 0,
pending_b: Vec::with_capacity((m_factor - 1) as usize),
last_p_display: None,
}
}
pub fn reset(&mut self) {
self.next_display_index = 0;
self.pending_b.clear();
self.last_p_display = None;
}
pub fn push(&mut self, yuv: Vec<u8>) -> Vec<EncodeFrame> {
let display_index = self.next_display_index;
self.next_display_index += 1;
let is_idr = display_index.is_multiple_of(self.gop_length);
let is_anchor =
is_idr || (display_index - self.gop_position_zero(display_index)).is_multiple_of(self.m_factor);
if is_idr {
self.pending_b.clear();
self.last_p_display = Some(display_index);
return vec![EncodeFrame {
yuv,
role: FrameRole::Idr { display_index },
}];
}
if is_anchor {
let mut out = Vec::with_capacity(1 + self.pending_b.len());
out.push(EncodeFrame {
yuv,
role: FrameRole::P { display_index },
});
let l0 = self
.last_p_display
.expect("anchor frame must have a previous P or IDR");
for (b_display, b_yuv) in self.pending_b.drain(..) {
out.push(EncodeFrame {
yuv: b_yuv,
role: FrameRole::B {
display_index: b_display,
l0_display_index: l0,
l1_display_index: display_index,
},
});
}
self.last_p_display = Some(display_index);
return out;
}
self.pending_b.push((display_index, yuv));
Vec::new()
}
pub fn flush(&mut self) -> Vec<EncodeFrame> {
self.pending_b.clear();
self.last_p_display = None;
Vec::new()
}
pub fn pending_count(&self) -> usize {
self.pending_b.len()
}
fn gop_position_zero(&self, display_index: u32) -> u32 {
(display_index / self.gop_length) * self.gop_length
}
}
#[cfg(test)]
mod tests {
use super::*;
fn dummy_yuv(seed: u8) -> Vec<u8> {
vec![seed; 1536]
}
#[test]
fn m1_emits_p_immediately() {
let mut buf = ReorderBuffer::new( 30, 1);
let out = buf.push(dummy_yuv(0));
assert_eq!(out.len(), 1);
assert!(matches!(out[0].role, FrameRole::Idr { display_index: 0 }));
let out = buf.push(dummy_yuv(1));
assert_eq!(out.len(), 1);
assert!(matches!(out[0].role, FrameRole::P { display_index: 1 }));
}
#[test]
fn m2_alternates_p_and_b_in_encode_order() {
let mut buf = ReorderBuffer::new( 30, 2);
let out = buf.push(dummy_yuv(0));
assert_eq!(out.len(), 1);
assert!(matches!(out[0].role, FrameRole::Idr { display_index: 0 }));
let out = buf.push(dummy_yuv(1));
assert_eq!(out.len(), 0);
assert_eq!(buf.pending_count(), 1);
let out = buf.push(dummy_yuv(2));
assert_eq!(out.len(), 2);
assert!(matches!(out[0].role, FrameRole::P { display_index: 2 }));
assert!(matches!(
out[1].role,
FrameRole::B {
display_index: 1,
l0_display_index: 0,
l1_display_index: 2
}
));
assert_eq!(buf.pending_count(), 0);
let out = buf.push(dummy_yuv(3));
assert_eq!(out.len(), 0);
let out = buf.push(dummy_yuv(4));
assert_eq!(out.len(), 2);
assert!(matches!(out[0].role, FrameRole::P { display_index: 4 }));
assert!(matches!(
out[1].role,
FrameRole::B {
display_index: 3,
l0_display_index: 2,
l1_display_index: 4
}
));
}
#[test]
fn idr_at_gop_boundary_resets() {
let mut buf = ReorderBuffer::new( 4, 2);
let _ = buf.push(dummy_yuv(0)); let _ = buf.push(dummy_yuv(1)); let _ = buf.push(dummy_yuv(2)); let out = buf.push(dummy_yuv(3));
assert_eq!(out.len(), 0);
assert_eq!(buf.pending_count(), 1);
let out = buf.push(dummy_yuv(4));
assert_eq!(out.len(), 1);
assert!(matches!(out[0].role, FrameRole::Idr { display_index: 4 }));
assert_eq!(buf.pending_count(), 0);
}
#[test]
fn flush_drops_pending() {
let mut buf = ReorderBuffer::new(30, 2);
let _ = buf.push(dummy_yuv(0));
let _ = buf.push(dummy_yuv(1));
assert_eq!(buf.pending_count(), 1);
let out = buf.flush();
assert_eq!(out.len(), 0);
assert_eq!(buf.pending_count(), 0);
}
#[test]
fn frame_role_helpers() {
assert!(FrameRole::Idr { display_index: 0 }.is_idr());
assert!(!FrameRole::Idr { display_index: 0 }.is_b());
assert!(FrameRole::B {
display_index: 1,
l0_display_index: 0,
l1_display_index: 2,
}.is_b());
assert!(!FrameRole::P { display_index: 2 }.is_b());
assert_eq!(
FrameRole::B {
display_index: 5,
l0_display_index: 4,
l1_display_index: 6,
}
.display_index(),
5
);
}
#[test]
fn reset_clears_state() {
let mut buf = ReorderBuffer::new(30, 2);
let _ = buf.push(dummy_yuv(0));
let _ = buf.push(dummy_yuv(1));
buf.reset();
assert_eq!(buf.pending_count(), 0);
let out = buf.push(dummy_yuv(0));
assert_eq!(out.len(), 1);
assert!(matches!(out[0].role, FrameRole::Idr { display_index: 0 }));
}
}