Skip to main content

web_codecs/video/
encoder.rs

1use std::{cell::RefCell, rc::Rc, time::Duration};
2
3use tokio::sync::{mpsc, watch};
4use wasm_bindgen::{prelude::*, JsCast};
5
6use crate::{EncodedFrame, Error, Timestamp};
7
8use super::{Dimensions, VideoDecoderConfig, VideoFrame};
9
10use derive_more::Display;
11
12#[derive(Debug, Display, Clone, Copy)]
13pub enum VideoBitrateMode {
14	#[display("constant")]
15	Constant,
16
17	#[display("variable")]
18	Variable,
19
20	#[display("quantizer")]
21	Quantizer,
22}
23
24#[derive(Debug, Default, Clone)]
25pub struct VideoEncoderConfig {
26	pub codec: String,
27	pub resolution: Dimensions,
28	pub display: Option<Dimensions>,
29	pub hardware_acceleration: Option<bool>,
30	pub latency_optimized: Option<bool>,
31	pub bitrate: Option<u32>,          // bits per second
32	pub framerate: Option<f64>,        // frames per second
33	pub alpha_preserved: Option<bool>, // keep alpha channel
34	pub scalability_mode: Option<String>,
35	pub bitrate_mode: Option<VideoBitrateMode>,
36
37	// NOTE: This is a custom configuration
38	/// The maximum duration of a Group of Pictures (GOP) before forcing a new keyframe.
39	pub max_gop_duration: Option<Duration>, // seconds
40}
41
42impl VideoEncoderConfig {
43	pub fn new<T: Into<String>>(codec: T, resolution: Dimensions) -> Self {
44		Self {
45			codec: codec.into(),
46			resolution,
47			display: None,
48			hardware_acceleration: None,
49			latency_optimized: None,
50			bitrate: None,
51			framerate: None,
52			alpha_preserved: None,
53			scalability_mode: None,
54			bitrate_mode: None,
55			max_gop_duration: None,
56		}
57	}
58
59	pub async fn is_supported(&self) -> Result<bool, Error> {
60		let res =
61			wasm_bindgen_futures::JsFuture::from(web_sys::VideoEncoder::is_config_supported(&self.into())).await?;
62		let support: web_sys::VideoEncoderSupport = res.unchecked_into();
63		Ok(support.get_supported().unwrap_or(false))
64	}
65
66	pub fn is_valid(&self) -> Result<(), Error> {
67		if self.resolution.width == 0 || self.resolution.height == 0 {
68			return Err(Error::InvalidDimensions);
69		}
70
71		if let Some(display) = self.display {
72			if display.width == 0 || display.height == 0 {
73				return Err(Error::InvalidDimensions);
74			}
75		}
76
77		Ok(())
78	}
79
80	pub fn init(self) -> Result<(VideoEncoder, VideoEncoded), Error> {
81		let (frames_tx, frames_rx) = mpsc::unbounded_channel();
82		let (closed_tx, closed_rx) = watch::channel(Ok(()));
83		let config = Rc::new(RefCell::new(None));
84
85		let decoder = VideoEncoder::new(self, config.clone(), frames_tx, closed_tx)?;
86		let decoded = VideoEncoded::new(config, frames_rx, closed_rx);
87
88		Ok((decoder, decoded))
89	}
90}
91
92impl From<&VideoEncoderConfig> for web_sys::VideoEncoderConfig {
93	fn from(this: &VideoEncoderConfig) -> Self {
94		let config = web_sys::VideoEncoderConfig::new(&this.codec, this.resolution.height, this.resolution.width);
95
96		if let Some(Dimensions { width, height }) = this.display {
97			config.set_display_height(height);
98			config.set_display_width(width);
99		}
100
101		if let Some(preferred) = this.hardware_acceleration {
102			config.set_hardware_acceleration(match preferred {
103				true => web_sys::HardwareAcceleration::PreferHardware,
104				false => web_sys::HardwareAcceleration::PreferSoftware,
105			});
106		}
107
108		if let Some(value) = this.latency_optimized {
109			config.set_latency_mode(match value {
110				true => web_sys::LatencyMode::Realtime,
111				false => web_sys::LatencyMode::Quality,
112			});
113		}
114
115		if let Some(value) = this.bitrate {
116			config.set_bitrate(value);
117		}
118
119		if let Some(value) = this.framerate {
120			config.set_framerate(value);
121		}
122
123		if let Some(value) = this.alpha_preserved {
124			config.set_alpha(match value {
125				true => web_sys::AlphaOption::Keep,
126				false => web_sys::AlphaOption::Discard,
127			});
128		}
129
130		if let Some(value) = &this.scalability_mode {
131			config.set_scalability_mode(value);
132		}
133
134		if let Some(_value) = &this.bitrate_mode {
135			// TODO not supported yet
136		}
137
138		config
139	}
140}
141
142#[derive(Debug, Default)]
143pub struct VideoEncodeOptions {
144	// Force or deny a key frame.
145	pub key_frame: Option<bool>,
146	// TODO
147	// pub quantizer: Option<u8>,
148}
149
150pub struct VideoEncoder {
151	inner: web_sys::VideoEncoder,
152	config: VideoEncoderConfig,
153
154	last_keyframe: Rc<RefCell<Option<Timestamp>>>,
155
156	// These are held to avoid dropping them.
157	#[allow(dead_code)]
158	on_error: Closure<dyn FnMut(JsValue)>,
159	#[allow(dead_code)]
160	on_frame: Closure<dyn FnMut(JsValue, JsValue)>,
161}
162
163impl VideoEncoder {
164	fn new(
165		config: VideoEncoderConfig,
166		on_config: Rc<RefCell<Option<VideoDecoderConfig>>>,
167		on_frame: mpsc::UnboundedSender<EncodedFrame>,
168		on_error: watch::Sender<Result<(), Error>>,
169	) -> Result<Self, Error> {
170		let last_keyframe = Rc::new(RefCell::new(None));
171		let last_keyframe2 = last_keyframe.clone();
172
173		let on_error2 = on_error.clone();
174		let on_error = Closure::wrap(Box::new(move |e: JsValue| {
175			on_error.send_replace(Err(Error::from(e))).ok();
176		}) as Box<dyn FnMut(_)>);
177
178		let on_frame = Closure::wrap(Box::new(move |frame: JsValue, meta: JsValue| {
179			// First parameter is the frame, second optional parameter is metadata.
180			let frame: web_sys::EncodedVideoChunk = frame.unchecked_into();
181			let frame = EncodedFrame::from(frame);
182
183			if let Ok(metadata) = meta.dyn_into::<js_sys::Object>() {
184				// TODO handle metadata
185				if let Ok(config) = js_sys::Reflect::get(&metadata, &"decoderConfig".into()) {
186					if !config.is_falsy() {
187						let config: web_sys::VideoDecoderConfig = config.unchecked_into();
188						let config = VideoDecoderConfig::from(config);
189						on_config.borrow_mut().replace(config);
190					}
191				}
192			}
193
194			if frame.keyframe {
195				let mut last_keyframe = last_keyframe2.borrow_mut();
196				if frame.timestamp > last_keyframe.unwrap_or_default() {
197					*last_keyframe = Some(frame.timestamp);
198				}
199			}
200
201			if on_frame.send(frame).is_err() {
202				on_error2.send_replace(Err(Error::Dropped)).ok();
203			}
204		}) as Box<dyn FnMut(_, _)>);
205
206		let init = web_sys::VideoEncoderInit::new(on_error.as_ref().unchecked_ref(), on_frame.as_ref().unchecked_ref());
207		let inner: web_sys::VideoEncoder = web_sys::VideoEncoder::new(&init).unwrap();
208		inner.configure(&(&config).into())?;
209
210		Ok(Self {
211			config,
212			inner,
213			last_keyframe,
214			on_error,
215			on_frame,
216		})
217	}
218
219	pub fn encode(&mut self, frame: &VideoFrame, options: VideoEncodeOptions) -> Result<(), Error> {
220		let o = web_sys::VideoEncoderEncodeOptions::new();
221
222		if let Some(key_frame) = options.key_frame {
223			o.set_key_frame(key_frame);
224		} else if let Some(max_gop_duration) = self.config.max_gop_duration {
225			let timestamp = frame.timestamp();
226			let mut last_keyframe = self.last_keyframe.borrow_mut();
227
228			let duration = timestamp - last_keyframe.unwrap_or_default();
229			if duration >= max_gop_duration {
230				o.set_key_frame(true);
231			}
232
233			*last_keyframe = Some(timestamp);
234		}
235
236		self.inner.encode_with_options(frame, &o)?;
237
238		Ok(())
239	}
240
241	pub fn queue_size(&self) -> u32 {
242		self.inner.encode_queue_size()
243	}
244
245	pub fn config(&self) -> &VideoEncoderConfig {
246		&self.config
247	}
248
249	pub async fn flush(&mut self) -> Result<(), Error> {
250		wasm_bindgen_futures::JsFuture::from(self.inner.flush()).await?;
251		Ok(())
252	}
253}
254
255impl Drop for VideoEncoder {
256	fn drop(&mut self) {
257		let _ = self.inner.close();
258	}
259}
260
261pub struct VideoEncoded {
262	config: Rc<RefCell<Option<VideoDecoderConfig>>>,
263	frames: mpsc::UnboundedReceiver<EncodedFrame>,
264	closed: watch::Receiver<Result<(), Error>>,
265}
266
267impl VideoEncoded {
268	fn new(
269		config: Rc<RefCell<Option<VideoDecoderConfig>>>,
270		frames: mpsc::UnboundedReceiver<EncodedFrame>,
271		closed: watch::Receiver<Result<(), Error>>,
272	) -> Self {
273		Self { config, frames, closed }
274	}
275
276	pub async fn frame(&mut self) -> Result<Option<EncodedFrame>, Error> {
277		tokio::select! {
278			biased;
279			frame = self.frames.recv() => Ok(frame),
280			Ok(()) = self.closed.changed() => Err(self.closed.borrow().clone().err().unwrap()),
281		}
282	}
283
284	/// Returns the decoder config, after the first frame has been encoded.
285	pub fn config(&self) -> Option<VideoDecoderConfig> {
286		self.config.borrow().clone()
287	}
288}