1use bytes::Bytes;
17use hang::catalog::VideoConfig;
18use moq_net::Timestamp;
19
20use super::decoder::Config;
21use crate::{Error, Frame};
22
23#[cfg(target_os = "macos")]
24use inline::Inner;
25#[cfg(not(target_os = "macos"))]
26use threaded::Inner;
27
28pub struct Sink(Inner);
54
55impl Sink {
56 pub async fn open(catalog: &VideoConfig, config: &Config) -> Result<Self, Error> {
60 Ok(Self(Inner::open(catalog, config).await?))
61 }
62
63 pub fn name(&self) -> &str {
65 self.0.name()
66 }
67
68 pub async fn decode(&mut self, payload: Bytes, timestamp: Timestamp, keyframe: bool) -> Result<Vec<Frame>, Error> {
74 self.0.decode(payload, timestamp, keyframe).await
75 }
76
77 pub async fn flush(&mut self) -> Result<Vec<Frame>, Error> {
82 self.0.flush().await
83 }
84}
85
86#[cfg(not(target_os = "macos"))]
87mod threaded {
88 use bytes::Bytes;
89 use hang::catalog::VideoConfig;
90 use moq_net::Timestamp;
91 use tokio::sync::{mpsc, oneshot};
92
93 use super::super::decoder::{Config, Decoder};
94 use crate::worker::{Ready, Worker};
95 use crate::{Error, Frame};
96
97 enum Request {
101 Decode {
102 payload: Bytes,
103 timestamp: Timestamp,
104 keyframe: bool,
105 resp: oneshot::Sender<Result<Vec<Frame>, Error>>,
106 },
107 Flush {
108 resp: oneshot::Sender<Result<Vec<Frame>, Error>>,
109 },
110 }
111
112 fn run(catalog: VideoConfig, config: Config, ready: Ready, mut requests: mpsc::UnboundedReceiver<Request>) {
115 let mut decoder = match Decoder::new(&catalog, &config) {
116 Ok(decoder) => decoder,
117 Err(err) => return ready.err(err),
118 };
119 if !ready.ok(decoder.name()) {
121 return;
122 }
123
124 while let Some(req) = requests.blocking_recv() {
127 match req {
128 Request::Decode {
129 payload,
130 timestamp,
131 keyframe,
132 resp,
133 } => {
134 let _ = resp.send(decoder.decode(&payload, timestamp, keyframe));
135 }
136 Request::Flush { resp } => {
137 let _ = resp.send(decoder.flush());
138 }
139 }
140 }
141 }
143
144 pub struct Inner(Worker<Request>);
146
147 impl Inner {
148 pub async fn open(catalog: &VideoConfig, config: &Config) -> Result<Self, Error> {
149 let catalog = catalog.clone();
150 let config = config.clone();
151 let worker = Worker::open("moq-video-decode", move |ready, requests| {
152 run(catalog, config, ready, requests)
153 })
154 .await?;
155 Ok(Self(worker))
156 }
157
158 pub fn name(&self) -> &str {
159 self.0.name()
160 }
161
162 pub async fn decode(
163 &mut self,
164 payload: Bytes,
165 timestamp: Timestamp,
166 keyframe: bool,
167 ) -> Result<Vec<Frame>, Error> {
168 self.0
169 .request(|resp| Request::Decode {
170 payload,
171 timestamp,
172 keyframe,
173 resp,
174 })
175 .await
176 }
177
178 pub async fn flush(&mut self) -> Result<Vec<Frame>, Error> {
179 self.0.request(|resp| Request::Flush { resp }).await
180 }
181 }
182}
183
184#[cfg(target_os = "macos")]
185mod inline {
186 use bytes::Bytes;
187 use hang::catalog::VideoConfig;
188 use moq_net::Timestamp;
189
190 use super::super::decoder::{Config, Decoder};
191 use crate::{Error, Frame};
192
193 pub struct Inner(Decoder);
195
196 impl Inner {
197 pub async fn open(catalog: &VideoConfig, config: &Config) -> Result<Self, Error> {
198 Ok(Self(Decoder::new(catalog, config)?))
199 }
200
201 pub fn name(&self) -> &str {
202 self.0.name()
203 }
204
205 pub async fn decode(
208 &mut self,
209 payload: Bytes,
210 timestamp: Timestamp,
211 keyframe: bool,
212 ) -> Result<Vec<Frame>, Error> {
213 self.0.decode(&payload, timestamp, keyframe)
214 }
215
216 pub async fn flush(&mut self) -> Result<Vec<Frame>, Error> {
217 self.0.flush()
218 }
219 }
220}
221
222#[cfg(all(test, not(target_os = "macos")))]
225mod tests {
226 use std::collections::HashSet;
227 use std::sync::{Arc, Mutex};
228 use std::thread::ThreadId;
229
230 use super::super::Kind;
231 use super::super::backend::probe;
232 use super::*;
233
234 fn probe_catalog() -> VideoConfig {
235 let mut catalog = VideoConfig::new(hang::catalog::H264 {
236 inline: true,
237 profile: 0x42,
238 constraints: 0,
239 level: 30,
240 });
241 catalog.coded_width = Some(probe::SIZE.width);
242 catalog.coded_height = Some(probe::SIZE.height);
243 catalog
244 }
245
246 fn probe_config() -> Config {
247 let mut config = Config::new();
248 config.kind = Kind::Named(probe::NAME.into());
249 config
250 }
251
252 fn at(index: u64) -> Timestamp {
253 Timestamp::from_micros(index * 33_333).unwrap()
254 }
255
256 #[test]
263 fn the_codec_stays_on_one_thread_however_it_is_driven() {
264 let _probe = probe::exclusive();
265
266 let sink = Arc::new(Mutex::new(Some(
267 pollster::block_on(Sink::open(&probe_catalog(), &probe_config())).unwrap(),
268 )));
269
270 let mut callers = vec![std::thread::current().id()];
273 for index in 0..3u64 {
274 let sink = sink.clone();
275 let caller = std::thread::spawn(move || {
276 let mut guard = sink.lock().unwrap();
277 let sink = guard.as_mut().unwrap();
278 let frames = pollster::block_on(sink.decode(Bytes::from_static(b"au"), at(index), index == 0)).unwrap();
279 assert_eq!(frames.len(), 1);
282 assert_eq!(frames[0].timestamp, at(index));
283 std::thread::current().id()
284 });
285 callers.push(caller.join().unwrap());
286 }
287
288 let closer = std::thread::spawn(move || {
290 sink.lock().unwrap().take();
291 std::thread::current().id()
292 });
293 callers.push(closer.join().unwrap());
294
295 let log = probe::take();
296 for what in ["open", "decode", "drop"] {
297 assert!(log.iter().any(|(event, _)| *event == what), "no {what} in {log:?}");
298 }
299
300 let threads: HashSet<ThreadId> = log.iter().map(|(_, id)| *id).collect();
301 assert_eq!(threads.len(), 1, "the codec ran on more than one thread: {log:?}");
302
303 let codec = threads.into_iter().next().unwrap();
304 assert!(
305 !callers.contains(&codec),
306 "the codec ran on a caller's thread rather than its own: {log:?}"
307 );
308 }
309}