use bytes::Bytes;
use hang::catalog::VideoConfig;
use moq_net::Timestamp;
use super::decoder::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(catalog: &VideoConfig, config: &Config) -> Result<Self, Error> {
Ok(Self(Inner::open(catalog, config).await?))
}
pub fn name(&self) -> &str {
self.0.name()
}
pub async fn decode(&mut self, payload: Bytes, timestamp: Timestamp, keyframe: bool) -> Result<Vec<Frame>, Error> {
self.0.decode(payload, timestamp, keyframe).await
}
}
#[cfg(not(target_os = "macos"))]
mod threaded {
use bytes::Bytes;
use hang::catalog::VideoConfig;
use moq_net::Timestamp;
use tokio::sync::{mpsc, oneshot};
use super::super::decoder::{Config, Decoder};
use crate::worker::{Ready, Worker};
use crate::{Error, Frame};
enum Request {
Decode {
payload: Bytes,
timestamp: Timestamp,
keyframe: bool,
resp: oneshot::Sender<Result<Vec<Frame>, Error>>,
},
}
fn run(catalog: VideoConfig, config: Config, ready: Ready, mut requests: mpsc::UnboundedReceiver<Request>) {
let mut decoder = match Decoder::new(&catalog, &config) {
Ok(decoder) => decoder,
Err(err) => return ready.err(err),
};
if !ready.ok(decoder.name()) {
return;
}
while let Some(req) = requests.blocking_recv() {
match req {
Request::Decode {
payload,
timestamp,
keyframe,
resp,
} => {
let _ = resp.send(decoder.decode(&payload, timestamp, keyframe));
}
}
}
}
pub struct Inner(Worker<Request>);
impl Inner {
pub async fn open(catalog: &VideoConfig, config: &Config) -> Result<Self, Error> {
let catalog = catalog.clone();
let config = config.clone();
let worker = Worker::open("moq-video-decode", move |ready, requests| {
run(catalog, config, ready, requests)
})
.await?;
Ok(Self(worker))
}
pub fn name(&self) -> &str {
self.0.name()
}
pub async fn decode(
&mut self,
payload: Bytes,
timestamp: Timestamp,
keyframe: bool,
) -> Result<Vec<Frame>, Error> {
self.0
.request(|resp| Request::Decode {
payload,
timestamp,
keyframe,
resp,
})
.await
}
}
}
#[cfg(target_os = "macos")]
mod inline {
use bytes::Bytes;
use hang::catalog::VideoConfig;
use moq_net::Timestamp;
use super::super::decoder::{Config, Decoder};
use crate::{Error, Frame};
pub struct Inner(Decoder);
impl Inner {
pub async fn open(catalog: &VideoConfig, config: &Config) -> Result<Self, Error> {
Ok(Self(Decoder::new(catalog, config)?))
}
pub fn name(&self) -> &str {
self.0.name()
}
pub async fn decode(
&mut self,
payload: Bytes,
timestamp: Timestamp,
keyframe: bool,
) -> Result<Vec<Frame>, Error> {
self.0.decode(&payload, timestamp, keyframe)
}
}
}
#[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::Kind;
use super::super::backend::probe;
use super::*;
fn probe_catalog() -> VideoConfig {
let mut catalog = VideoConfig::new(hang::catalog::H264 {
inline: true,
profile: 0x42,
constraints: 0,
level: 30,
});
catalog.coded_width = Some(probe::SIZE.width);
catalog.coded_height = Some(probe::SIZE.height);
catalog
}
fn probe_config() -> Config {
let mut config = Config::new();
config.kind = Kind::Named(probe::NAME.into());
config
}
fn at(index: u64) -> Timestamp {
Timestamp::from_micros(index * 33_333).unwrap()
}
#[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_catalog(), &probe_config())).unwrap(),
)));
let mut callers = vec![std::thread::current().id()];
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();
let frames = pollster::block_on(sink.decode(Bytes::from_static(b"au"), at(index), index == 0)).unwrap();
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].timestamp, at(index));
std::thread::current().id()
});
callers.push(caller.join().unwrap());
}
let closer = std::thread::spawn(move || {
sink.lock().unwrap().take();
std::thread::current().id()
});
callers.push(closer.join().unwrap());
let log = probe::take();
for what in ["open", "decode", "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:?}"
);
}
}