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/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 000000000..1a45eee77 --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock new file mode 100644 index 000000000..99f72ddb1 --- /dev/null +++ b/fuzz/Cargo.lock @@ -0,0 +1,431 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + +[[package]] +name = "atomig" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0f41f4bb89f5c6450325e283fb78c4a3d042181b54f3855ee2f872919f9863" +dependencies = [ + "atomig-macro", +] + +[[package]] +name = "atomig-macro" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49c98dba06b920588de7d63f6acc23f1e6a9fade5fd6198e564506334fb5a4f5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[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.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[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.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom", + "libc", +] + +[[package]] +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "nasm-rs" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "706bf8a5e8c8ddb99128c3291d31bd21f4bcde17f0f4c20ec678d85c74faa149" +dependencies = [ + "jobserver", + "log", +] + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rav1d" +version = "1.1.0" +dependencies = [ + "assert_matches", + "atomig", + "av-data", + "bitflags", + "cc", + "cfg-if", + "libc", + "nasm-rs", + "parking_lot", + "paste", + "raw-cpuid", + "static_assertions", + "strum", + "to_method", + "zerocopy", +] + +[[package]] +name = "rav1d-fuzz" +version = "0.0.0" +dependencies = [ + "libfuzzer-sys", + "rav1d", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[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.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "to_method" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c4ceeeca15c8384bbc3e011dbd8fccb7f068a440b752b7d9b32ceb0ca0e2e8" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 000000000..9b84db176 --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "rav1d-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" + +[dependencies.rav1d] +path = ".." +default-features = false # Disables ASM; comment out to enable +features = ["bitdepth_8", "bitdepth_16"] + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[profile.release] +debug = 1 + +[[bin]] +name = "fuzz_target_1" +path = "fuzz_targets/fuzz_target_1.rs" +test = false +doc = false diff --git a/fuzz/fuzz_targets/fuzz_target_1.rs b/fuzz/fuzz_targets/fuzz_target_1.rs new file mode 100644 index 000000000..5241e50e0 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_target_1.rs @@ -0,0 +1,65 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use rav1d::Rav1dError; + +fuzz_target!(|data: &[u8]| { + let boxed_data = Vec::from(data).into_boxed_slice(); + let _ = decode(boxed_data); // don't care about returned errors +}); + +// the below is based on +// https://github.com/rust-av/dav1d-rs/blob/master/tools/src/main.rs + +fn handle_pending_pictures(dec: &mut rav1d::rust_api::Decoder, drain: bool) -> std::io::Result<()> { + loop { + match dec.get_picture() { + Ok(p) => println!("{:?}", p), + // Need to send more data to the decoder before it can decode new pictures + Err(Rav1dError::TryAgain) => return Ok(()), + Err(e) => { + return Err(std::io::Error::other(format!("Error getting decoded pictures: {}", e))); + } + } + + if !drain { + break; + } + } + Ok(()) +} + +fn decode(data: Box<[u8]>) -> std::io::Result<()> { + let mut settings = rav1d::rust_api::Settings::new(); + settings.set_logger_enabled(false); + let mut dec = rav1d::rust_api::Decoder::with_settings(&settings).expect("failed to create decoder instance"); + + // Send packet to the decoder + match dec.send_data(data, None, None, None) { + Err(Rav1dError::TryAgain) => { + // If the decoder did not consume all data, output all + // pending pictures and send pending data to the decoder + // until it is all used up. + loop { + handle_pending_pictures(&mut dec, false)?; + + match dec.send_pending_data() { + Err(Rav1dError::TryAgain) => continue, + Err(e) => { + return Err(std::io::Error::other(format!("Error sending pending data to the decoder: {}", e))); + } + _ => break, + } + } + } + Err(e) => { + return Err(std::io::Error::other(format!("Error sending pending data to the decoder: {}", e))); + } + _ => (), + } + + // Handle all pending pictures before sending the next data. + handle_pending_pictures(&mut dec, false)?; + + Ok(()) +} \ No newline at end of file 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..71e163e66 --- /dev/null +++ b/src/rust_api.rs @@ -0,0 +1,514 @@ +// 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 + } + + pub fn set_logger_enabled(&mut self, enabled: bool) { + self.inner.logger = if enabled { + Some(Default::default()) + } else { + None + }; + } +} + +/// 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() + } +}