diff --git a/Cargo.lock b/Cargo.lock index 396bbecb2..8efa77068 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,18 +34,44 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "av-data" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "124ae24335161b3d2226594640a67903da0866e2591312591fc8ddad64c1b38c" +dependencies = [ + "byte-slice-cast", + "bytes", + "num-derive", + "num-rational", + "num-traits", + "thiserror", +] + [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" + [[package]] name = "cc" version = "1.1.5" @@ -98,6 +124,56 @@ dependencies = [ "jobserver", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "parking_lot" version = "0.12.3" @@ -151,6 +227,7 @@ version = "1.0.0" dependencies = [ "assert_matches", "atomig", + "av-data", "bitflags", "cc", "cfg-if", @@ -159,6 +236,7 @@ dependencies = [ "parking_lot", "paste", "raw-cpuid", + "static_assertions", "strum", "to_method", "zerocopy", @@ -210,6 +288,12 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strum" version = "0.26.3" @@ -254,6 +338,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + [[package]] name = "to_method" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index dc4f051b4..d71465b89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,12 +20,14 @@ crate-type = ["staticlib", "rlib"] [dependencies] assert_matches = "1.5.0" atomig = { version = "0.4.0", features = ["derive"] } +av-data = "0.4.2" bitflags = "2.4.0" cfg-if = "1.0.0" libc = "0.2" parking_lot = "0.12.2" paste = "1.0.14" raw-cpuid = "11.0.1" +static_assertions = "1" strum = { version = "0.26", features = ["derive"] } to_method = "1.1.0" zerocopy = { version = "0.7.32", features = ["derive"] } diff --git a/include/dav1d/headers.rs b/include/dav1d/headers.rs index 0bb34a7d5..74a8481ed 100644 --- a/include/dav1d/headers.rs +++ b/include/dav1d/headers.rs @@ -671,7 +671,7 @@ pub const DAV1D_MC_SMPTE240: Dav1dMatrixCoefficients = Rav1dMatrixCoefficients:: pub const DAV1D_MC_SMPTE_YCGCO: Dav1dMatrixCoefficients = Rav1dMatrixCoefficients::SMPTE_YCGCO.to_dav1d(); pub const DAV1D_MC_BT2020_NCL: Dav1dMatrixCoefficients = - Rav1dMatrixCoefficients::BT2020_CL.to_dav1d(); + Rav1dMatrixCoefficients::BT2020_NCL.to_dav1d(); pub const DAV1D_MC_BT2020_CL: Dav1dMatrixCoefficients = Rav1dMatrixCoefficients::BT2020_CL.to_dav1d(); pub const DAV1D_MC_SMPTE2085: Dav1dMatrixCoefficients = diff --git a/lib.rs b/lib.rs index 3bc3ec59c..7be11047a 100644 --- a/lib.rs +++ b/lib.rs @@ -93,3 +93,846 @@ pub mod src { } // mod src pub use src::error::Dav1dResult; + +// --------------------------------------------------------------------------------------- + +/// Public Rust API. +/// +/// This is more or less the same API as , +/// and is indeed a fork of that work. +pub mod dav1d { + // This whole module was originally copied from https://github.com/rust-av/dav1d-rs/ + // (specifically https://github.com/rust-av/dav1d-rs/blob/94b1deaa1e25bf29c77bb5cc8a08ddaf7663eede/src/lib.rs) + // with some modifications. + // `dav1d-rs` is under the MIT license, replicated here: + + // MIT License + // + // Copyright (c) 2018 Luca Barbato + // + // Permission is hereby granted, free of charge, to any person obtaining a copy + // of this software and associated documentation files (the "Software"), to deal + // in the Software without restriction, including without limitation the rights + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + // copies of the Software, and to permit persons to whom the Software is + // furnished to do so, subject to the following conditions: + // + // The above copyright notice and this permission notice shall be included in all + // copies or substantial portions of the Software. + // + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + // SOFTWARE. + + // The code below provides a safe API around the rav1d C FFI layer. + + use crate as rav1d; + + pub use av_data::pixel; + use std::ffi::c_void; + use std::fmt; + use std::mem; + use std::ptr::NonNull; + use std::sync::Arc; + + use rav1d::include::dav1d::data::*; + use rav1d::include::dav1d::dav1d::*; + use rav1d::include::dav1d::headers::*; + use rav1d::include::dav1d::picture::*; + use rav1d::src::error::{Rav1dError, Rav1dResult}; + use rav1d::src::lib::*; + use rav1d::src::send_sync_non_null::SendSyncNonNull; + use rav1d::Dav1dResult; + + fn option_nonnull(ptr: *mut T) -> Option> { + if ptr.is_null() { + None + } else { + Some(NonNull::new(ptr).unwrap()) + } + } + + fn option_send_sync_non_null(r#box: Box) -> Option> { + Some(SendSyncNonNull::from_box(r#box)) + } + + fn rav1d_result(ret: Dav1dResult) -> Rav1dResult { + Rav1dResult::try_from(ret).unwrap() + } + + /// Error enum return by various `dav1d` operations. + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + #[non_exhaustive] + pub enum Error { + /// Try again. + /// + /// If this is returned by [`Decoder::send_data`] or [`Decoder::send_pending_data`] then there + /// are decoded frames pending that first have to be retrieved via [`Decoder::get_picture`] + /// before processing any further pending data. + /// + /// If this is returned by [`Decoder::get_picture`] then no decoded frames are pending + /// currently and more data needs to be sent to the decoder. + Again, + /// Invalid argument. + /// + /// One of the arguments passed to the function was invalid. + InvalidArgument, + /// Not enough memory. + /// + /// Not enough memory is currently available for performing this operation. + NotEnoughMemory, + /// Unsupported bitstream. + /// + /// The provided bitstream is not supported by `dav1d`. + UnsupportedBitstream, + /// Unknown error. + UnknownError(Rav1dError), + } + + impl From for Error { + fn from(err: Rav1dError) -> Self { + match err { + Rav1dError::EAGAIN => Error::Again, + Rav1dError::ENOMEM => Error::NotEnoughMemory, + Rav1dError::EINVAL => Error::InvalidArgument, + Rav1dError::ENOPROTOOPT => Error::UnsupportedBitstream, + _ => Error::UnknownError(err), + } + } + } + + impl Error { + pub const fn is_again(&self) -> bool { + matches!(self, Error::Again) + } + } + + impl fmt::Display for Error { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::Again => write!(fmt, "Try again"), + Error::InvalidArgument => write!(fmt, "Invalid argument"), + Error::NotEnoughMemory => write!(fmt, "Not enough memory available"), + Error::UnsupportedBitstream => write!(fmt, "Unsupported bitstream"), + Error::UnknownError(err) => write!(fmt, "Unknown error {err:?}"), + } + } + } + + impl std::error::Error for Error {} + + /// Settings for creating a new [`Decoder`] instance. + /// See documentation for native `Dav1dSettings` struct. + pub struct Settings { + dav1d_settings: Dav1dSettings, + } + + unsafe impl Send for Settings {} + unsafe impl Sync for Settings {} + + impl Default for Settings { + fn default() -> Self { + Self::new() + } + } + + impl Settings { + /// Creates a new [`Settings`] instance with default settings. + pub fn new() -> Self { + unsafe { + let mut dav1d_settings = mem::MaybeUninit::uninit(); + + dav1d_default_settings(NonNull::new(dav1d_settings.as_mut_ptr()).unwrap()); + + Self { + dav1d_settings: dav1d_settings.assume_init(), + } + } + } + + pub fn set_n_threads(&mut self, n_threads: u32) { + self.dav1d_settings.n_threads = n_threads as i32; + } + + pub fn get_n_threads(&self) -> u32 { + self.dav1d_settings.n_threads as u32 + } + + pub fn set_max_frame_delay(&mut self, max_frame_delay: u32) { + self.dav1d_settings.max_frame_delay = max_frame_delay as i32; + } + + pub fn get_max_frame_delay(&self) -> u32 { + self.dav1d_settings.max_frame_delay as u32 + } + + pub fn set_apply_grain(&mut self, apply_grain: bool) { + self.dav1d_settings.apply_grain = i32::from(apply_grain); + } + + pub fn get_apply_grain(&self) -> bool { + self.dav1d_settings.apply_grain != 0 + } + + pub fn set_operating_point(&mut self, operating_point: u32) { + self.dav1d_settings.operating_point = operating_point as i32; + } + + pub fn get_operating_point(&self) -> u32 { + self.dav1d_settings.operating_point as u32 + } + + pub fn set_all_layers(&mut self, all_layers: bool) { + self.dav1d_settings.all_layers = i32::from(all_layers); + } + + pub fn get_all_layers(&self) -> bool { + self.dav1d_settings.all_layers != 0 + } + + pub fn set_frame_size_limit(&mut self, frame_size_limit: u32) { + self.dav1d_settings.frame_size_limit = frame_size_limit; + } + + pub fn get_frame_size_limit(&self) -> u32 { + self.dav1d_settings.frame_size_limit + } + + pub fn set_strict_std_compliance(&mut self, strict_std_compliance: bool) { + self.dav1d_settings.strict_std_compliance = i32::from(strict_std_compliance); + } + + pub fn get_strict_std_compliance(&self) -> bool { + self.dav1d_settings.strict_std_compliance != 0 + } + + pub fn set_output_invisible_frames(&mut self, output_invisible_frames: bool) { + self.dav1d_settings.output_invisible_frames = i32::from(output_invisible_frames); + } + + pub fn get_output_invisible_frames(&self) -> bool { + self.dav1d_settings.output_invisible_frames != 0 + } + + pub fn set_inloop_filters(&mut self, inloop_filters: InloopFilterType) { + self.dav1d_settings.inloop_filters = inloop_filters.bits(); + } + + pub fn get_inloop_filters(&self) -> InloopFilterType { + InloopFilterType::from_bits_truncate(self.dav1d_settings.inloop_filters) + } + + pub fn set_decode_frame_type(&mut self, decode_frame_type: DecodeFrameType) { + self.dav1d_settings.decode_frame_type = decode_frame_type.into(); + } + + pub fn get_decode_frame_type(&self) -> DecodeFrameType { + DecodeFrameType::try_from(self.dav1d_settings.decode_frame_type) + .expect("Invalid Dav1dDecodeFrameType") + } + } + + bitflags::bitflags! { + #[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] + pub struct InloopFilterType: u32 { + const DEBLOCK = DAV1D_INLOOPFILTER_DEBLOCK; + const CDEF = DAV1D_INLOOPFILTER_CDEF; + const RESTORATION = DAV1D_INLOOPFILTER_RESTORATION; + } + } + + #[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] + pub enum DecodeFrameType { + #[default] + All, + Reference, + Intra, + Key, + } + + impl TryFrom for DecodeFrameType { + type Error = TryFromEnumError; + + fn try_from(value: u32) -> Result { + match value { + DAV1D_DECODEFRAMETYPE_ALL => Ok(DecodeFrameType::All), + DAV1D_DECODEFRAMETYPE_REFERENCE => Ok(DecodeFrameType::Reference), + DAV1D_DECODEFRAMETYPE_INTRA => Ok(DecodeFrameType::Intra), + DAV1D_DECODEFRAMETYPE_KEY => Ok(DecodeFrameType::Key), + _ => Err(TryFromEnumError(())), + } + } + } + + impl From for u32 { + fn from(v: DecodeFrameType) -> u32 { + match v { + DecodeFrameType::All => DAV1D_DECODEFRAMETYPE_ALL, + DecodeFrameType::Reference => DAV1D_DECODEFRAMETYPE_REFERENCE, + DecodeFrameType::Intra => DAV1D_DECODEFRAMETYPE_INTRA, + DecodeFrameType::Key => DAV1D_DECODEFRAMETYPE_KEY, + } + } + } + + /// The error type returned when a conversion from a C enum fails. + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + pub struct TryFromEnumError(()); + + impl std::fmt::Display for TryFromEnumError { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fmt.write_str("Invalid enum value") + } + } + + impl From for TryFromEnumError { + fn from(x: std::convert::Infallible) -> TryFromEnumError { + match x {} + } + } + + impl std::error::Error for TryFromEnumError {} + + /// A `dav1d` decoder instance. + pub struct Decoder { + dec: Dav1dContext, + pending_data: Option, + } + + unsafe extern "C" fn release_wrapped_data>( + _data: *const u8, + cookie: Option>, + ) { + let cookie = cookie.unwrap().as_ptr().as_ptr(); + let buf = unsafe { Box::from_raw(cookie as *mut T) }; + drop(buf); + } + + impl Decoder { + /// Creates a new [`Decoder`] instance with given [`Settings`]. + pub fn with_settings(settings: &Settings) -> Result { + unsafe { + let mut dec = mem::MaybeUninit::uninit(); + + let ret = dav1d_open( + Some(NonNull::new(dec.as_mut_ptr()).unwrap()), + Some(NonNull::new(&settings.dav1d_settings as *const _ as *mut _).unwrap()), + ); + + match rav1d_result(ret) { + Ok(_) => Ok(Decoder { + dec: dec.assume_init().unwrap(), + pending_data: None, + }), + Err(err) => Err(Error::from(err)), + } + } + } + + /// Creates a new [`Decoder`] instance with the default settings. + pub fn new() -> Result { + Self::with_settings(&Settings::default()) + } + + /// Flush the decoder. + /// + /// This flushes all delayed frames in the decoder and clears the internal decoder state. + /// + /// All currently pending frames are available afterwards via [`Decoder::get_picture`]. + pub fn flush(&mut self) { + unsafe { + dav1d_flush(self.dec); + if let Some(mut pending_data) = self.pending_data.take() { + dav1d_data_unref(Some(NonNull::new(&mut pending_data).unwrap())); + } + } + } + + /// Send new AV1 data to the decoder. + /// + /// After this returned `Ok(())` or `Err([Error::Again])` there might be decoded frames + /// available via [`Decoder::get_picture`]. + /// + /// # Panics + /// + /// If a previous call returned [`Error::Again`] then this must not be called again until + /// [`Decoder::send_pending_data`] has returned `Ok(())`. + pub fn send_data + Send + Sync + 'static>( + &mut self, + buf: T, + offset: Option, + timestamp: Option, + duration: Option, + ) -> Result<(), Error> { + assert!( + self.pending_data.is_none(), + "Have pending data that needs to be handled first" + ); + + let buf = Box::new(buf); + let slice = (*buf).as_ref(); + let len = slice.len(); + + unsafe { + let mut data: Dav1dData = mem::zeroed(); + let _ret = dav1d_data_wrap( + option_nonnull(&mut data), + option_nonnull(slice.as_ptr() as *mut _), + len, + Some(release_wrapped_data::), + option_send_sync_non_null(buf).map(|v| v.cast()), + ); + if let Some(offset) = offset { + data.m.offset = offset as libc::off_t; + } + if let Some(timestamp) = timestamp { + data.m.timestamp = timestamp; + } + if let Some(duration) = duration { + data.m.duration = duration; + } + + let ret = dav1d_send_data(Some(self.dec), option_nonnull(&mut data)); + if let Err(err) = rav1d_result(ret) { + let ret = Error::from(err); + + if ret.is_again() { + self.pending_data = Some(data); + } else { + dav1d_data_unref(option_nonnull(&mut data)); + } + + return Err(ret); + } + + if data.sz > 0 { + self.pending_data = Some(data); + return Err(Error::Again); + } + + Ok(()) + } + } + + /// Sends any pending data to the decoder. + /// + /// This has to be called after [`Decoder::send_data`] has returned `Err([Error::Again])` to + /// consume any futher pending data. + /// + /// After this returned `Ok(())` or `Err([Error::Again])` there might be decoded frames + /// available via [`Decoder::get_picture`]. + pub fn send_pending_data(&mut self) -> Result<(), Error> { + let mut data = match self.pending_data.take() { + None => { + return Ok(()); + } + Some(data) => data, + }; + + unsafe { + let ret = dav1d_send_data(Some(self.dec), option_nonnull(&mut data)); + if let Err(err) = rav1d_result(ret) { + let ret = Error::from(err); + + if ret.is_again() { + self.pending_data = Some(data); + } else { + dav1d_data_unref(option_nonnull(&mut data)); + } + + return Err(ret); + } + + if data.sz > 0 { + self.pending_data = Some(data); + return Err(Error::Again); + } + + Ok(()) + } + } + + /// Get the next decoded frame from the decoder. + /// + /// If this returns `Err([Error::Again])` then further data has to be sent to the decoder + /// before further decoded frames become available. + /// + /// To make most use of frame threading this function should only be called once per submitted + /// input frame and not until it returns `Err([Error::Again])`. Calling it in a loop should + /// only be done to drain all pending frames at the end. + pub fn get_picture(&mut self) -> Result { + unsafe { + let mut pic: Dav1dPicture = mem::zeroed(); + let ret = dav1d_get_picture(Some(self.dec), option_nonnull(&mut pic)); + + if let Err(err) = rav1d_result(ret) { + Err(Error::from(err)) + } else { + let inner = InnerPicture { pic }; + Ok(Picture { + inner: Arc::new(inner), + }) + } + } + } + + /// Get the decoder delay. + pub fn get_frame_delay(&self) -> u32 { + unsafe { + dav1d_get_frame_delay(option_nonnull(&self.dec as *const _ as *mut _)).0 as u32 + } + } + } + + impl Drop for Decoder { + fn drop(&mut self) { + unsafe { + if let Some(mut pending_data) = self.pending_data.take() { + dav1d_data_unref(option_nonnull(&mut pending_data)); + } + let mut dec = Some(self.dec); + dav1d_close(option_nonnull(&mut dec)); + }; + } + } + + unsafe impl Send for Decoder {} + unsafe impl Sync for Decoder {} + + struct InnerPicture { + pub pic: Dav1dPicture, + } + + /// A decoded frame. + #[derive(Clone)] + pub struct Picture { + inner: Arc, + } + + /// Pixel layout of a frame. + #[derive(Debug, Eq, PartialEq, Copy, Clone)] + pub enum PixelLayout { + /// Monochrome. + I400, + /// 4:2:0 planar. + I420, + /// 4:2:2 planar. + I422, + /// 4:4:4 planar. + I444, + } + + /// Frame component. + #[derive(Eq, PartialEq, Copy, Clone, Debug)] + pub enum PlanarImageComponent { + /// Y component. + Y, + /// U component. + U, + /// V component. + V, + } + + impl From for PlanarImageComponent { + fn from(index: usize) -> Self { + match index { + 0 => PlanarImageComponent::Y, + 1 => PlanarImageComponent::U, + 2 => PlanarImageComponent::V, + _ => panic!("Invalid YUV index: {}", index), + } + } + } + + impl From for usize { + fn from(component: PlanarImageComponent) -> Self { + match component { + PlanarImageComponent::Y => 0, + PlanarImageComponent::U => 1, + PlanarImageComponent::V => 2, + } + } + } + + /// A single plane of a decoded frame. + /// + /// This can be used like a `&[u8]`. + #[derive(Clone)] + pub struct Plane(Picture, PlanarImageComponent); + + impl AsRef<[u8]> for Plane { + fn as_ref(&self) -> &[u8] { + let (stride, height) = self.0.plane_data_geometry(self.1); + unsafe { + std::slice::from_raw_parts( + self.0.plane_data_ptr(self.1) as *const u8, + (stride * height) as usize, + ) + } + } + } + + impl std::ops::Deref for Plane { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.as_ref() + } + } + + static_assertions::assert_impl_all!(Plane: Send, Sync); + + /// Number of bits per component. + #[derive(Copy, Clone, Debug, PartialEq, Eq)] + pub struct BitsPerComponent(pub usize); + + impl Picture { + /// Stride in pixels of the `component` for the decoded frame. + pub fn stride(&self, component: PlanarImageComponent) -> u32 { + let s = match component { + PlanarImageComponent::Y => 0, + _ => 1, + }; + self.inner.pic.stride[s] as u32 + } + + /// Raw pointer to the data of the `component` for the decoded frame. + pub fn plane_data_ptr(&self, component: PlanarImageComponent) -> *mut c_void { + let index: usize = component.into(); + self.inner.pic.data[index].unwrap().as_ptr() + } + + /// Plane geometry of the `component` for the decoded frame. + /// + /// This returns the stride and height. + pub fn plane_data_geometry(&self, component: PlanarImageComponent) -> (u32, u32) { + let height = match component { + PlanarImageComponent::Y => self.height(), + _ => match self.pixel_layout() { + PixelLayout::I420 => (self.height() + 1) / 2, + PixelLayout::I400 | PixelLayout::I422 | PixelLayout::I444 => self.height(), + }, + }; + (self.stride(component), height) + } + + /// Plane data of the `component` for the decoded frame. + pub fn plane(&self, component: PlanarImageComponent) -> Plane { + Plane(self.clone(), component) + } + + /// Bit depth of the plane data. + /// + /// This returns 8 or 16 for the underlying integer type used for the plane data. + /// + /// Check [`Picture::bits_per_component`] for the number of bits that are used. + pub fn bit_depth(&self) -> usize { + self.inner.pic.p.bpc as usize + } + + /// Bits used per component of the plane data. + /// + /// Check [`Picture::bit_depth`] for the number of storage bits. + pub fn bits_per_component(&self) -> Option { + unsafe { + match (*self.inner.pic.seq_hdr.unwrap().as_ptr()).hbd { + 0 => Some(BitsPerComponent(8)), + 1 => Some(BitsPerComponent(10)), + 2 => Some(BitsPerComponent(12)), + _ => None, + } + } + } + + /// Width of the frame. + pub fn width(&self) -> u32 { + self.inner.pic.p.w as u32 + } + + /// Height of the frame. + pub fn height(&self) -> u32 { + self.inner.pic.p.h as u32 + } + + /// Pixel layout of the frame. + pub fn pixel_layout(&self) -> PixelLayout { + #[allow(non_upper_case_globals)] + match self.inner.pic.p.layout { + DAV1D_PIXEL_LAYOUT_I400 => PixelLayout::I400, + DAV1D_PIXEL_LAYOUT_I420 => PixelLayout::I420, + DAV1D_PIXEL_LAYOUT_I422 => PixelLayout::I422, + DAV1D_PIXEL_LAYOUT_I444 => PixelLayout::I444, + _ => unreachable!(), + } + } + + /// Timestamp of the frame. + /// + /// This is the same timestamp as the one provided to [`Decoder::send_data`]. + pub fn timestamp(&self) -> Option { + let ts = self.inner.pic.m.timestamp; + if ts == i64::MIN { + None + } else { + Some(ts) + } + } + + /// Duration of the frame. + /// + /// This is the same duration as the one provided to [`Decoder::send_data`] or `0` if none was + /// provided. + pub fn duration(&self) -> i64 { + self.inner.pic.m.duration + } + + /// Offset of the frame. + /// + /// This is the same offset as the one provided to [`Decoder::send_data`] or `-1` if none was + /// provided. + pub fn offset(&self) -> i64 { + self.inner.pic.m.offset as i64 + } + + /// Chromaticity coordinates of the source colour primaries. + pub fn color_primaries(&self) -> pixel::ColorPrimaries { + unsafe { + #[allow(non_upper_case_globals)] + match (*self.inner.pic.seq_hdr.unwrap().as_ptr()).pri { + DAV1D_COLOR_PRI_BT709 => pixel::ColorPrimaries::BT709, + DAV1D_COLOR_PRI_UNKNOWN => pixel::ColorPrimaries::Unspecified, + DAV1D_COLOR_PRI_BT470M => pixel::ColorPrimaries::BT470M, + DAV1D_COLOR_PRI_BT470BG => pixel::ColorPrimaries::BT470BG, + DAV1D_COLOR_PRI_BT601 => pixel::ColorPrimaries::BT470BG, + DAV1D_COLOR_PRI_SMPTE240 => pixel::ColorPrimaries::ST240M, + DAV1D_COLOR_PRI_FILM => pixel::ColorPrimaries::Film, + DAV1D_COLOR_PRI_BT2020 => pixel::ColorPrimaries::BT2020, + DAV1D_COLOR_PRI_XYZ => pixel::ColorPrimaries::ST428, + DAV1D_COLOR_PRI_SMPTE431 => pixel::ColorPrimaries::P3DCI, + DAV1D_COLOR_PRI_SMPTE432 => pixel::ColorPrimaries::P3Display, + DAV1D_COLOR_PRI_EBU3213 => pixel::ColorPrimaries::Tech3213, + 23..=DAV1D_COLOR_PRI_RESERVED => pixel::ColorPrimaries::Unspecified, + _ => unreachable!(), + } + } + } + + /// Transfer characteristics function. + pub fn transfer_characteristic(&self) -> pixel::TransferCharacteristic { + unsafe { + #[allow(non_upper_case_globals)] + match (*self.inner.pic.seq_hdr.unwrap().as_ptr()).trc { + DAV1D_TRC_BT709 => pixel::TransferCharacteristic::BT1886, + DAV1D_TRC_UNKNOWN => pixel::TransferCharacteristic::Unspecified, + DAV1D_TRC_BT470M => pixel::TransferCharacteristic::BT470M, + DAV1D_TRC_BT470BG => pixel::TransferCharacteristic::BT470BG, + DAV1D_TRC_BT601 => pixel::TransferCharacteristic::ST170M, + DAV1D_TRC_SMPTE240 => pixel::TransferCharacteristic::ST240M, + DAV1D_TRC_LINEAR => pixel::TransferCharacteristic::Linear, + DAV1D_TRC_LOG100 => pixel::TransferCharacteristic::Logarithmic100, + DAV1D_TRC_LOG100_SQRT10 => pixel::TransferCharacteristic::Logarithmic316, + DAV1D_TRC_IEC61966 => pixel::TransferCharacteristic::SRGB, + DAV1D_TRC_BT1361 => pixel::TransferCharacteristic::BT1886, + DAV1D_TRC_SRGB => pixel::TransferCharacteristic::SRGB, + DAV1D_TRC_BT2020_10BIT => pixel::TransferCharacteristic::BT2020Ten, + DAV1D_TRC_BT2020_12BIT => pixel::TransferCharacteristic::BT2020Twelve, + DAV1D_TRC_SMPTE2084 => pixel::TransferCharacteristic::PerceptualQuantizer, + DAV1D_TRC_SMPTE428 => pixel::TransferCharacteristic::ST428, + DAV1D_TRC_HLG => pixel::TransferCharacteristic::HybridLogGamma, + 19..=DAV1D_TRC_RESERVED => pixel::TransferCharacteristic::Unspecified, + _ => unreachable!(), + } + } + } + + /// Matrix coefficients used in deriving luma and chroma signals from the + /// green, blue and red or X, Y and Z primaries. + pub fn matrix_coefficients(&self) -> pixel::MatrixCoefficients { + unsafe { + #[allow(non_upper_case_globals)] + match (*self.inner.pic.seq_hdr.unwrap().as_ptr()).mtrx { + DAV1D_MC_IDENTITY => pixel::MatrixCoefficients::Identity, + DAV1D_MC_BT709 => pixel::MatrixCoefficients::BT709, + DAV1D_MC_UNKNOWN => pixel::MatrixCoefficients::Unspecified, + DAV1D_MC_FCC => pixel::MatrixCoefficients::BT470M, + DAV1D_MC_BT470BG => pixel::MatrixCoefficients::BT470BG, + DAV1D_MC_BT601 => pixel::MatrixCoefficients::BT470BG, + DAV1D_MC_SMPTE240 => pixel::MatrixCoefficients::ST240M, + DAV1D_MC_SMPTE_YCGCO => pixel::MatrixCoefficients::YCgCo, + DAV1D_MC_BT2020_NCL => pixel::MatrixCoefficients::BT2020NonConstantLuminance, + DAV1D_MC_BT2020_CL => pixel::MatrixCoefficients::BT2020ConstantLuminance, + DAV1D_MC_SMPTE2085 => pixel::MatrixCoefficients::ST2085, + DAV1D_MC_CHROMAT_NCL => { + pixel::MatrixCoefficients::ChromaticityDerivedNonConstantLuminance + } + DAV1D_MC_CHROMAT_CL => { + pixel::MatrixCoefficients::ChromaticityDerivedConstantLuminance + } + DAV1D_MC_ICTCP => pixel::MatrixCoefficients::ICtCp, + 15..=DAV1D_MC_RESERVED => pixel::MatrixCoefficients::Unspecified, + _ => unreachable!(), + } + } + } + + /// YUV color range. + pub fn color_range(&self) -> pixel::YUVRange { + unsafe { + match (*self.inner.pic.seq_hdr.unwrap().as_ptr()).color_range { + 0 => pixel::YUVRange::Limited, + _ => pixel::YUVRange::Full, + } + } + } + + /// Sample position for subsampled chroma. + pub fn chroma_location(&self) -> pixel::ChromaLocation { + // According to y4m mapping declared in dav1d's output/y4m2.c and applied from FFmpeg's yuv4mpegdec.c + unsafe { + match (*self.inner.pic.seq_hdr.unwrap().as_ptr()).chr { + DAV1D_CHR_UNKNOWN | DAV1D_CHR_COLOCATED => pixel::ChromaLocation::Center, + DAV1D_CHR_VERTICAL => pixel::ChromaLocation::Left, + _ => unreachable!(), + } + } + } + } + + static_assertions::assert_impl_all!(Picture: Send, Sync); + + unsafe impl Send for InnerPicture {} + unsafe impl Sync for InnerPicture {} + + impl Drop for InnerPicture { + fn drop(&mut self) { + unsafe { + dav1d_picture_unref(option_nonnull(&mut self.pic)); + } + } + } + + impl std::fmt::Debug for Picture { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Picture") + .field("width", &self.width()) + .field("height", &self.height()) + .field("bit_depth", &self.bit_depth()) + .field("pixel_layout", &self.pixel_layout()) + .field("timestamp", &self.timestamp()) + .field("duration", &self.duration()) + .field("offset", &self.offset()) + .field("color_primaries", &self.color_primaries()) + .field("transfer_characteristic", &self.transfer_characteristic()) + .field("matrix_coefficients", &self.matrix_coefficients()) + .field("color_range", &self.color_range()) + .field("chroma_location", &self.chroma_location()) + .finish() + } + } +} + +pub use dav1d::*; diff --git a/src/decode.rs b/src/decode.rs index 6695e9798..9a3de607b 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -4994,7 +4994,10 @@ pub fn rav1d_submit_frame(c: &Rav1dContext, state: &mut Rav1dState) -> Rav1dResu ) { fc.task_thread.error.store(1, Ordering::Relaxed); let _ = mem::take(&mut *fc.in_cdf.try_write().unwrap()); - if f.frame_hdr.as_ref().unwrap().refresh_context != 0 { + if f.frame_hdr + .as_ref() + .is_some_and(|frame_hdr| frame_hdr.refresh_context != 0) + { let _ = mem::take(&mut f.out_cdf); } for i in 0..7 {