blob: 3db812057db7ed8b833239ede223fce305fea864 [file] [log] [blame]
// 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()) };
}
}
}