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
78#[cfg(not(target_os = "macos"))]
79mod threaded {
80 use bytes::Bytes;
81 use hang::catalog::VideoConfig;
82 use moq_net::Timestamp;
83 use tokio::sync::{mpsc, oneshot};
84
85 use super::super::decoder::{Config, Decoder};
86 use crate::worker::{Ready, Worker};
87 use crate::{Error, Frame};
88
89 enum Request {
93 Decode {
94 payload: Bytes,
95 timestamp: Timestamp,
96 keyframe: bool,
97 resp: oneshot::Sender<Result<Vec<Frame>, Error>>,
98 },
99 }
100
101 fn run(catalog: VideoConfig, config: Config, ready: Ready, mut requests: mpsc::UnboundedReceiver<Request>) {
104 let mut decoder = match Decoder::new(&catalog, &config) {
105 Ok(decoder) => decoder,
106 Err(err) => return ready.err(err),
107 };
108 if !ready.ok(decoder.name()) {
110 return;
111 }
112
113 while let Some(req) = requests.blocking_recv() {
116 match req {
117 Request::Decode {
118 payload,
119 timestamp,
120 keyframe,
121 resp,
122 } => {
123 let _ = resp.send(decoder.decode(&payload, timestamp, keyframe));
124 }
125 }
126 }
127 }
129
130 pub struct Inner(Worker<Request>);
132
133 impl Inner {
134 pub async fn open(catalog: &VideoConfig, config: &Config) -> Result<Self, Error> {
135 let catalog = catalog.clone();
136 let config = config.clone();
137 let worker = Worker::open("moq-video-decode", move |ready, requests| {
138 run(catalog, config, ready, requests)
139 })
140 .await?;
141 Ok(Self(worker))
142 }
143
144 pub fn name(&self) -> &str {
145 self.0.name()
146 }
147
148 pub async fn decode(
149 &mut self,
150 payload: Bytes,
151 timestamp: Timestamp,
152 keyframe: bool,
153 ) -> Result<Vec<Frame>, Error> {
154 self.0
155 .request(|resp| Request::Decode {
156 payload,
157 timestamp,
158 keyframe,
159 resp,
160 })
161 .await
162 }
163 }
164}
165
166#[cfg(target_os = "macos")]
167mod inline {
168 use bytes::Bytes;
169 use hang::catalog::VideoConfig;
170 use moq_net::Timestamp;
171
172 use super::super::decoder::{Config, Decoder};
173 use crate::{Error, Frame};
174
175 pub struct Inner(Decoder);
177
178 impl Inner {
179 pub async fn open(catalog: &VideoConfig, config: &Config) -> Result<Self, Error> {
180 Ok(Self(Decoder::new(catalog, config)?))
181 }
182
183 pub fn name(&self) -> &str {
184 self.0.name()
185 }
186
187 pub async fn decode(
190 &mut self,
191 payload: Bytes,
192 timestamp: Timestamp,
193 keyframe: bool,
194 ) -> Result<Vec<Frame>, Error> {
195 self.0.decode(&payload, timestamp, keyframe)
196 }
197 }
198}
199
200#[cfg(all(test, not(target_os = "macos")))]
203mod tests {
204 use std::collections::HashSet;
205 use std::sync::{Arc, Mutex};
206 use std::thread::ThreadId;
207
208 use super::super::Kind;
209 use super::super::backend::probe;
210 use super::*;
211
212 fn probe_catalog() -> VideoConfig {
213 let mut catalog = VideoConfig::new(hang::catalog::H264 {
214 inline: true,
215 profile: 0x42,
216 constraints: 0,
217 level: 30,
218 });
219 catalog.coded_width = Some(probe::SIZE.width);
220 catalog.coded_height = Some(probe::SIZE.height);
221 catalog
222 }
223
224 fn probe_config() -> Config {
225 let mut config = Config::new();
226 config.kind = Kind::Named(probe::NAME.into());
227 config
228 }
229
230 fn at(index: u64) -> Timestamp {
231 Timestamp::from_micros(index * 33_333).unwrap()
232 }
233
234 #[test]
241 fn the_codec_stays_on_one_thread_however_it_is_driven() {
242 let _probe = probe::exclusive();
243
244 let sink = Arc::new(Mutex::new(Some(
245 pollster::block_on(Sink::open(&probe_catalog(), &probe_config())).unwrap(),
246 )));
247
248 let mut callers = vec![std::thread::current().id()];
251 for index in 0..3u64 {
252 let sink = sink.clone();
253 let caller = std::thread::spawn(move || {
254 let mut guard = sink.lock().unwrap();
255 let sink = guard.as_mut().unwrap();
256 let frames = pollster::block_on(sink.decode(Bytes::from_static(b"au"), at(index), index == 0)).unwrap();
257 assert_eq!(frames.len(), 1);
260 assert_eq!(frames[0].timestamp, at(index));
261 std::thread::current().id()
262 });
263 callers.push(caller.join().unwrap());
264 }
265
266 let closer = std::thread::spawn(move || {
268 sink.lock().unwrap().take();
269 std::thread::current().id()
270 });
271 callers.push(closer.join().unwrap());
272
273 let log = probe::take();
274 for what in ["open", "decode", "drop"] {
275 assert!(log.iter().any(|(event, _)| *event == what), "no {what} in {log:?}");
276 }
277
278 let threads: HashSet<ThreadId> = log.iter().map(|(_, id)| *id).collect();
279 assert_eq!(threads.len(), 1, "the codec ran on more than one thread: {log:?}");
280
281 let codec = threads.into_iter().next().unwrap();
282 assert!(
283 !callers.contains(&codec),
284 "the codec ran on a caller's thread rather than its own: {log:?}"
285 );
286 }
287}