use derive_more::{IsVariant, TryUnwrap, Unwrap};
use ffmpeg_next::{codec::Parameters, frame};
use mediadecode::{
PixelFormat, decoder::ImageDecoder, demuxer::AttachmentPacket, frame::ImageFrame,
packet::PacketFlags as MdPacketFlags,
};
use crate::{
DecoderLimits, Error, Ffmpeg, boundary,
convert::{self, ConvertError},
decoder::{build_codec_context, ensure_video_codec_type, find_decoder},
extras::{AttachmentPacketExtra, ImageFrameExtra},
frame::alloc_av_video_frame,
};
pub struct CarrierImageDecoder<C: crate::FfmpegCarrier> {
decoder: ffmpeg_next::decoder::Video,
scratch: frame::Video,
limits: DecoderLimits,
_callback_state: Box<crate::ffi::CallbackState>,
_carrier: core::marker::PhantomData<C>,
}
impl<C: crate::FfmpegCarrier + crate::CarrierOps> CarrierImageDecoder<C> {
pub(crate) fn open_impl(
parameters: Parameters,
limits: DecoderLimits,
) -> Result<Self, ImageDecodeError> {
let (ctx, callback_state) =
build_codec_context(¶meters, limits).map_err(ImageDecodeError::Decode)?;
let codec = find_decoder(¶meters).map_err(ImageDecodeError::Decode)?;
let opened = ctx
.decoder()
.open_as(codec)
.map_err(|e| ImageDecodeError::Decode(Error::Ffmpeg(e)))?;
ensure_video_codec_type(&opened).map_err(ImageDecodeError::Decode)?;
let decoder = ffmpeg_next::decoder::Video(opened);
let scratch = alloc_av_video_frame().map_err(ImageDecodeError::Decode)?;
Ok(Self {
decoder,
scratch,
limits,
_callback_state: callback_state,
_carrier: core::marker::PhantomData,
})
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) const fn limits_impl(&self) -> DecoderLimits {
self.limits
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) const fn inner_impl(&self) -> &ffmpeg_next::decoder::Video {
&self.decoder
}
}
impl<C: crate::FfmpegCarrier + crate::CarrierOps> CarrierImageDecoder<C> {
pub(crate) fn decode_impl(
&mut self,
packet: &AttachmentPacket<AttachmentPacketExtra, C::Buffer>,
) -> Result<ImageFrame<PixelFormat, ImageFrameExtra, C::Buffer>, ImageDecodeError> {
let bytes: &[u8] = packet.data().as_ref();
if bytes.is_empty() {
return Err(ImageDecodeError::EmptyPayload);
}
if bytes.len() > self.limits.max_image_input_bytes() {
return Err(ImageDecodeError::InputTooLarge(InputTooLarge::new(
bytes.len(),
self.limits.max_image_input_bytes(),
)));
}
if packet.flags().bits() & crate::buffer::TRUSTED_BIT != 0 {
return Err(ImageDecodeError::TrustedPayload(
crate::buffer::TrustedPayload::new(bytes.len()),
));
}
if packet.flags().contains(MdPacketFlags::CORRUPT) {
return Err(ImageDecodeError::Corrupt(Corrupt::new(
CorruptSource::Packet,
)));
}
self.decoder.flush();
let mut av_pkt =
boundary::try_packet_copy(bytes).map_err(|e| ImageDecodeError::Decode(Error::Ffmpeg(e)))?;
unsafe { boundary::write_md_flags(&mut av_pkt, packet.flags()) };
let state: *const crate::ffi::CallbackState = &*self._callback_state;
self
.decoder
.send_packet(&av_pkt)
.map_err(|e| ImageDecodeError::Decode(crate::decoder::software_exit(state, e)))?;
self
.decoder
.send_eof()
.map_err(|e| ImageDecodeError::Decode(crate::decoder::software_exit(state, e)))?;
match self.decoder.receive_frame(&mut self.scratch) {
Ok(()) => {}
Err(e)
if matches!(e, ffmpeg_next::Error::Eof)
|| matches!(e, ffmpeg_next::Error::Other { errno }
if errno == ffmpeg_next::error::EAGAIN) =>
{
self.decoder.flush();
return Err(ImageDecodeError::NoImage);
}
Err(e) => {
self.decoder.flush();
return Err(ImageDecodeError::Decode(crate::decoder::software_exit(
state, e,
)));
}
}
let frame_flags = unsafe { (*self.scratch.as_ptr()).flags };
let decode_error_flags = unsafe { (*self.scratch.as_ptr()).decode_error_flags };
if frame_is_corrupt(frame_flags, decode_error_flags) {
self.decoder.flush();
return Err(ImageDecodeError::Corrupt(Corrupt::new(
CorruptSource::DecodedFrame,
)));
}
let image = unsafe {
convert::av_frame_to_image_frame_as::<C>(self.scratch.as_ptr(), self.limits.frame())
}
.map_err(|e| {
self.decoder.flush();
ImageDecodeError::Convert(e)
})?;
self.decoder.flush();
Ok(image)
}
}
macro_rules! image_lane_face {
($($lane:ty),+ $(,)?) => { $(
impl CarrierImageDecoder<$lane> {
pub fn open(
parameters: Parameters,
limits: DecoderLimits,
) -> Result<Self, ImageDecodeError> {
Self::open_impl(parameters, limits)
}
pub const fn limits(&self) -> DecoderLimits {
self.limits_impl()
}
pub const fn inner(&self) -> &ffmpeg_next::decoder::Video {
self.inner_impl()
}
}
impl ImageDecoder for CarrierImageDecoder<$lane> {
type Adapter = Ffmpeg;
type Buffer = <$lane as crate::FfmpegCarrier>::Buffer;
type Error = ImageDecodeError;
fn decode(
&mut self,
packet: &AttachmentPacket<AttachmentPacketExtra, Self::Buffer>,
) -> Result<ImageFrame<PixelFormat, ImageFrameExtra, Self::Buffer>, Self::Error> {
self.decode_impl(packet)
}
}
)+ };
}
image_lane_face!(crate::View, crate::Owned);
#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
#[error("a {bytes}-byte image payload exceeds the {limit}-byte input ceiling")]
pub struct InputTooLarge {
bytes: usize,
limit: usize,
}
impl InputTooLarge {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(bytes: usize, limit: usize) -> Self {
Self { bytes, limit }
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn bytes(&self) -> usize {
self.bytes
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn limit(&self) -> usize {
self.limit
}
}
#[derive(thiserror::Error, Debug, Clone, IsVariant, Unwrap, TryUnwrap)]
#[unwrap(ref, ref_mut)]
#[try_unwrap(ref, ref_mut)]
pub enum ImageDecodeError {
#[error(transparent)]
InputTooLarge(#[from] InputTooLarge),
#[error(transparent)]
Decode(#[from] Error),
#[error(transparent)]
Convert(#[from] ConvertError),
#[error("the attachment carries no bytes; there is no image to decode")]
EmptyPayload,
#[error("the attachment's bytes decoded to no image")]
NoImage,
#[error(transparent)]
Corrupt(#[from] Corrupt),
#[error(transparent)]
TrustedPayload(#[from] crate::buffer::TrustedPayload),
}
#[inline]
const fn frame_is_corrupt(frame_flags: i32, decode_error_flags: i32) -> bool {
frame_flags & ffmpeg_next::ffi::AV_FRAME_FLAG_CORRUPT != 0 || decode_error_flags != 0
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, IsVariant)]
pub enum CorruptSource {
Packet,
DecodedFrame,
}
impl core::fmt::Display for CorruptSource {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Packet => f.write_str("the attachment packet"),
Self::DecodedFrame => f.write_str("the decoded frame"),
}
}
}
#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
#[error("{declared_by} is marked corrupt; this decoder will not return a picture from it")]
pub struct Corrupt {
declared_by: CorruptSource,
}
impl Corrupt {
#[inline]
pub const fn new(declared_by: CorruptSource) -> Self {
Self { declared_by }
}
#[inline]
pub const fn declared_by(&self) -> CorruptSource {
self.declared_by
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_corruption_gate_reads_both_fields() {
const CORRUPT: i32 = ffmpeg_next::ffi::AV_FRAME_FLAG_CORRUPT;
const KEY: i32 = 1 << 1;
assert!(!frame_is_corrupt(0, 0));
assert!(!frame_is_corrupt(KEY, 0));
assert!(frame_is_corrupt(CORRUPT, 0));
assert!(frame_is_corrupt(KEY | CORRUPT, 0));
assert!(frame_is_corrupt(
KEY,
ffmpeg_next::ffi::FF_DECODE_ERROR_CONCEALMENT_ACTIVE
));
assert!(frame_is_corrupt(
KEY,
ffmpeg_next::ffi::FF_DECODE_ERROR_INVALID_BITSTREAM
));
assert!(frame_is_corrupt(
KEY,
ffmpeg_next::ffi::FF_DECODE_ERROR_MISSING_REFERENCE
));
assert!(frame_is_corrupt(
KEY,
ffmpeg_next::ffi::FF_DECODE_ERROR_DECODE_SLICES
));
assert!(frame_is_corrupt(0, 1 << 30));
}
}