use core::fmt::Debug;
use crate::{
Timebase, Timestamp,
adapter::{AudioAdapter, SubtitleAdapter, VideoAdapter},
packet::{AudioPacket, PacketFlags, SubtitlePacket, VideoPacket},
};
#[repr(transparent)]
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TrackIndex(usize);
impl TrackIndex {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(index: usize) -> Self {
Self(index)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn get(self) -> usize {
self.0
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum TrackKind {
Video,
Audio,
Subtitle,
Data,
Attachment,
#[default]
Unknown,
}
pub trait DemuxAdapter {
type CodecId: Copy + Eq + Debug;
type Video: VideoAdapter<CodecId = Self::CodecId>;
type Audio: AudioAdapter<CodecId = Self::CodecId>;
type Subtitle: SubtitleAdapter<CodecId = Self::CodecId>;
type DataExtra;
type AttachmentExtra;
type TrackExtra;
type Text: AsRef<str> + Debug;
}
pub type DemuxVideoPacket<E, D> =
VideoPacket<<<E as DemuxAdapter>::Video as VideoAdapter>::PacketExtra, D>;
pub type DemuxAudioPacket<E, D> =
AudioPacket<<<E as DemuxAdapter>::Audio as AudioAdapter>::PacketExtra, D>;
pub type DemuxSubtitlePacket<E, D> =
SubtitlePacket<<<E as DemuxAdapter>::Subtitle as SubtitleAdapter>::PacketExtra, D>;
pub type DemuxDataPacket<E, D> = DataPacket<<E as DemuxAdapter>::DataExtra, D>;
pub type DemuxAttachmentPacket<E, D> = AttachmentPacket<<E as DemuxAdapter>::AttachmentExtra, D>;
pub struct DataPacket<E, D> {
pts: Option<Timestamp>,
duration: Option<Timestamp>,
flags: PacketFlags,
data: D,
extra: E,
}
impl<E, D> DataPacket<E, D> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(data: D, extra: E) -> Self {
Self {
pts: None,
duration: None,
flags: PacketFlags::empty(),
data,
extra,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn pts(&self) -> Option<Timestamp> {
self.pts
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn duration(&self) -> Option<Timestamp> {
self.duration
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn flags(&self) -> PacketFlags {
self.flags
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn data(&self) -> &D {
&self.data
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn extra(&self) -> &E {
&self.extra
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn extra_mut(&mut self) -> &mut E {
&mut self.extra
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn into_data(self) -> D {
self.data
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn into_parts(self) -> (D, E) {
(self.data, self.extra)
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_pts(mut self, v: Option<Timestamp>) -> Self {
self.pts = v;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_duration(mut self, v: Option<Timestamp>) -> Self {
self.duration = v;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_flags(mut self, v: PacketFlags) -> Self {
self.flags = v;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_pts(&mut self, v: Option<Timestamp>) -> &mut Self {
self.pts = v;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_duration(&mut self, v: Option<Timestamp>) -> &mut Self {
self.duration = v;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_flags(&mut self, v: PacketFlags) -> &mut Self {
self.flags = v;
self
}
}
pub struct AttachmentPacket<E, D> {
flags: PacketFlags,
data: D,
extra: E,
}
impl<E, D> AttachmentPacket<E, D> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(data: D, extra: E) -> Self {
Self {
flags: PacketFlags::empty(),
data,
extra,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn flags(&self) -> PacketFlags {
self.flags
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn data(&self) -> &D {
&self.data
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn extra(&self) -> &E {
&self.extra
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn extra_mut(&mut self) -> &mut E {
&mut self.extra
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn into_data(self) -> D {
self.data
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn into_parts(self) -> (D, E) {
(self.data, self.extra)
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_flags(mut self, v: PacketFlags) -> Self {
self.flags = v;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_flags(&mut self, v: PacketFlags) -> &mut Self {
self.flags = v;
self
}
}
pub enum TrackParams<E: DemuxAdapter> {
Video {
codec: E::CodecId,
width: u32,
height: u32,
pixel_format: <E::Video as VideoAdapter>::PixelFormat,
frame_rate: Option<Timebase>,
},
Audio {
codec: E::CodecId,
sample_rate: u32,
channel_count: u8,
sample_format: <E::Audio as AudioAdapter>::SampleFormat,
channel_layout: <E::Audio as AudioAdapter>::ChannelLayout,
},
Subtitle {
codec: E::CodecId,
},
Data {
codec: E::CodecId,
},
Attachment {
codec: E::CodecId,
},
Unknown {
codec: E::CodecId,
},
}
impl<E: DemuxAdapter> TrackParams<E> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn kind(&self) -> TrackKind {
match self {
Self::Video { .. } => TrackKind::Video,
Self::Audio { .. } => TrackKind::Audio,
Self::Subtitle { .. } => TrackKind::Subtitle,
Self::Data { .. } => TrackKind::Data,
Self::Attachment { .. } => TrackKind::Attachment,
Self::Unknown { .. } => TrackKind::Unknown,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn codec(&self) -> E::CodecId {
match self {
Self::Video { codec, .. }
| Self::Audio { codec, .. }
| Self::Subtitle { codec }
| Self::Data { codec }
| Self::Attachment { codec }
| Self::Unknown { codec } => *codec,
}
}
}
pub struct TrackInfo<E: DemuxAdapter> {
timebase: Timebase,
duration: Option<Timestamp>,
params: TrackParams<E>,
filename: Option<E::Text>,
mime_type: Option<E::Text>,
extra: E::TrackExtra,
}
impl<E: DemuxAdapter> TrackInfo<E> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(timebase: Timebase, params: TrackParams<E>, extra: E::TrackExtra) -> Self {
Self {
timebase,
duration: None,
params,
filename: None,
mime_type: None,
extra,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn kind(&self) -> TrackKind {
self.params.kind()
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn timebase(&self) -> Timebase {
self.timebase
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn duration(&self) -> Option<Timestamp> {
self.duration
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn params(&self) -> &TrackParams<E> {
&self.params
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn filename(&self) -> Option<&E::Text> {
self.filename.as_ref()
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn mime_type(&self) -> Option<&E::Text> {
self.mime_type.as_ref()
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn extra(&self) -> &E::TrackExtra {
&self.extra
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn extra_mut(&mut self) -> &mut E::TrackExtra {
&mut self.extra
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_duration(mut self, v: Option<Timestamp>) -> Self {
self.duration = v;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub fn with_filename(mut self, v: Option<E::Text>) -> Self {
self.filename = v;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub fn with_mime_type(mut self, v: Option<E::Text>) -> Self {
self.mime_type = v;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_duration(&mut self, v: Option<Timestamp>) -> &mut Self {
self.duration = v;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn set_filename(&mut self, v: Option<E::Text>) -> &mut Self {
self.filename = v;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn set_mime_type(&mut self, v: Option<E::Text>) -> &mut Self {
self.mime_type = v;
self
}
}
pub enum DemuxedPacket<E: DemuxAdapter, D> {
Video {
track: TrackIndex,
packet: DemuxVideoPacket<E, D>,
},
Audio {
track: TrackIndex,
packet: DemuxAudioPacket<E, D>,
},
Subtitle {
track: TrackIndex,
packet: DemuxSubtitlePacket<E, D>,
},
Data {
track: TrackIndex,
packet: DemuxDataPacket<E, D>,
},
Attachment {
track: TrackIndex,
packet: DemuxAttachmentPacket<E, D>,
},
}
impl<E: DemuxAdapter, D> DemuxedPacket<E, D> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn track(&self) -> TrackIndex {
match self {
Self::Video { track, .. }
| Self::Audio { track, .. }
| Self::Subtitle { track, .. }
| Self::Data { track, .. }
| Self::Attachment { track, .. } => *track,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn kind(&self) -> TrackKind {
match self {
Self::Video { .. } => TrackKind::Video,
Self::Audio { .. } => TrackKind::Audio,
Self::Subtitle { .. } => TrackKind::Subtitle,
Self::Data { .. } => TrackKind::Data,
Self::Attachment { .. } => TrackKind::Attachment,
}
}
}
pub trait Demuxer {
type Adapter: DemuxAdapter;
type Buffer: AsRef<[u8]>;
type Error;
fn tracks(&self) -> &[TrackInfo<Self::Adapter>];
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::*;
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"))
}
#[test]
fn track_index_round_trips() {
assert_eq!(TrackIndex::new(3).get(), 3);
assert_eq!(TrackIndex::default(), TrackIndex::new(0));
}
#[test]
fn the_kind_is_read_off_the_params_arm() {
let rows: [(TrackParams<Loopback>, TrackKind); 6] = [
(
TrackParams::Video {
codec: 1,
width: 1920,
height: 1080,
pixel_format: 7,
frame_rate: None,
},
TrackKind::Video,
),
(
TrackParams::Audio {
codec: 2,
sample_rate: 48_000,
channel_count: 2,
sample_format: 3,
channel_layout: 4,
},
TrackKind::Audio,
),
(TrackParams::Subtitle { codec: 3 }, TrackKind::Subtitle),
(TrackParams::Data { codec: 4 }, TrackKind::Data),
(TrackParams::Attachment { codec: 5 }, TrackKind::Attachment),
(TrackParams::Unknown { codec: 0 }, TrackKind::Unknown),
];
for (params, expected) in rows {
let codec = params.codec();
let info = TrackInfo::<Loopback>::new(ms_tb(), params, ());
assert_eq!(info.kind(), expected);
assert_eq!(info.params().kind(), expected);
assert_eq!(info.params().codec(), codec);
}
}
#[test]
fn attachment_identity_lives_on_the_track() {
let info = TrackInfo::<Loopback>::new(ms_tb(), TrackParams::Attachment { codec: 9 }, ())
.with_filename(Some("Arial.ttf"))
.with_mime_type(Some("application/x-truetype-font"));
assert_eq!(info.filename().copied(), Some("Arial.ttf"));
assert_eq!(
info.mime_type().copied(),
Some("application/x-truetype-font")
);
assert_eq!(info.duration(), None);
}
#[test]
fn data_packet_follows_the_house_shape() {
let pts = Timestamp::new(1500, ms_tb());
let p: DataPacket<(), &[u8]> = DataPacket::new(&b"klv"[..], ())
.with_pts(Some(pts))
.with_duration(Some(Timestamp::new(40, ms_tb())))
.with_flags(PacketFlags::KEY);
assert_eq!(p.pts(), Some(pts));
assert_eq!(p.duration(), Some(Timestamp::new(40, ms_tb())));
assert!(p.flags().contains(PacketFlags::KEY));
let (data, _) = p.into_parts();
assert_eq!(data, b"klv");
}
#[test]
fn an_attachment_packet_has_no_timestamp_seat() {
let mut p: AttachmentPacket<(), &[u8]> = AttachmentPacket::new(&b"\x00\x01TTF"[..], ());
assert_eq!(p.flags(), PacketFlags::empty());
p.set_flags(PacketFlags::CORRUPT);
assert!(p.flags().contains(PacketFlags::CORRUPT));
assert_eq!(p.data(), &&b"\x00\x01TTF"[..]);
}
#[test]
fn the_envelope_carries_the_coordinate_and_the_kind() {
let track = TrackIndex::new(2);
let packets: [(DemuxedPacket<Loopback, &[u8]>, TrackKind); 5] = [
(
DemuxedPacket::Video {
track,
packet: VideoPacket::new(&[][..], ()),
},
TrackKind::Video,
),
(
DemuxedPacket::Audio {
track,
packet: AudioPacket::new(&[][..], ()),
},
TrackKind::Audio,
),
(
DemuxedPacket::Subtitle {
track,
packet: SubtitlePacket::new(&[][..], ()),
},
TrackKind::Subtitle,
),
(
DemuxedPacket::Data {
track,
packet: DataPacket::new(&[][..], ()),
},
TrackKind::Data,
),
(
DemuxedPacket::Attachment {
track,
packet: AttachmentPacket::new(&[][..], ()),
},
TrackKind::Attachment,
),
];
for (packet, expected) in packets {
assert_eq!(packet.track(), track);
assert_eq!(packet.kind(), expected);
}
}
struct LoopDemuxer {
tracks: [TrackInfo<Loopback>; 1],
drained: bool,
}
#[derive(Debug)]
struct LoopError;
impl Demuxer for LoopDemuxer {
type Adapter = Loopback;
type Buffer = &'static [u8];
type Error = LoopError;
fn tracks(&self) -> &[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 {
track: TrackIndex::new(0),
packet: AudioPacket::new(&[][..], ()),
}))
}
fn seek(&mut self, _target: Timestamp) -> Result<(), LoopError> {
self.drained = false;
Ok(())
}
}
#[test]
fn the_session_face_is_implementable_and_none_means_eof() {
fn _accepts<D: Demuxer>() {}
_accepts::<LoopDemuxer>();
let mut d = LoopDemuxer {
tracks: [TrackInfo::new(
ms_tb(),
TrackParams::Audio {
codec: 1,
sample_rate: 48_000,
channel_count: 2,
sample_format: 0,
channel_layout: 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());
}
}