From 17d3f6381640b7061f36bcd7c475a2b60bf845b8 Mon Sep 17 00:00:00 2001 From: DongHun Kwak Date: Thu, 6 Apr 2023 09:10:28 +0900 Subject: [PATCH 1/1] Import dav1d 0.9.3 --- .cargo_vcs_info.json | 6 + .github/dependabot.yml | 11 + .github/workflows/dav1d.yml | 80 ++++++ .gitignore | 3 + Cargo.toml | 33 +++ Cargo.toml.orig | 20 ++ LICENSE | 21 ++ README.md | 51 ++++ src/lib.rs | 665 ++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 890 insertions(+) create mode 100644 .cargo_vcs_info.json create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/dav1d.yml create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 Cargo.toml.orig create mode 100644 LICENSE create mode 100644 README.md create mode 100644 src/lib.rs diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..6fed80c --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "ffef505eaa632faed520447ee2e3863fd2048236" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..cdbc175 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: +- package-ecosystem: cargo + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 + ignore: + - dependency-name: system-deps + versions: + - 3.0.0 diff --git a/.github/workflows/dav1d.yml b/.github/workflows/dav1d.yml new file mode 100644 index 0000000..4e91423 --- /dev/null +++ b/.github/workflows/dav1d.yml @@ -0,0 +1,80 @@ +name: dav1d + +on: [push, pull_request] + +jobs: + + linux-tests: + + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + + - name: Install nasm + uses: ilammy/setup-nasm@v1 + + - name: Install Rust stable + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + + - name: Install Python 3.9 + uses: actions/setup-python@v4 + with: + python-version: '3.9' + + - name: Install pip packages + run: | + pip install -U pip + pip install -U wheel setuptools + pip install -U meson ninja + + - name: Build dav1d + env: + DAV1D_DIR: dav1d_dir + LIB_PATH: lib/x86_64-linux-gnu + run: | + git clone --branch 1.1.0 --depth 1 https://code.videolan.org/videolan/dav1d.git + cd dav1d + meson build -Dprefix=$HOME/$DAV1D_DIR --buildtype release + ninja -C build + ninja -C build install + echo "PKG_CONFIG_PATH=$HOME/$DAV1D_DIR/$LIB_PATH/pkgconfig" >> $GITHUB_ENV + echo "LD_LIBRARY_PATH=$HOME/$DAV1D_DIR/$LIB_PATH" >> $GITHUB_ENV + + - name: Run tests + run: | + cargo test --workspace --all-features + + windows-tests-gnu: + + runs-on: windows-latest + + env: + MSYSTEM: MINGW64 + MSYS2_PATH_TYPE: inherit + + steps: + - uses: actions/checkout@v3 + + - name: Install Rust Windows gnu + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable-x86_64-pc-windows-gnu + override: true + + - name: Install msys2 packages + uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + install: mingw-w64-x86_64-pkgconf mingw-w64-x86_64-dav1d + update: true + + - name: Run tests + shell: msys2 {0} + run: | + cargo test --workspace --all-features diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4308d82 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +target/ +**/*.rs.bk +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1abc907 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,33 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +name = "dav1d" +version = "0.9.3" +authors = ["Luca Barbato "] +description = "libdav1d bindings" +readme = "README.md" +keywords = [ + "dav1d-rs", + "av1", +] +license = "MIT" +repository = "https://github.com/rust-av/dav1d-rs" + +[dependencies.bitflags] +version = "1" + +[dependencies.dav1d-sys] +version = "0.7.1" + +[features] +v1_1 = ["dav1d-sys/v1_1"] diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 0000000..9ccf97f --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,20 @@ +[package] +authors = ["Luca Barbato "] +description = "libdav1d bindings" +edition = "2021" +keywords = ["dav1d-rs","av1"] +license = "MIT" +name = "dav1d" +readme = "README.md" +repository = "https://github.com/rust-av/dav1d-rs" +version = "0.9.3" + +[dependencies] +bitflags = "1" +dav1d-sys = { version = "0.7.1", path = "dav1d-sys" } + +[features] +v1_1 = ["dav1d-sys/v1_1"] + +[workspace] +members = ["dav1d-sys", "tools"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..552f0cf --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..210afbb --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# libdav1d bindings [![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Actions Status](https://github.com/rust-av/dav1d-rs/workflows/dav1d/badge.svg)](https://github.com/rust-av/dav1d-rs/actions) + +It is a simple FFI binding and safe abstraction over [dav1d][1]. + + +## Building + +To build the code, always have a look at [CI](https://github.com/rust-av/dav1d-rs/blob/master/.github/workflows/dav1d.yml) to install the necessary dependencies on all +supported operating systems. + +### Overriding the dav1d library + +The bindings use [system-deps](https://docs.rs/system-deps) to find dav1d. You may override the `PKG_CONFIG_PATH` or +direcly set the env vars `SYSTEM_DEPS_DAV1D_SEARCH_NATIVE` and/or `SYSTEM_DEPS_DAV1D_LIB`. + +## Building with vcpkg for Windows x64 + +To build with [vcpkg](https://vcpkg.io/en/index.html), you need to follow these +steps: + +1. Install `pkg-config` through [chocolatey](https://chocolatey.org/) + + choco install pkgconfiglite + +2. Install `dav1d` + + vcpkg install dav1d:x64-windows + +3. Add to the `PKG_CONFIG_PATH` environment variable the path `$VCPKG_INSTALLATION_ROOT\installed\x64-windows\lib\pkgconfig` + +4. Build code + + cargo build --workspace + +To speed up the computation, you can build your packages only in `Release` mode +adding the `set(VCPKG_BUILD_TYPE release)` line to the +`$VCPKG_INSTALLATION_ROOT\triplets\x64-windows.cmake` file. + +Building for Windows x86 is the same, just replace `x64` with `x86` in the +steps above. + +## Supported versions + +The bindings require dav1d 1.0.0 + +## TODO +- [x] Simple bindings +- [x] Safe abstraction +- [ ] Examples + +[1]: https://code.videolan.org/videolan/dav1d diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..5f58868 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,665 @@ +use dav1d_sys::*; + +use std::ffi::c_void; +use std::fmt; +use std::i64; +use std::mem; +use std::ptr; +use std::sync::Arc; + +/// 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(i32), +} + +impl From for Error { + fn from(err: i32) -> Self { + assert!(err < 0); + + match err { + DAV1D_ERR_AGAIN => Error::Again, + DAV1D_ERR_INVAL => Error::InvalidArgument, + DAV1D_ERR_NOMEM => Error::NotEnoughMemory, + DAV1D_ERR_NOPROTOOPT => 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. +#[derive(Debug)] +pub struct Settings { + dav1d_settings: Dav1dSettings, +} + +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(dav1d_settings.as_mut_ptr()); + + 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) + } + + #[cfg(feature = "v1_1")] + pub fn set_decode_frame_type(&mut self, decode_frame_type: DecodeFrameType) { + self.dav1d_settings.decode_frame_type = decode_frame_type.into(); + } + + #[cfg(feature = "v1_1")] + pub fn get_decode_frame_type(&self) -> DecodeFrameType { + DecodeFrameType::try_from(self.dav1d_settings.decode_frame_type) + .expect("Invalid Dav1dDecodeFrameType") + } +} + +bitflags::bitflags! { + #[derive(Default)] + pub struct InloopFilterType: u32 { + const DEBLOCK = DAV1D_INLOOPFILTER_DEBLOCK; + const CDEF = DAV1D_INLOOPFILTER_CDEF; + const RESTORATION = DAV1D_INLOOPFILTER_RESTORATION; + } +} + +#[cfg(feature = "v1_1")] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum DecodeFrameType { + All, + Reference, + Intra, + Key, +} + +#[cfg(feature = "v1_1")] +impl Default for DecodeFrameType { + fn default() -> Self { + DecodeFrameType::All + } +} + +#[cfg(feature = "v1_1")] +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(())), + } + } +} + +#[cfg(feature = "v1_1")] +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, + } + } +} + +#[cfg(feature = "v1_1")] +/// The error type returned when a conversion from a C enum fails. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct TryFromEnumError(()); + +#[cfg(feature = "v1_1")] +impl std::fmt::Display for TryFromEnumError { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fmt.write_str("Invalid enum value") + } +} + +#[cfg(feature = "v1_1")] +impl From for TryFromEnumError { + fn from(x: std::convert::Infallible) -> TryFromEnumError { + match x {} + } +} + +#[cfg(feature = "v1_1")] +impl std::error::Error for TryFromEnumError {} + +/// A `dav1d` decoder instance. +#[derive(Debug)] +pub struct Decoder { + dec: ptr::NonNull, + pending_data: Option, +} + +unsafe extern "C" fn release_wrapped_data>(_data: *const u8, cookie: *mut c_void) { + let buf = 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(dec.as_mut_ptr(), &settings.dav1d_settings); + + if ret < 0 { + return Err(Error::from(ret)); + } + + Ok(Decoder { + dec: ptr::NonNull::new(dec.assume_init()).unwrap(), + pending_data: None, + }) + } + } + + /// 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.as_ptr()); + if let Some(mut pending_data) = self.pending_data.take() { + dav1d_data_unref(&mut pending_data); + } + } + } + + /// 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 + '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( + &mut data, + slice.as_ptr(), + len, + Some(release_wrapped_data::), + Box::into_raw(buf) as *mut c_void, + ); + 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; + } + + let ret = dav1d_send_data(self.dec.as_ptr(), &mut data); + if ret < 0 { + let ret = Error::from(ret); + + if ret.is_again() { + self.pending_data = Some(data); + } else { + dav1d_data_unref(&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(self.dec.as_ptr(), &mut data); + if ret < 0 { + let ret = Error::from(ret); + + if ret.is_again() { + self.pending_data = Some(data); + } else { + dav1d_data_unref(&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(self.dec.as_ptr(), &mut pic); + + if ret < 0 { + Err(Error::from(ret)) + } else { + let inner = InnerPicture { pic }; + Ok(Picture { + inner: Arc::new(inner), + }) + } + } + } + + #[cfg(feature = "v1_1")] + /// Get the decoder delay. + pub fn get_frame_delay(&self) -> u32 { + unsafe { dav1d_get_frame_delay(self.dec.as_ptr()) as u32 } + } +} + +impl Drop for Decoder { + fn drop(&mut self) { + unsafe { + if let Some(mut pending_data) = self.pending_data.take() { + dav1d_data_unref(&mut pending_data); + } + let mut dec = self.dec.as_ptr(); + dav1d_close(&mut dec); + }; + } +} + +unsafe impl Send for Decoder {} +unsafe impl Sync for Decoder {} + +#[derive(Debug)] +struct InnerPicture { + pub pic: Dav1dPicture, +} + +/// A decoded frame. +#[derive(Debug, 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, Debug)] +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() + } +} + +unsafe impl Send for Plane {} +unsafe impl Sync for Plane {} + +/// 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] + } + + /// 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) as u32, 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).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 as i64 + } + + /// 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 + } +} + +unsafe impl Send for Picture {} +unsafe impl Sync for Picture {} + +impl Drop for InnerPicture { + fn drop(&mut self) { + unsafe { + dav1d_picture_unref(&mut self.pic); + } + } +} -- 2.7.4