diff --git a/Cargo.toml b/Cargo.toml index ded5216414..ffe884d7e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,7 @@ num-traits = { version = "0.2.0" } # Optional dependencies color_quant = { version = "1.1", optional = true } -dav1d = { version = "0.11", optional = true } +rav1d-safe = { version = "0.3.1", optional = true } exr = { version = "1.74.0", default-features = false, optional = true } gif = { version = "0.14.1", optional = true } image-webp = { version = "0.2.0", optional = true } @@ -98,7 +98,7 @@ webp = ["dep:image-webp"] rayon = ["dep:rayon", "ravif?/threading", "exr?/rayon"] # Enables multi-threading nasm = ["ravif?/asm"] # Enables use of nasm by rav1e (requires nasm to be installed) color_quant = ["dep:color_quant"] # Enables color quantization -avif-native = ["dep:mp4parse", "dep:dav1d"] # Enable native dependency libdav1d +avif-native = ["dep:mp4parse", "dep:rav1d-safe"] # Enable native dependency libdav1d benchmarks = [] # Build some inline benchmarks. Useful only during development (requires nightly Rust) serde = ["dep:serde"] diff --git a/src/codecs/avif/decoder.rs b/src/codecs/avif/decoder.rs index cfdcd63ee3..7f240a125e 100644 --- a/src/codecs/avif/decoder.rs +++ b/src/codecs/avif/decoder.rs @@ -20,20 +20,28 @@ use crate::codecs::avif::ycgco::{ ycgco444_to_rgba8, }; use crate::codecs::avif::yuv::*; -use dav1d::{PixelLayout, PlanarImageComponent}; use mp4parse::{read_avif, ImageMirror, ImageRotation, ParseStrictness}; +use rav1d_safe::{ColorRange, Frame, MatrixCoefficients, PixelLayout}; fn error_map>>(err: E) -> ImageError { ImageError::Decoding(DecodingError::new(ImageFormat::Avif.into(), err)) } +/// Trim a plane slice to exactly `stride * height` elements. +/// rav1d-safe's PlaneView::as_slice() returns the full backing buffer which may +/// be larger than stride * height; the YUV conversion code requires an exact size. +fn trim_plane(slice: &[T], stride: usize, height: usize) -> &[T] { + let expected = stride * height; + &slice[..expected] +} + /// AVIF Decoder. /// /// Reads one image into the chosen input. pub struct AvifDecoder { inner: PhantomData, - picture: dav1d::Picture, - alpha_picture: Option, + picture: Frame, + alpha_picture: Option, icc_profile: Option>, orientation: Orientation, } @@ -81,18 +89,30 @@ impl AvifDecoder { let ctx = read_avif(&mut r, ParseStrictness::Permissive).map_err(error_map)?; let coded = ctx.primary_item_coded_data().unwrap_or_default(); - let mut primary_decoder = dav1d::Decoder::new().map_err(error_map)?; - primary_decoder - .send_data(coded.to_vec(), None, None, None) - .map_err(error_map)?; - let picture = read_until_ready(&mut primary_decoder)?; + let mut primary_decoder = rav1d_safe::Decoder::new().map_err(error_map)?; + let picture = primary_decoder + .decode(coded) + .map_err(error_map)? + .ok_or_else(|| { + error_map(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "no picture decoded from primary item", + )) + })?; let alpha_item = ctx.alpha_item_coded_data().unwrap_or_default(); let alpha_picture = if !alpha_item.is_empty() { - let mut alpha_decoder = dav1d::Decoder::new().map_err(error_map)?; - alpha_decoder - .send_data(alpha_item.to_vec(), None, None, None) - .map_err(error_map)?; - Some(read_until_ready(&mut alpha_decoder)?) + let mut alpha_decoder = rav1d_safe::Decoder::new().map_err(error_map)?; + Some( + alpha_decoder + .decode(alpha_item) + .map_err(error_map)? + .ok_or_else(|| { + error_map(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "no picture decoded from alpha item", + )) + })?, + ) } else { None }; @@ -163,123 +183,6 @@ fn convert_orientation(rotation: ImageRotation, mirror: Option) -> } } -/// Reshaping incorrectly aligned or sized FFI data into Rust constraints -fn reshape_plane(source: &[u8], stride: usize, width: usize, height: usize) -> Vec { - let mut target_plane = vec![0u16; width * height]; - for (shaped_row, src_row) in target_plane - .chunks_exact_mut(width) - .zip(source.chunks_exact(stride)) - { - for (dst, src) in shaped_row.iter_mut().zip(src_row.as_chunks::<2>().0) { - *dst = u16::from_ne_bytes(*src); - } - } - target_plane -} - -struct Plane16View<'a> { - data: std::borrow::Cow<'a, [u16]>, - stride: usize, -} - -impl Default for Plane16View<'_> { - fn default() -> Self { - Plane16View { - data: std::borrow::Cow::Owned(vec![]), - stride: 0, - } - } -} - -/// This is correct to transmute FFI data for Y plane and Alpha plane -fn transmute_y_plane16( - plane: &dav1d::Plane, - stride: usize, - width: usize, - height: usize, -) -> Plane16View<'_> { - let mut y_plane_stride = stride >> 1; - - let mut bind_y = vec![]; - let plane_ref = plane.as_ref(); - - let mut shape_y_plane = || { - y_plane_stride = width; - bind_y = reshape_plane(plane_ref, stride, width, height); - }; - - if stride & 1 == 0 { - match bytemuck::try_cast_slice(plane_ref) { - Ok(slice) => Plane16View { - data: std::borrow::Cow::Borrowed(slice), - stride: y_plane_stride, - }, - Err(_) => { - shape_y_plane(); - Plane16View { - data: std::borrow::Cow::Owned(bind_y), - stride: y_plane_stride, - } - } - } - } else { - shape_y_plane(); - Plane16View { - data: std::borrow::Cow::Owned(bind_y), - stride: y_plane_stride, - } - } -} - -/// This is correct to transmute FFI data for Y plane and Alpha plane -fn transmute_chroma_plane16( - plane: &dav1d::Plane, - pixel_layout: PixelLayout, - stride: usize, - width: usize, - height: usize, -) -> Plane16View<'_> { - let plane_ref = plane.as_ref(); - let mut chroma_plane_stride = stride >> 1; - let mut bind_chroma = vec![]; - - let mut shape_chroma_plane = || { - chroma_plane_stride = match pixel_layout { - PixelLayout::I400 => unreachable!(), - PixelLayout::I420 | PixelLayout::I422 => width.div_ceil(2), - PixelLayout::I444 => width, - }; - let u_plane_height = match pixel_layout { - PixelLayout::I400 => unreachable!(), - PixelLayout::I420 => height.div_ceil(2), - PixelLayout::I422 | PixelLayout::I444 => height, - }; - bind_chroma = reshape_plane(plane_ref, stride, chroma_plane_stride, u_plane_height); - }; - - if stride & 1 == 0 { - match bytemuck::try_cast_slice(plane_ref) { - Ok(slice) => Plane16View { - data: std::borrow::Cow::Borrowed(slice), - stride: chroma_plane_stride, - }, - Err(_) => { - shape_chroma_plane(); - Plane16View { - data: std::borrow::Cow::Owned(bind_chroma), - stride: chroma_plane_stride, - } - } - } - } else { - shape_chroma_plane(); - Plane16View { - data: std::borrow::Cow::Owned(bind_chroma), - stride: chroma_plane_stride, - } - } -} - #[derive(Copy, Clone, Debug, PartialOrd, Eq, PartialEq)] enum YuvMatrixStrategy { KrKb(YuvStandardMatrix), @@ -287,23 +190,17 @@ enum YuvMatrixStrategy { Identity, } -/// Getting one of prebuilt matrix of fails -fn get_matrix( - david_matrix: dav1d::pixel::MatrixCoefficients, -) -> Result { - match david_matrix { - dav1d::pixel::MatrixCoefficients::Identity => Ok(YuvMatrixStrategy::Identity), - dav1d::pixel::MatrixCoefficients::BT709 => { - Ok(YuvMatrixStrategy::KrKb(YuvStandardMatrix::Bt709)) - } +/// Getting one of prebuilt matrix or fails +fn get_matrix(matrix: MatrixCoefficients) -> Result { + match matrix { + MatrixCoefficients::Identity => Ok(YuvMatrixStrategy::Identity), + MatrixCoefficients::BT709 => Ok(YuvMatrixStrategy::KrKb(YuvStandardMatrix::Bt709)), // This is arguable, some applications prefer to go with Bt.709 as default, // and some applications prefer Bt.601 as default. // For ex. `Chrome` always prefer Bt.709 even for SD content // However, nowadays standard should be Bt.709 for HD+ size otherwise Bt.601 - dav1d::pixel::MatrixCoefficients::Unspecified => { - Ok(YuvMatrixStrategy::KrKb(YuvStandardMatrix::Bt709)) - } - dav1d::pixel::MatrixCoefficients::Reserved => Err(ImageError::Unsupported( + MatrixCoefficients::Unspecified => Ok(YuvMatrixStrategy::KrKb(YuvStandardMatrix::Bt709)), + MatrixCoefficients::Reserved => Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Avif.into(), UnsupportedErrorKind::GenericFeature( @@ -311,23 +208,13 @@ fn get_matrix( ), ), )), - dav1d::pixel::MatrixCoefficients::BT470M => { - Ok(YuvMatrixStrategy::KrKb(YuvStandardMatrix::Bt470_6)) - } - dav1d::pixel::MatrixCoefficients::BT470BG => { - Ok(YuvMatrixStrategy::KrKb(YuvStandardMatrix::Bt601)) - } - dav1d::pixel::MatrixCoefficients::ST170M => { - Ok(YuvMatrixStrategy::KrKb(YuvStandardMatrix::Smpte240)) - } - dav1d::pixel::MatrixCoefficients::ST240M => { - Ok(YuvMatrixStrategy::KrKb(YuvStandardMatrix::Smpte240)) - } - dav1d::pixel::MatrixCoefficients::YCgCo => Ok(YuvMatrixStrategy::CgCo), - dav1d::pixel::MatrixCoefficients::BT2020NonConstantLuminance => { - Ok(YuvMatrixStrategy::KrKb(YuvStandardMatrix::Bt2020)) - } - dav1d::pixel::MatrixCoefficients::BT2020ConstantLuminance => { + MatrixCoefficients::FCC => Ok(YuvMatrixStrategy::KrKb(YuvStandardMatrix::Bt470_6)), + MatrixCoefficients::BT470BG => Ok(YuvMatrixStrategy::KrKb(YuvStandardMatrix::Bt601)), + MatrixCoefficients::BT601 => Ok(YuvMatrixStrategy::KrKb(YuvStandardMatrix::Smpte240)), + MatrixCoefficients::SMPTE240 => Ok(YuvMatrixStrategy::KrKb(YuvStandardMatrix::Smpte240)), + MatrixCoefficients::YCgCo => Ok(YuvMatrixStrategy::CgCo), + MatrixCoefficients::BT2020NCL => Ok(YuvMatrixStrategy::KrKb(YuvStandardMatrix::Bt2020)), + MatrixCoefficients::BT2020CL => { // This matrix significantly differs from others because linearize values is required // to compute Y instead of Y'. // Actually it is almost everywhere is not implemented. @@ -342,14 +229,13 @@ fn get_matrix( ), )) } - dav1d::pixel::MatrixCoefficients::ST2085 => Err(ImageError::Unsupported( + MatrixCoefficients::SMPTE2085 => Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Avif.into(), UnsupportedErrorKind::GenericFeature("ST2085 matrix is not supported".to_string()), ), )), - dav1d::pixel::MatrixCoefficients::ChromaticityDerivedConstantLuminance - | dav1d::pixel::MatrixCoefficients::ChromaticityDerivedNonConstantLuminance => Err( + MatrixCoefficients::ChromaDerivedCL | MatrixCoefficients::ChromaDerivedNCL => Err( ImageError::Unsupported(UnsupportedError::from_format_and_kind( ImageFormat::Avif.into(), UnsupportedErrorKind::GenericFeature( @@ -357,7 +243,7 @@ fn get_matrix( ), )), ), - dav1d::pixel::MatrixCoefficients::ICtCp => Err(ImageError::Unsupported( + MatrixCoefficients::ICtCp => Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Avif.into(), UnsupportedErrorKind::GenericFeature( @@ -406,54 +292,59 @@ impl ImageDecoder for AvifDecoder { ))); } - let yuv_range = match self.picture.color_range() { - dav1d::pixel::YUVRange::Limited => YuvIntensityRange::Tv, - dav1d::pixel::YUVRange::Full => YuvIntensityRange::Pc, + let color_info = self.picture.color_info(); + + let yuv_range = match color_info.color_range { + ColorRange::Limited => YuvIntensityRange::Tv, + ColorRange::Full => YuvIntensityRange::Pc, }; - let matrix_strategy = get_matrix(self.picture.matrix_coefficients())?; + let pixel_layout = self.picture.pixel_layout(); + let matrix_strategy = get_matrix(color_info.matrix_coefficients)?; // Identity matrix should be possible only on 4:4:4 - if matrix_strategy == YuvMatrixStrategy::Identity - && self.picture.pixel_layout() != PixelLayout::I444 - { + if matrix_strategy == YuvMatrixStrategy::Identity && pixel_layout != PixelLayout::I444 { return Err(ImageError::Decoding(DecodingError::new( ImageFormat::Avif.into(), - AvifDecoderError::YuvLayoutOnIdentityMatrix(self.picture.pixel_layout()), + AvifDecoderError::YuvLayoutOnIdentityMatrix(pixel_layout), ))); } - if matrix_strategy == YuvMatrixStrategy::CgCo - && self.picture.pixel_layout() == PixelLayout::I400 - { + if matrix_strategy == YuvMatrixStrategy::CgCo && pixel_layout == PixelLayout::I400 { return Err(ImageError::Decoding(DecodingError::new( ImageFormat::Avif.into(), - AvifDecoderError::UnsupportedLayoutAndMatrix( - self.picture.pixel_layout(), - matrix_strategy, - ), + AvifDecoderError::UnsupportedLayoutAndMatrix(pixel_layout, matrix_strategy), ))); } if bit_depth == 8 { - let ref_y = self.picture.plane(PlanarImageComponent::Y); - let ref_u = self.picture.plane(PlanarImageComponent::U); - let ref_v = self.picture.plane(PlanarImageComponent::V); + let planes = match self.picture.planes() { + rav1d_safe::Planes::Depth8(p) => p, + _ => unreachable!("bit_depth is 8 but planes are not 8-bit"), + }; + + let y_view = planes.y(); + let u_view = planes.u(); + let v_view = planes.v(); let image = YuvPlanarImage { - y_plane: ref_y.as_ref(), - y_stride: self.picture.stride(PlanarImageComponent::Y) as usize, - u_plane: ref_u.as_ref(), - u_stride: self.picture.stride(PlanarImageComponent::U) as usize, - v_plane: ref_v.as_ref(), - v_stride: self.picture.stride(PlanarImageComponent::V) as usize, + y_plane: trim_plane(y_view.as_slice(), y_view.stride(), y_view.height()), + y_stride: y_view.stride(), + u_plane: u_view + .as_ref() + .map_or(&[], |v| trim_plane(v.as_slice(), v.stride(), v.height())), + u_stride: u_view.as_ref().map_or(0, |v| v.stride()), + v_plane: v_view + .as_ref() + .map_or(&[], |v| trim_plane(v.as_slice(), v.stride(), v.height())), + v_stride: v_view.as_ref().map_or(0, |v| v.stride()), width: width as usize, height: height as usize, }; match matrix_strategy { YuvMatrixStrategy::KrKb(standard) => { - let worker = match self.picture.pixel_layout() { + let worker = match pixel_layout { PixelLayout::I400 => yuv400_to_rgba8, PixelLayout::I420 => yuv420_to_rgba8, PixelLayout::I422 => yuv422_to_rgba8, @@ -463,7 +354,7 @@ impl ImageDecoder for AvifDecoder { worker(image, buf, yuv_range, standard)?; } YuvMatrixStrategy::CgCo => { - let worker = match self.picture.pixel_layout() { + let worker = match pixel_layout { PixelLayout::I400 => unreachable!(), PixelLayout::I420 => ycgco420_to_rgba8, PixelLayout::I422 => ycgco422_to_rgba8, @@ -473,7 +364,7 @@ impl ImageDecoder for AvifDecoder { worker(image, buf, yuv_range)?; } YuvMatrixStrategy::Identity => { - let worker = match self.picture.pixel_layout() { + let worker = match pixel_layout { PixelLayout::I400 => unreachable!(), PixelLayout::I420 => unreachable!(), PixelLayout::I422 => unreachable!(), @@ -485,20 +376,24 @@ impl ImageDecoder for AvifDecoder { } // Squashing alpha plane into a picture - if let Some(picture) = self.alpha_picture { - if picture.pixel_layout() != PixelLayout::I400 { + if let Some(ref alpha_pic) = self.alpha_picture { + if alpha_pic.pixel_layout() != PixelLayout::I400 { return Err(ImageError::Decoding(DecodingError::new( ImageFormat::Avif.into(), - AvifDecoderError::AlphaPlaneFormat(picture.pixel_layout()), + AvifDecoderError::AlphaPlaneFormat(alpha_pic.pixel_layout()), ))); } - let stride = picture.stride(PlanarImageComponent::Y) as usize; - let plane = picture.plane(PlanarImageComponent::Y); + let alpha_planes = match alpha_pic.planes() { + rav1d_safe::Planes::Depth8(p) => p, + _ => unreachable!("alpha bit_depth mismatch"), + }; + let alpha_y = alpha_planes.y(); + let stride = alpha_y.stride(); for (buf, slice) in Iterator::zip( buf.chunks_exact_mut(width as usize * 4), - plane.as_ref().chunks_exact(stride), + alpha_y.as_slice().chunks_exact(stride), ) { for (rgba, a_src) in buf.as_chunks_mut::<4>().0.iter_mut().zip(slice) { rgba[3] = *a_src; @@ -506,14 +401,24 @@ impl ImageDecoder for AvifDecoder { } } } else { - // // 8+ bit-depth case + // 10/12 bit-depth case if let Ok(buf) = bytemuck::try_cast_slice_mut(buf) { let target_slice: &mut [u16] = buf; - self.process_16bit_picture(target_slice, yuv_range, matrix_strategy)?; + self.process_16bit_picture( + target_slice, + yuv_range, + matrix_strategy, + pixel_layout, + )?; } else { // If buffer from Decoder is unaligned let mut aligned_store = vec![0u16; buf.len() / 2]; - self.process_16bit_picture(&mut aligned_store, yuv_range, matrix_strategy)?; + self.process_16bit_picture( + &mut aligned_store, + yuv_range, + matrix_strategy, + pixel_layout, + )?; let buf_chunks = buf.as_chunks_mut::<2>().0.iter_mut(); for (dst, src) in buf_chunks.zip(aligned_store.iter()) { *dst = src.to_ne_bytes(); @@ -535,60 +440,38 @@ impl AvifDecoder { target: &mut [u16], yuv_range: YuvIntensityRange, matrix_strategy: YuvMatrixStrategy, + pixel_layout: PixelLayout, ) -> ImageResult<()> { - let y_dav1d_plane = self.picture.plane(PlanarImageComponent::Y); - let (width, height) = (self.picture.width(), self.picture.height()); let bit_depth = self.picture.bit_depth(); - // dav1d may return not aligned and not correctly constrained data, - // or at least I can't find guarantees on that - // so if it is happened, instead casting we'll need to reshape it into a target slice - // required criteria: bytemuck allows this align of this data, and stride must be dividable by 2 - - let y_plane_view = transmute_y_plane16( - &y_dav1d_plane, - self.picture.stride(PlanarImageComponent::Y) as usize, - width as usize, - height as usize, - ); - - let u_dav1d_plane = self.picture.plane(PlanarImageComponent::U); - let v_dav1d_plane = self.picture.plane(PlanarImageComponent::V); - let mut u_plane_view = Plane16View::default(); - let mut v_plane_view = Plane16View::default(); - - if self.picture.pixel_layout() != PixelLayout::I400 { - u_plane_view = transmute_chroma_plane16( - &u_dav1d_plane, - self.picture.pixel_layout(), - self.picture.stride(PlanarImageComponent::U) as usize, - width as usize, - height as usize, - ); - v_plane_view = transmute_chroma_plane16( - &v_dav1d_plane, - self.picture.pixel_layout(), - self.picture.stride(PlanarImageComponent::V) as usize, - width as usize, - height as usize, - ); - } + let planes = match self.picture.planes() { + rav1d_safe::Planes::Depth16(p) => p, + _ => unreachable!("bit_depth > 8 but planes are not 16-bit"), + }; + + let y_view = planes.y(); + let u_view = planes.u(); + let v_view = planes.v(); let image = YuvPlanarImage { - y_plane: y_plane_view.data.as_ref(), - y_stride: y_plane_view.stride, - u_plane: u_plane_view.data.as_ref(), - u_stride: u_plane_view.stride, - v_plane: v_plane_view.data.as_ref(), - v_stride: v_plane_view.stride, + y_plane: trim_plane(y_view.as_slice(), y_view.stride(), y_view.height()), + y_stride: y_view.stride(), + u_plane: u_view + .as_ref() + .map_or(&[], |v| trim_plane(v.as_slice(), v.stride(), v.height())), + u_stride: u_view.as_ref().map_or(0, |v| v.stride()), + v_plane: v_view + .as_ref() + .map_or(&[], |v| trim_plane(v.as_slice(), v.stride(), v.height())), + v_stride: v_view.as_ref().map_or(0, |v| v.stride()), width: width as usize, height: height as usize, }; match matrix_strategy { YuvMatrixStrategy::KrKb(standard) => { - let worker = match self.picture.pixel_layout() { + let worker = match pixel_layout { PixelLayout::I400 => { if bit_depth == 10 { yuv400_to_rgba10 @@ -621,7 +504,7 @@ impl AvifDecoder { worker(image, target, yuv_range, standard)?; } YuvMatrixStrategy::CgCo => { - let worker = match self.picture.pixel_layout() { + let worker = match pixel_layout { PixelLayout::I400 => unreachable!(), PixelLayout::I420 => { if bit_depth == 10 { @@ -648,7 +531,7 @@ impl AvifDecoder { worker(image, target, yuv_range)?; } YuvMatrixStrategy::Identity => { - let worker = match self.picture.pixel_layout() { + let worker = match pixel_layout { PixelLayout::I400 => unreachable!(), PixelLayout::I420 => unreachable!(), PixelLayout::I422 => unreachable!(), @@ -665,25 +548,24 @@ impl AvifDecoder { } // Squashing alpha plane into a picture - if let Some(picture) = &self.alpha_picture { - if picture.pixel_layout() != PixelLayout::I400 { + if let Some(ref alpha_pic) = self.alpha_picture { + if alpha_pic.pixel_layout() != PixelLayout::I400 { return Err(ImageError::Decoding(DecodingError::new( ImageFormat::Avif.into(), - AvifDecoderError::AlphaPlaneFormat(picture.pixel_layout()), + AvifDecoderError::AlphaPlaneFormat(alpha_pic.pixel_layout()), ))); } - let a_dav1d_plane = picture.plane(PlanarImageComponent::Y); - let a_plane_view = transmute_y_plane16( - &a_dav1d_plane, - picture.stride(PlanarImageComponent::Y) as usize, - width as usize, - height as usize, - ); + let alpha_planes = match alpha_pic.planes() { + rav1d_safe::Planes::Depth16(p) => p, + _ => unreachable!("alpha bit_depth mismatch for 16-bit"), + }; + let alpha_y = alpha_planes.y(); + let a_stride = alpha_y.stride(); for (buf, slice) in Iterator::zip( target.chunks_exact_mut(width as usize * 4), - a_plane_view.data.as_ref().chunks_exact(a_plane_view.stride), + alpha_y.as_slice().chunks_exact(a_stride), ) { for (rgba, a_src) in buf.as_chunks_mut::<4>().0.iter_mut().zip(slice) { rgba[3] = *a_src; @@ -700,19 +582,3 @@ impl AvifDecoder { Ok(()) } } - -/// `get_picture` and `send_pending_data` yield `Again` as a non-fatal error requesting more data is sent to the decoder -/// This ensures that in the case of `Again` all pending data is submitted -/// This should be called after `send_data` (which does not yield `Again` when called the first time) -fn read_until_ready(decoder: &mut dav1d::Decoder) -> ImageResult { - loop { - match decoder.get_picture() { - Err(dav1d::Error::Again) => match decoder.send_pending_data() { - Ok(()) => {} - Err(dav1d::Error::Again) => {} - Err(e) => return Err(error_map(e)), - }, - r => return r.map_err(error_map), - } - } -}