use crate::codec::{AudioEncoder, VideoEncoder};
use crate::cover::{CoverCapture, CoverSource};
use crate::io::ByteSink;
use crate::mp4::{CoverArt, Mp4Muxer, Mp4TrackConfig, Mp4TrackFormat};
use crate::transfer::FrameSource;
use crate::{AudioBuffer, Error, ErrorKind, FrameIndex, Result, Timeline};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct OutputOptions {
pub max_samples_per_track: usize,
pub max_samples_per_encode: usize,
pub cover_source: CoverSource,
}
impl Default for OutputOptions {
fn default() -> Self {
Self {
max_samples_per_track: 1_000_000,
max_samples_per_encode: 64,
cover_source: CoverSource::default(),
}
}
}
pub struct MediaOutput<S, V, A> {
muxer: Mp4Muxer<S>,
video: V,
audio: A,
timeline: Timeline,
next_video: u64,
next_audio: u64,
max_samples_per_encode: usize,
cover: CoverCapture,
explicit_cover_art: bool,
failure: Option<Error>,
}
impl<S, V, A> MediaOutput<S, V, A>
where
S: ByteSink,
V: VideoEncoder,
A: AudioEncoder,
{
pub async fn new(
sink: S,
video: V,
audio: A,
timeline: Timeline,
options: OutputOptions,
) -> Result<Self> {
if audio.format().sample_rate != timeline.audio_sample_rate() {
return Err(Error::new(
ErrorKind::InvalidInput,
"audio encoder sample rate does not match the output timeline",
));
}
if audio.config().timescale != audio.format().sample_rate {
return Err(Error::new(
ErrorKind::InvalidInput,
"audio MP4 timescale must preserve the encoder sample clock",
));
}
if options.max_samples_per_encode == 0 {
return Err(Error::new(
ErrorKind::InvalidInput,
"the encoder packet batch limit must be nonzero",
));
}
let tracks = vec![
Mp4TrackConfig {
encoder: video.config().clone(),
format: Mp4TrackFormat::Video(video.format().dimensions),
},
Mp4TrackConfig {
encoder: audio.config().clone(),
format: Mp4TrackFormat::Audio {
channels: audio.format().channels,
},
},
];
let muxer = Mp4Muxer::new(sink, tracks, options.max_samples_per_track).await?;
Ok(Self {
muxer,
video,
audio,
timeline,
next_video: 0,
next_audio: 0,
max_samples_per_encode: options.max_samples_per_encode,
cover: CoverCapture::new(options.cover_source),
explicit_cover_art: false,
failure: None,
})
}
pub fn next_video_index(&self) -> FrameIndex {
FrameIndex(self.next_video)
}
pub fn next_audio_index(&self) -> FrameIndex {
FrameIndex(self.next_audio)
}
pub async fn put_video<'a>(
&'a mut self,
index: FrameIndex,
frame: FrameSource<'a>,
) -> Result<()> {
self.ensure_healthy()?;
check_index("video", self.next_video, index)?;
self.cover.offer(index.0, frame);
let samples = match self.video.encode(index, frame).await {
Ok(samples) => samples,
Err(error) => return Err(self.fail(error)),
};
if let Err(error) = check_batch_limit(self.max_samples_per_encode, samples.len()) {
return Err(self.fail(error));
}
for sample in samples {
if let Err(error) = self.muxer.write_sample(0, sample).await {
return Err(self.fail(error));
}
}
self.next_video = advance(index)?;
Ok(())
}
pub async fn put_audio(&mut self, index: FrameIndex, buffer: AudioBuffer) -> Result<()> {
self.ensure_healthy()?;
check_index("audio", self.next_audio, index)?;
let format = self.audio.format();
if buffer.sample_rate != format.sample_rate || buffer.channels != format.channels {
return Err(Error::new(
ErrorKind::InvalidInput,
"audio write format is incompatible with the encoder",
));
}
let expected = self.timeline.audio_interval_for_frame(index)?;
if buffer.range != expected {
return Err(Error::new(
ErrorKind::InvalidInput,
"audio write does not cover the indexed frame interval",
));
}
let samples = match self.audio.encode(index, buffer).await {
Ok(samples) => samples,
Err(error) => return Err(self.fail(error)),
};
if let Err(error) = check_batch_limit(self.max_samples_per_encode, samples.len()) {
return Err(self.fail(error));
}
for sample in samples {
if let Err(error) = self.muxer.write_sample(1, sample).await {
return Err(self.fail(error));
}
}
self.next_audio = advance(index)?;
Ok(())
}
pub fn set_cover_art(&mut self, cover_art: Option<CoverArt>) -> Result<()> {
let explicit = cover_art.is_some();
self.muxer.set_cover_art(cover_art)?;
self.explicit_cover_art = explicit;
Ok(())
}
pub async fn finish(mut self) -> Result<S> {
self.ensure_healthy()?;
if self.next_video != self.next_audio {
return Err(Error::new(
ErrorKind::InvalidState,
"video and audio indexed writes must be synchronized before finish",
));
}
let delayed_video = self.video.finish().await?;
check_batch_limit(self.max_samples_per_encode, delayed_video.len())?;
for sample in delayed_video {
self.muxer.write_sample(0, sample).await?;
}
let delayed_audio = self.audio.finish().await?;
check_batch_limit(self.max_samples_per_encode, delayed_audio.samples.len())?;
for sample in delayed_audio.samples {
self.muxer.write_sample(1, sample).await?;
}
self.muxer.set_audio_gapless(1, delayed_audio.gapless)?;
if !self.explicit_cover_art {
self.muxer.set_cover_art(self.cover.cover_art())?;
}
self.muxer.finish().await
}
fn ensure_healthy(&self) -> Result<()> {
match &self.failure {
Some(error) => Err(Error::new(
ErrorKind::InvalidState,
format!("output session previously failed: {error}"),
)),
None => Ok(()),
}
}
fn fail(&mut self, error: Error) -> Error {
self.failure = Some(error.clone());
error
}
}
fn check_index(stream: &str, expected: u64, actual: FrameIndex) -> Result<()> {
if actual.0 != expected {
return Err(Error::new(
ErrorKind::InvalidInput,
format!(
"{stream} put expected frame {expected}, received {}",
actual.0
),
));
}
Ok(())
}
fn advance(index: FrameIndex) -> Result<u64> {
index.0.checked_add(1).ok_or_else(|| {
Error::new(
ErrorKind::ResourceLimit,
"indexed output cannot advance beyond the maximum frame index",
)
})
}
fn check_batch_limit(limit: usize, actual: usize) -> Result<()> {
if actual > limit {
return Err(Error::new(
ErrorKind::ResourceLimit,
"encoder packet batch exceeds the configured output limit",
));
}
Ok(())
}