Skip to main content

media_pp/elements/source/
app_source.rs

1use std::{sync::Arc, time::Duration};
2
3use crate::pp_log::{PpLog, pp_info};
4use crossbeam_channel::{Receiver, Sender, TrySendError, bounded, select};
5use thiserror::Error as ThisError;
6
7use crate::{
8    buffer::MediaBuffer,
9    bus::{Bus, BusEvent},
10    control::{ControlMsg, ControlReceiver, apply_one, drain_control, wait_out_pause},
11    element::{Element, ElementType, Source, SourceElement, element_pp_log},
12    error::Result,
13    pad::SrcPad,
14};
15
16/// Errors specific to `AppSource`. Converts into the crate-wide `Error`
17/// via `?` (see [`crate::error::Error`]).
18#[derive(Debug, ThisError)]
19pub enum AppSourceError {
20    #[error("AppSource has already ended (its Pipeline finished, or Eos was already pushed)")]
21    Closed,
22}
23
24/// A source whose data comes from application code pushing buffers in,
25/// rather than this element reading them itself — GStreamer's `appsrc`
26/// equivalent, the reverse of [`crate::elements::AppSink`]. Push encoded
27/// [`MediaBuffer::Packet`]s (straight into a decoder) or already-decoded
28/// [`MediaBuffer::Video`]/[`MediaBuffer::Audio`] (e.g. frames from a
29/// camera SDK, or synthetic test data) via [`AppSourceHandle`], from any
30/// thread — a live capture callback, a network receive loop, a test.
31///
32/// Unlike [`crate::elements::FileDemuxer`], whose blocking read can only
33/// be checked against [`ControlMsg`](crate::control::ControlMsg) once per
34/// loop iteration (see [`drain_control`]'s docs), `AppSource::run` selects
35/// on its control channel and its data channel together — a `Stop` (or
36/// any other control message) is handled the moment it arrives, even if
37/// [`AppSourceHandle::push`] never gets called again.
38///
39/// Push [`MediaBuffer::Eos`] when done, or just drop every
40/// [`AppSourceHandle`] clone — either ends `run` the same way, pushing
41/// exactly one `Eos` of its own to `src_pads()`.
42///
43/// Has no timeline of its own, so [`SourceElement::seek`] is a no-op that
44/// reports back whatever was requested as where it "landed" — nothing to
45/// reposition when the app, not a file offset, decides what comes next.
46pub struct AppSource {
47    pp_log: PpLog,
48    name: Arc<str>,
49    pad: SrcPad,
50    data_rx: Receiver<MediaBuffer>,
51}
52
53/// A cheaply-cloneable handle for pushing buffers into an [`AppSource`]
54/// from any thread — `Clone` is just two refcount bumps (`name` and the
55/// channel sender are both cheap to share).
56#[derive(Clone)]
57pub struct AppSourceHandle {
58    name: Arc<str>,
59    data_tx: Sender<MediaBuffer>,
60}
61
62impl AppSource {
63    /// `capacity` bounds how many pushed buffers may sit unconsumed before
64    /// [`AppSourceHandle::push`] blocks — same trade-off as
65    /// [`crate::queue::Queue`]'s own `capacity`.
66    pub fn new(name: impl Into<String>, capacity: usize) -> (Self, AppSourceHandle) {
67        let name: Arc<str> = name.into().into();
68        let pp_log = element_pp_log(ElementType::AppSource, &name, None);
69        pp_info!(pp_log: &pp_log, "created: capacity={capacity}");
70        let pad = SrcPad::new(format!("{name}_src"));
71        let (data_tx, data_rx) = bounded(capacity);
72        (
73            Self {
74                name: name.clone(),
75                pp_log,
76                pad,
77                data_rx,
78            },
79            AppSourceHandle { name, data_tx },
80        )
81    }
82}
83
84impl AppSourceHandle {
85    pub fn name(&self) -> Arc<str> {
86        self.name.clone()
87    }
88
89    /// Blocks until there's room in the channel, or `AppSource` (every
90    /// clone of it, e.g. after its `Pipeline` finished) is gone.
91    pub fn push(&self, buf: MediaBuffer) -> Result<()> {
92        self.data_tx
93            .send(buf)
94            .map_err(|_| AppSourceError::Closed.into())
95    }
96
97    /// Non-blocking `push`, for a live producer where falling behind
98    /// matters more than losing a buffer — e.g. a camera callback that
99    /// can't afford to stall. `Ok(false)` (not an error) means the
100    /// channel was full and `buf` was *not* sent; `Err` only means
101    /// `AppSource` itself is gone.
102    pub fn try_push(&self, buf: MediaBuffer) -> Result<bool> {
103        match self.data_tx.try_send(buf) {
104            Ok(()) => Ok(true),
105            Err(TrySendError::Full(_)) => Ok(false),
106            Err(TrySendError::Disconnected(_)) => Err(AppSourceError::Closed.into()),
107        }
108    }
109}
110
111impl Element for AppSource {
112    fn name(&self) -> Arc<str> {
113        self.name.clone()
114    }
115
116    fn element_type(&self) -> ElementType {
117        ElementType::AppSource
118    }
119
120    fn pp_log(&self) -> &PpLog {
121        &self.pp_log
122    }
123
124    fn pp_log_mut(&mut self) -> &mut PpLog {
125        &mut self.pp_log
126    }
127}
128
129impl Source for AppSource {
130    fn src_pads(&mut self) -> &mut [SrcPad] {
131        std::slice::from_mut(&mut self.pad)
132    }
133}
134
135impl SourceElement for AppSource {
136    fn run(&mut self, control: &ControlReceiver, bus: &Bus) -> Result<()> {
137        pp_info!(self, "started");
138        loop {
139            // Non-blocking first: if control is already backed up, clear
140            // it before the `select!` below picks an arbitrary ready arm
141            // (it'd be just as correct to skip straight to `select!`, but
142            // this keeps `AppSource` consistent with every other
143            // `SourceElement::run` calling `drain_control` per iteration).
144            if drain_control(control, self, bus)?.stopped {
145                pp_info!(self, "stopped");
146                return Ok(());
147            }
148
149            select! {
150                recv(control.rx) -> req => {
151                    match req {
152                        Ok(req) => {
153                            if apply_one(self, bus, req.msg, &req.ack)? {
154                                pp_info!(self, "stopped");
155                                return Ok(());
156                            }
157                            if req.msg == ControlMsg::Pause
158                                && wait_out_pause(control, self, bus)?
159                            {
160                                pp_info!(self, "stopped");
161                                return Ok(());
162                            }
163                        }
164                        // The Pipeline itself is gone — nothing left to drive this.
165                        Err(_) => {
166                            pp_info!(self, "run: control channel gone, ending");
167                            return Ok(());
168                        }
169                    }
170                }
171                recv(self.data_rx) -> buf => {
172                    match buf {
173                        Ok(buf) if buf.is_eos() => {
174                            pp_info!(self, "event=eos phase=source_received");
175                            break;
176                        }
177                        Ok(buf) => {
178                            if let Err(error) = self.pad.push(buf) {
179                                bus.post(
180                                    &self.pp_log,
181                                    BusEvent::Error {
182                                        element_type: ElementType::AppSource,
183                                        name: self.name.clone(),
184                                        error,
185                                    },
186                                );
187                            }
188                        }
189                        // Every `AppSourceHandle` dropped without an explicit Eos.
190                        Err(_) => {
191                            pp_info!(self, "run: every AppSourceHandle dropped, ending");
192                            break;
193                        }
194                    }
195                }
196            }
197        }
198        self.pad.push_eos(&self.pp_log)
199    }
200
201    /// No-op: `AppSource` has nothing of its own to reposition — whatever
202    /// comes next is whatever the app pushes next, not a position in a
203    /// file. Reports `target` back as where it "landed" so downstream
204    /// (e.g. a [`crate::elements::Pacer`] resetting its clock offset)
205    /// still sees a consistent [`crate::bus::BusEvent::Seeked`].
206    fn seek(&mut self, target: Duration) -> Result<Duration> {
207        Ok(target)
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use std::{
214        sync::atomic::{AtomicUsize, Ordering},
215        thread,
216    };
217
218    use super::*;
219    use crate::pipeline::Pipeline;
220
221    struct CountingSink {
222        pp_log: PpLog,
223        count: Arc<AtomicUsize>,
224    }
225
226    impl Element for CountingSink {
227        fn name(&self) -> Arc<str> {
228            "counter".into()
229        }
230
231        fn element_type(&self) -> ElementType {
232            ElementType::Other
233        }
234
235        fn pp_log(&self) -> &PpLog {
236            &self.pp_log
237        }
238
239        fn pp_log_mut(&mut self) -> &mut PpLog {
240            &mut self.pp_log
241        }
242    }
243
244    impl crate::element::Sink for CountingSink {
245        fn consume(&mut self, buf: MediaBuffer) -> Result<()> {
246            if !buf.is_eos() {
247                self.count.fetch_add(1, Ordering::SeqCst);
248            }
249            Ok(())
250        }
251
252        fn control(&mut self, _msg: ControlMsg) -> Result<()> {
253            Ok(())
254        }
255    }
256
257    fn packet() -> MediaBuffer {
258        MediaBuffer::Packet(Arc::new(ffmpeg_next::Packet::empty()))
259    }
260
261    fn wire(source: AppSource, count: Arc<AtomicUsize>) -> Arc<Pipeline> {
262        let sink = CountingSink {
263            count,
264            pp_log: element_pp_log(ElementType::Other, "counter", None),
265        };
266        Pipeline::new("test", source, |source, ctx| {
267            let branch = ctx.branch().to(Box::new(sink))?;
268            ctx.attach(source, 0, branch)?;
269            Ok(())
270        })
271        .expect("test pipeline wiring must succeed")
272    }
273
274    #[test]
275    fn pushed_buffers_reach_downstream_then_eos_ends_it() {
276        let (source, handle) = AppSource::new("app-source", 4);
277        let count = Arc::new(AtomicUsize::new(0));
278        let pipeline = wire(source, count.clone());
279        pipeline.run();
280
281        for _ in 0..5 {
282            handle.push(packet()).unwrap();
283        }
284        handle.push(MediaBuffer::Eos).unwrap();
285
286        let events: Vec<_> = pipeline.bus().iter().collect();
287        assert!(
288            !events.iter().any(|e| matches!(e, BusEvent::Error { .. })),
289            "unexpected error event(s): {events:?}"
290        );
291        assert!(events.iter().any(|e| matches!(e, BusEvent::Eos { .. })));
292        assert_eq!(count.load(Ordering::SeqCst), 5);
293    }
294
295    #[test]
296    fn dropping_every_handle_without_eos_still_ends_cleanly() {
297        let (source, handle) = AppSource::new("app-source", 4);
298        let count = Arc::new(AtomicUsize::new(0));
299        let pipeline = wire(source, count.clone());
300        pipeline.run();
301
302        handle.push(packet()).unwrap();
303        handle.push(packet()).unwrap();
304        drop(handle); // no explicit Eos — the channel disconnecting must end `run` on its own
305
306        let events: Vec<_> = pipeline.bus().iter().collect();
307        assert!(
308            !events.iter().any(|e| matches!(e, BusEvent::Error { .. })),
309            "unexpected error event(s): {events:?}"
310        );
311        assert!(events.iter().any(|e| matches!(e, BusEvent::Eos { .. })));
312        assert_eq!(count.load(Ordering::SeqCst), 2);
313    }
314
315    /// Regression guard for the exact reason `run` selects on `control`
316    /// and its data channel together instead of just blocking on
317    /// `data_rx.recv()`: with nothing ever pushed (and no `Eos`/drop
318    /// either), a plain blocking recv would never wake up to see `Stop`
319    /// at all — this must return promptly instead of hanging.
320    #[test]
321    fn stop_ends_promptly_even_with_no_producer() {
322        let (source, _handle) = AppSource::new("app-source", 4);
323        let count = Arc::new(AtomicUsize::new(0));
324        let pipeline = wire(source, count.clone());
325        pipeline.run();
326
327        // Give the background thread a moment to actually start looping
328        // (blocked in `select!`, waiting on data that's never coming)
329        // before `stop()` lands.
330        thread::sleep(Duration::from_millis(50));
331        pipeline.stop();
332
333        let events: Vec<_> = pipeline.bus().iter().collect();
334        assert!(
335            !events.iter().any(|e| matches!(e, BusEvent::Error { .. })),
336            "unexpected error event(s): {events:?}"
337        );
338        assert_eq!(count.load(Ordering::SeqCst), 0);
339    }
340}