use core::ffi::c_int;
use ffmpeg_next::{Packet, ffi::AVPixelFormat};
use mediadecode::{
PixelFormat, Timestamp,
channel::AudioChannelLayout,
frame::{AudioFrame, Dimensions, Plane, SubtitleFrame, VideoFrame},
packet::{AudioPacket, PacketFlags as MdPacketFlags, SubtitlePacket, VideoPacket},
subtitle::SubtitlePayload,
};
use crate::{
FfmpegBuffer,
extras::{
AudioFrameExtra, AudioPacketExtra, SubtitleFrameExtra, SubtitlePacketExtra, VideoFrameExtra,
VideoPacketExtra,
},
sample_format::SampleFormat,
};
pub const fn from_av_pixel_format(raw: i32) -> PixelFormat {
match raw {
x if x == AVPixelFormat::AV_PIX_FMT_NV12 as i32 => PixelFormat::Nv12,
x if x == AVPixelFormat::AV_PIX_FMT_NV21 as i32 => PixelFormat::Nv21,
x if x == AVPixelFormat::AV_PIX_FMT_NV16 as i32 => PixelFormat::Nv16,
x if x == AVPixelFormat::AV_PIX_FMT_NV24 as i32 => PixelFormat::Nv24,
x if x == AVPixelFormat::AV_PIX_FMT_NV42 as i32 => PixelFormat::Nv42,
x if x == AVPixelFormat::AV_PIX_FMT_P010LE as i32 => PixelFormat::P010Le,
x if x == AVPixelFormat::AV_PIX_FMT_P010BE as i32 => PixelFormat::P010Be,
x if x == AVPixelFormat::AV_PIX_FMT_P012LE as i32 => PixelFormat::P012Le,
x if x == AVPixelFormat::AV_PIX_FMT_P016LE as i32 => PixelFormat::P016Le,
x if x == AVPixelFormat::AV_PIX_FMT_P210LE as i32 => PixelFormat::P210Le,
x if x == AVPixelFormat::AV_PIX_FMT_P212LE as i32 => PixelFormat::P212Le,
x if x == AVPixelFormat::AV_PIX_FMT_P216LE as i32 => PixelFormat::P216Le,
x if x == AVPixelFormat::AV_PIX_FMT_P410LE as i32 => PixelFormat::P410Le,
x if x == AVPixelFormat::AV_PIX_FMT_P412LE as i32 => PixelFormat::P412Le,
x if x == AVPixelFormat::AV_PIX_FMT_P416LE as i32 => PixelFormat::P416Le,
x if x == AVPixelFormat::AV_PIX_FMT_YUV420P as i32 => PixelFormat::Yuv420p,
x if x == AVPixelFormat::AV_PIX_FMT_YUV422P as i32 => PixelFormat::Yuv422p,
x if x == AVPixelFormat::AV_PIX_FMT_YUV440P as i32 => PixelFormat::Yuv440p,
x if x == AVPixelFormat::AV_PIX_FMT_YUV444P as i32 => PixelFormat::Yuv444p,
x if x == AVPixelFormat::AV_PIX_FMT_YUV411P as i32 => PixelFormat::Yuv411p,
x if x == AVPixelFormat::AV_PIX_FMT_YUV410P as i32 => PixelFormat::Yuv410p,
x if x == AVPixelFormat::AV_PIX_FMT_YUV420P9LE as i32 => PixelFormat::Yuv420p9Le,
x if x == AVPixelFormat::AV_PIX_FMT_YUV420P10LE as i32 => PixelFormat::Yuv420p10Le,
x if x == AVPixelFormat::AV_PIX_FMT_YUV420P12LE as i32 => PixelFormat::Yuv420p12Le,
x if x == AVPixelFormat::AV_PIX_FMT_YUV420P14LE as i32 => PixelFormat::Yuv420p14Le,
x if x == AVPixelFormat::AV_PIX_FMT_YUV420P16LE as i32 => PixelFormat::Yuv420p16Le,
x if x == AVPixelFormat::AV_PIX_FMT_YUV422P9LE as i32 => PixelFormat::Yuv422p9Le,
x if x == AVPixelFormat::AV_PIX_FMT_YUV422P10LE as i32 => PixelFormat::Yuv422p10Le,
x if x == AVPixelFormat::AV_PIX_FMT_YUV422P12LE as i32 => PixelFormat::Yuv422p12Le,
x if x == AVPixelFormat::AV_PIX_FMT_YUV422P14LE as i32 => PixelFormat::Yuv422p14Le,
x if x == AVPixelFormat::AV_PIX_FMT_YUV422P16LE as i32 => PixelFormat::Yuv422p16Le,
x if x == AVPixelFormat::AV_PIX_FMT_YUV444P9LE as i32 => PixelFormat::Yuv444p9Le,
x if x == AVPixelFormat::AV_PIX_FMT_YUV444P10LE as i32 => PixelFormat::Yuv444p10Le,
x if x == AVPixelFormat::AV_PIX_FMT_YUV444P12LE as i32 => PixelFormat::Yuv444p12Le,
x if x == AVPixelFormat::AV_PIX_FMT_YUV444P14LE as i32 => PixelFormat::Yuv444p14Le,
x if x == AVPixelFormat::AV_PIX_FMT_YUV444P16LE as i32 => PixelFormat::Yuv444p16Le,
x if x == AVPixelFormat::AV_PIX_FMT_YUVA420P as i32 => PixelFormat::Yuva420p,
x if x == AVPixelFormat::AV_PIX_FMT_YUVA422P as i32 => PixelFormat::Yuva422p,
x if x == AVPixelFormat::AV_PIX_FMT_YUVA444P as i32 => PixelFormat::Yuva444p,
x if x == AVPixelFormat::AV_PIX_FMT_YUYV422 as i32 => PixelFormat::Yuyv422,
x if x == AVPixelFormat::AV_PIX_FMT_UYVY422 as i32 => PixelFormat::Uyvy422,
x if x == AVPixelFormat::AV_PIX_FMT_YVYU422 as i32 => PixelFormat::Yvyu422,
x if x == AVPixelFormat::AV_PIX_FMT_RGB24 as i32 => PixelFormat::Rgb24,
x if x == AVPixelFormat::AV_PIX_FMT_BGR24 as i32 => PixelFormat::Bgr24,
x if x == AVPixelFormat::AV_PIX_FMT_RGBA as i32 => PixelFormat::Rgba,
x if x == AVPixelFormat::AV_PIX_FMT_BGRA as i32 => PixelFormat::Bgra,
x if x == AVPixelFormat::AV_PIX_FMT_ARGB as i32 => PixelFormat::Argb,
x if x == AVPixelFormat::AV_PIX_FMT_ABGR as i32 => PixelFormat::Abgr,
x if x == AVPixelFormat::AV_PIX_FMT_RGB48LE as i32 => PixelFormat::Rgb48Le,
x if x == AVPixelFormat::AV_PIX_FMT_BGR48LE as i32 => PixelFormat::Bgr48Le,
x if x == AVPixelFormat::AV_PIX_FMT_RGBA64LE as i32 => PixelFormat::Rgba64Le,
x if x == AVPixelFormat::AV_PIX_FMT_BGRA64LE as i32 => PixelFormat::Bgra64Le,
x if x == AVPixelFormat::AV_PIX_FMT_GRAY8 as i32 => PixelFormat::Gray8,
x if x == AVPixelFormat::AV_PIX_FMT_GRAY16LE as i32 => PixelFormat::Gray16Le,
x if x == AVPixelFormat::AV_PIX_FMT_BAYER_BGGR8 as i32 => PixelFormat::BayerBggr8,
x if x == AVPixelFormat::AV_PIX_FMT_BAYER_RGGB8 as i32 => PixelFormat::BayerRggb8,
x if x == AVPixelFormat::AV_PIX_FMT_BAYER_GBRG8 as i32 => PixelFormat::BayerGbrg8,
x if x == AVPixelFormat::AV_PIX_FMT_BAYER_GRBG8 as i32 => PixelFormat::BayerGrbg8,
x if x == AVPixelFormat::AV_PIX_FMT_BAYER_BGGR16LE as i32 => PixelFormat::BayerBggr16Le,
x if x == AVPixelFormat::AV_PIX_FMT_BAYER_RGGB16LE as i32 => PixelFormat::BayerRggb16Le,
x if x == AVPixelFormat::AV_PIX_FMT_BAYER_GBRG16LE as i32 => PixelFormat::BayerGbrg16Le,
x if x == AVPixelFormat::AV_PIX_FMT_BAYER_GRBG16LE as i32 => PixelFormat::BayerGrbg16Le,
_ => PixelFormat::Unknown(raw as u32),
}
}
pub const fn is_hardware_pix_fmt(raw: i32) -> bool {
matches!(
raw,
x if x == AVPixelFormat::AV_PIX_FMT_VIDEOTOOLBOX as i32
|| x == AVPixelFormat::AV_PIX_FMT_VAAPI as i32
|| x == AVPixelFormat::AV_PIX_FMT_CUDA as i32
|| x == AVPixelFormat::AV_PIX_FMT_D3D11 as i32
|| x == AVPixelFormat::AV_PIX_FMT_DRM_PRIME as i32
|| x == AVPixelFormat::AV_PIX_FMT_MEDIACODEC as i32
|| x == AVPixelFormat::AV_PIX_FMT_VULKAN as i32
)
}
fn try_packet_copy(data: &[u8]) -> std::result::Result<Packet, ffmpeg_next::Error> {
if data.len() > c_int::MAX as usize {
return Err(ffmpeg_next::Error::Other {
errno: libc::EINVAL,
});
}
let mut pkt = Packet::new(data.len());
match pkt.data_mut() {
Some(slot) if slot.len() == data.len() => {
if !data.is_empty() {
unsafe {
core::ptr::copy_nonoverlapping(data.as_ptr(), slot.as_mut_ptr(), data.len());
}
}
Ok(pkt)
}
_ => Err(ffmpeg_next::Error::Other {
errno: libc::ENOMEM,
}),
}
}
fn map_md_flags_to_av(flags: MdPacketFlags) -> ffmpeg_next::packet::Flags {
let mut av_flags = ffmpeg_next::packet::Flags::empty();
if flags.contains(MdPacketFlags::KEY) {
av_flags |= ffmpeg_next::packet::Flags::KEY;
}
if flags.contains(MdPacketFlags::CORRUPT) {
av_flags |= ffmpeg_next::packet::Flags::CORRUPT;
}
av_flags
}
pub fn ffmpeg_packet_from_video_packet(
packet: &mediadecode::packet::VideoPacket<VideoPacketExtra, FfmpegBuffer>,
) -> std::result::Result<Packet, ffmpeg_next::Error> {
let mut out = try_packet_copy(packet.data().as_ref())?;
if let Some(ts) = packet.pts() {
out.set_pts(Some(ts.pts()));
}
if let Some(ts) = packet.dts() {
out.set_dts(Some(ts.pts()));
}
if let Some(d) = packet.duration() {
out.set_duration(d.pts());
}
out.set_flags(map_md_flags_to_av(packet.flags()));
out.set_stream(packet.extra().stream_index() as usize);
Ok(out)
}
pub fn ffmpeg_packet_from_audio_packet(
packet: &mediadecode::packet::AudioPacket<AudioPacketExtra, FfmpegBuffer>,
) -> std::result::Result<Packet, ffmpeg_next::Error> {
let mut out = try_packet_copy(packet.data().as_ref())?;
if let Some(ts) = packet.pts() {
out.set_pts(Some(ts.pts()));
}
if let Some(ts) = packet.dts() {
out.set_dts(Some(ts.pts()));
}
if let Some(d) = packet.duration() {
out.set_duration(d.pts());
}
out.set_flags(map_md_flags_to_av(packet.flags()));
out.set_stream(packet.extra().stream_index() as usize);
Ok(out)
}
pub fn ffmpeg_packet_from_subtitle_packet(
packet: &mediadecode::packet::SubtitlePacket<SubtitlePacketExtra, FfmpegBuffer>,
) -> std::result::Result<Packet, ffmpeg_next::Error> {
let mut out = try_packet_copy(packet.data().as_ref())?;
if let Some(ts) = packet.pts() {
out.set_pts(Some(ts.pts()));
}
if let Some(d) = packet.duration() {
out.set_duration(d.pts());
}
out.set_flags(map_md_flags_to_av(packet.flags()));
out.set_stream(packet.extra().stream_index() as usize);
Ok(out)
}
pub fn video_packet_from_ffmpeg(
packet: &Packet,
) -> Option<VideoPacket<VideoPacketExtra, FfmpegBuffer>> {
let buf = FfmpegBuffer::from_packet(packet)?;
let mut out = VideoPacket::new(buf, VideoPacketExtra::new(packet.stream() as i32))
.with_flags(md_flags_from_av(packet.flags()));
if let Some(p) = packet.pts() {
out = out.with_pts(Some(Timestamp::new(p, mediadecode::Timebase::default())));
}
if let Some(d) = packet.dts() {
out = out.with_dts(Some(Timestamp::new(d, mediadecode::Timebase::default())));
}
let dur = packet.duration();
if dur > 0 {
out = out.with_duration(Some(Timestamp::new(dur, mediadecode::Timebase::default())));
}
Some(out)
}
pub fn audio_packet_from_ffmpeg(
packet: &Packet,
) -> Option<AudioPacket<AudioPacketExtra, FfmpegBuffer>> {
let buf = FfmpegBuffer::from_packet(packet)?;
let mut out = AudioPacket::new(buf, AudioPacketExtra::new(packet.stream() as i32))
.with_flags(md_flags_from_av(packet.flags()));
if let Some(p) = packet.pts() {
out = out.with_pts(Some(Timestamp::new(p, mediadecode::Timebase::default())));
}
if let Some(d) = packet.dts() {
out = out.with_dts(Some(Timestamp::new(d, mediadecode::Timebase::default())));
}
let dur = packet.duration();
if dur > 0 {
out = out.with_duration(Some(Timestamp::new(dur, mediadecode::Timebase::default())));
}
Some(out)
}
pub fn subtitle_packet_from_ffmpeg(
packet: &Packet,
) -> Option<SubtitlePacket<SubtitlePacketExtra, FfmpegBuffer>> {
let buf = FfmpegBuffer::from_packet(packet)?;
let mut out = SubtitlePacket::new(buf, SubtitlePacketExtra::new(packet.stream() as i32))
.with_flags(md_flags_from_av(packet.flags()));
if let Some(p) = packet.pts() {
out = out.with_pts(Some(Timestamp::new(p, mediadecode::Timebase::default())));
}
let dur = packet.duration();
if dur > 0 {
out = out.with_duration(Some(Timestamp::new(dur, mediadecode::Timebase::default())));
}
Some(out)
}
fn md_flags_from_av(flags: ffmpeg_next::packet::Flags) -> MdPacketFlags {
let mut out = MdPacketFlags::empty();
if flags.contains(ffmpeg_next::packet::Flags::KEY) {
out |= MdPacketFlags::KEY;
}
if flags.contains(ffmpeg_next::packet::Flags::CORRUPT) {
out |= MdPacketFlags::CORRUPT;
}
out
}
pub fn empty_video_frame() -> VideoFrame<PixelFormat, VideoFrameExtra, FfmpegBuffer> {
try_empty_video_frame().expect("empty_video_frame: av_buffer_alloc returned null (OOM)")
}
pub fn try_empty_video_frame() -> Option<VideoFrame<PixelFormat, VideoFrameExtra, FfmpegBuffer>> {
let planes = [
Plane::new(FfmpegBuffer::try_empty()?, 0),
Plane::new(FfmpegBuffer::try_empty()?, 0),
Plane::new(FfmpegBuffer::try_empty()?, 0),
Plane::new(FfmpegBuffer::try_empty()?, 0),
];
Some(VideoFrame::new(
Dimensions::new(0, 0),
PixelFormat::Unknown(0),
planes,
0,
VideoFrameExtra::default(),
))
}
pub fn empty_audio_frame()
-> AudioFrame<SampleFormat, AudioChannelLayout, AudioFrameExtra, FfmpegBuffer> {
try_empty_audio_frame().expect("empty_audio_frame: av_buffer_alloc returned null (OOM)")
}
pub fn try_empty_audio_frame()
-> Option<AudioFrame<SampleFormat, AudioChannelLayout, AudioFrameExtra, FfmpegBuffer>> {
let planes = [
Plane::new(FfmpegBuffer::try_empty()?, 0),
Plane::new(FfmpegBuffer::try_empty()?, 0),
Plane::new(FfmpegBuffer::try_empty()?, 0),
Plane::new(FfmpegBuffer::try_empty()?, 0),
Plane::new(FfmpegBuffer::try_empty()?, 0),
Plane::new(FfmpegBuffer::try_empty()?, 0),
Plane::new(FfmpegBuffer::try_empty()?, 0),
Plane::new(FfmpegBuffer::try_empty()?, 0),
];
Some(AudioFrame::new(
0,
0,
0,
SampleFormat::NONE,
AudioChannelLayout::default(),
planes,
0,
AudioFrameExtra::default(),
))
}
pub fn empty_subtitle_frame() -> SubtitleFrame<SubtitleFrameExtra, FfmpegBuffer> {
try_empty_subtitle_frame().expect("empty_subtitle_frame: av_buffer_alloc returned null (OOM)")
}
pub fn try_empty_subtitle_frame() -> Option<SubtitleFrame<SubtitleFrameExtra, FfmpegBuffer>> {
let buf = FfmpegBuffer::copy_from_slice(&[]).or_else(FfmpegBuffer::try_empty)?;
Some(SubtitleFrame::new(
SubtitlePayload::Text {
text: buf,
language: None,
},
SubtitleFrameExtra::default(),
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nv12_round_trips() {
assert_eq!(
from_av_pixel_format(AVPixelFormat::AV_PIX_FMT_NV12 as i32),
PixelFormat::Nv12,
);
}
#[test]
fn p010be_maps_to_p010be() {
assert_eq!(
from_av_pixel_format(AVPixelFormat::AV_PIX_FMT_P010BE as i32),
PixelFormat::P010Be,
);
}
#[test]
fn unknown_for_garbage_value() {
assert!(matches!(
from_av_pixel_format(-99_999),
PixelFormat::Unknown(_)
));
}
#[test]
fn hw_formats_detected() {
assert!(is_hardware_pix_fmt(
AVPixelFormat::AV_PIX_FMT_VIDEOTOOLBOX as i32
));
assert!(is_hardware_pix_fmt(AVPixelFormat::AV_PIX_FMT_VAAPI as i32));
assert!(is_hardware_pix_fmt(AVPixelFormat::AV_PIX_FMT_CUDA as i32));
assert!(is_hardware_pix_fmt(AVPixelFormat::AV_PIX_FMT_D3D11 as i32));
}
#[test]
fn cpu_formats_not_detected_as_hw() {
assert!(!is_hardware_pix_fmt(AVPixelFormat::AV_PIX_FMT_NV12 as i32));
assert!(!is_hardware_pix_fmt(
AVPixelFormat::AV_PIX_FMT_YUV420P as i32
));
assert!(!is_hardware_pix_fmt(AVPixelFormat::AV_PIX_FMT_NONE as i32));
}
#[test]
fn hw_formats_map_to_unknown_in_pixel_format() {
assert!(matches!(
from_av_pixel_format(AVPixelFormat::AV_PIX_FMT_VIDEOTOOLBOX as i32),
PixelFormat::Unknown(_)
));
assert!(matches!(
from_av_pixel_format(AVPixelFormat::AV_PIX_FMT_VAAPI as i32),
PixelFormat::Unknown(_)
));
}
}