| // Copyright 2024 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| use crate::codecs::Decoder; |
| use crate::codecs::DecoderConfig; |
| use crate::decoder::CodecChoice; |
| use crate::decoder::GridImageHelper; |
| use crate::image::Image; |
| use crate::image::YuvRange; |
| use crate::internal_utils::pixels::*; |
| use crate::*; |
| |
| use dav1d_sys::bindings::*; |
| |
| use std::mem::MaybeUninit; |
| |
| #[derive(Default)] |
| pub struct Dav1d { |
| context: Option<*mut Dav1dContext>, |
| picture: Option<Dav1dPicture>, |
| config: Option<DecoderConfig>, |
| } |
| |
| unsafe extern "C" fn avif_dav1d_free_callback( |
| _buf: *const u8, |
| _cookie: *mut ::std::os::raw::c_void, |
| ) { |
| // Do nothing. The buffers are owned by the decoder. |
| } |
| |
| // See https://code.videolan.org/videolan/dav1d/-/blob/9849ede1304da1443cfb4a86f197765081034205/include/dav1d/common.h#L55-59 |
| const DAV1D_EAGAIN: i32 = if libc::EPERM > 0 { -libc::EAGAIN } else { libc::EAGAIN }; |
| |
| impl Dav1d { |
| fn initialize_impl(&mut self, low_latency: bool) -> AvifResult<()> { |
| if self.context.is_some() { |
| return Ok(()); |
| } |
| let config = self.config.unwrap_ref(); |
| let mut settings_uninit: MaybeUninit<Dav1dSettings> = MaybeUninit::uninit(); |
| unsafe { dav1d_default_settings(settings_uninit.as_mut_ptr()) }; |
| let mut settings = unsafe { settings_uninit.assume_init() }; |
| if low_latency { |
| settings.max_frame_delay = 1; |
| } |
| settings.n_threads = i32::try_from(config.max_threads).unwrap_or(1); |
| settings.operating_point = config.operating_point as i32; |
| settings.all_layers = if config.all_layers { 1 } else { 0 }; |
| let frame_size_limit = match config.image_size_limit { |
| Some(value) => value.get(), |
| None => 0, |
| }; |
| // Set a maximum frame size limit to avoid OOM'ing fuzzers. In 32-bit builds, if |
| // frame_size_limit > 8192 * 8192, dav1d reduces frame_size_limit to 8192 * 8192 and logs |
| // a message, so we set frame_size_limit to at most 8192 * 8192 to avoid the dav1d_log |
| // message. |
| settings.frame_size_limit = if cfg!(target_pointer_width = "32") { |
| std::cmp::min(frame_size_limit, 8192 * 8192) |
| } else { |
| frame_size_limit |
| }; |
| |
| let mut dec = MaybeUninit::uninit(); |
| let ret = unsafe { dav1d_open(dec.as_mut_ptr(), (&settings) as *const _) }; |
| if ret != 0 { |
| return Err(AvifError::UnknownError(format!( |
| "dav1d_open returned {ret}" |
| ))); |
| } |
| self.context = Some(unsafe { dec.assume_init() }); |
| Ok(()) |
| } |
| |
| fn picture_to_image( |
| &self, |
| dav1d_picture: &Dav1dPicture, |
| image: &mut Image, |
| category: Category, |
| ) -> AvifResult<()> { |
| match category { |
| Category::Alpha => { |
| if image.width > 0 |
| && image.height > 0 |
| && (image.width != (dav1d_picture.p.w as u32) |
| || image.height != (dav1d_picture.p.h as u32) |
| || image.depth != (dav1d_picture.p.bpc as u8)) |
| { |
| // Alpha plane does not match the previous alpha plane. |
| return Err(AvifError::UnknownError("".into())); |
| } |
| image.width = dav1d_picture.p.w as u32; |
| image.height = dav1d_picture.p.h as u32; |
| image.depth = dav1d_picture.p.bpc as u8; |
| image.row_bytes[3] = dav1d_picture.stride[0] as u32; |
| image.planes[3] = Some(Pixels::from_raw_pointer( |
| dav1d_picture.data[0] as *mut u8, |
| image.depth as u32, |
| image.height, |
| image.row_bytes[3], |
| )?); |
| image.image_owns_planes[3] = false; |
| let seq_hdr = unsafe { &(*dav1d_picture.seq_hdr) }; |
| image.yuv_range = |
| if seq_hdr.color_range == 0 { YuvRange::Limited } else { YuvRange::Full }; |
| } |
| _ => { |
| image.width = dav1d_picture.p.w as u32; |
| image.height = dav1d_picture.p.h as u32; |
| image.depth = dav1d_picture.p.bpc as u8; |
| |
| image.yuv_format = match dav1d_picture.p.layout { |
| 0 => PixelFormat::Yuv400, |
| 1 => PixelFormat::Yuv420, |
| 2 => PixelFormat::Yuv422, |
| 3 => PixelFormat::Yuv444, |
| _ => return Err(AvifError::UnknownError("".into())), // not reached. |
| }; |
| let seq_hdr = unsafe { &(*dav1d_picture.seq_hdr) }; |
| image.yuv_range = |
| if seq_hdr.color_range == 0 { YuvRange::Limited } else { YuvRange::Full }; |
| image.chroma_sample_position = (seq_hdr.chr as u32).into(); |
| |
| image.color_primaries = (seq_hdr.pri as u16).into(); |
| image.transfer_characteristics = (seq_hdr.trc as u16).into(); |
| image.matrix_coefficients = (seq_hdr.mtrx as u16).into(); |
| |
| for plane in 0usize..image.yuv_format.plane_count() { |
| let stride_index = if plane == 0 { 0 } else { 1 }; |
| image.row_bytes[plane] = dav1d_picture.stride[stride_index] as u32; |
| image.planes[plane] = Some(Pixels::from_raw_pointer( |
| dav1d_picture.data[plane] as *mut u8, |
| image.depth as u32, |
| image.height, |
| image.row_bytes[plane], |
| )?); |
| image.image_owns_planes[plane] = false; |
| } |
| if image.yuv_format == PixelFormat::Yuv400 { |
| // Clear left over chroma planes from previous frames. |
| image.clear_chroma_planes(); |
| } |
| } |
| } |
| Ok(()) |
| } |
| } |
| |
| // The type of the fields from dav1d_sys::bindings::* are dependent on the |
| // compiler that is used to generate the bindings, version of dav1d, etc. |
| // So allow clippy to ignore unnecessary cast warnings. |
| #[allow(clippy::unnecessary_cast)] |
| impl Decoder for Dav1d { |
| fn codec(&self) -> CodecChoice { |
| CodecChoice::Dav1d |
| } |
| |
| fn initialize(&mut self, config: &DecoderConfig) -> AvifResult<()> { |
| self.config = Some(config.clone()); |
| Ok(()) |
| } |
| |
| fn get_next_image( |
| &mut self, |
| av1_payload: &[u8], |
| spatial_id: u8, |
| image: &mut Image, |
| category: Category, |
| ) -> AvifResult<()> { |
| if self.context.is_none() { |
| self.initialize_impl(true)?; |
| } |
| unsafe { |
| let mut data: Dav1dData = std::mem::zeroed(); |
| let res = dav1d_data_wrap( |
| (&mut data) as *mut _, |
| av1_payload.as_ptr(), |
| av1_payload.len(), |
| Some(avif_dav1d_free_callback), |
| /*cookie=*/ std::ptr::null_mut(), |
| ); |
| if res != 0 { |
| return Err(AvifError::UnknownError(format!( |
| "dav1d_data_wrap returned {res}" |
| ))); |
| } |
| let mut next_frame: Dav1dPicture = std::mem::zeroed(); |
| let got_picture; |
| loop { |
| if !data.data.is_null() { |
| let res = dav1d_send_data(self.context.unwrap(), (&mut data) as *mut _); |
| if res < 0 && res != DAV1D_EAGAIN { |
| dav1d_data_unref((&mut data) as *mut _); |
| return Err(AvifError::UnknownError(format!( |
| "dav1d_send_data returned {res}" |
| ))); |
| } |
| } |
| |
| let res = dav1d_get_picture(self.context.unwrap(), (&mut next_frame) as *mut _); |
| if res == DAV1D_EAGAIN { |
| // send more data. |
| if !data.data.is_null() { |
| continue; |
| } |
| return Err(AvifError::UnknownError("".into())); |
| } else if res < 0 { |
| if !data.data.is_null() { |
| dav1d_data_unref((&mut data) as *mut _); |
| } |
| return Err(AvifError::UnknownError(format!( |
| "dav1d_send_picture returned {res}" |
| ))); |
| } else { |
| // Got a picture. |
| let frame_spatial_id = (*next_frame.frame_hdr).spatial_id as u8; |
| if spatial_id != 0xFF && spatial_id != frame_spatial_id { |
| // layer selection: skip this unwanted layer. |
| dav1d_picture_unref((&mut next_frame) as *mut _); |
| } else { |
| got_picture = true; |
| break; |
| } |
| } |
| } |
| if !data.data.is_null() { |
| dav1d_data_unref((&mut data) as *mut _); |
| } |
| |
| // Drain all buffered frames in the decoder. |
| // |
| // The sample should have only one frame of the desired layer. If there are more frames |
| // after that frame, we need to discard them so that they won't be mistakenly output |
| // when the decoder is used to decode another sample. |
| let mut buffered_frame: Dav1dPicture = std::mem::zeroed(); |
| loop { |
| let res = dav1d_get_picture(self.context.unwrap(), (&mut buffered_frame) as *mut _); |
| if res < 0 { |
| if res != DAV1D_EAGAIN { |
| if got_picture { |
| dav1d_picture_unref((&mut next_frame) as *mut _); |
| } |
| return Err(AvifError::UnknownError(format!( |
| "error draining buffered frames {res}" |
| ))); |
| } |
| } else { |
| dav1d_picture_unref((&mut buffered_frame) as *mut _); |
| } |
| if res != 0 { |
| break; |
| } |
| } |
| |
| if got_picture { |
| // unref previous frame. |
| if self.picture.is_some() { |
| let mut previous_picture = self.picture.unwrap(); |
| dav1d_picture_unref((&mut previous_picture) as *mut _); |
| } |
| self.picture = Some(next_frame); |
| } else if category == Category::Alpha && self.picture.is_some() { |
| // Special case for alpha, re-use last frame. |
| } else { |
| return Err(AvifError::UnknownError("".into())); |
| } |
| } |
| self.picture_to_image(self.picture.unwrap_ref(), image, category)?; |
| Ok(()) |
| } |
| |
| fn get_next_image_grid( |
| &mut self, |
| _payloads: &[Vec<u8>], |
| _spatial_id: u8, |
| _grid_image_helper: &mut GridImageHelper, |
| ) -> AvifResult<()> { |
| Err(AvifError::NotImplemented) |
| } |
| } |
| |
| impl Drop for Dav1d { |
| fn drop(&mut self) { |
| if self.picture.is_some() { |
| unsafe { dav1d_picture_unref(self.picture.unwrap_mut() as *mut _) }; |
| } |
| if self.context.is_some() { |
| unsafe { dav1d_close(&mut self.context.unwrap()) }; |
| } |
| } |
| } |