media_pp/elements/source/pipeline_bridge.rs
1use std::{
2 sync::{
3 Arc, Mutex, Weak,
4 atomic::{AtomicU64, Ordering},
5 },
6 time::Duration,
7};
8
9use crate::pp_log::{PpLog, pp_info, pp_warn};
10use thiserror::Error as ThisError;
11
12use crate::{
13 buffer::MediaBuffer,
14 bus::{Bus, BusEvent},
15 contract::{InputContract, OutputContract},
16 control::{ControlMsg, ControlReceiver, drain_control},
17 element::{Element, ElementType, Sink, Source, SourceElement, element_pp_log},
18 error::Result,
19 pad::SrcPad,
20 queue::OverflowPolicy,
21};
22
23/// How long [`PipelineBridge::run`] waits for a buffer before looking at its
24/// own control channel again.
25///
26/// The reason this is a poll rather than a blocking receive: a bridge with no
27/// input is the ordinary state, not a fault, and a `Stop` sent to it has to
28/// arrive while it is in exactly that state. Short enough that stopping feels
29/// immediate, long enough that an idle bridge is not a spin.
30const CONTROL_POLL_INTERVAL: Duration = Duration::from_millis(10);
31
32/// Errors specific to [`PipelineBridge`].
33#[derive(Debug, ThisError)]
34pub enum PipelineBridgeError {
35 /// This sink was replaced by a later [`PipelineBridgeHandle::connect`],
36 /// or had already reported its own end.
37 ///
38 /// Reported rather than ignored so the pipeline still pushing into it
39 /// hears about it: a `Queue` posts it to its own bus and stops, which is
40 /// how a producer nobody is reading learns to end.
41 #[error("this bridge input was superseded by a later connection")]
42 Superseded,
43
44 /// The buffer could not be handed over before the configured
45 /// [`OverflowPolicy::Block`] timeout elapsed.
46 #[error("the bridge did not take a buffer within {0:?}")]
47 SendTimedOut(Duration),
48
49 /// Seeking was asked of a bridge. The timeline belongs to whatever feeds
50 /// it, in a pipeline this one has no authority over.
51 #[error("a PipelineBridge cannot seek what another pipeline is producing")]
52 SeekUnsupported,
53
54 /// The bridge's own pipeline has finished, so there is nothing on the
55 /// other side any more.
56 ///
57 /// Distinct from [`PipelineBridgeError::Superseded`] because the feeding
58 /// side answers them differently: a superseded input can connect again,
59 /// and this one has nowhere left to connect to.
60 #[error("the pipeline on the other side of this bridge has finished")]
61 Disconnected,
62}
63
64/// Construction-time options for [`PipelineBridge::new`].
65#[derive(Debug, Clone, Copy)]
66pub struct PipelineBridgeOptions {
67 /// How many buffers may sit between the two pipelines.
68 ///
69 /// The same trade a [`crate::queue::Queue`] makes: room to absorb one
70 /// side being briefly busy, at the cost of that many buffers' worth of
71 /// latency and of whatever they hold open.
72 pub depth: usize,
73 /// What happens when that room runs out — see [`OverflowPolicy`].
74 pub policy: OverflowPolicy,
75}
76
77impl Default for PipelineBridgeOptions {
78 fn default() -> Self {
79 Self {
80 depth: 8,
81 policy: OverflowPolicy::default(),
82 }
83 }
84}
85
86/// Carries buffers from one [`crate::pipeline::Pipeline`] into another, so
87/// the two can start, end and fail independently.
88///
89/// # What it is for
90///
91/// A pipeline is one-shot: a source that dies is not restarted, it is
92/// replaced, and replacing it means building a new pipeline. Everything
93/// downstream of it would go with it — unless the boundary falls between
94/// them. This is that boundary, in the general case.
95///
96/// The general case is what was missing. Crossing from one pipeline into
97/// another was already possible through [`crate::elements::AudioMixer`] or a
98/// video compositor, and an application whose graph meets at one of those
99/// needs nothing here. But both of them decide what they carry: a media
100/// kind, a format, and a rate of their own. Packets, or frames that are not
101/// to be composited, or anything else that only needs to *cross*, had
102/// nowhere to do it.
103///
104/// # Shape
105///
106/// ```text
107/// pipeline "up" pipeline "down"
108/// source ─ … ─ [ PipelineBridgeSink ] [ PipelineBridge ] ─ … ─ sink
109/// └──────── one bounded queue ───────┘
110/// ```
111///
112/// The downstream half is this element, driven as its pipeline's own
113/// `SourceElement`. The upstream half is a [`Sink`] from
114/// [`PipelineBridgeHandle::connect`], which whichever pipeline is feeding it
115/// terminates at.
116///
117/// # One input at a time
118///
119/// Deliberately, and unlike a mixer: with no way to combine buffers, several
120/// inputs would only interleave in whatever order they arrived. A second
121/// [`PipelineBridgeHandle::connect`] *replaces* the first, which is the
122/// reconnection path — the old [`Sink`] then refuses with
123/// [`PipelineBridgeError::Superseded`] rather than feeding its own
124/// replacement.
125///
126/// # What an empty bridge does
127///
128/// Nothing, and its pipeline stays alive doing it. A mixer with no input
129/// emits silence and a compositor re-emits its last picture, because each has
130/// a rate of its own; a bridge has only what it is given. So a downstream
131/// pipeline with no upstream is idle rather than finished, and picks up again
132/// when something connects.
133///
134/// # Ends
135///
136/// An input's `Eos` ends *the input*, not the bridge — otherwise the first
137/// disconnection would tear down the very pipeline this exists to keep
138/// running. [`PipelineBridgeHandle::input_ended`] reports it and the next
139/// `connect` starts another.
140///
141/// What ends the bridge is [`PipelineBridgeHandle::finish`], which sends
142/// `Eos` downstream and returns from `run`. A muxer down there writes its
143/// trailer on that; stopping the pipeline instead tells it to abandon the
144/// file. The downstream pipeline's own `finish` does the same thing from the
145/// other side — two doors into one room, which is right when the two halves
146/// have different owners.
147///
148/// # Both sides can still be controlled
149///
150/// Each pipeline keeps its own `pause`, `resume`, `finish` and `stop`, and
151/// they mean what they always did. Pausing the *downstream* one stops the
152/// bridge emitting, which fills the queue between them, which is felt on the
153/// feeding side as ordinary backpressure — blocking or dropping according to
154/// [`PipelineBridgeOptions::policy`]. That is a queue behaving like a queue
155/// rather than anything the bridge decides.
156///
157/// What does not cross is control itself, with one exception: see
158/// [`Sink::control`] on the input this hands out. Seeking is refused here
159/// outright ([`SourceElement::is_seekable`] is `false`) — the timeline
160/// belongs to whatever feeds the bridge, and an application holding both
161/// pipelines seeks the one that owns it.
162///
163/// # Timestamps cross unchanged
164///
165/// The two pipelines have their own [`crate::clock::Clock`] and
166/// [`crate::playback_clock::PlaybackClock`], and this does not re-time what
167/// passes through it — it cannot, not knowing what that is. A mixer re-times
168/// to its own tick and a compositor to its own rate; a bridge hands over the
169/// timestamps it was given.
170///
171/// So downstream must be somewhere those still mean something: a muxer,
172/// which writes what it is handed, or anything preceded by
173/// [`crate::elements::TimestampOrigin`] to re-base them onto the clock that
174/// is actually going to be measured against. What must *not* follow a bridge
175/// unguarded is a [`crate::elements::Pacer`] — it would be pacing one
176/// pipeline's timestamps against another pipeline's clock, which is a stream
177/// released all at once or one that never arrives.
178pub struct PipelineBridge {
179 pp_log: PpLog,
180 name: Arc<str>,
181 shared: Arc<BridgeShared>,
182 pad: SrcPad,
183}
184
185/// Shared between the bridge and every handle and sink derived from it.
186struct BridgeShared {
187 /// The buffers in flight, and which connection put them there.
188 ///
189 /// One lock for the queue and the connection identity together: a sink
190 /// has to check that it is still the live one *and* push under the same
191 /// lock, or a replacement racing with it could have its own first buffer
192 /// overtaken by the old input's last.
193 state: Mutex<BridgeState>,
194 /// Woken by a push, by a connection change, and by `finish`.
195 changed: std::sync::Condvar,
196 /// Issues a distinct identity for every `connect`, including one
197 /// replacing another.
198 next_connection: AtomicU64,
199 /// Buffers `OverflowPolicy::DropNewest` has thrown away.
200 ///
201 /// Counted here rather than reported where it happens, because where it
202 /// happens is a `Sink` on the feeding pipeline's thread and the bus that
203 /// should hear about it belongs to this one. The run loop posts what it
204 /// sees this move by — same visibility a `Queue` gives its own drops,
205 /// from the side that has a bus to say it on.
206 dropped: AtomicU64,
207 options: PipelineBridgeOptions,
208}
209
210struct BridgeState {
211 buffers: std::collections::VecDeque<MediaBuffer>,
212 /// Which connection may push. `None` before the first `connect` and
213 /// after an input ends.
214 connection: Option<u64>,
215 /// Set by an input's `Eos`, cleared by the next `connect`.
216 input_ended: bool,
217 /// Set by `finish`: the bridge sends `Eos` downstream and returns.
218 finished: bool,
219 /// Set by a `Flush` from the feeding side, cleared once the bridge has
220 /// passed it on. A flag rather than a queued marker because a flush that
221 /// waited its turn behind the buffers it invalidates would be no flush.
222 flush: bool,
223}
224
225impl PipelineBridge {
226 /// Creates one, and the handle the feeding side connects through.
227 pub fn new(
228 name: impl Into<String>,
229 options: PipelineBridgeOptions,
230 ) -> (Self, PipelineBridgeHandle) {
231 let name: Arc<str> = name.into().into();
232 let pp_log = element_pp_log(ElementType::Other, &name, None);
233 pp_info!(
234 pp_log: &pp_log,
235 "created: depth={}, policy={:?}",
236 options.depth,
237 options.policy
238 );
239 let shared = Arc::new(BridgeShared {
240 state: Mutex::new(BridgeState {
241 buffers: std::collections::VecDeque::new(),
242 connection: None,
243 input_ended: false,
244 finished: false,
245 flush: false,
246 }),
247 changed: std::sync::Condvar::new(),
248 next_connection: AtomicU64::new(1),
249 dropped: AtomicU64::new(0),
250 options,
251 });
252 let handle = PipelineBridgeHandle {
253 shared: Arc::downgrade(&shared),
254 name: name.clone(),
255 };
256 let pad = SrcPad::with_contract(format!("{name}_src"), OutputContract::Passthrough);
257 (
258 Self {
259 pp_log,
260 name,
261 shared,
262 pad,
263 },
264 handle,
265 )
266 }
267}
268
269/// A cheaply-cloneable way to connect a pipeline to a [`PipelineBridge`], and
270/// to end it.
271///
272/// Holds only a [`Weak`] reference, for the same reason
273/// [`crate::elements::MixerHandle`] does: keeping one after the bridge's own
274/// pipeline has finished must not keep its buffers alive forever, and every
275/// operation becomes a harmless `None` once it is gone.
276#[derive(Clone)]
277pub struct PipelineBridgeHandle {
278 shared: Weak<BridgeShared>,
279 name: Arc<str>,
280}
281
282impl PipelineBridgeHandle {
283 /// A [`Sink`] for the feeding pipeline to terminate at, replacing
284 /// whatever was connected before.
285 ///
286 /// `None` once the bridge's own pipeline has finished.
287 pub fn connect(&self) -> Option<Box<dyn Sink>> {
288 let shared = self.shared.upgrade()?;
289 let id = shared.next_connection.fetch_add(1, Ordering::Relaxed);
290 {
291 let mut state = shared.state.lock().unwrap();
292 // What a *live* input left behind goes with it: cutting one off
293 // mid-stream abandons its timeline, and handing on the tail of it
294 // would put buffers from a stream nobody is producing any more in
295 // front of the new input's own.
296 //
297 // An input that reached its own end is the opposite case. It
298 // finished; what it handed over is complete, and dropping it here
299 // would lose data a producer successfully delivered — the tail of
300 // a clip, or of the connection a reconnection is replacing.
301 if !state.input_ended {
302 state.buffers.clear();
303 }
304 state.connection = Some(id);
305 state.input_ended = false;
306 }
307 shared.changed.notify_all();
308 Some(Box::new(PipelineBridgeSink {
309 pp_log: element_pp_log(ElementType::Other, &self.name, None),
310 name: self.name.clone(),
311 id,
312 shared: self.shared.clone(),
313 }))
314 }
315
316 /// Whether the connected input has reached its own end.
317 ///
318 /// `false` before anything has connected: nothing has ended, it simply
319 /// has not begun. What this is for is noticing that a source finished so
320 /// another can take its place — see [`PipelineBridgeHandle::connect`].
321 pub fn input_ended(&self) -> bool {
322 self.shared
323 .upgrade()
324 .is_some_and(|shared| shared.state.lock().unwrap().input_ended)
325 }
326
327 /// Ends the bridge: `Eos` goes downstream and its `run` returns.
328 ///
329 /// This is the ordered ending, and the difference from stopping the
330 /// downstream pipeline is what a muxer down there does about it — writes
331 /// its trailer, rather than abandoning the file.
332 pub fn finish(&self) {
333 let Some(shared) = self.shared.upgrade() else {
334 return;
335 };
336 shared.state.lock().unwrap().finished = true;
337 shared.changed.notify_all();
338 }
339}
340
341/// The upstream half: what a feeding pipeline's branch ends at.
342struct PipelineBridgeSink {
343 pp_log: PpLog,
344 name: Arc<str>,
345 id: u64,
346 shared: Weak<BridgeShared>,
347}
348
349impl Element for PipelineBridgeSink {
350 fn name(&self) -> Arc<str> {
351 self.name.clone()
352 }
353
354 fn element_type(&self) -> ElementType {
355 ElementType::Other
356 }
357
358 fn pp_log(&self) -> &PpLog {
359 &self.pp_log
360 }
361
362 fn pp_log_mut(&mut self) -> &mut PpLog {
363 &mut self.pp_log
364 }
365}
366
367impl Sink for PipelineBridgeSink {
368 /// Anything, because a bridge is defined by not caring: it exists for
369 /// what a mixer and a compositor cannot carry. Paired with the
370 /// `Passthrough` on the other half, so whatever contract arrives keeps
371 /// propagating past the boundary rather than stopping at it.
372 fn input_contract(&self) -> InputContract {
373 InputContract::Any
374 }
375
376 fn consume(&mut self, buf: MediaBuffer) -> Result<()> {
377 let Some(shared) = self.shared.upgrade() else {
378 // The bridge's own pipeline has finished. Reported rather than
379 // swallowed, for the reason a superseded input is: a producer
380 // nobody reads has no other way to learn it should stop, and
381 // without this the whole feeding pipeline would go on reading,
382 // decoding and handing buffers to nothing at all.
383 return Err(PipelineBridgeError::Disconnected.into());
384 };
385 let deadline = std::time::Instant::now();
386 let mut state = shared.state.lock().unwrap();
387 loop {
388 if state.connection != Some(self.id) {
389 return Err(PipelineBridgeError::Superseded.into());
390 }
391 if let MediaBuffer::Eos = buf {
392 // The input's end, not the bridge's — see the type docs.
393 state.input_ended = true;
394 state.connection = None;
395 drop(state);
396 shared.changed.notify_all();
397 pp_info!(self, "input ended");
398 return Ok(());
399 }
400 if state.buffers.len() < shared.options.depth {
401 state.buffers.push_back(buf);
402 drop(state);
403 shared.changed.notify_all();
404 return Ok(());
405 }
406 match shared.options.policy {
407 OverflowPolicy::DropNewest => {
408 drop(state);
409 // Silent to the caller, as this policy is: falling behind
410 // is what it exists to absorb. The bridge's own run loop
411 // is what says so, on the bus that belongs to it.
412 shared.dropped.fetch_add(1, Ordering::Relaxed);
413 return Ok(());
414 }
415 OverflowPolicy::Block(timeout) => {
416 let left = timeout.saturating_sub(deadline.elapsed());
417 if left.is_zero() {
418 return Err(PipelineBridgeError::SendTimedOut(timeout).into());
419 }
420 let (guard, _) = shared
421 .changed
422 .wait_timeout(state, left.min(CONTROL_POLL_INTERVAL))
423 .unwrap();
424 state = guard;
425 }
426 }
427 }
428 }
429
430 /// `Flush` crosses; nothing else does.
431 ///
432 /// The test is whether the message means something inside an element or
433 /// something about the pipeline that sent it. `Flush` is the first: drop
434 /// what you are holding, which downstream *must* hear after a seek or it
435 /// keeps frames belonging to a timeline that has been left. Ordering is
436 /// not a difficulty for this one message, because arriving ahead of the
437 /// buffers it invalidates is exactly what it is for.
438 ///
439 /// The rest name the sender's own clock. Injecting `Pause` here would
440 /// leave this side's elements believing they are paused while this side's
441 /// [`crate::clock::Clock`] — the one a `Pacer` and the playback clock
442 /// actually read — keeps running. Two authorities over one timeline is
443 /// the defect, not the missing feature; the downstream pipeline has its
444 /// own `pause`, `finish` and `stop` for what its owner wants of it.
445 fn control(&mut self, msg: ControlMsg) -> Result<()> {
446 let Some(shared) = self.shared.upgrade() else {
447 return Ok(());
448 };
449 if matches!(msg, ControlMsg::Flush) {
450 {
451 let mut state = shared.state.lock().unwrap();
452 state.buffers.clear();
453 state.flush = true;
454 }
455 shared.changed.notify_all();
456 }
457 Ok(())
458 }
459}
460
461impl Element for PipelineBridge {
462 fn name(&self) -> Arc<str> {
463 self.name.clone()
464 }
465
466 fn element_type(&self) -> ElementType {
467 ElementType::Other
468 }
469
470 fn pp_log(&self) -> &PpLog {
471 &self.pp_log
472 }
473
474 fn pp_log_mut(&mut self) -> &mut PpLog {
475 &mut self.pp_log
476 }
477}
478
479impl Source for PipelineBridge {
480 fn src_pads(&mut self) -> &mut [SrcPad] {
481 std::slice::from_mut(&mut self.pad)
482 }
483}
484
485impl SourceElement for PipelineBridge {
486 /// Live, because it cannot be asked for a buffer it has not been given.
487 /// Whether what feeds it is live is the other pipeline's business and not
488 /// something this can see.
489 fn is_live(&self) -> bool {
490 true
491 }
492
493 /// The timeline belongs to whatever is upstream, in a pipeline this one
494 /// has no authority over.
495 fn is_seekable(&self) -> bool {
496 false
497 }
498
499 fn seek(&mut self, _target: Duration) -> Result<Duration> {
500 Err(PipelineBridgeError::SeekUnsupported.into())
501 }
502
503 fn run(&mut self, control: &ControlReceiver, bus: &Bus) -> Result<()> {
504 pp_info!(self, "started");
505 let mut reported_drops = 0;
506 loop {
507 let dropped = self.shared.dropped.load(Ordering::Relaxed);
508 if dropped > reported_drops {
509 reported_drops = dropped;
510 bus.post(
511 &self.pp_log,
512 BusEvent::Dropped {
513 element_type: self.element_type(),
514 name: self.name.clone(),
515 },
516 );
517 }
518 let outcome = drain_control(control, self, bus)?;
519 if outcome.stopped {
520 pp_info!(self, "stopped");
521 return Ok(());
522 }
523 // Waited on with a timeout rather than blocked on, because having
524 // nothing to carry is this element's ordinary state and a `Stop`
525 // has to arrive while it is in it.
526 let taken = {
527 let shared = &self.shared;
528 let state = shared.state.lock().unwrap();
529 let (mut state, _) = shared
530 .changed
531 .wait_timeout(state, CONTROL_POLL_INTERVAL)
532 .unwrap();
533 if state.finished {
534 drop(state);
535 pp_info!(self, "finished");
536 if let Err(error) = self.pad.push(MediaBuffer::Eos) {
537 pp_warn!(self, "end of stream was not delivered: {error}");
538 }
539 return Ok(());
540 }
541 let flush = std::mem::take(&mut state.flush);
542 (state.buffers.pop_front(), flush)
543 };
544 let (taken, flush) = taken;
545 if flush {
546 self.pad.control(ControlMsg::Flush)?;
547 }
548 if let Some(buffer) = taken {
549 self.shared.changed.notify_all();
550 // One buffer's failure is not this bridge's end, the same way
551 // a `Queue` reports a failing downstream and keeps its worker.
552 if let Err(error) = self.pad.push(buffer) {
553 bus.post(
554 &self.pp_log,
555 BusEvent::Error {
556 element_type: self.element_type(),
557 name: self.name.clone(),
558 error,
559 },
560 );
561 }
562 }
563 }
564 }
565}
566
567#[cfg(test)]
568mod tests {
569 use std::sync::{Mutex as StdMutex, atomic::AtomicUsize};
570
571 use super::*;
572 use crate::{elements::AppSink, pipeline::Pipeline};
573
574 /// What crossed the bridge, in order.
575 /// A sink, the timestamps that reached it, and how many ends of stream it
576 /// saw.
577 type Watched = (
578 Box<dyn Sink>,
579 Arc<StdMutex<Vec<Option<i64>>>>,
580 Arc<AtomicUsize>,
581 );
582
583 fn watched() -> Watched {
584 let seen = Arc::new(StdMutex::new(Vec::new()));
585 let ends = Arc::new(AtomicUsize::new(0));
586 let sink = AppSink::new("watcher", {
587 let seen = Arc::clone(&seen);
588 let ends = Arc::clone(&ends);
589 move |buffer| {
590 match &buffer {
591 MediaBuffer::Packet(packet) => seen.lock().unwrap().push(packet.pts()),
592 MediaBuffer::Eos => {
593 ends.fetch_add(1, Ordering::Relaxed);
594 }
595 _ => {}
596 }
597 Ok(())
598 }
599 });
600 (Box::new(sink), seen, ends)
601 }
602
603 /// Counts flushes and keeps what arrived between them.
604 struct FlushWatcher {
605 pp_log: PpLog,
606 flushed: Arc<AtomicUsize>,
607 seen: Arc<StdMutex<Vec<Option<i64>>>>,
608 }
609
610 impl Element for FlushWatcher {
611 fn name(&self) -> Arc<str> {
612 "flush-watcher".into()
613 }
614 fn element_type(&self) -> ElementType {
615 ElementType::Other
616 }
617 fn pp_log(&self) -> &PpLog {
618 &self.pp_log
619 }
620 fn pp_log_mut(&mut self) -> &mut PpLog {
621 &mut self.pp_log
622 }
623 }
624
625 impl Sink for FlushWatcher {
626 fn consume(&mut self, buf: MediaBuffer) -> Result<()> {
627 if let MediaBuffer::Packet(packet) = &buf {
628 self.seen.lock().unwrap().push(packet.pts());
629 }
630 Ok(())
631 }
632
633 fn control(&mut self, msg: ControlMsg) -> Result<()> {
634 if matches!(msg, ControlMsg::Flush) {
635 self.flushed.fetch_add(1, Ordering::Relaxed);
636 }
637 Ok(())
638 }
639 }
640
641 fn packet(pts: i64) -> MediaBuffer {
642 let mut packet = ffmpeg_next::Packet::empty();
643 packet.set_pts(Some(pts));
644 MediaBuffer::Packet(Arc::new(packet))
645 }
646
647 /// The downstream pipeline, running, with the bridge as its source.
648 fn downstream(bridge: PipelineBridge, sink: Box<dyn Sink>) -> std::sync::Arc<Pipeline> {
649 let pipeline = Pipeline::new("down", bridge, move |source, context| {
650 let branch = context.branch().to(sink)?;
651 context.attach(source, 0, branch)?;
652 Ok(())
653 })
654 .expect("wire the downstream pipeline");
655 pipeline.run().expect("run the downstream pipeline");
656 pipeline
657 }
658
659 fn wait_until(mut ready: impl FnMut() -> bool) {
660 let deadline = std::time::Instant::now() + Duration::from_secs(2);
661 while !ready() && std::time::Instant::now() < deadline {
662 std::thread::sleep(Duration::from_millis(5));
663 }
664 }
665
666 /// The whole point, in one line: what goes in one pipeline comes out of
667 /// the other.
668 #[test]
669 fn a_buffer_put_in_one_end_comes_out_of_the_other() {
670 let (bridge, handle) = PipelineBridge::new("bridge", PipelineBridgeOptions::default());
671 let (sink, seen, _) = watched();
672 let pipeline = downstream(bridge, sink);
673
674 let mut input = handle.connect().expect("the bridge is running");
675 input.consume(packet(7)).expect("hand it over");
676
677 wait_until(|| !seen.lock().unwrap().is_empty());
678 pipeline.stop();
679 assert_eq!(*seen.lock().unwrap(), vec![Some(7)]);
680 }
681
682 /// An input ending is not the bridge ending. If it were, the first
683 /// disconnection would take down the pipeline this exists to keep
684 /// running — and a second input could never replace the first.
685 #[test]
686 fn an_input_ending_leaves_the_bridge_running_for_the_next_one() {
687 let (bridge, handle) = PipelineBridge::new("bridge", PipelineBridgeOptions::default());
688 let (sink, seen, ends) = watched();
689 let pipeline = downstream(bridge, sink);
690
691 let mut first = handle.connect().expect("the bridge is running");
692 first.consume(packet(1)).expect("hand it over");
693 first.consume(MediaBuffer::Eos).expect("end the input");
694 wait_until(|| handle.input_ended());
695
696 assert!(handle.input_ended(), "the handle reports the input's end");
697 assert_eq!(
698 ends.load(Ordering::Relaxed),
699 0,
700 "an input's end must not reach downstream as the bridge's"
701 );
702 assert!(pipeline.is_running(), "nor end the pipeline it drives");
703
704 let mut second = handle.connect().expect("still running");
705 assert!(!handle.input_ended(), "a new input is not an ended one");
706 second.consume(packet(2)).expect("hand it over");
707
708 wait_until(|| seen.lock().unwrap().len() == 2);
709 pipeline.stop();
710 assert_eq!(*seen.lock().unwrap(), vec![Some(1), Some(2)]);
711 }
712
713 /// A replaced input must not be able to feed its own replacement, and
714 /// must be told rather than ignored: the pipeline still pushing into it
715 /// is how a producer nobody reads learns to stop.
716 #[test]
717 fn a_superseded_input_is_refused_rather_than_swallowed() {
718 let (bridge, handle) = PipelineBridge::new("bridge", PipelineBridgeOptions::default());
719 let (sink, seen, _) = watched();
720 let pipeline = downstream(bridge, sink);
721
722 let mut first = handle.connect().expect("the bridge is running");
723 let mut second = handle.connect().expect("replacing the first");
724
725 let refused = first.consume(packet(1));
726 second
727 .consume(packet(2))
728 .expect("the live input still works");
729
730 wait_until(|| !seen.lock().unwrap().is_empty());
731 pipeline.stop();
732
733 assert!(
734 matches!(
735 refused,
736 Err(crate::error::Error::PipelineBridgeError(
737 PipelineBridgeError::Superseded
738 ))
739 ),
740 "the replaced input has to hear about it, got {refused:?}"
741 );
742 assert_eq!(
743 *seen.lock().unwrap(),
744 vec![Some(2)],
745 "and must not have put anything in front of its replacement"
746 );
747 }
748
749 /// A bridge with nothing to carry is the ordinary state, not a fault —
750 /// and a `Stop` has to arrive while it is in it. Blocking on the queue
751 /// instead of polling would make an idle bridge an unstoppable pipeline.
752 #[test]
753 fn a_starved_bridge_still_stops() {
754 let (bridge, handle) = PipelineBridge::new("bridge", PipelineBridgeOptions::default());
755 let (sink, _, _) = watched();
756 let pipeline = downstream(bridge, sink);
757 // Nothing ever connects.
758 drop(handle);
759
760 let started = std::time::Instant::now();
761 pipeline.stop();
762 assert!(
763 started.elapsed() < Duration::from_secs(1),
764 "stopping an idle bridge took {:?}",
765 started.elapsed()
766 );
767 assert!(!pipeline.is_running());
768 }
769
770 /// `finish` is the ordered ending, and the difference from stopping is
771 /// what a muxer downstream does about it: writes its trailer rather than
772 /// abandoning the file.
773 #[test]
774 fn finish_sends_end_of_stream_downstream() {
775 let (bridge, handle) = PipelineBridge::new("bridge", PipelineBridgeOptions::default());
776 let (sink, _, ends) = watched();
777 let pipeline = downstream(bridge, sink);
778
779 handle.finish();
780 wait_until(|| ends.load(Ordering::Relaxed) > 0);
781
782 assert_eq!(
783 ends.load(Ordering::Relaxed),
784 1,
785 "downstream has to see exactly one end of stream"
786 );
787 wait_until(|| !pipeline.is_running());
788 assert!(!pipeline.is_running(), "and the bridge's own run returns");
789 }
790
791 /// A seek on the feeding side leaves this side holding frames from a
792 /// timeline that has been left. `Flush` is the one message that crosses,
793 /// and it has to arrive ahead of them rather than behind.
794 #[test]
795 fn a_flush_crosses_and_takes_the_queued_buffers_with_it() {
796 let (bridge, handle) = PipelineBridge::new("bridge", PipelineBridgeOptions::default());
797 let flushed = Arc::new(AtomicUsize::new(0));
798 let seen = Arc::new(StdMutex::new(Vec::new()));
799 let sink = FlushWatcher {
800 pp_log: element_pp_log(ElementType::Other, "flush-watcher", None),
801 flushed: Arc::clone(&flushed),
802 seen: Arc::clone(&seen),
803 };
804 let pipeline = downstream(bridge, Box::new(sink));
805 let mut input = handle.connect().expect("the bridge is running");
806
807 input.consume(packet(1)).expect("queued");
808 input
809 .control(ControlMsg::Flush)
810 .expect("a flush from the feeding pipeline");
811 input.consume(packet(2)).expect("after the flush");
812
813 wait_until(|| flushed.load(Ordering::Relaxed) > 0 && !seen.lock().unwrap().is_empty());
814 pipeline.stop();
815
816 assert_eq!(flushed.load(Ordering::Relaxed), 1, "the flush crossed");
817 assert_eq!(
818 *seen.lock().unwrap(),
819 vec![Some(2)],
820 "and what belonged to the timeline it left did not"
821 );
822 }
823
824 /// A downstream pipeline that has ended is not a reason for the feeding
825 /// one to keep reading, decoding and handing buffers to nothing.
826 #[test]
827 fn feeding_a_bridge_whose_pipeline_has_gone_is_refused() {
828 let (bridge, handle) = PipelineBridge::new("bridge", PipelineBridgeOptions::default());
829 let mut input = handle.connect().expect("the bridge is alive");
830 drop(bridge);
831
832 assert!(
833 matches!(
834 input.consume(packet(1)),
835 Err(crate::error::Error::PipelineBridgeError(
836 PipelineBridgeError::Disconnected
837 ))
838 ),
839 "the feeding side has to hear that there is nothing on the other side"
840 );
841 }
842
843 /// The other half of that rule: an input cut off while it was still
844 /// running leaves nothing behind, because what it had queued belongs to a
845 /// stream that is no longer being produced.
846 #[test]
847 fn superseding_a_live_input_discards_what_it_had_queued() {
848 let (bridge, handle) = PipelineBridge::new("bridge", PipelineBridgeOptions::default());
849 // No pipeline, so nothing drains and what is queued stays queued.
850 let shared = Arc::clone(&bridge.shared);
851 let mut first = handle.connect().expect("the bridge is alive");
852
853 first.consume(packet(1)).expect("queued behind nothing");
854 assert_eq!(shared.state.lock().unwrap().buffers.len(), 1);
855
856 let _second = handle.connect().expect("replacing a live input");
857
858 assert!(
859 shared.state.lock().unwrap().buffers.is_empty(),
860 "an abandoned timeline's buffers must not reach the new input's reader"
861 );
862 drop(bridge);
863 }
864
865 /// Falling behind is what `DropNewest` exists to absorb, so the feeding
866 /// side is not told — but something has to say it happened, and the bus
867 /// that can is the bridge's own.
868 #[test]
869 fn dropping_under_the_newest_policy_is_reported_on_the_bridges_own_bus() {
870 let (bridge, handle) = PipelineBridge::new(
871 "bridge",
872 PipelineBridgeOptions {
873 depth: 1,
874 policy: OverflowPolicy::DropNewest,
875 },
876 );
877 // No pipeline: nothing drains, so the second buffer has nowhere to go.
878 let shared = Arc::clone(&bridge.shared);
879 let mut input = handle.connect().expect("the bridge is alive");
880
881 input.consume(packet(1)).expect("fills the one slot");
882 input
883 .consume(packet(2))
884 .expect("dropped, and not an error here");
885
886 assert_eq!(
887 shared.dropped.load(Ordering::Relaxed),
888 1,
889 "the drop is counted for the run loop to report"
890 );
891 drop(bridge);
892 }
893}