videocall_cli/
video_encoder.rs1use anyhow::{anyhow, Result};
20use std::mem::MaybeUninit;
21use std::os::raw::{c_int, c_ulong};
22use vpx_sys::*;
23
24macro_rules! vpx {
25 ($f:expr) => {{
26 let res = unsafe { $f };
27 let res_int = unsafe { std::mem::transmute::<vpx_sys::vpx_codec_err_t, i32>(res) };
28 if res_int != 0 {
29 return Err(anyhow!("vpx function error code ({}).", res_int));
30 }
31 res
32 }};
33}
34
35macro_rules! vpx_ptr {
36 ($f:expr) => {{
37 let res = unsafe { $f };
38 if res.is_null() {
39 return Err(anyhow!("vpx function returned null pointer."));
40 }
41 res
42 }};
43}
44
45pub struct VideoEncoderBuilder {
46 pub min_quantizer: u32,
47 pub max_quantizer: u32,
48 pub bitrate_kbps: u32,
49 pub fps: u32,
50 pub resolution: (u32, u32),
51 pub cpu_used: u32,
52 pub profile: u32,
53}
54
55impl VideoEncoderBuilder {
56 pub fn new(fps: u32, cpu_used: u8) -> Self {
57 Self {
58 bitrate_kbps: 500,
59 max_quantizer: 60,
60 min_quantizer: 40,
61 resolution: (640, 480),
62 fps,
63 cpu_used: cpu_used as u32,
64 profile: 0,
65 }
66 }
67}
68
69impl VideoEncoderBuilder {
70 pub fn set_resolution(mut self, width: u32, height: u32) -> Self {
71 self.resolution = (width, height);
72 self
73 }
74
75 pub fn build(&self) -> Result<VideoEncoder> {
76 let (width, height) = self.resolution;
77 if width % 2 != 0 || width == 0 {
78 return Err(anyhow!("Width must be divisible by 2"));
79 }
80 if height % 2 != 0 || height == 0 {
81 return Err(anyhow!("Height must be divisible by 2"));
82 }
83 let cfg_ptr = vpx_ptr!(vpx_codec_vp9_cx());
84 let mut cfg = unsafe { MaybeUninit::zeroed().assume_init() };
85 vpx!(vpx_codec_enc_config_default(cfg_ptr, &mut cfg, 0));
86
87 cfg.g_w = width;
88 cfg.g_h = height;
89 cfg.g_timebase.num = 1;
90 cfg.g_timebase.den = self.fps as c_int;
91 cfg.rc_target_bitrate = self.bitrate_kbps;
92 cfg.rc_min_quantizer = self.min_quantizer;
93 cfg.rc_max_quantizer = self.max_quantizer;
94 cfg.g_threads = 2;
95 cfg.g_lag_in_frames = 1;
96 cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT;
97 cfg.g_pass = vpx_enc_pass::VPX_RC_ONE_PASS;
98 cfg.g_profile = self.profile;
99 cfg.rc_end_usage = vpx_rc_mode::VPX_VBR;
100 cfg.kf_max_dist = 150;
101 cfg.kf_min_dist = 150;
102 cfg.kf_mode = vpx_kf_mode::VPX_KF_AUTO;
103
104 let ctx = MaybeUninit::zeroed();
105 let mut ctx = unsafe { ctx.assume_init() };
106
107 vpx!(vpx_codec_enc_init_ver(
108 &mut ctx,
109 cfg_ptr,
110 &cfg,
111 0,
112 VPX_ENCODER_ABI_VERSION as i32
113 ));
114 unsafe {
115 vpx_codec_control_(&mut ctx, vp8e_enc_control_id::VP8E_SET_CPUUSED as c_int, 5);
116 vpx_codec_control_(
117 &mut ctx,
118 vp8e_enc_control_id::VP9E_SET_TILE_COLUMNS as c_int,
119 4,
120 );
121 vpx_codec_control_(&mut ctx, vp8e_enc_control_id::VP9E_SET_ROW_MT as c_int, 1);
122 vpx_codec_control_(
123 &mut ctx,
124 vp8e_enc_control_id::VP9E_SET_FRAME_PARALLEL_DECODING as c_int,
125 1,
126 );
127 }
129 Ok(VideoEncoder {
130 ctx,
131 cfg,
132 width: self.resolution.0,
133 height: self.resolution.1,
134 })
135 }
136}
137
138pub struct VideoEncoder {
139 ctx: vpx_codec_ctx_t,
140 cfg: vpx_codec_enc_cfg_t,
141 width: u32,
142 height: u32,
143}
144
145impl VideoEncoder {
146 pub fn update_bitrate_kbps(&mut self, bitrate: u32) -> anyhow::Result<()> {
147 self.cfg.rc_target_bitrate = bitrate;
148 vpx!(vpx_codec_enc_config_set(&mut self.ctx, &self.cfg));
149 Ok(())
150 }
151
152 pub fn encode(&mut self, pts: i64, data: &[u8]) -> anyhow::Result<Frames<'_>> {
153 let image = MaybeUninit::zeroed();
154 let mut image = unsafe { image.assume_init() };
155
156 vpx_ptr!(vpx_img_wrap(
157 &mut image,
158 vpx_img_fmt::VPX_IMG_FMT_I420,
159 self.width as _,
160 self.height as _,
161 1,
162 data.as_ptr() as _,
163 ));
164
165 let flags: i64 = 0;
166
167 vpx!(vpx_codec_encode(
168 &mut self.ctx,
169 &image,
170 pts,
171 1, flags, VPX_DL_REALTIME as c_ulong,
174 ));
175
176 Ok(Frames {
177 ctx: &mut self.ctx,
178 iter: std::ptr::null(),
179 })
180 }
181}
182
183#[derive(Clone, Copy, Debug)]
184pub struct Frame<'a> {
185 pub data: &'a [u8],
187 pub key: bool,
189 pub pts: i64,
191}
192
193pub struct Frames<'a> {
194 ctx: &'a mut vpx_codec_ctx_t,
195 iter: vpx_codec_iter_t,
196}
197
198impl<'a> Iterator for Frames<'a> {
199 type Item = Frame<'a>;
200 #[allow(clippy::unnecessary_cast)]
201 fn next(&mut self) -> Option<Self::Item> {
202 loop {
203 unsafe {
204 let pkt = vpx_codec_get_cx_data(self.ctx, &mut self.iter);
205 if pkt.is_null() {
206 return None;
207 } else if (*pkt).kind == vpx_codec_cx_pkt_kind::VPX_CODEC_CX_FRAME_PKT {
208 let f = &(*pkt).data.frame;
209 return Some(Frame {
210 data: std::slice::from_raw_parts(f.buf as _, f.sz as usize),
211 key: (f.flags & VPX_FRAME_IS_KEY) != 0,
212 pts: f.pts,
213 });
214 }
215 }
216 }
217 }
218}