use std::sync::Arc;
use super::Encoded;
use super::encoder::Config;
use crate::{Error, Frame};
#[cfg(target_os = "macos")]
use inline::Inner;
#[cfg(not(target_os = "macos"))]
use threaded::Inner;
pub struct Sink(Inner);
impl Sink {
pub async fn open(config: &Config) -> Result<Self, Error> {
Ok(Self(Inner::open(config).await?))
}
pub fn name(&self) -> &str {
self.0.name()
}
pub fn keyframe(&mut self) {
self.0.keyframe();
}
pub async fn encode(&mut self, frame: impl Into<Arc<Frame>>) -> Result<Vec<Encoded>, Error> {
self.0.encode(frame.into()).await
}
pub async fn set_bitrate(&mut self, bitrate: u64) -> Result<(), Error> {
self.0.set_bitrate(bitrate).await
}
pub async fn flush(&mut self) -> Result<Vec<Encoded>, Error> {
self.0.flush().await
}
pub async fn finish(self) -> Result<Vec<Encoded>, Error> {
self.0.finish().await
}
}
#[cfg(not(target_os = "macos"))]
mod threaded {
use std::sync::Arc;
use tokio::sync::{mpsc, oneshot};
use super::super::Encoded;
use super::super::encoder::{Config, Encoder};
use crate::worker::{Ready, Worker};
use crate::{Error, Frame};
enum Request {
Encode {
frame: Arc<Frame>,
resp: oneshot::Sender<Result<Vec<Encoded>, Error>>,
},
Keyframe,
SetBitrate {
bitrate: u64,
resp: oneshot::Sender<Result<(), Error>>,
},
Flush {
resp: oneshot::Sender<Result<Vec<Encoded>, Error>>,
},
Finish {
resp: oneshot::Sender<Result<Vec<Encoded>, Error>>,
},
}
fn run(config: Config, ready: Ready, mut requests: mpsc::UnboundedReceiver<Request>) {
let mut encoder = match Encoder::new(&config) {
Ok(encoder) => encoder,
Err(err) => return ready.err(err),
};
if !ready.ok(encoder.name()) {
return;
}
let mut draining = None;
while let Some(req) = requests.blocking_recv() {
match req {
Request::Encode { frame, resp } => {
let _ = resp.send(encoder.encode(&frame));
}
Request::Keyframe => encoder.keyframe(),
Request::SetBitrate { bitrate, resp } => {
let _ = resp.send(encoder.set_bitrate(bitrate));
}
Request::Flush { resp } => {
let _ = resp.send(encoder.flush());
}
Request::Finish { resp } => {
draining = Some(resp);
break;
}
}
}
if let Some(resp) = draining {
let _ = resp.send(encoder.finish());
}
}
pub struct Inner(Worker<Request>);
impl Inner {
pub async fn open(config: &Config) -> Result<Self, Error> {
let config = config.clone();
let worker = Worker::open("moq-video-encode", move |ready, requests| run(config, ready, requests)).await?;
Ok(Self(worker))
}
pub fn name(&self) -> &str {
self.0.name()
}
pub fn keyframe(&mut self) {
let _ = self.0.send(Request::Keyframe);
}
pub async fn encode(&mut self, frame: Arc<Frame>) -> Result<Vec<Encoded>, Error> {
self.0.request(|resp| Request::Encode { frame, resp }).await
}
pub async fn set_bitrate(&mut self, bitrate: u64) -> Result<(), Error> {
self.0.request(|resp| Request::SetBitrate { bitrate, resp }).await
}
pub async fn flush(&mut self) -> Result<Vec<Encoded>, Error> {
self.0.request(|resp| Request::Flush { resp }).await
}
pub async fn finish(mut self) -> Result<Vec<Encoded>, Error> {
self.0.request(|resp| Request::Finish { resp }).await
}
}
}
#[cfg(target_os = "macos")]
mod inline {
use std::sync::Arc;
use super::super::Encoded;
use super::super::encoder::{Config, Encoder};
use crate::{Error, Frame};
pub struct Inner(Encoder);
impl Inner {
pub async fn open(config: &Config) -> Result<Self, Error> {
Ok(Self(Encoder::new(config)?))
}
pub fn name(&self) -> &str {
self.0.name()
}
pub fn keyframe(&mut self) {
self.0.keyframe();
}
pub async fn encode(&mut self, frame: Arc<Frame>) -> Result<Vec<Encoded>, Error> {
self.0.encode(&frame)
}
pub async fn set_bitrate(&mut self, bitrate: u64) -> Result<(), Error> {
self.0.set_bitrate(bitrate)
}
pub async fn flush(&mut self) -> Result<Vec<Encoded>, Error> {
self.0.flush()
}
pub async fn finish(self) -> Result<Vec<Encoded>, Error> {
self.0.finish()
}
}
}
#[cfg(all(test, not(target_os = "macos")))]
mod tests {
use std::collections::HashSet;
use std::sync::{Arc, Mutex};
use std::thread::ThreadId;
use super::super::backend::probe;
use super::super::{Codec, Kind};
use super::*;
use crate::{I420, Surface};
fn gray(index: u64) -> Frame {
let i420 = I420::new(320, 240, vec![0x80u8; I420::len(320, 240)]).unwrap();
Frame::new(
Surface::I420(i420),
moq_net::Timestamp::from_micros(index * 33_333).unwrap(),
)
}
fn probe_config() -> Config {
let mut config = Config::new(320, 240, 30);
config.codec = Codec::H264;
config.kind = Kind::Named(probe::NAME.into());
config
}
#[test]
fn a_cancelled_call_poisons_the_sink() {
let _probe = probe::exclusive();
let mut sink = pollster::block_on(Sink::open(&probe_config())).unwrap();
let gate = probe::hold();
pollster::block_on(async {
let mut encode = Box::pin(sink.encode(gray(0)));
assert!(
futures::poll!(encode.as_mut()).is_pending(),
"the encode should still be waiting on the held codec"
);
});
drop(gate);
let err = pollster::block_on(sink.encode(gray(1))).expect_err("the sink should refuse");
assert!(err.to_string().contains("cancelled"), "unexpected error: {err}");
assert!(pollster::block_on(sink.flush()).is_err());
drop(sink);
let log = probe::take();
assert!(
log.iter().any(|(event, _)| *event == "encode"),
"the cancelled request should still have reached the codec: {log:?}"
);
}
#[test]
fn the_codec_stays_on_one_thread_however_it_is_driven() {
let _probe = probe::exclusive();
let sink = Arc::new(Mutex::new(Some(
pollster::block_on(Sink::open(&probe_config())).unwrap(),
)));
let mut callers = vec![std::thread::current().id()];
let mut flushed = Vec::new();
for index in 0..3u64 {
let sink = sink.clone();
let caller = std::thread::spawn(move || {
let mut guard = sink.lock().unwrap();
let sink = guard.as_mut().unwrap();
sink.keyframe();
pollster::block_on(sink.encode(gray(index))).unwrap();
pollster::block_on(sink.set_bitrate(500_000 + index)).unwrap();
let flushed = match index {
0 => pollster::block_on(sink.flush()).unwrap(),
_ => Vec::new(),
};
(std::thread::current().id(), flushed)
});
let (caller, drained) = caller.join().unwrap();
callers.push(caller);
flushed.extend(drained);
}
let flushed: Vec<_> = flushed.iter().map(|frame| frame.timestamp.as_micros()).collect();
assert_eq!(flushed, vec![0], "the group boundary did not empty the codec");
let closer = std::thread::spawn(move || {
let sink = sink.lock().unwrap().take().unwrap();
let tail = pollster::block_on(sink.finish()).unwrap();
(std::thread::current().id(), tail)
});
let (closer, tail) = closer.join().unwrap();
callers.push(closer);
let tail: Vec<_> = tail.iter().map(|frame| frame.timestamp.as_micros()).collect();
assert_eq!(tail, vec![2 * 33_333], "the drain lost the codec's tail");
let log = probe::take();
for what in ["open", "encode", "set_bitrate", "flush", "finish", "drop"] {
assert!(log.iter().any(|(event, _)| *event == what), "no {what} in {log:?}");
}
let threads: HashSet<ThreadId> = log.iter().map(|(_, id)| *id).collect();
assert_eq!(threads.len(), 1, "the codec ran on more than one thread: {log:?}");
let codec = threads.into_iter().next().unwrap();
assert!(
!callers.contains(&codec),
"the codec ran on a caller's thread rather than its own: {log:?}"
);
}
}