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