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 run(&mut self, control: &ControlReceiver, bus: &Bus) -> Result<()> {
142        pp_info!(self, "started");
143        loop {
144            // Non-blocking first: if control is already backed up, clear
145            // it before the `select!` below picks an arbitrary ready arm
146            // (it'd be just as correct to skip straight to `select!`, but
147            // this keeps `AppSource` consistent with every other
148            // `SourceElement::run` calling `drain_control` per iteration).
149            if drain_control(control, self, bus)?.stopped {
150                pp_info!(self, "stopped");
151                return Ok(());
152            }
153
154            select! {
155                recv(control.rx) -> req => {
156                    match req {
157                        Ok(req) => {
158                            match req.kind {
159                                RequestKind::Finish => {
160                                    apply_finish(self, bus, &req.ack);
161                                    pp_info!(self, "finished");
162                                    return Ok(());
163                                }
164                                RequestKind::Control(msg) => {
165                                    if apply_one(self, bus, msg, &req.ack)? {
166                                        pp_info!(self, "stopped");
167                                        return Ok(());
168                                    }
169                                    if msg == ControlMsg::Pause
170                                        && wait_out_pause(control, self, bus)?
171                                    {
172                                        pp_info!(self, "stopped");
173                                        return Ok(());
174                                    }
175                                }
176                            }
177                        }
178                        // The Pipeline itself is gone — nothing left to drive this.
179                        Err(_) => {
180                            pp_info!(self, "run: control channel gone, ending");
181                            return Ok(());
182                        }
183                    }
184                }
185                recv(self.data_rx) -> buf => {
186                    match buf {
187                        Ok(buf) if buf.is_eos() => {
188                            pp_info!(self, "event=eos phase=source_received");
189                            break;
190                        }
191                        Ok(buf) => {
192                            if let Err(error) = self.pad.push(buf) {
193                                bus.post(
194                                    &self.pp_log,
195                                    BusEvent::Error {
196                                        element_type: ElementType::AppSource,
197                                        name: self.name.clone(),
198                                        error,
199                                    },
200                                );
201                            }
202                        }
203                        // Every `AppSourceHandle` dropped without an explicit Eos.
204                        Err(_) => {
205                            pp_info!(self, "run: every AppSourceHandle dropped, ending");
206                            break;
207                        }
208                    }
209                }
210            }
211        }
212        self.pad.push_eos(&self.pp_log)
213    }
214
215    /// No-op: `AppSource` has nothing of its own to reposition — whatever
216    /// comes next is whatever the app pushes next, not a position in a
217    /// file. Reports `target` back as where it "landed" so downstream
218    /// (e.g. a [`crate::elements::Pacer`] resetting its clock offset)
219    /// still sees a consistent [`crate::bus::BusEvent::Seeked`].
220    fn seek(&mut self, target: Duration) -> Result<Duration> {
221        Ok(target)
222    }
223}
224
225#[cfg(test)]
226mod tests {
227    use std::{
228        sync::atomic::{AtomicUsize, Ordering},
229        thread,
230    };
231
232    use super::*;
233    use crate::pipeline::Pipeline;
234
235    struct CountingSink {
236        pp_log: PpLog,
237        count: Arc<AtomicUsize>,
238    }
239
240    impl Element for CountingSink {
241        fn name(&self) -> Arc<str> {
242            "counter".into()
243        }
244
245        fn element_type(&self) -> ElementType {
246            ElementType::Other
247        }
248
249        fn pp_log(&self) -> &PpLog {
250            &self.pp_log
251        }
252
253        fn pp_log_mut(&mut self) -> &mut PpLog {
254            &mut self.pp_log
255        }
256    }
257
258    impl crate::element::Sink for CountingSink {
259        fn consume(&mut self, buf: MediaBuffer) -> Result<()> {
260            if !buf.is_eos() {
261                self.count.fetch_add(1, Ordering::SeqCst);
262            }
263            Ok(())
264        }
265
266        fn control(&mut self, _msg: ControlMsg) -> Result<()> {
267            Ok(())
268        }
269    }
270
271    fn packet() -> MediaBuffer {
272        MediaBuffer::Packet(Arc::new(ffmpeg_next::Packet::empty()))
273    }
274
275    fn wire(source: AppSource, count: Arc<AtomicUsize>) -> Arc<Pipeline> {
276        let sink = CountingSink {
277            count,
278            pp_log: element_pp_log(ElementType::Other, "counter", None),
279        };
280        Pipeline::new("test", source, |source, ctx| {
281            let branch = ctx.branch().to(Box::new(sink))?;
282            ctx.attach(source, 0, branch)?;
283            Ok(())
284        })
285        .expect("test pipeline wiring must succeed")
286    }
287
288    #[test]
289    fn pushed_buffers_reach_downstream_then_eos_ends_it() {
290        let (source, handle) = AppSource::new("app-source", 4);
291        let count = Arc::new(AtomicUsize::new(0));
292        let pipeline = wire(source, count.clone());
293        pipeline.run().unwrap();
294
295        for _ in 0..5 {
296            handle.push(packet()).unwrap();
297        }
298        handle.push(MediaBuffer::Eos).unwrap();
299
300        let events: Vec<_> = pipeline.bus().iter().collect();
301        assert!(
302            !events.iter().any(|e| matches!(e, BusEvent::Error { .. })),
303            "unexpected error event(s): {events:?}"
304        );
305        assert!(events.iter().any(|e| matches!(e, BusEvent::Eos { .. })));
306        assert_eq!(count.load(Ordering::SeqCst), 5);
307    }
308
309    #[test]
310    fn dropping_every_handle_without_eos_still_ends_cleanly() {
311        let (source, handle) = AppSource::new("app-source", 4);
312        let count = Arc::new(AtomicUsize::new(0));
313        let pipeline = wire(source, count.clone());
314        pipeline.run().unwrap();
315
316        handle.push(packet()).unwrap();
317        handle.push(packet()).unwrap();
318        drop(handle); // no explicit Eos — the channel disconnecting must end `run` on its own
319
320        let events: Vec<_> = pipeline.bus().iter().collect();
321        assert!(
322            !events.iter().any(|e| matches!(e, BusEvent::Error { .. })),
323            "unexpected error event(s): {events:?}"
324        );
325        assert!(events.iter().any(|e| matches!(e, BusEvent::Eos { .. })));
326        assert_eq!(count.load(Ordering::SeqCst), 2);
327    }
328
329    /// Regression guard for the exact reason `run` selects on `control`
330    /// and its data channel together instead of just blocking on
331    /// `data_rx.recv()`: with nothing ever pushed (and no `Eos`/drop
332    /// either), a plain blocking recv would never wake up to see `Stop`
333    /// at all — this must return promptly instead of hanging.
334    #[test]
335    fn stop_ends_promptly_even_with_no_producer() {
336        let (source, _handle) = AppSource::new("app-source", 4);
337        let count = Arc::new(AtomicUsize::new(0));
338        let pipeline = wire(source, count.clone());
339        pipeline.run().unwrap();
340
341        // Give the background thread a moment to actually start looping
342        // (blocked in `select!`, waiting on data that's never coming)
343        // before `stop()` lands.
344        thread::sleep(Duration::from_millis(50));
345        pipeline.stop();
346
347        let events: Vec<_> = pipeline.bus().iter().collect();
348        assert!(
349            !events.iter().any(|e| matches!(e, BusEvent::Error { .. })),
350            "unexpected error event(s): {events:?}"
351        );
352        assert_eq!(count.load(Ordering::SeqCst), 0);
353    }
354}