use std::collections::{BinaryHeap, HashMap};
use std::time::{Duration, Instant};
use orfail::OrFail;
use crate::audio::AudioData;
use crate::media::{MediaSample, MediaStreamId};
use crate::stats::ProcessorStats;
use crate::video::VideoFrame;
pub trait MediaProcessor {
fn spec(&self) -> MediaProcessorSpec;
fn process_input(&mut self, input: MediaProcessorInput) -> orfail::Result<()>;
fn process_output(&mut self) -> orfail::Result<MediaProcessorOutput>;
fn set_error(&self) {
self.spec().stats.set_error();
}
}
pub struct BoxedMediaProcessor(Box<dyn 'static + Send + MediaProcessor>);
impl BoxedMediaProcessor {
pub fn new<P: 'static + Send + MediaProcessor>(processor: P) -> Self {
Self(Box::new(processor))
}
}
impl std::fmt::Debug for BoxedMediaProcessor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BoxedMediaProcessor")
.finish_non_exhaustive()
}
}
impl MediaProcessor for BoxedMediaProcessor {
fn spec(&self) -> MediaProcessorSpec {
self.0.spec()
}
fn process_input(&mut self, input: MediaProcessorInput) -> orfail::Result<()> {
self.0.process_input(input)
}
fn process_output(&mut self) -> orfail::Result<MediaProcessorOutput> {
self.0.process_output()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MediaProcessorWorkloadHint {
IoIntensive,
CpuIntensive {
cost: std::num::NonZeroUsize,
},
}
impl MediaProcessorWorkloadHint {
pub const READER: Self = Self::IoIntensive;
pub const WRITER: Self = Self::IoIntensive;
pub const CPU_MISC: Self = Self::cpu_intensive(1);
pub const AUDIO_DECODER: Self = Self::cpu_intensive(1);
pub const AUDIO_MIXER: Self = Self::cpu_intensive(1);
pub const AUDIO_ENCODER: Self = Self::cpu_intensive(1);
pub const VIDEO_DECODER: Self = Self::cpu_intensive(2);
pub const VIDEO_MIXER: Self = Self::cpu_intensive(10);
pub const VIDEO_ENCODER: Self = Self::cpu_intensive(10);
pub const PLUGIN: Self = Self::cpu_intensive(1);
const fn cpu_intensive(cost: usize) -> Self {
Self::CpuIntensive {
cost: std::num::NonZeroUsize::new(cost).expect("bug"),
}
}
}
#[derive(Debug, Clone)]
pub struct MediaProcessorSpec {
pub input_stream_ids: Vec<MediaStreamId>,
pub output_stream_ids: Vec<MediaStreamId>,
pub workload_hint: MediaProcessorWorkloadHint,
pub stats: ProcessorStats,
}
#[derive(Debug)]
pub struct MediaProcessorInput {
pub stream_id: MediaStreamId,
pub sample: Option<MediaSample>, }
impl MediaProcessorInput {
pub fn eos(stream_id: MediaStreamId) -> Self {
Self {
stream_id,
sample: None,
}
}
pub fn sample(stream_id: MediaStreamId, sample: MediaSample) -> Self {
Self {
stream_id,
sample: Some(sample),
}
}
pub fn audio_data(stream_id: MediaStreamId, data: AudioData) -> Self {
Self {
stream_id,
sample: Some(MediaSample::audio_data(data)),
}
}
pub fn video_frame(stream_id: MediaStreamId, frame: VideoFrame) -> Self {
Self {
stream_id,
sample: Some(MediaSample::video_frame(frame)),
}
}
}
#[derive(Debug)]
pub enum MediaProcessorOutput {
Processed {
stream_id: MediaStreamId,
sample: MediaSample,
},
Pending {
awaiting_stream_id: Option<MediaStreamId>,
},
Finished,
}
impl MediaProcessorOutput {
pub fn expect_processed(self) -> Option<(MediaStreamId, MediaSample)> {
if let Self::Processed { stream_id, sample } = self {
Some((stream_id, sample))
} else {
None
}
}
pub fn pending(awaiting_stream_id: MediaStreamId) -> Self {
Self::Pending {
awaiting_stream_id: Some(awaiting_stream_id),
}
}
pub fn awaiting_any() -> Self {
Self::Pending {
awaiting_stream_id: None,
}
}
pub fn audio_data(stream_id: MediaStreamId, data: AudioData) -> Self {
Self::Processed {
stream_id,
sample: MediaSample::audio_data(data),
}
}
pub fn video_frame(stream_id: MediaStreamId, frame: VideoFrame) -> Self {
Self::Processed {
stream_id,
sample: MediaSample::video_frame(frame),
}
}
}
#[derive(Debug)]
struct PacerQueueItem(MediaStreamId, MediaSample);
impl PartialEq for PacerQueueItem {
fn eq(&self, other: &Self) -> bool {
self.1.timestamp() == other.1.timestamp()
}
}
impl Eq for PacerQueueItem {}
impl PartialOrd for PacerQueueItem {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for PacerQueueItem {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
other.1.timestamp().cmp(&self.1.timestamp())
}
}
#[derive(Debug)]
pub struct RealtimePacer {
stream_ids: HashMap<MediaStreamId, MediaStreamId>,
stream_timestamps: HashMap<MediaStreamId, Duration>,
queue: BinaryHeap<PacerQueueItem>,
start_time: Option<Instant>,
}
impl RealtimePacer {
pub fn new(
input_stream_ids: Vec<MediaStreamId>,
output_stream_ids: Vec<MediaStreamId>,
) -> orfail::Result<Self> {
(input_stream_ids.len() == output_stream_ids.len()).or_fail()?;
Ok(Self {
stream_ids: input_stream_ids
.iter()
.copied()
.zip(output_stream_ids)
.collect(),
stream_timestamps: input_stream_ids
.iter()
.copied()
.zip(std::iter::repeat(Duration::ZERO))
.collect(),
queue: BinaryHeap::new(),
start_time: None,
})
}
fn elapsed(&mut self) -> Duration {
if let Some(t) = self.start_time {
t.elapsed()
} else {
let t = Instant::now();
self.start_time = Some(t);
t.elapsed()
}
}
}
impl MediaProcessor for RealtimePacer {
fn spec(&self) -> MediaProcessorSpec {
MediaProcessorSpec {
input_stream_ids: self.stream_ids.keys().copied().collect(),
output_stream_ids: self.stream_ids.values().copied().collect(),
stats: ProcessorStats::other("realtime_pacer"),
workload_hint: MediaProcessorWorkloadHint::CPU_MISC,
}
}
fn process_input(&mut self, input: MediaProcessorInput) -> orfail::Result<()> {
let output_stream_id = self.stream_ids.get(&input.stream_id).copied().or_fail()?;
if let Some(sample) = input.sample {
self.stream_timestamps
.insert(input.stream_id, sample.timestamp());
self.queue.push(PacerQueueItem(output_stream_id, sample));
} else {
self.stream_ids.remove(&input.stream_id);
}
Ok(())
}
fn process_output(&mut self) -> orfail::Result<MediaProcessorOutput> {
let Some(PacerQueueItem(stream_id, sample)) = self.queue.pop() else {
if self.stream_ids.is_empty() {
return Ok(MediaProcessorOutput::Finished);
} else {
return Ok(MediaProcessorOutput::awaiting_any());
}
};
let now = self.elapsed();
let Some(time_to_wait) = sample
.timestamp()
.checked_sub(now)
.take_if(|d| !d.is_zero())
else {
return Ok(MediaProcessorOutput::Processed { stream_id, sample });
};
if self.queue.len() < 10 {
self.queue.push(PacerQueueItem(stream_id, sample));
if let Some((input_stream_id, _)) =
self.stream_timestamps.iter().min_by_key(|(_, t)| *t)
{
return Ok(MediaProcessorOutput::pending(*input_stream_id));
} else {
return Ok(MediaProcessorOutput::awaiting_any());
}
}
std::thread::sleep(time_to_wait);
Ok(MediaProcessorOutput::Processed { stream_id, sample })
}
}