use std::fmt;
use ffmpeg_next as ffmpeg;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MediaKind {
VideoPacket,
AudioPacket,
VideoFrame,
AudioFrame,
}
impl MediaKind {
pub fn packet_for(medium: ffmpeg::media::Type) -> Option<Self> {
match medium {
ffmpeg::media::Type::Video => Some(MediaKind::VideoPacket),
ffmpeg::media::Type::Audio => Some(MediaKind::AudioPacket),
_ => None,
}
}
const fn bit(self) -> u8 {
match self {
MediaKind::VideoPacket => 1 << 0,
MediaKind::AudioPacket => 1 << 1,
MediaKind::VideoFrame => 1 << 2,
MediaKind::AudioFrame => 1 << 3,
}
}
const ALL: [MediaKind; 4] = [
MediaKind::VideoPacket,
MediaKind::AudioPacket,
MediaKind::VideoFrame,
MediaKind::AudioFrame,
];
}
impl fmt::Display for MediaKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
MediaKind::VideoPacket => "VideoPacket",
MediaKind::AudioPacket => "AudioPacket",
MediaKind::VideoFrame => "VideoFrame",
MediaKind::AudioFrame => "AudioFrame",
};
f.write_str(name)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MediaKindSet(u8);
impl MediaKindSet {
pub const fn of(kind: MediaKind) -> Self {
Self(kind.bit())
}
pub const fn from_slice(kinds: &[MediaKind]) -> Self {
let mut bits = 0;
let mut index = 0;
while index < kinds.len() {
bits |= kinds[index].bit();
index += 1;
}
Self(bits)
}
pub const PACKETS: Self = Self::from_slice(&[MediaKind::VideoPacket, MediaKind::AudioPacket]);
pub const FRAMES: Self = Self::from_slice(&[MediaKind::VideoFrame, MediaKind::AudioFrame]);
pub const fn contains(self, kind: MediaKind) -> bool {
self.0 & kind.bit() != 0
}
pub const fn is_subset_of(self, other: Self) -> bool {
self.0 & !other.0 == 0
}
}
impl fmt::Display for MediaKindSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut first = true;
for kind in MediaKind::ALL {
if !self.contains(kind) {
continue;
}
if !first {
f.write_str("|")?;
}
write!(f, "{kind}")?;
first = false;
}
if first {
f.write_str("nothing")
} else {
Ok(())
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MemoryDomain {
System,
Cuda,
D3d11,
D3d12,
}
impl MemoryDomain {
const fn bit(self) -> u8 {
match self {
MemoryDomain::System => 1 << 0,
MemoryDomain::Cuda => 1 << 1,
MemoryDomain::D3d11 => 1 << 2,
MemoryDomain::D3d12 => 1 << 3,
}
}
const ALL: [MemoryDomain; 4] = [
MemoryDomain::System,
MemoryDomain::Cuda,
MemoryDomain::D3d11,
MemoryDomain::D3d12,
];
}
impl fmt::Display for MemoryDomain {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
MemoryDomain::System => "System",
MemoryDomain::Cuda => "CUDA",
MemoryDomain::D3d11 => "D3D11",
MemoryDomain::D3d12 => "D3D12",
};
f.write_str(name)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MemoryDomainSet(u8);
impl MemoryDomainSet {
pub const ALL: Self = Self::from_slice(&MemoryDomain::ALL);
pub const fn of(domain: MemoryDomain) -> Self {
Self(domain.bit())
}
pub const fn from_slice(domains: &[MemoryDomain]) -> Self {
let mut bits = 0;
let mut index = 0;
while index < domains.len() {
bits |= domains[index].bit();
index += 1;
}
Self(bits)
}
pub const fn contains(self, domain: MemoryDomain) -> bool {
self.0 & domain.bit() != 0
}
pub const fn is_subset_of(self, other: Self) -> bool {
self.0 & !other.0 == 0
}
}
impl fmt::Display for MemoryDomainSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if *self == Self::ALL {
return f.write_str("any memory");
}
let mut first = true;
for domain in MemoryDomain::ALL {
if !self.contains(domain) {
continue;
}
if !first {
f.write_str("|")?;
}
write!(f, "{domain}")?;
first = false;
}
if first {
f.write_str("nothing")
} else {
Ok(())
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PortContract {
Packets(MediaKindSet),
Frames(MediaKindSet, MemoryDomainSet),
}
impl PortContract {
pub const fn packet(kind: MediaKind) -> Self {
Self::Packets(MediaKindSet::of(kind))
}
pub const fn frame(kind: MediaKind, memory: MemoryDomain) -> Self {
Self::Frames(MediaKindSet::of(kind), MemoryDomainSet::of(memory))
}
pub const fn any_frame(kind: MediaKind) -> Self {
Self::Frames(MediaKindSet::of(kind), MemoryDomainSet::ALL)
}
pub fn accepts(&self, produced: &PortContract) -> bool {
match (self, produced) {
(PortContract::Packets(accepted), PortContract::Packets(produced)) => {
produced.is_subset_of(*accepted)
}
(
PortContract::Frames(accepted, accepted_memory),
PortContract::Frames(produced, produced_memory),
) => produced.is_subset_of(*accepted) && produced_memory.is_subset_of(*accepted_memory),
_ => false,
}
}
}
impl fmt::Display for PortContract {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PortContract::Packets(kinds) => write!(f, "{kinds}"),
PortContract::Frames(kinds, memory) => write!(f, "{kinds} ({memory})"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InputContract {
Fixed(PortContract),
Any,
Unknown,
}
impl fmt::Display for InputContract {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
InputContract::Fixed(contract) => write!(f, "{contract}"),
InputContract::Any => f.write_str("anything"),
InputContract::Unknown => f.write_str("unknown"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputContract {
Fixed(PortContract),
Passthrough,
Unknown,
}
impl fmt::Display for OutputContract {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OutputContract::Fixed(contract) => write!(f, "{contract}"),
OutputContract::Passthrough => f.write_str("whatever it receives"),
OutputContract::Unknown => f.write_str("unknown"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_producers_kinds_must_all_be_accepted() {
let video_only = PortContract::any_frame(MediaKind::VideoFrame);
let both = PortContract::Frames(MediaKindSet::FRAMES, MemoryDomainSet::ALL);
assert!(video_only.accepts(&video_only));
assert!(both.accepts(&video_only));
assert!(!video_only.accepts(&both));
}
#[test]
fn encoded_audio_and_encoded_video_are_different_kinds() {
let video = PortContract::packet(MediaKind::VideoPacket);
let audio = PortContract::packet(MediaKind::AudioPacket);
assert!(!video.accepts(&audio));
assert!(!audio.accepts(&video));
let muxer = PortContract::Packets(MediaKindSet::PACKETS);
assert!(muxer.accepts(&video));
assert!(muxer.accepts(&audio));
}
#[test]
fn packets_and_frames_never_link() {
let packets = PortContract::Packets(MediaKindSet::PACKETS);
let frames = PortContract::Frames(MediaKindSet::FRAMES, MemoryDomainSet::ALL);
assert!(!packets.accepts(&frames));
assert!(!frames.accepts(&packets));
}
#[test]
fn a_medium_maps_to_its_encoded_kind_or_to_nothing() {
assert_eq!(
MediaKind::packet_for(ffmpeg::media::Type::Video),
Some(MediaKind::VideoPacket)
);
assert_eq!(
MediaKind::packet_for(ffmpeg::media::Type::Audio),
Some(MediaKind::AudioPacket)
);
assert_eq!(MediaKind::packet_for(ffmpeg::media::Type::Subtitle), None);
}
#[test]
fn every_domain_a_producer_may_emit_must_be_accepted() {
let system = PortContract::frame(MediaKind::VideoFrame, MemoryDomain::System);
let d3d11 = PortContract::frame(MediaKind::VideoFrame, MemoryDomain::D3d11);
let anywhere = PortContract::any_frame(MediaKind::VideoFrame);
assert!(system.accepts(&system));
assert!(!system.accepts(&d3d11));
assert!(anywhere.accepts(&d3d11));
assert!(anywhere.accepts(&system));
assert!(!d3d11.accepts(&anywhere));
}
#[test]
fn kinds_render_for_diagnostics() {
assert_eq!(
PortContract::packet(MediaKind::VideoPacket).to_string(),
"VideoPacket"
);
assert_eq!(
PortContract::frame(MediaKind::VideoFrame, MemoryDomain::D3d11).to_string(),
"VideoFrame (D3D11)"
);
assert_eq!(
PortContract::any_frame(MediaKind::VideoFrame).to_string(),
"VideoFrame (any memory)"
);
assert_eq!(MediaKindSet::PACKETS.to_string(), "VideoPacket|AudioPacket");
}
}