use super::{Encodable, EncodedFrame, EncoderConfig};
use anyhow::Result;
use std::ffi::{c_int, CStr};
use std::mem::MaybeUninit;
use std::os::raw::c_char;
use vpx_sys::*;
const FPS: u64 = 30;
pub struct Vp9Encoder {
ctx: vpx_codec_ctx_t,
cfg: vpx_codec_enc_cfg_t,
pub width: u32,
pub height: u32,
}
unsafe impl Send for Vp9Encoder {}
unsafe impl Sync for Vp9Encoder {}
fn c_str_to_rust_str(c_str_ptr: *const c_char) -> String {
if c_str_ptr.is_null() {
return "Unknown error".to_string();
}
unsafe { CStr::from_ptr(c_str_ptr).to_string_lossy().into_owned() }
}
impl Vp9Encoder {
pub fn new(width: u32, height: u32, bitrate_kbps: u32) -> Result<Self> {
unsafe {
let mut cfg: vpx_codec_enc_cfg_t = MaybeUninit::zeroed().assume_init();
let ret = vpx_codec_enc_config_default(vpx_codec_vp9_cx(), &mut cfg, 0);
if ret != VPX_CODEC_OK {
anyhow::bail!("Failed to get default VP9 encoder config");
}
cfg.g_w = width;
cfg.g_h = height;
cfg.g_timebase.num = 1;
cfg.g_timebase.den = FPS as c_int;
cfg.rc_target_bitrate = bitrate_kbps;
cfg.rc_end_usage = vpx_rc_mode::VPX_VBR;
cfg.kf_max_dist = 150;
cfg.kf_min_dist = 150;
cfg.kf_mode = vpx_kf_mode::VPX_KF_AUTO;
let mut ctx: vpx_codec_ctx_t = MaybeUninit::zeroed().assume_init();
let ret = vpx_codec_enc_init_ver(
&mut ctx,
vpx_codec_vp9_cx(),
&cfg,
0,
VPX_ENCODER_ABI_VERSION as i32,
);
if ret != VPX_CODEC_OK {
let err_msg = c_str_to_rust_str(vpx_codec_err_to_string(ret));
anyhow::bail!("Failed to initialize VP9 encoder: {err_msg}");
}
Ok(Vp9Encoder {
ctx,
cfg,
width,
height,
})
}
}
pub fn encode(&mut self, frame_count: i64, yuv_data: Option<&[u8]>) -> Result<Frames<'_>> {
unsafe {
let image_ptr = if let Some(data) = yuv_data {
let mut image: vpx_image_t = MaybeUninit::zeroed().assume_init();
vpx_img_wrap(
&mut image,
vpx_img_fmt::VPX_IMG_FMT_I420,
self.width,
self.height,
1,
data.as_ptr() as *mut u8,
);
&image as *const _
} else {
std::ptr::null()
};
let ret = vpx_codec_encode(
&mut self.ctx,
image_ptr,
frame_count, 1, 0,
VPX_DL_REALTIME as u64,
);
if ret != VPX_CODEC_OK {
let err_msg = c_str_to_rust_str(vpx_codec_error(&mut self.ctx as *mut _));
anyhow::bail!("Failed to encode frame: {err_msg:?}");
}
Ok(Frames {
ctx: &mut self.ctx,
iter: std::ptr::null(),
})
}
}
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.height
}
}
impl Drop for Vp9Encoder {
fn drop(&mut self) {
unsafe {
vpx_codec_destroy(&mut self.ctx);
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct Frame<'a> {
pub data: &'a [u8],
pub key: bool,
pub pts: i64,
}
pub struct Frames<'a> {
ctx: &'a mut vpx_codec_ctx_t,
iter: vpx_codec_iter_t,
}
impl<'a> Iterator for Frames<'a> {
type Item = Frame<'a>;
#[allow(clippy::unnecessary_cast)]
fn next(&mut self) -> Option<Self::Item> {
loop {
unsafe {
let pkt = vpx_codec_get_cx_data(self.ctx, &mut self.iter);
if pkt.is_null() {
return None;
} else if (*pkt).kind == vpx_codec_cx_pkt_kind::VPX_CODEC_CX_FRAME_PKT {
let f = &(*pkt).data.frame;
return Some(Frame {
data: std::slice::from_raw_parts(f.buf as _, f.sz as usize),
key: (f.flags & VPX_FRAME_IS_KEY) != 0,
pts: f.pts,
});
}
}
}
}
}
impl Encodable for Vp9Encoder {
fn new(config: EncoderConfig) -> Result<Self> {
unsafe {
let mut cfg: vpx_codec_enc_cfg_t = MaybeUninit::zeroed().assume_init();
let ret = vpx_codec_enc_config_default(vpx_codec_vp9_cx(), &mut cfg, 0);
if ret != VPX_CODEC_OK {
anyhow::bail!("Failed to get default VP9 encoder config");
}
cfg.g_w = config.width;
cfg.g_h = config.height;
cfg.g_timebase.num = 1;
cfg.g_timebase.den = config.framerate as c_int;
cfg.rc_target_bitrate = config.bitrate_kbps;
cfg.rc_min_quantizer = config.min_quantizer;
cfg.rc_max_quantizer = config.max_quantizer;
cfg.g_threads = 2;
cfg.g_lag_in_frames = 1;
cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT;
cfg.g_pass = vpx_enc_pass::VPX_RC_ONE_PASS;
cfg.g_profile = 0;
cfg.rc_end_usage = vpx_rc_mode::VPX_VBR;
cfg.kf_max_dist = config.keyframe_interval;
cfg.kf_min_dist = config.keyframe_interval;
cfg.kf_mode = vpx_kf_mode::VPX_KF_AUTO;
let mut ctx: vpx_codec_ctx_t = MaybeUninit::zeroed().assume_init();
let ret = vpx_codec_enc_init_ver(
&mut ctx,
vpx_codec_vp9_cx(),
&cfg,
0,
VPX_ENCODER_ABI_VERSION as i32,
);
if ret != VPX_CODEC_OK {
let err_msg = c_str_to_rust_str(vpx_codec_err_to_string(ret));
anyhow::bail!("Failed to initialize VP9 encoder: {err_msg}");
}
vpx_codec_control_(&mut ctx, vp8e_enc_control_id::VP8E_SET_CPUUSED as c_int, 5);
vpx_codec_control_(
&mut ctx,
vp8e_enc_control_id::VP9E_SET_TILE_COLUMNS as c_int,
4,
);
vpx_codec_control_(&mut ctx, vp8e_enc_control_id::VP9E_SET_ROW_MT as c_int, 1);
vpx_codec_control_(
&mut ctx,
vp8e_enc_control_id::VP9E_SET_FRAME_PARALLEL_DECODING as c_int,
1,
);
Ok(Vp9Encoder {
ctx,
cfg,
width: config.width,
height: config.height,
})
}
}
fn update_bitrate_kbps(&mut self, kbps: u32) -> Result<()> {
self.cfg.rc_target_bitrate = kbps;
unsafe {
let ret = vpx_codec_enc_config_set(&mut self.ctx, &self.cfg);
if ret != VPX_CODEC_OK {
let err_msg = c_str_to_rust_str(vpx_codec_error(&mut self.ctx as *mut _));
anyhow::bail!("Failed to update encoder bitrate: {err_msg}");
}
}
Ok(())
}
fn encode(&mut self, pts: i64, i420: &[u8]) -> Result<Option<EncodedFrame>> {
unsafe {
let mut image: vpx_image_t = MaybeUninit::zeroed().assume_init();
vpx_img_wrap(
&mut image,
vpx_img_fmt::VPX_IMG_FMT_I420,
self.width,
self.height,
1,
i420.as_ptr() as *mut u8,
);
let ret = vpx_codec_encode(&mut self.ctx, &image, pts, 1, 0, VPX_DL_REALTIME as u64);
if ret != VPX_CODEC_OK {
let err_msg = c_str_to_rust_str(vpx_codec_error(&mut self.ctx as *mut _));
anyhow::bail!("Failed to encode frame: {err_msg}");
}
let mut iter: vpx_codec_iter_t = std::ptr::null();
loop {
let pkt = vpx_codec_get_cx_data(&mut self.ctx, &mut iter);
if pkt.is_null() {
return Ok(None);
}
if (*pkt).kind == vpx_codec_cx_pkt_kind::VPX_CODEC_CX_FRAME_PKT {
let f = &(*pkt).data.frame;
let data =
std::slice::from_raw_parts(f.buf as *const u8, f.sz as usize).to_vec();
return Ok(Some(EncodedFrame {
data,
is_keyframe: (f.flags & VPX_FRAME_IS_KEY) != 0,
pts: f.pts,
}));
}
}
}
}
}