diff --git a/Cargo.lock b/Cargo.lock index e43b449fd..5c04519e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,18 +34,43 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "av-data" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fca67ba5d317924c02180c576157afd54babe48a76ebc66ce6d34bb8ba08308e" +dependencies = [ + "byte-slice-cast", + "bytes", + "num-derive", + "num-rational", + "num-traits", +] + [[package]] name = "bitflags" version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + [[package]] name = "cc" version = "1.2.23" @@ -114,6 +139,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", +] + +[[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" @@ -173,6 +248,7 @@ version = "1.1.0" dependencies = [ "assert_matches", "atomig", + "av-data", "bitflags", "cc", "cfg-if", diff --git a/Cargo.toml b/Cargo.toml index ce2f11105..94cb6ab89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ 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" diff --git a/src/include/dav1d/common.rs b/src/include/dav1d/common.rs index f461cf365..ddfab652f 100644 --- a/src/include/dav1d/common.rs +++ b/src/include/dav1d/common.rs @@ -42,7 +42,7 @@ pub struct Dav1dDataProps { #[derive(Clone)] #[repr(C)] -pub(crate) struct Rav1dDataProps { +pub struct Rav1dDataProps { pub timestamp: i64, pub duration: i64, pub offset: i64, diff --git a/src/include/dav1d/dav1d.rs b/src/include/dav1d/dav1d.rs index e7d7da594..c642d1bca 100644 --- a/src/include/dav1d/dav1d.rs +++ b/src/include/dav1d/dav1d.rs @@ -36,7 +36,7 @@ pub const DAV1D_INLOOPFILTER_RESTORATION: Dav1dInloopFilterType = bitflags! { #[derive(Clone, Copy, PartialEq, Eq, Hash, Default)] - pub(crate) struct Rav1dInloopFilterType: u8 { + pub struct Rav1dInloopFilterType: u8 { const DEBLOCK = 1 << 1; const CDEF = 1 << 2; const RESTORATION = 1 << 3; @@ -66,7 +66,7 @@ pub const DAV1D_DECODEFRAMETYPE_KEY: Dav1dDecodeFrameType = Rav1dDecodeFrameType::Key as Dav1dDecodeFrameType; #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromRepr, Default)] -pub(crate) enum Rav1dDecodeFrameType { +pub enum Rav1dDecodeFrameType { /// decode and return all frames #[default] All = 0, diff --git a/src/include/dav1d/headers.rs b/src/include/dav1d/headers.rs index 3a541c0d3..595f31c35 100644 --- a/src/include/dav1d/headers.rs +++ b/src/include/dav1d/headers.rs @@ -4,10 +4,12 @@ use std::fmt::{Debug, Display, Formatter}; use std::ops::{BitAnd, Deref, Sub}; use std::sync::Arc; +use av_data::pixel; use strum::{EnumCount, FromRepr}; use crate::align::ArrayDefault; use crate::enum_map::EnumKey; +use crate::error::Rav1dError; use crate::levels::SegmentId; use crate::relaxed_atomic::RelaxedAtomic; @@ -379,7 +381,7 @@ impl From for Dav1dWarpedMotionParams { // TODO(kkysen) Eventually the [`impl Default`] might not be needed. /// Pixel layout of a frame. -#[derive(Clone, Copy, PartialEq, Eq, EnumCount, FromRepr, Default)] +#[derive(Clone, Copy, PartialEq, Eq, EnumCount, FromRepr, Default, Debug)] pub enum Rav1dPixelLayout { /// Monochrome. #[default] @@ -567,6 +569,25 @@ impl Rav1dColorPrimaries { } } +impl From for pixel::ColorPrimaries { + fn from(val: Rav1dColorPrimaries) -> Self { + match val { + Rav1dColorPrimaries::BT709 => pixel::ColorPrimaries::BT709, + Rav1dColorPrimaries::Unspecified => pixel::ColorPrimaries::Unspecified, + Rav1dColorPrimaries::BT470M => pixel::ColorPrimaries::BT470M, + Rav1dColorPrimaries::BT470BG => pixel::ColorPrimaries::BT470BG, + Rav1dColorPrimaries::BT601 => pixel::ColorPrimaries::BT470BG, + Rav1dColorPrimaries::SMPTE240 => pixel::ColorPrimaries::ST240M, + Rav1dColorPrimaries::Film => pixel::ColorPrimaries::Film, + Rav1dColorPrimaries::BT2020 => pixel::ColorPrimaries::BT2020, + Rav1dColorPrimaries::XYZ => pixel::ColorPrimaries::ST428, + Rav1dColorPrimaries::SMPTE431 => pixel::ColorPrimaries::P3DCI, + Rav1dColorPrimaries::SMPTE432 => pixel::ColorPrimaries::P3Display, + Rav1dColorPrimaries::EBU3213 => pixel::ColorPrimaries::Tech3213, + } + } +} + impl From for Dav1dColorPrimaries { fn from(value: Rav1dColorPrimaries) -> Self { value.to_dav1d() @@ -649,6 +670,36 @@ impl Rav1dTransferCharacteristics { } } +impl From for pixel::TransferCharacteristic { + fn from(val: Rav1dTransferCharacteristics) -> Self { + match val { + Rav1dTransferCharacteristics::BT709 => pixel::TransferCharacteristic::BT1886, + Rav1dTransferCharacteristics::Unspecified => pixel::TransferCharacteristic::Unspecified, + Rav1dTransferCharacteristics::BT470M => pixel::TransferCharacteristic::BT470M, + Rav1dTransferCharacteristics::BT470BG => pixel::TransferCharacteristic::BT470BG, + Rav1dTransferCharacteristics::BT601 => pixel::TransferCharacteristic::ST170M, + Rav1dTransferCharacteristics::SMPTE240 => pixel::TransferCharacteristic::ST240M, + Rav1dTransferCharacteristics::Linear => pixel::TransferCharacteristic::Linear, + Rav1dTransferCharacteristics::Log100 => pixel::TransferCharacteristic::Logarithmic100, + Rav1dTransferCharacteristics::Log100Sqrt10 => { + pixel::TransferCharacteristic::Logarithmic316 + } + Rav1dTransferCharacteristics::IEC61966 => pixel::TransferCharacteristic::SRGB, + Rav1dTransferCharacteristics::BT1361 => pixel::TransferCharacteristic::BT1886, + Rav1dTransferCharacteristics::SRGB => pixel::TransferCharacteristic::SRGB, + Rav1dTransferCharacteristics::BT2020_10Bit => pixel::TransferCharacteristic::BT2020Ten, + Rav1dTransferCharacteristics::BT2020_12Bit => { + pixel::TransferCharacteristic::BT2020Twelve + } + Rav1dTransferCharacteristics::SMPTE2084 => { + pixel::TransferCharacteristic::PerceptualQuantizer + } + Rav1dTransferCharacteristics::SMPTE428 => pixel::TransferCharacteristic::ST428, + Rav1dTransferCharacteristics::HLG => pixel::TransferCharacteristic::HybridLogGamma, + } + } +} + impl From for Dav1dTransferCharacteristics { fn from(value: Rav1dTransferCharacteristics) -> Self { value.to_dav1d() @@ -715,6 +766,33 @@ impl Rav1dMatrixCoefficients { } } +impl From for pixel::MatrixCoefficients { + fn from(val: Rav1dMatrixCoefficients) -> Self { + match val { + Rav1dMatrixCoefficients::Identity => pixel::MatrixCoefficients::Identity, + Rav1dMatrixCoefficients::BT709 => pixel::MatrixCoefficients::BT709, + Rav1dMatrixCoefficients::Unspecified => pixel::MatrixCoefficients::Unspecified, + Rav1dMatrixCoefficients::FCC => pixel::MatrixCoefficients::BT470M, + Rav1dMatrixCoefficients::BT470BG => pixel::MatrixCoefficients::BT470BG, + Rav1dMatrixCoefficients::BT601 => pixel::MatrixCoefficients::BT470BG, + Rav1dMatrixCoefficients::SMPTE240 => pixel::MatrixCoefficients::ST240M, + Rav1dMatrixCoefficients::SMPTE_YCgCo => pixel::MatrixCoefficients::YCgCo, + Rav1dMatrixCoefficients::BT2020NCL => { + pixel::MatrixCoefficients::BT2020NonConstantLuminance + } + Rav1dMatrixCoefficients::BT2020CL => pixel::MatrixCoefficients::BT2020ConstantLuminance, + Rav1dMatrixCoefficients::SMPTE2085 => pixel::MatrixCoefficients::ST2085, + Rav1dMatrixCoefficients::ChromatNCL => { + pixel::MatrixCoefficients::ChromaticityDerivedNonConstantLuminance + } + Rav1dMatrixCoefficients::ChromatCL => { + pixel::MatrixCoefficients::ChromaticityDerivedConstantLuminance + } + Rav1dMatrixCoefficients::ICtCp => pixel::MatrixCoefficients::ICtCp, + } + } +} + impl From for Dav1dMatrixCoefficients { fn from(value: Rav1dMatrixCoefficients) -> Self { value.to_dav1d() @@ -754,6 +832,21 @@ impl From for Dav1dChromaSamplePosition { } } +impl TryInto for Rav1dChromaSamplePosition { + type Error = Rav1dError; + + fn try_into(self) -> Result { + // According to y4m mapping declared in dav1d's output/y4m2.c and applied from FFmpeg's yuv4mpegdec.c + match self { + Rav1dChromaSamplePosition::Unknown | Rav1dChromaSamplePosition::Colocated => { + Ok(pixel::ChromaLocation::Center) + } + Rav1dChromaSamplePosition::Vertical => Ok(pixel::ChromaLocation::Left), + Rav1dChromaSamplePosition::_Reserved => Err(Rav1dError::InvalidArgument), + } + } +} + impl TryFrom for Rav1dChromaSamplePosition { type Error = (); diff --git a/src/include/dav1d/picture.rs b/src/include/dav1d/picture.rs index 09c110c7a..3d794bef9 100644 --- a/src/include/dav1d/picture.rs +++ b/src/include/dav1d/picture.rs @@ -32,7 +32,7 @@ use crate::with_offset::WithOffset; // Number of bytes to align AND pad picture memory buffers by, so that SIMD // implementations can over-read by a few bytes, and use aligned read/write // instructions. -pub(crate) const RAV1D_PICTURE_ALIGNMENT: usize = 64; +pub const RAV1D_PICTURE_ALIGNMENT: usize = 64; pub const DAV1D_PICTURE_ALIGNMENT: usize = RAV1D_PICTURE_ALIGNMENT; #[derive(Default)] @@ -47,7 +47,7 @@ pub struct Dav1dPictureParameters { // TODO(kkysen) Eventually the [`impl Default`] might not be needed. #[derive(Clone, Default)] #[repr(C)] -pub(crate) struct Rav1dPictureParameters { +pub struct Rav1dPictureParameters { pub w: c_int, pub h: c_int, pub layout: Rav1dPixelLayout, @@ -404,7 +404,7 @@ impl Drop for Rav1dPictureData { // on [`Rav1dPictureParameters`] and [`Rav1dPixelLayout`]. #[derive(Clone, Default)] #[repr(C)] -pub(crate) struct Rav1dPicture { +pub struct Rav1dPicture { pub seq_hdr: Option>>, pub frame_hdr: Option>>, pub data: Option>, @@ -657,7 +657,7 @@ pub struct Dav1dPicAllocator { #[derive(Clone)] #[repr(C)] -pub(crate) struct Rav1dPicAllocator { +pub struct Rav1dPicAllocator { /// See [`Dav1dPicAllocator::cookie`]. /// /// # Safety diff --git a/src/lib.rs b/src/lib.rs index b070250e8..1a50fce7c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -139,6 +139,8 @@ mod pool; mod qm; mod recon; mod refmvs; +/// This API copies that of `dav1d-rs`, and should function as a drop-in replacement, aside from `dav1d::Error` being renamed to `Rav1dError` +pub mod rust_api; mod scan; mod tables; mod thread_task; @@ -152,14 +154,14 @@ use std::sync::{Arc, Once}; use std::{cmp, mem, ptr, slice, thread}; use parking_lot::Mutex; +pub use rust_api::*; use to_method::To as _; use crate::c_arc::RawArc; use crate::c_box::{CBox, CRef, FnFree}; use crate::cpu::{rav1d_init_cpu, rav1d_num_logical_processors}; use crate::decode::rav1d_decode_frame_exit; -pub use crate::error::Dav1dResult; -use crate::error::{Rav1dError, Rav1dResult}; +pub use crate::error::{Dav1dResult, Rav1dError, Rav1dResult}; use crate::extensions::OptionError as _; use crate::in_range::InRange; #[cfg(feature = "bitdepth_16")] diff --git a/src/rust_api.rs b/src/rust_api.rs new file mode 100644 index 000000000..776a446f4 --- /dev/null +++ b/src/rust_api.rs @@ -0,0 +1,506 @@ +// 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 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. + +use std::fmt::{Debug, Formatter}; +use std::sync::Arc; +use std::{fmt, slice}; + +pub use av_data::pixel; + +use crate::error::Rav1dError; +use crate::in_range::InRange; +pub use crate::include::dav1d::dav1d::{ + Rav1dDecodeFrameType as DecodeFrameType, Rav1dInloopFilterType as InloopFilterType, +}; +pub use crate::include::dav1d::headers::{ + Rav1dContentLightLevel as ContentLightLevel, Rav1dMasteringDisplay as MasteringDisplay, + Rav1dPixelLayout as PixelLayout, +}; +use crate::include::dav1d::picture::Rav1dPicture; +pub use crate::include::dav1d::picture::RAV1D_PICTURE_ALIGNMENT as PICTURE_ALIGNMENT; +use crate::internal::Rav1dContext; +use crate::pixels::Pixels; +use crate::{ + rav1d_close, rav1d_flush, rav1d_get_frame_delay, rav1d_get_picture, rav1d_open, + rav1d_send_data, CRef, Rav1dData, Rav1dSettings, +}; + +/// Settings for creating a new [`Decoder`] instance. +/// See documentation for native `Dav1dSettings` struct. +#[derive(Default)] +pub struct Settings { + pub(crate) inner: Rav1dSettings, +} + +static_assertions::assert_impl_all!(Settings: Send, Sync); + +impl Settings { + /// Creates a new [`Settings`] instance with default settings. + pub fn new() -> Self { + Self::default() + } + + pub fn set_n_threads(&mut self, n_threads: u32) { + self.inner.n_threads = InRange::new(n_threads.try_into().unwrap()).unwrap(); + } + + pub fn get_n_threads(&self) -> u32 { + self.inner.n_threads.get() as u32 + } + + pub fn set_max_frame_delay(&mut self, max_frame_delay: u32) { + self.inner.max_frame_delay = InRange::new(max_frame_delay.try_into().unwrap()).unwrap(); + } + + pub fn get_max_frame_delay(&self) -> u32 { + self.inner.max_frame_delay.get() as u32 + } + + pub fn set_apply_grain(&mut self, apply_grain: bool) { + self.inner.apply_grain = apply_grain; + } + + pub fn get_apply_grain(&self) -> bool { + self.inner.apply_grain + } + + pub fn set_operating_point(&mut self, operating_point: u8) { + self.inner.operating_point = InRange::new(operating_point).unwrap(); + } + + pub fn get_operating_point(&self) -> u8 { + self.inner.operating_point.get() + } + + pub fn set_all_layers(&mut self, all_layers: bool) { + self.inner.all_layers = all_layers; + } + + pub fn get_all_layers(&self) -> bool { + self.inner.all_layers + } + + pub fn set_frame_size_limit(&mut self, frame_size_limit: u32) { + self.inner.frame_size_limit = frame_size_limit; + } + + pub fn get_frame_size_limit(&self) -> u32 { + self.inner.frame_size_limit + } + + pub fn set_strict_std_compliance(&mut self, strict_std_compliance: bool) { + self.inner.strict_std_compliance = strict_std_compliance; + } + + pub fn get_strict_std_compliance(&self) -> bool { + self.inner.strict_std_compliance + } + + pub fn set_output_invisible_frames(&mut self, output_invisible_frames: bool) { + self.inner.output_invisible_frames = output_invisible_frames; + } + + pub fn get_output_invisible_frames(&self) -> bool { + self.inner.output_invisible_frames + } + + pub fn set_inloop_filters(&mut self, inloop_filters: InloopFilterType) { + self.inner.inloop_filters = inloop_filters; + } + + pub fn get_inloop_filters(&self) -> InloopFilterType { + self.inner.inloop_filters + } + + pub fn set_decode_frame_type(&mut self, decode_frame_type: DecodeFrameType) { + self.inner.decode_frame_type = decode_frame_type; + } + + pub fn get_decode_frame_type(&self) -> DecodeFrameType { + self.inner.decode_frame_type + } +} + +/// A `rav1d` decoder instance. +pub struct Decoder { + ctx: Arc, + pending_data: Option, + n_threads: InRange, + max_frame_delay: InRange, +} + +impl Decoder { + /// Creates a new [`Decoder`] instance with given [`Settings`]. + pub fn with_settings(settings: &Settings) -> Result { + rav1d_open(&settings.inner).map(|ctx| Decoder { + ctx, + pending_data: None, + n_threads: settings.inner.n_threads, + max_frame_delay: settings.inner.max_frame_delay, + }) + } + + /// 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) { + rav1d_flush(&self.ctx); + } + + /// Send new AV1 data to the decoder. + /// + /// After this returned `Ok(())` or `Err(`[`Rav1dError::TryAgain`]`)` there might be decoded frames + /// available via [`Decoder::get_picture`]. + /// + /// # Panics + /// + /// If a previous call returned [`Rav1dError::TryAgain`] then this must not be called again until + /// [`Decoder::send_pending_data`] has returned `Ok(())`. + pub fn send_data( + &mut self, + buf: Box<[u8]>, + offset: Option, + timestamp: Option, + duration: Option, + ) -> Result<(), Rav1dError> { + assert!( + self.pending_data.is_none(), + "Have pending data that needs to be handled first" + ); + + let mut data = Rav1dData::wrap(CRef::Box(buf))?; + if let Some(offset) = offset { + data.m.offset = offset; + } + if let Some(timestamp) = timestamp { + data.m.timestamp = timestamp; + } + if let Some(duration) = duration { + data.m.duration = duration; + } + self.pending_data = Some(data); + self.send_pending_data() + } + + /// Sends any pending data to the decoder. + /// + /// This has to be called after [`Decoder::send_data`] has returned `Err(`[`Rav1dError::TryAgain`]`)` to + /// consume any futher pending data. + /// + /// After this returned `Ok(())` or `Err(`[`Rav1dError::TryAgain`]`)` there might be decoded frames + /// available via [`Decoder::get_picture`]. + pub fn send_pending_data(&mut self) -> Result<(), Rav1dError> { + let Some(mut data) = self.pending_data.take() else { + return Ok(()); + }; + + if let Err(err) = rav1d_send_data(&self.ctx, &mut data) { + if matches!(err, Rav1dError::TryAgain) { + self.pending_data = Some(data); + } + return Err(err); + } + + if data.data.as_ref().is_some_and(|d| !d.is_empty()) { + self.pending_data = Some(data); + return Err(Rav1dError::TryAgain); + } + + Ok(()) + } + + /// Get the next decoded frame from the decoder. + /// + /// If this returns `Err(`[`Rav1dError::TryAgain`]`)` 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(`[`Rav1dError::TryAgain`]`)`. Calling it in a loop should + /// only be done to drain all pending frames at the end. + pub fn get_picture(&mut self) -> Result { + let mut pic = Rav1dPicture::default(); + rav1d_get_picture(&self.ctx, &mut pic)?; + + Ok(Picture { + inner: Arc::new(pic), + }) + } + + /// Get the decoder delay. + pub fn get_frame_delay(&self) -> u32 { + // The only fields this actually needs from `Rav1dSettings` + // are n_threads and max_frame_delay so we just pass these in directly + + rav1d_get_frame_delay(&Rav1dSettings { + n_threads: self.n_threads, + max_frame_delay: self.max_frame_delay, + ..Default::default() + }) as u32 + } +} + +impl Drop for Decoder { + fn drop(&mut self) { + rav1d_close(&self.ctx); + } +} + +static_assertions::assert_impl_all!(Decoder: Send, Sync); + +/// A decoded frame. +#[derive(Clone)] +pub struct Picture { + inner: Arc, +} + +/// Frame component. +#[derive(Eq, PartialEq, Copy, Clone, Debug)] +pub enum PlanarImageComponent { + /// Y component (Luminance). + Y, + /// U component (Chrominance). + U, + /// V component (Chrominance). + V, +} + +impl TryFrom for PlanarImageComponent { + type Error = Rav1dError; + fn try_from(index: usize) -> Result { + match index { + 0 => Ok(PlanarImageComponent::Y), + 1 => Ok(PlanarImageComponent::U), + 2 => Ok(PlanarImageComponent::V), + _ => Err(Rav1dError::InvalidArgument), + } + } +} + +impl From for usize { + fn from(component: PlanarImageComponent) -> Self { + match component { + PlanarImageComponent::Y => 0, + PlanarImageComponent::U => 1, + PlanarImageComponent::V => 2, + } + } +} + +/// Number of bits per component. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct BitsPerComponent(pub u8); + +impl BitsPerComponent { + /// Get the number of bits per component from the high bit depth flag + pub fn from_hbd(hbd: u8) -> Result { + match hbd { + 0 => Ok(BitsPerComponent(8)), + 1 => Ok(BitsPerComponent(10)), + 2 => Ok(BitsPerComponent(12)), + _ => Err(Rav1dError::InvalidArgument), + } + } +} + +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.stride[s].try_into().unwrap() + } + + /// Plane data of the `component` for the decoded frame. + pub fn plane_data(&self, component: PlanarImageComponent) -> &[u8] { + let height = match component { + PlanarImageComponent::Y => self.height(), + _ => match self.pixel_layout() { + PixelLayout::I420 => self.height().div_ceil(2), + PixelLayout::I422 | PixelLayout::I444 => self.height(), + PixelLayout::I400 => return &[], // grayscale images don't have color components + }, + }; + + let stride = self.stride(component); + + // Get a raw pointer to the plane data of the `component` for the decoded frame. + let index: usize = component.into(); + let raw_plane_data_pointer = self.inner.data.as_ref().unwrap().data[index] + .as_byte_mut_ptr() + .cast_const(); + + if stride == 0 || raw_plane_data_pointer.is_null() { + return &[]; + } + let data_length = (stride as usize) + .checked_mul(height as usize) + .expect("The product of stride and height exceeded usize::MAX"); + // SAFETY: The following invariants are upheld: + // 1. Pointer validity: Checked above - if null or stride is 0, we return &[]. + // 2. Pointer alignment: The allocator guarantees RAV1D_PICTURE_ALIGNMENT (64-byte) + // alignment (see Rav1dPictureDataComponentInner::new), which exceeds any + // primitive type's alignment requirements. + // 3. Allocated size: The allocator guarantees the buffer is at least stride * height + // bytes (the allocator callback contract in Dav1dPicAllocator). The checked_mul + // ensures this calculation doesn't overflow. + // 4. Initialization: The allocator is required to initialize the data per the + // alloc_picture_callback safety requirements. + // 5. Lifetime: The returned slice borrows &self, keeping the Arc + // alive for the duration of the borrow. + // 6. No mutable aliases: The Picture is only returned after decoding is complete, + // so the decoder no longer writes to this buffer. The public API only exposes + // shared (&[u8]) access, and no &mut references to this data can exist once + // the Picture is handed to the user. + // + // Past dav1d-rs PRs relevant to this line: + // https://github.com/rust-av/dav1d-rs/pull/121 + // https://github.com/rust-av/dav1d-rs/pull/123 + unsafe { slice::from_raw_parts(raw_plane_data_pointer, data_length) } + } + + /// 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.p.bpc.into() + } + + /// 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 { + BitsPerComponent::from_hbd(self.inner.seq_hdr.as_ref().unwrap().hbd).ok() + } + + /// Width of the frame. + pub fn width(&self) -> u32 { + self.inner.p.w.try_into().unwrap() + } + + /// Height of the frame. + pub fn height(&self) -> u32 { + self.inner.p.h.try_into().unwrap() + } + + /// Pixel layout of the frame. + pub fn pixel_layout(&self) -> PixelLayout { + self.inner.p.layout + } + + /// 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.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.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.m.offset + } + + /// Chromaticity coordinates of the source colour primaries. + pub fn color_primaries(&self) -> pixel::ColorPrimaries { + self.inner.seq_hdr.as_ref().unwrap().pri.try_into().unwrap() + } + + /// Transfer characteristics function. + pub fn transfer_characteristic(&self) -> pixel::TransferCharacteristic { + self.inner.seq_hdr.as_ref().unwrap().trc.try_into().unwrap() + } + + /// 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 { + self.inner + .seq_hdr + .as_ref() + .unwrap() + .mtrx + .try_into() + .unwrap() + } + + /// YUV color range. + pub fn color_range(&self) -> pixel::YUVRange { + match self.inner.seq_hdr.as_ref().unwrap().color_range { + 0 => pixel::YUVRange::Limited, + _ => pixel::YUVRange::Full, + } + } + + /// Sample position for subsampled chroma. + pub fn chroma_location(&self) -> pixel::ChromaLocation { + self.inner.seq_hdr.as_ref().unwrap().chr.try_into().unwrap() + } +} + +impl Debug for Picture { + fn fmt(&self, f: &mut 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() + } +}