filmcraft/crates/codecs/src/lib.rs

185 lines
6.9 KiB
Rust

//! The container + codec hub.
//!
//! - [`VideoDecoder`]: the trait every video codec implements (our own H.264/HEVC/VP9/ProRes/…, MJPEG, and
//! OS hardware decoders registered by the platform layer). Factories are tried in registration
//! order, so a hardware decoder can take precedence over the pure-Rust one.
//! - [`Mp4Source`]: a [`MediaSource`](filmcraft_media::MediaSource) over MP4/MOV using
//! `filmcraft-isobmff`: GOP-aware random access (seek to the preceding sync sample and decode
//! forward, caching every decoded frame of the GOP), sequential fast path for playback, and
//! packet-cached audio decoding.
//! - [`MkvSource`], [`MxfSource`] (OP1a / OP-Atom: AVC, MPEG-2 incl. D-10 / XDCAM, VC-3, ProRes,
//! PCM) and [`OggSource`] (Ogg Opus with granule-position seeking, Ogg Vorbis): the same
//! GOP-aware video access and packet-cached audio.
//! - [`MpegSource`]: MPEG-2 transport streams (`.ts`, `.m2ts`, `.mts`), program streams (`.mpg`,
//! `.vob`, `.mod`) and MPEG-1/2 video elementary streams: MPEG-1/2, H.264 and HEVC video; MPEG
//! audio, AAC (ADTS / LATM), AC-3 and LPCM.
//! - [`AudioFileSource`]: standalone compressed audio files (MP3, FLAC, AIFF, …).
//! - [`openers`]: the openers to register with the engine's media pool.
#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable))]
pub mod apv;
pub mod audio;
pub mod gop;
pub mod hw;
mod hw_frame;
pub mod mkv;
pub mod mp4;
pub mod mpeg;
pub mod mxf;
pub mod ogg;
mod stream_color;
pub mod video;
use std::sync::{Arc, RwLock};
pub use apv::ApvSource;
pub use audio::AudioFileSource;
pub use gop::{FRAME_BUDGET, GopStats, cached_bytes, gop_stats, live_decoders};
pub use mkv::MkvSource;
pub use mp4::Mp4Source;
pub use mpeg::MpegSource;
pub use mxf::MxfSource;
pub use ogg::OggSource;
pub use video::{DecodedFrame, VideoDecoder, VideoDecoderFactory};
#[derive(Debug, thiserror::Error)]
pub enum CodecError {
#[error("unsupported codec: {0}")]
Unsupported(String),
#[error("decode error: {0}")]
Decode(String),
#[error("container: {0}")]
Container(String),
/// The frame is no longer wanted (`filmcraft_media::cancel`).
#[error("cancelled")]
Cancelled,
}
pub type Result<T> = std::result::Result<T, CodecError>;
impl From<CodecError> for filmcraft_media::MediaError {
fn from(e: CodecError) -> Self {
match e {
CodecError::Unsupported(s) => filmcraft_media::MediaError::Unsupported(s),
CodecError::Cancelled => filmcraft_media::MediaError::Cancelled,
other => filmcraft_media::MediaError::Decode(other.to_string()),
}
}
}
/// Our own (pure-Rust) decoders, in the order they are tried.
const BUILTIN_FACTORIES: [VideoDecoderFactory; 9] = [
video::h264_factory,
video::hevc_factory,
video::vp9_factory,
video::av1_factory,
video::apv_factory,
video::prores_factory,
video::dnx_factory,
video::mjpeg_factory,
video::mpeg2_factory,
];
fn factories() -> &'static RwLock<Vec<VideoDecoderFactory>> {
static F: std::sync::OnceLock<RwLock<Vec<VideoDecoderFactory>>> = std::sync::OnceLock::new();
F.get_or_init(|| RwLock::new(BUILTIN_FACTORIES.to_vec()))
}
/// Register a video decoder factory (tried before previously registered ones).
pub fn register_video_decoder(f: VideoDecoderFactory) {
let mut g = factories().write().unwrap_or_else(|e| e.into_inner());
if !g.iter().any(|x| std::ptr::fn_addr_eq(*x, f)) {
g.insert(0, f);
}
}
/// Whether `f` is among the registered video decoder factories (startup diagnostics, tests).
pub fn video_decoder_registered(f: VideoDecoderFactory) -> bool {
factories().read().unwrap_or_else(|e| e.into_inner()).iter().any(|x| std::ptr::fn_addr_eq(*x, f))
}
/// Create a decoder for a sample entry.
pub fn make_video_decoder(entry: &filmcraft_isobmff::SampleEntry) -> Result<Box<dyn VideoDecoder>> {
let g = factories().read().unwrap_or_else(|e| e.into_inner());
for f in g.iter() {
if let Some(r) = f(entry) {
return r;
}
}
Err(CodecError::Unsupported(format!("no decoder for {} video", entry.codec.name())))
}
/// Create one of our own (software) decoders for a sample entry, skipping registered factories:
/// what a hardware decoder falls back to when it fails mid-stream.
pub fn software_video_decoder(entry: &filmcraft_isobmff::SampleEntry) -> Result<Box<dyn VideoDecoder>> {
for f in BUILTIN_FACTORIES {
if let Some(r) = f(entry) {
return r;
}
}
Err(CodecError::Unsupported(format!("no decoder for {} video", entry.codec.name())))
}
/// Openers for the engine's media pool (MP4/MOV, Matroska/WebM, MXF, Ogg Opus/Vorbis, MPEG TS/PS and
/// MPEG-1/2 video elementary streams, APV raw bitstreams, standalone audio).
pub fn openers() -> Vec<filmcraft_media::Opener> {
vec![mp4::opener, mkv::opener, mxf::opener, ogg::opener, mpeg::opener, apv::opener, audio::opener]
}
fn reader_registry() -> &'static RwLock<Vec<filmcraft_media::ReaderOpener>> {
static R: std::sync::OnceLock<RwLock<Vec<filmcraft_media::ReaderOpener>>> = std::sync::OnceLock::new();
R.get_or_init(|| RwLock::new(vec![mp4::reader_opener, mkv::reader_opener, mxf::reader_opener, ogg::reader_opener, mpeg::reader_opener, apv::reader_opener]))
}
/// Openers that read containers through a [`filmcraft_media::ByteReader`] (index now, samples on
/// demand) instead of the whole file, in the order they are tried.
pub fn reader_openers() -> Vec<filmcraft_media::ReaderOpener> {
reader_registry().read().unwrap_or_else(|e| e.into_inner()).clone()
}
/// Register a reader opener tried before the built-in ones (platform media sources such as the
/// web app's WebCodecs-decoded MP4).
pub fn register_reader_opener(f: filmcraft_media::ReaderOpener) {
let mut g = reader_registry().write().unwrap_or_else(|e| e.into_inner());
if !g.iter().any(|x| std::ptr::fn_addr_eq(*x, f)) {
g.insert(0, f);
}
}
/// A media reader as the demuxers' byte source.
#[derive(Clone)]
pub(crate) struct Src(pub filmcraft_media::SharedReader);
impl filmcraft_isobmff::ByteSource for Src {
fn len(&self) -> u64 {
self.0.len()
}
fn read_at(&self, offset: u64, buf: &mut [u8]) -> std::io::Result<()> {
self.0.read_at(offset, buf)
}
}
impl filmcraft_matroska::ByteSource for Src {
fn len(&self) -> u64 {
self.0.len()
}
fn read_at(&self, offset: u64, buf: &mut [u8]) -> std::io::Result<()> {
self.0.read_at(offset, buf)
}
}
/// Convenience: an `Arc` media source from bytes (tries MP4/MOV then audio files).
pub fn open_bytes(name: &str, bytes: Arc<[u8]>) -> std::result::Result<filmcraft_media::SharedSource, filmcraft_media::MediaError> {
filmcraft_media::open_bytes(name, bytes, &openers())
}
#[cfg(test)]
mod audio_timing_tests;
#[cfg(test)]
mod rounded_pts_tests;
#[cfg(test)]
mod stream_color_tests;
#[cfg(test)]
mod tests;