videocall_codecs/
encoder.rs1use anyhow::Result;
20use std::ffi::{c_int, CStr};
21use std::mem::MaybeUninit;
22use std::os::raw::c_char;
23use vpx_sys::*;
24
25const FPS: u64 = 30;
26
27pub struct Vp9Encoder {
31 ctx: vpx_codec_ctx_t,
32 pub width: u32,
33 pub height: u32,
34}
35
36unsafe impl Send for Vp9Encoder {}
39unsafe impl Sync for Vp9Encoder {}
40
41fn c_str_to_rust_str(c_str_ptr: *const c_char) -> String {
43 if c_str_ptr.is_null() {
44 return "Unknown error".to_string();
45 }
46 unsafe { CStr::from_ptr(c_str_ptr).to_string_lossy().into_owned() }
47}
48
49impl Vp9Encoder {
50 pub fn new(width: u32, height: u32, bitrate_kbps: u32) -> Result<Self> {
51 unsafe {
52 let mut cfg: vpx_codec_enc_cfg_t = MaybeUninit::zeroed().assume_init();
53 let ret = vpx_codec_enc_config_default(vpx_codec_vp9_cx(), &mut cfg, 0);
54 if ret != VPX_CODEC_OK {
55 anyhow::bail!("Failed to get default VP9 encoder config");
56 }
57
58 cfg.g_w = width;
59 cfg.g_h = height;
60 cfg.g_timebase.num = 1;
61 cfg.g_timebase.den = FPS as c_int;
62 cfg.rc_target_bitrate = bitrate_kbps;
63 cfg.rc_end_usage = vpx_rc_mode::VPX_VBR;
64 cfg.kf_max_dist = 150;
66 cfg.kf_min_dist = 150;
67 cfg.kf_mode = vpx_kf_mode::VPX_KF_AUTO;
68
69 let mut ctx: vpx_codec_ctx_t = MaybeUninit::zeroed().assume_init();
70 let ret = vpx_codec_enc_init_ver(
71 &mut ctx,
72 vpx_codec_vp9_cx(),
73 &cfg,
74 0,
75 VPX_ENCODER_ABI_VERSION as i32,
76 );
77 if ret != VPX_CODEC_OK {
78 let err_msg = c_str_to_rust_str(vpx_codec_err_to_string(ret));
79 anyhow::bail!("Failed to initialize VP9 encoder: {err_msg}");
80 }
81
82 Ok(Vp9Encoder { ctx, width, height })
83 }
84 }
85
86 pub fn encode(&mut self, frame_count: i64, yuv_data: Option<&[u8]>) -> Result<Frames<'_>> {
87 unsafe {
88 let image_ptr = if let Some(data) = yuv_data {
89 let mut image: vpx_image_t = MaybeUninit::zeroed().assume_init();
90 vpx_img_wrap(
91 &mut image,
92 vpx_img_fmt::VPX_IMG_FMT_I420,
93 self.width,
94 self.height,
95 1,
96 data.as_ptr() as *mut u8,
97 );
98 &image as *const _
99 } else {
100 std::ptr::null()
102 };
103
104 let ret = vpx_codec_encode(
105 &mut self.ctx,
106 image_ptr,
107 frame_count, 1, 0,
110 VPX_DL_REALTIME as u64,
111 );
112 if ret != VPX_CODEC_OK {
113 let err_msg = c_str_to_rust_str(vpx_codec_error(&mut self.ctx as *mut _));
114 anyhow::bail!("Failed to encode frame: {err_msg:?}");
115 }
116
117 Ok(Frames {
118 ctx: &mut self.ctx,
119 iter: std::ptr::null(),
120 })
121 }
122 }
123
124 pub fn width(&self) -> u32 {
125 self.width
126 }
127
128 pub fn height(&self) -> u32 {
129 self.height
130 }
131}
132
133impl Drop for Vp9Encoder {
134 fn drop(&mut self) {
135 unsafe {
136 vpx_codec_destroy(&mut self.ctx);
137 }
138 }
139}
140
141#[derive(Clone, Copy, Debug)]
144pub struct Frame<'a> {
145 pub data: &'a [u8],
147 pub key: bool,
149 pub pts: i64,
151}
152
153pub struct Frames<'a> {
154 ctx: &'a mut vpx_codec_ctx_t,
155 iter: vpx_codec_iter_t,
156}
157
158impl<'a> Iterator for Frames<'a> {
159 type Item = Frame<'a>;
160 #[allow(clippy::unnecessary_cast)]
161 fn next(&mut self) -> Option<Self::Item> {
162 loop {
163 unsafe {
164 let pkt = vpx_codec_get_cx_data(self.ctx, &mut self.iter);
165 if pkt.is_null() {
166 return None;
167 } else if (*pkt).kind == vpx_codec_cx_pkt_kind::VPX_CODEC_CX_FRAME_PKT {
168 let f = &(*pkt).data.frame;
169 return Some(Frame {
170 data: std::slice::from_raw_parts(f.buf as _, f.sz as usize),
171 key: (f.flags & VPX_FRAME_IS_KEY) != 0,
172 pts: f.pts,
173 });
174 }
175 }
176 }
177 }
178}