use crate::encoder::nal::build_filler_nal;
use crate::encoder::rate_control::{
RateControlConfig, RateControlMode, RateController, RcFrameKind,
};
use crate::encoder::sei::{
build_buffering_period_payload, build_pic_timing_payload, build_recovery_point_payload,
build_sei_nal,
};
use crate::encoder::{
EncodedB, EncodedFrameRef, EncodedIdr, EncodedP, Encoder, EncoderConfig, YuvFrame,
};
use crate::vui::{HrdParameters, TimingInfo, VuiParameters};
#[derive(Debug, Clone, Copy)]
pub enum SessionRateControl {
ConstantQp(i32),
Controlled(RateControlConfig),
}
#[derive(Debug, Clone, Copy)]
pub struct SessionConfig {
pub width: u32,
pub height: u32,
pub gop_length: u32,
pub cabac: bool,
pub b_frames: u32,
pub rate_control: SessionRateControl,
pub recovery_point_sei: bool,
}
impl SessionConfig {
pub fn constant_qp(width: u32, height: u32, qp: i32) -> Self {
Self {
width,
height,
gop_length: 30,
cabac: false,
b_frames: 0,
rate_control: SessionRateControl::ConstantQp(qp),
recovery_point_sei: false,
}
}
pub fn rate_controlled(width: u32, height: u32, rc: RateControlConfig) -> Self {
Self {
width,
height,
gop_length: 30,
cabac: false,
b_frames: 0,
rate_control: SessionRateControl::Controlled(rc),
recovery_point_sei: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SessionFrameKind {
Idr,
P,
B,
}
#[derive(Debug, Clone)]
pub struct SessionFrame {
pub annex_b: Vec<u8>,
pub is_idr: bool,
pub kind: SessionFrameKind,
pub display_index: u64,
pub qp: i32,
pub payload_bits: u64,
pub sei_bits: u64,
pub filler_bits: u64,
}
enum PrevPic {
Idr(EncodedIdr),
P(EncodedP),
}
impl PrevPic {
fn as_ref(&self) -> EncodedFrameRef<'_> {
match self {
PrevPic::Idr(e) => EncodedFrameRef::from(e),
PrevPic::P(e) => EncodedFrameRef::from(e),
}
}
}
struct PendingFrame {
y: Vec<u8>,
u: Vec<u8>,
v: Vec<u8>,
display_idx: u64,
}
impl PendingFrame {
fn as_yuv(&self, width: u32, height: u32) -> YuvFrame<'_> {
YuvFrame {
width,
height,
y: &self.y,
u: &self.u,
v: &self.v,
}
}
}
pub struct EncoderSession {
enc: Encoder,
cfg: SessionConfig,
rc: Option<RateController>,
hrd: Option<HrdParameters>,
idx: u64,
encoded: u64,
gop_start: u64,
dec_in_gop: u64,
next_ref_frame_num: u32,
prev: Option<PrevPic>,
pending: Vec<PendingFrame>,
next_idr_pic_id: u32,
}
fn session_hrd_parameters(rcfg: &RateControlConfig) -> HrdParameters {
let arrival = match rcfg.mode {
RateControlMode::Cbr => rcfg.target_bitrate,
RateControlMode::CappedVbr => rcfg.max_bitrate.max(rcfg.target_bitrate),
};
HrdParameters {
cpb_cnt_minus1: 0,
bit_rate_scale: 0,
cpb_size_scale: 0,
bit_rate_value_minus1: vec![arrival.div_ceil(64) - 1],
cpb_size_value_minus1: vec![rcfg.vbv_buffer_bits.div_ceil(16) - 1],
cbr_flag: vec![rcfg.mode == RateControlMode::Cbr],
initial_cpb_removal_delay_length_minus1: 23,
cpb_removal_delay_length_minus1: 23,
dpb_output_delay_length_minus1: 23,
time_offset_length: 24,
}
}
fn insert_sei_before_first_vcl(annex_b: &[u8], sei: &[u8]) -> Vec<u8> {
let mut insert_at = annex_b.len();
let mut i = 0usize;
while i + 4 < annex_b.len() {
if annex_b[i..i + 4] == [0, 0, 0, 1] {
let nal_type = annex_b[i + 4] & 0x1F;
if (1..=5).contains(&nal_type) {
insert_at = i;
break;
}
i += 4;
} else {
i += 1;
}
}
let mut out = Vec::with_capacity(annex_b.len() + sei.len());
out.extend_from_slice(&annex_b[..insert_at]);
out.extend_from_slice(sei);
out.extend_from_slice(&annex_b[insert_at..]);
out
}
const MAX_VBV_RETRIES: u32 = 6;
impl EncoderSession {
pub fn new(cfg: SessionConfig) -> Self {
assert!(cfg.gop_length >= 1, "gop_length must be >= 1");
assert!(cfg.b_frames <= 16, "b_frames must be <= 16");
if let SessionRateControl::ConstantQp(qp) = cfg.rate_control {
assert!((0..=51).contains(&qp), "constant QP out of 0..=51");
}
let mut ecfg = EncoderConfig::new(cfg.width, cfg.height);
if cfg.cabac {
ecfg.cabac = true;
ecfg.profile_idc = 77;
}
if cfg.b_frames > 0 {
ecfg.profile_idc = ecfg.profile_idc.max(77);
ecfg.max_num_ref_frames = ecfg.max_num_ref_frames.max(2);
}
if let SessionRateControl::ConstantQp(qp) = cfg.rate_control {
ecfg.qp = qp;
}
let (rc, hrd) = match cfg.rate_control {
SessionRateControl::ConstantQp(_) => (None, None),
SessionRateControl::Controlled(rcfg) => {
let hrd = session_hrd_parameters(&rcfg);
ecfg.vui = Some(VuiParameters {
timing_info: Some(TimingInfo {
num_units_in_tick: rcfg.fps_den,
time_scale: 2 * rcfg.fps_num,
fixed_frame_rate_flag: true,
}),
nal_hrd_parameters: Some(hrd.clone()),
low_delay_hrd_flag: Some(false),
..VuiParameters::default()
});
(
Some(RateController::new(rcfg, cfg.width, cfg.height)),
Some(hrd),
)
}
};
Self {
enc: Encoder::new(ecfg),
cfg,
rc,
hrd,
idx: 0,
encoded: 0,
gop_start: 0,
dec_in_gop: 0,
next_ref_frame_num: 0,
prev: None,
pending: Vec::new(),
next_idr_pic_id: 0,
}
}
pub fn next_idr_pic_id(&self) -> u32 {
self.next_idr_pic_id
}
pub fn parameter_sets_annex_b(&self) -> Vec<u8> {
self.enc.parameter_sets_annex_b(self.cfg.cabac)
}
pub fn rate_controller(&self) -> Option<&RateController> {
self.rc.as_ref()
}
pub fn frames_encoded(&self) -> u64 {
self.encoded
}
fn encode_at(
&self,
frame: &YuvFrame<'_>,
is_idr: bool,
frame_num: u32,
poc_lsb: u32,
qp: i32,
row_budget_bits: Option<u64>,
) -> PrevPic {
if is_idr {
let e = match (self.cfg.cabac, row_budget_bits) {
(true, Some(budget)) => self.enc.encode_idr_cabac_rate_adaptive(frame, qp, budget),
(true, None) => self.enc.encode_idr_cabac_with_qp(frame, qp),
(false, Some(budget)) => self.enc.encode_idr_rate_adaptive(frame, qp, budget),
(false, None) => self.enc.encode_idr_with_qp(frame, qp),
};
PrevPic::Idr(e)
} else {
let prev = self.prev.as_ref().expect("P frame requires a reference");
let r = prev.as_ref();
let e = match (self.cfg.cabac, row_budget_bits) {
(true, Some(budget)) => self
.enc
.encode_p_cabac_rate_adaptive(frame, &r, frame_num, poc_lsb, qp, budget),
(true, None) => self
.enc
.encode_p_cabac_with_qp(frame, &r, frame_num, poc_lsb, qp),
(false, Some(budget)) => self
.enc
.encode_p_rate_adaptive(frame, &r, frame_num, poc_lsb, qp, budget),
(false, None) => self.enc.encode_p_with_qp(frame, &r, frame_num, poc_lsb, qp),
};
PrevPic::P(e)
}
}
fn encode_b_at(
&self,
frame: &YuvFrame<'_>,
l1: &PrevPic,
frame_num: u32,
poc_lsb: u32,
qp: i32,
row_budget_bits: Option<u64>,
) -> EncodedB {
let l0 = self
.prev
.as_ref()
.expect("B frame requires a previous anchor");
let r0 = l0.as_ref();
let r1 = l1.as_ref();
match (self.cfg.cabac, row_budget_bits) {
(true, Some(budget)) => self
.enc
.encode_b_cabac_rate_adaptive(frame, &r0, &r1, frame_num, poc_lsb, qp, budget),
(true, None) => self
.enc
.encode_b_cabac_with_qp(frame, &r0, &r1, frame_num, poc_lsb, qp),
(false, Some(budget)) => self
.enc
.encode_b_rate_adaptive(frame, &r0, &r1, frame_num, poc_lsb, qp, budget),
(false, None) => self
.enc
.encode_b_with_qp(frame, &r0, &r1, frame_num, poc_lsb, qp),
}
}
pub fn encode_frame(&mut self, y: &[u8], u: &[u8], v: &[u8]) -> SessionFrame {
assert_eq!(
self.cfg.b_frames, 0,
"encode_frame has no lookahead; use push_frame/finish with b_frames > 0"
);
let mut out = self.push_frame(y, u, v);
debug_assert_eq!(out.len(), 1);
out.pop().expect("b_frames == 0 emits one AU per picture")
}
pub fn push_frame(&mut self, y: &[u8], u: &[u8], v: &[u8]) -> Vec<SessionFrame> {
let (w, h) = (self.cfg.width as usize, self.cfg.height as usize);
assert_eq!(y.len(), w * h, "luma plane size");
assert_eq!(u.len(), w * h / 4, "cb plane size");
assert_eq!(v.len(), w * h / 4, "cr plane size");
let d = self.idx;
self.idx += 1;
let mut out = Vec::new();
if d % u64::from(self.cfg.gop_length) == 0 {
out.extend(self.flush_pending());
self.gop_start = d;
self.dec_in_gop = 0;
self.next_ref_frame_num = 0;
let frame = YuvFrame {
width: self.cfg.width,
height: self.cfg.height,
y,
u,
v,
};
let (sf, pic) = self.encode_au(&frame, SessionFrameKind::Idr, d, None);
self.prev = pic;
out.push(sf);
} else if self.cfg.b_frames == 0 {
let frame = YuvFrame {
width: self.cfg.width,
height: self.cfg.height,
y,
u,
v,
};
let (sf, pic) = self.encode_au(&frame, SessionFrameKind::P, d, None);
self.prev = pic;
out.push(sf);
} else {
self.pending.push(PendingFrame {
y: y.to_vec(),
u: u.to_vec(),
v: v.to_vec(),
display_idx: d,
});
if self.pending.len() as u32 == self.cfg.b_frames + 1 {
out.extend(self.encode_minigop());
}
}
out
}
pub fn finish(&mut self) -> Vec<SessionFrame> {
self.flush_pending()
}
fn flush_pending(&mut self) -> Vec<SessionFrame> {
if self.pending.is_empty() {
return Vec::new();
}
self.encode_minigop()
}
fn encode_minigop(&mut self) -> Vec<SessionFrame> {
let mut pend = std::mem::take(&mut self.pending);
let anchor = pend.pop().expect("encode_minigop needs pending frames");
let mut out = Vec::with_capacity(pend.len() + 1);
let frame = anchor.as_yuv(self.cfg.width, self.cfg.height);
let (sf, pic) = self.encode_au(&frame, SessionFrameKind::P, anchor.display_idx, None);
out.push(sf);
let anchor_pic = pic.expect("anchor encode returns a reference picture");
for b in &pend {
let frame = b.as_yuv(self.cfg.width, self.cfg.height);
let (sf, _) = self.encode_au(
&frame,
SessionFrameKind::B,
b.display_idx,
Some(&anchor_pic),
);
out.push(sf);
}
self.prev = Some(anchor_pic);
out
}
fn encode_au(
&mut self,
frame: &YuvFrame<'_>,
kind: SessionFrameKind,
display_idx: u64,
l1: Option<&PrevPic>,
) -> (SessionFrame, Option<PrevPic>) {
let is_idr = kind == SessionFrameKind::Idr;
let disp_in_gop = display_idx - self.gop_start;
if is_idr {
let id = self.next_idr_pic_id;
self.enc.set_idr_pic_id(id);
}
let frame_num = match kind {
SessionFrameKind::Idr => 0,
SessionFrameKind::P => self.next_ref_frame_num & 0xFF,
SessionFrameKind::B => self.next_ref_frame_num.wrapping_sub(1) & 0xFF,
};
let poc_lsb = ((2 * disp_in_gop) & 0xFF) as u32;
let rc_kind = match kind {
SessionFrameKind::Idr => RcFrameKind::Idr,
SessionFrameKind::P => RcFrameKind::P,
SessionFrameKind::B => RcFrameKind::B,
};
let mut msgs: Vec<(u32, Vec<u8>)> = Vec::new();
if self.cfg.recovery_point_sei && is_idr {
msgs.push((6u32, build_recovery_point_payload(0, true, false, 0)));
}
let sei_nal: Option<Vec<u8>> = match (&self.hrd, &self.rc) {
(Some(hrd), Some(rc)) => {
if is_idr {
let rcfg = rc.config();
let arrival = match rcfg.mode {
RateControlMode::Cbr => f64::from(rcfg.target_bitrate),
RateControlMode::CappedVbr => {
f64::from(rcfg.max_bitrate.max(rcfg.target_bitrate))
}
};
let delay = ((90_000.0 * rc.cpb_fullness() / arrival).round() as u32).max(1);
let full =
(90_000.0 * f64::from(rcfg.vbv_buffer_bits) / arrival).round() as u32;
let offset = full.saturating_sub(delay);
msgs.push((
0u32,
build_buffering_period_payload(0, hrd, &[(delay, offset)]),
));
}
let cpb_removal_delay = (2 * self.dec_in_gop) as u32;
let dpb_output_delay = if self.cfg.b_frames > 0 {
let d = 2 * (disp_in_gop as i64 + 1 - self.dec_in_gop as i64);
debug_assert!(d >= 0, "negative dpb_output_delay");
d.max(0) as u32
} else {
0
};
msgs.push((
1u32,
build_pic_timing_payload(hrd, cpb_removal_delay, dpb_output_delay),
));
Some(build_sei_nal(&msgs))
}
_ if !msgs.is_empty() => Some(build_sei_nal(&msgs)),
_ => None,
};
let sei_bits = sei_nal.as_ref().map_or(0, |s| 8 * s.len() as u64);
let rc_plan = self
.rc
.as_ref()
.map(|rc| (rc.plan_frame(rc_kind), rc.config().max_qp));
let (pic, qp_used, filler_bits) = match rc_plan {
None => {
let qp = match self.cfg.rate_control {
SessionRateControl::ConstantQp(q) => q,
SessionRateControl::Controlled(_) => unreachable!(),
};
(
self.encode_pic(frame, kind, frame_num, poc_lsb, qp, None, l1),
qp,
0,
)
}
Some((plan, max_qp)) => {
let row_budget = Some(plan.target_bits.max(1.0) as u64);
let mut qp = plan.qp;
let mut pic = self.encode_pic(frame, kind, frame_num, poc_lsb, qp, row_budget, l1);
for _ in 0..MAX_VBV_RETRIES {
let bits = sei_bits + 8 * au_annex_b(&pic).len() as u64;
if bits <= plan.max_bits || qp >= max_qp {
break;
}
qp = (qp + 2).min(max_qp);
pic = self.encode_pic(frame, kind, frame_num, poc_lsb, qp, row_budget, l1);
}
let bits = sei_bits + 8 * au_annex_b(&pic).len() as u64;
let outcome = self
.rc
.as_mut()
.expect("rc_plan is Some")
.commit_frame(rc_kind, qp, bits);
(pic, qp, outcome.filler_bits)
}
};
let payload_bits = sei_bits + 8 * au_annex_b(&pic).len() as u64;
let mut annex_b = match &sei_nal {
Some(sei) => insert_sei_before_first_vcl(au_annex_b(&pic), sei),
None => au_annex_b(&pic).to_vec(),
};
let mut emitted_filler = 0u64;
if filler_bits > 0 {
let total_bytes = filler_bits.div_ceil(8);
if total_bytes >= 6 {
let ff = (total_bytes - 6) as usize;
let nal = build_filler_nal(ff);
emitted_filler = 8 * nal.len() as u64;
annex_b.extend_from_slice(&nal);
}
}
self.dec_in_gop += 1;
self.encoded += 1;
if is_idr {
self.next_idr_pic_id = (self.next_idr_pic_id + 1) & 0xFFFF;
}
let anchor_pic = match pic {
AuPic::Anchor(p) => {
self.next_ref_frame_num = (frame_num + 1) & 0xFF;
Some(p)
}
AuPic::B(_) => None,
};
(
SessionFrame {
annex_b,
is_idr,
kind,
display_index: display_idx,
qp: qp_used,
payload_bits,
sei_bits,
filler_bits: emitted_filler,
},
anchor_pic,
)
}
#[allow(clippy::too_many_arguments)]
fn encode_pic(
&self,
frame: &YuvFrame<'_>,
kind: SessionFrameKind,
frame_num: u32,
poc_lsb: u32,
qp: i32,
row_budget_bits: Option<u64>,
l1: Option<&PrevPic>,
) -> AuPic {
match kind {
SessionFrameKind::Idr | SessionFrameKind::P => AuPic::Anchor(self.encode_at(
frame,
kind == SessionFrameKind::Idr,
frame_num,
poc_lsb,
qp,
row_budget_bits,
)),
SessionFrameKind::B => AuPic::B(self.encode_b_at(
frame,
l1.expect("B encode requires the mini-GOP anchor"),
frame_num,
poc_lsb,
qp,
row_budget_bits,
)),
}
}
}
enum AuPic {
Anchor(PrevPic),
B(EncodedB),
}
fn au_annex_b(p: &AuPic) -> &[u8] {
match p {
AuPic::Anchor(PrevPic::Idr(e)) => &e.annex_b,
AuPic::Anchor(PrevPic::P(e)) => &e.annex_b,
AuPic::B(e) => &e.annex_b,
}
}