use core::ops::Deref;
pub use mediaspine::demuxer::*;
use crate::Timestamp;
#[cfg(all(test, any(feature = "std", feature = "alloc")))]
extern crate alloc;
pub trait Demuxer {
type Adapter: DemuxAdapter;
type Buffer: AsRef<[u8]>;
type TrackHandle: Clone + Deref<Target = TrackInfo<Self::Adapter>>;
type Error;
fn tracks(&self) -> &[Self::TrackHandle];
#[cfg_attr(not(tarpaulin), inline(always))]
fn chapters(&self) -> &[Chapter<Self::Adapter>] {
&[]
}
#[cfg_attr(not(tarpaulin), inline(always))]
fn duration(&self) -> Option<ContainerDuration> {
None
}
#[cfg_attr(not(tarpaulin), inline(always))]
fn measured_end(&self, _track: TrackIndex) -> Option<MeasuredEnd> {
None
}
fn next_packet(
&mut self,
) -> Result<Option<DemuxedPacket<Self::Adapter, Self::Buffer>>, Self::Error>;
fn seek(&mut self, target: Timestamp) -> Result<(), Self::Error>;
}
#[cfg(test)]
mod tests {
use core::num::NonZeroI32;
use super::*;
#[cfg(any(feature = "std", feature = "alloc"))]
use crate::packet::AudioPacket;
use crate::{
Timebase,
adapter::{AudioAdapter, SubtitleAdapter, VideoAdapter},
packet::SubtitlePacket,
};
#[cfg(any(feature = "std", feature = "alloc"))]
use alloc::{rc::Rc, vec, vec::Vec};
struct VLoop;
impl VideoAdapter for VLoop {
type CodecId = u32;
type PixelFormat = u32;
type PacketExtra = ();
type FrameExtra = ();
}
struct ALoop;
impl AudioAdapter for ALoop {
type CodecId = u32;
type SampleFormat = u32;
type ChannelLayout = u32;
type PacketExtra = ();
type FrameExtra = ();
}
struct SLoop;
impl SubtitleAdapter for SLoop {
type CodecId = u32;
type PacketExtra = ();
type FrameExtra = ();
}
struct Loopback;
impl DemuxAdapter for Loopback {
type CodecId = u32;
type Video = VLoop;
type Audio = ALoop;
type Subtitle = SLoop;
type DataExtra = ();
type AttachmentExtra = ();
type TrackExtra = ();
type Text = &'static str;
}
fn ms_tb() -> Timebase {
Timebase::new(1, NonZeroI32::new(1000).expect("non-zero"))
}
#[cfg(any(feature = "std", feature = "alloc"))]
struct LoopDemuxer {
tracks: Vec<Rc<TrackInfo<Loopback>>>,
drained: bool,
}
#[derive(Debug)]
struct LoopError;
#[cfg(any(feature = "std", feature = "alloc"))]
impl Demuxer for LoopDemuxer {
type Adapter = Loopback;
type Buffer = &'static [u8];
type TrackHandle = Rc<TrackInfo<Loopback>>;
type Error = LoopError;
fn tracks(&self) -> &[Rc<TrackInfo<Loopback>>] {
&self.tracks
}
fn next_packet(&mut self) -> Result<Option<DemuxedPacket<Loopback, &'static [u8]>>, LoopError> {
if self.drained {
return Ok(None);
}
self.drained = true;
Ok(Some(DemuxedPacket::Audio(AudioTrackPacket::new(
TrackIndex::new(0),
AudioPacket::new(&[][..], ()),
))))
}
fn seek(&mut self, _target: Timestamp) -> Result<(), LoopError> {
self.drained = false;
Ok(())
}
}
#[cfg(any(feature = "std", feature = "alloc"))]
#[test]
fn the_session_face_is_implementable_and_none_means_eof() {
fn _accepts<D: Demuxer>() {}
_accepts::<LoopDemuxer>();
let mut d = LoopDemuxer {
tracks: vec![Rc::new(TrackInfo::new(
ms_tb(),
TrackParams::Audio(AudioTrackParams::new(1, 48_000, 2, 0, 0)),
(),
))],
drained: false,
};
assert_eq!(d.tracks().len(), 1);
assert_eq!(d.tracks()[0].kind(), TrackKind::Audio);
assert!(matches!(
d.next_packet().expect("pull"),
Some(DemuxedPacket::Audio(_))
));
assert!(d.next_packet().expect("pull").is_none());
assert!(d.next_packet().expect("pull").is_none(), "EOF is sticky");
d.seek(Timestamp::new(0, ms_tb())).expect("seek");
assert!(d.next_packet().expect("pull").is_some());
}
#[cfg(any(feature = "std", feature = "alloc"))]
#[test]
fn a_demuxer_that_declares_no_chapters_answers_an_empty_table() {
let d = LoopDemuxer {
tracks: vec![Rc::new(TrackInfo::new(
ms_tb(),
TrackParams::Audio(AudioTrackParams::new(1, 48_000, 2, 0, 0)),
(),
))],
drained: false,
};
assert!(d.chapters().is_empty());
assert_eq!(
d.tracks().len(),
1,
"the chapter table is a different table, and reading it is not reading that one",
);
}
#[cfg(any(feature = "std", feature = "alloc"))]
#[test]
fn a_demuxer_with_no_container_duration_answers_none() {
let mut d = LoopDemuxer {
tracks: vec![Rc::new(TrackInfo::new(
ms_tb(),
TrackParams::Audio(AudioTrackParams::new(1, 48_000, 2, 0, 0)),
(),
))],
drained: false,
};
assert_eq!(d.duration(), None);
assert_eq!(d.tracks()[0].duration(), None, "a different question");
assert!(
d.next_packet().expect("pull").is_some(),
"reading the duration is not a pull",
);
}
#[cfg(any(feature = "std", feature = "alloc"))]
#[test]
fn a_demuxer_that_measures_nothing_answers_none_for_every_track() {
let mut d = LoopDemuxer {
tracks: vec![Rc::new(TrackInfo::new(
ms_tb(),
TrackParams::Audio(AudioTrackParams::new(1, 48_000, 2, 0, 0)),
(),
))],
drained: false,
};
assert_eq!(d.measured_end(TrackIndex::new(0)), None);
assert_eq!(
d.measured_end(TrackIndex::new(9)),
None,
"outside the table"
);
assert!(d.next_packet().expect("pull").is_some());
assert_eq!(
d.measured_end(TrackIndex::new(0)),
None,
"a backend that never overrode the method measures nothing, whatever it delivered",
);
}
#[cfg(any(feature = "std", feature = "alloc"))]
#[test]
fn the_table_survives_the_session_and_the_handles_stay_the_rows() {
let mut d = LoopDemuxer {
tracks: vec![
Rc::new(TrackInfo::new(
ms_tb(),
TrackParams::Audio(AudioTrackParams::new(1, 48_000, 2, 0, 0)),
(),
)),
Rc::new(TrackInfo::new(
ms_tb(),
TrackParams::Subtitle(SubtitleTrackParams::new(2)),
(),
)),
],
drained: false,
};
let held: Vec<Rc<TrackInfo<Loopback>>> = d.tracks().to_vec();
let expected: Vec<TrackKind> = held.iter().map(|t| t.kind()).collect();
assert_eq!(expected, vec![TrackKind::Audio, TrackKind::Subtitle]);
let mut pulled = 0;
while d.next_packet().expect("pull").is_some() {
pulled += 1;
assert_eq!(d.tracks().len(), 2, "the table is not spent by a pull");
}
assert_eq!(pulled, 1, "the mock's one packet, not an empty session");
assert_eq!(d.tracks().len(), held.len());
for (before, after) in held.iter().zip(d.tracks()) {
assert!(Rc::ptr_eq(before, after), "the same row, not a copy");
}
assert_eq!(
d.tracks().iter().map(|t| t.kind()).collect::<Vec<_>>(),
expected,
);
assert_eq!(held[0].timebase(), ms_tb());
}
struct BorrowedDemuxer<'a> {
tracks: &'a [&'a TrackInfo<Loopback>],
drained: bool,
}
impl<'a> Demuxer for BorrowedDemuxer<'a> {
type Adapter = Loopback;
type Buffer = &'static [u8];
type TrackHandle = &'a TrackInfo<Loopback>;
type Error = LoopError;
fn tracks(&self) -> &[&'a TrackInfo<Loopback>] {
self.tracks
}
fn next_packet(&mut self) -> Result<Option<DemuxedPacket<Loopback, &'static [u8]>>, LoopError> {
if self.drained {
return Ok(None);
}
self.drained = true;
Ok(Some(DemuxedPacket::Subtitle(SubtitleTrackPacket::new(
TrackIndex::new(0),
SubtitlePacket::new(&[][..], ()),
))))
}
fn seek(&mut self, _target: Timestamp) -> Result<(), LoopError> {
self.drained = false;
Ok(())
}
}
#[test]
fn a_borrowed_table_binds_the_handle_seat_without_an_allocator() {
let row = TrackInfo::<Loopback>::new(
ms_tb(),
TrackParams::Subtitle(SubtitleTrackParams::new(7)),
(),
);
let rows = [&row];
let mut d = BorrowedDemuxer {
tracks: &rows,
drained: false,
};
let held: &TrackInfo<Loopback> = d.tracks()[0];
assert!(d.next_packet().expect("pull").is_some());
assert!(d.next_packet().expect("pull").is_none());
assert_eq!(d.tracks().len(), 1, "the table outlives the pull loop");
assert_eq!(held.kind(), TrackKind::Subtitle);
assert!(core::ptr::eq(held, d.tracks()[0]));
}
#[test]
fn the_provided_chapter_table_needs_no_allocator() {
let row = TrackInfo::<Loopback>::new(
ms_tb(),
TrackParams::Subtitle(SubtitleTrackParams::new(7)),
(),
);
let rows = [&row];
let d = BorrowedDemuxer {
tracks: &rows,
drained: false,
};
assert!(d.chapters().is_empty());
}
#[test]
fn the_provided_container_duration_needs_no_allocator() {
let row = TrackInfo::<Loopback>::new(
ms_tb(),
TrackParams::Subtitle(SubtitleTrackParams::new(7)),
(),
);
let rows = [&row];
let d = BorrowedDemuxer {
tracks: &rows,
drained: false,
};
assert_eq!(d.duration(), None);
}
#[test]
fn the_provided_measured_end_needs_no_allocator() {
let row = TrackInfo::<Loopback>::new(
ms_tb(),
TrackParams::Subtitle(SubtitleTrackParams::new(7)),
(),
);
let rows = [&row];
let d = BorrowedDemuxer {
tracks: &rows,
drained: false,
};
assert_eq!(d.measured_end(TrackIndex::new(0)), None);
}
}