1use core::ffi::c_int;
10
11use ffmpeg_next::{Packet, ffi::AVPixelFormat};
12use mediadecode::{
13 PixelFormat, Timestamp,
14 channel::AudioChannelLayout,
15 frame::{AudioFrame, Dimensions, Plane, SubtitleFrame, VideoFrame},
16 packet::{AudioPacket, PacketFlags as MdPacketFlags, SubtitlePacket, VideoPacket},
17 subtitle::SubtitlePayload,
18};
19
20use crate::{
21 FfmpegBuffer,
22 extras::{
23 AudioFrameExtra, AudioPacketExtra, SubtitleFrameExtra, SubtitlePacketExtra, VideoFrameExtra,
24 VideoPacketExtra,
25 },
26 sample_format::SampleFormat,
27};
28
29pub const fn from_av_pixel_format(raw: i32) -> PixelFormat {
44 match raw {
45 x if x == AVPixelFormat::AV_PIX_FMT_NV12 as i32 => PixelFormat::Nv12,
47 x if x == AVPixelFormat::AV_PIX_FMT_NV21 as i32 => PixelFormat::Nv21,
48 x if x == AVPixelFormat::AV_PIX_FMT_NV16 as i32 => PixelFormat::Nv16,
49 x if x == AVPixelFormat::AV_PIX_FMT_NV24 as i32 => PixelFormat::Nv24,
50 x if x == AVPixelFormat::AV_PIX_FMT_NV42 as i32 => PixelFormat::Nv42,
51 x if x == AVPixelFormat::AV_PIX_FMT_P010LE as i32 => PixelFormat::P010Le,
53 x if x == AVPixelFormat::AV_PIX_FMT_P010BE as i32 => PixelFormat::P010Be,
64 x if x == AVPixelFormat::AV_PIX_FMT_P012LE as i32 => PixelFormat::P012Le,
65 x if x == AVPixelFormat::AV_PIX_FMT_P016LE as i32 => PixelFormat::P016Le,
66 x if x == AVPixelFormat::AV_PIX_FMT_P210LE as i32 => PixelFormat::P210Le,
67 x if x == AVPixelFormat::AV_PIX_FMT_P212LE as i32 => PixelFormat::P212Le,
68 x if x == AVPixelFormat::AV_PIX_FMT_P216LE as i32 => PixelFormat::P216Le,
69 x if x == AVPixelFormat::AV_PIX_FMT_P410LE as i32 => PixelFormat::P410Le,
70 x if x == AVPixelFormat::AV_PIX_FMT_P412LE as i32 => PixelFormat::P412Le,
71 x if x == AVPixelFormat::AV_PIX_FMT_P416LE as i32 => PixelFormat::P416Le,
72 x if x == AVPixelFormat::AV_PIX_FMT_YUV420P as i32 => PixelFormat::Yuv420p,
74 x if x == AVPixelFormat::AV_PIX_FMT_YUV422P as i32 => PixelFormat::Yuv422p,
75 x if x == AVPixelFormat::AV_PIX_FMT_YUV440P as i32 => PixelFormat::Yuv440p,
76 x if x == AVPixelFormat::AV_PIX_FMT_YUV444P as i32 => PixelFormat::Yuv444p,
77 x if x == AVPixelFormat::AV_PIX_FMT_YUV411P as i32 => PixelFormat::Yuv411p,
78 x if x == AVPixelFormat::AV_PIX_FMT_YUV410P as i32 => PixelFormat::Yuv410p,
79 x if x == AVPixelFormat::AV_PIX_FMT_YUV420P9LE as i32 => PixelFormat::Yuv420p9Le,
81 x if x == AVPixelFormat::AV_PIX_FMT_YUV420P10LE as i32 => PixelFormat::Yuv420p10Le,
82 x if x == AVPixelFormat::AV_PIX_FMT_YUV420P12LE as i32 => PixelFormat::Yuv420p12Le,
83 x if x == AVPixelFormat::AV_PIX_FMT_YUV420P14LE as i32 => PixelFormat::Yuv420p14Le,
84 x if x == AVPixelFormat::AV_PIX_FMT_YUV420P16LE as i32 => PixelFormat::Yuv420p16Le,
85 x if x == AVPixelFormat::AV_PIX_FMT_YUV422P9LE as i32 => PixelFormat::Yuv422p9Le,
87 x if x == AVPixelFormat::AV_PIX_FMT_YUV422P10LE as i32 => PixelFormat::Yuv422p10Le,
88 x if x == AVPixelFormat::AV_PIX_FMT_YUV422P12LE as i32 => PixelFormat::Yuv422p12Le,
89 x if x == AVPixelFormat::AV_PIX_FMT_YUV422P14LE as i32 => PixelFormat::Yuv422p14Le,
90 x if x == AVPixelFormat::AV_PIX_FMT_YUV422P16LE as i32 => PixelFormat::Yuv422p16Le,
91 x if x == AVPixelFormat::AV_PIX_FMT_YUV444P9LE as i32 => PixelFormat::Yuv444p9Le,
93 x if x == AVPixelFormat::AV_PIX_FMT_YUV444P10LE as i32 => PixelFormat::Yuv444p10Le,
94 x if x == AVPixelFormat::AV_PIX_FMT_YUV444P12LE as i32 => PixelFormat::Yuv444p12Le,
95 x if x == AVPixelFormat::AV_PIX_FMT_YUV444P14LE as i32 => PixelFormat::Yuv444p14Le,
96 x if x == AVPixelFormat::AV_PIX_FMT_YUV444P16LE as i32 => PixelFormat::Yuv444p16Le,
97 x if x == AVPixelFormat::AV_PIX_FMT_YUVA420P as i32 => PixelFormat::Yuva420p,
99 x if x == AVPixelFormat::AV_PIX_FMT_YUVA422P as i32 => PixelFormat::Yuva422p,
100 x if x == AVPixelFormat::AV_PIX_FMT_YUVA444P as i32 => PixelFormat::Yuva444p,
101 x if x == AVPixelFormat::AV_PIX_FMT_YUYV422 as i32 => PixelFormat::Yuyv422,
103 x if x == AVPixelFormat::AV_PIX_FMT_UYVY422 as i32 => PixelFormat::Uyvy422,
104 x if x == AVPixelFormat::AV_PIX_FMT_YVYU422 as i32 => PixelFormat::Yvyu422,
105 x if x == AVPixelFormat::AV_PIX_FMT_RGB24 as i32 => PixelFormat::Rgb24,
107 x if x == AVPixelFormat::AV_PIX_FMT_BGR24 as i32 => PixelFormat::Bgr24,
108 x if x == AVPixelFormat::AV_PIX_FMT_RGBA as i32 => PixelFormat::Rgba,
109 x if x == AVPixelFormat::AV_PIX_FMT_BGRA as i32 => PixelFormat::Bgra,
110 x if x == AVPixelFormat::AV_PIX_FMT_ARGB as i32 => PixelFormat::Argb,
111 x if x == AVPixelFormat::AV_PIX_FMT_ABGR as i32 => PixelFormat::Abgr,
112 x if x == AVPixelFormat::AV_PIX_FMT_RGB48LE as i32 => PixelFormat::Rgb48Le,
114 x if x == AVPixelFormat::AV_PIX_FMT_BGR48LE as i32 => PixelFormat::Bgr48Le,
115 x if x == AVPixelFormat::AV_PIX_FMT_RGBA64LE as i32 => PixelFormat::Rgba64Le,
116 x if x == AVPixelFormat::AV_PIX_FMT_BGRA64LE as i32 => PixelFormat::Bgra64Le,
117 x if x == AVPixelFormat::AV_PIX_FMT_GRAY8 as i32 => PixelFormat::Gray8,
119 x if x == AVPixelFormat::AV_PIX_FMT_GRAY16LE as i32 => PixelFormat::Gray16Le,
120 x if x == AVPixelFormat::AV_PIX_FMT_BAYER_BGGR8 as i32 => PixelFormat::BayerBggr8,
122 x if x == AVPixelFormat::AV_PIX_FMT_BAYER_RGGB8 as i32 => PixelFormat::BayerRggb8,
123 x if x == AVPixelFormat::AV_PIX_FMT_BAYER_GBRG8 as i32 => PixelFormat::BayerGbrg8,
124 x if x == AVPixelFormat::AV_PIX_FMT_BAYER_GRBG8 as i32 => PixelFormat::BayerGrbg8,
125 x if x == AVPixelFormat::AV_PIX_FMT_BAYER_BGGR16LE as i32 => PixelFormat::BayerBggr16Le,
126 x if x == AVPixelFormat::AV_PIX_FMT_BAYER_RGGB16LE as i32 => PixelFormat::BayerRggb16Le,
127 x if x == AVPixelFormat::AV_PIX_FMT_BAYER_GBRG16LE as i32 => PixelFormat::BayerGbrg16Le,
128 x if x == AVPixelFormat::AV_PIX_FMT_BAYER_GRBG16LE as i32 => PixelFormat::BayerGrbg16Le,
129 _ => PixelFormat::Unknown(raw as u32),
130 }
131}
132
133pub const fn is_hardware_pix_fmt(raw: i32) -> bool {
139 matches!(
140 raw,
141 x if x == AVPixelFormat::AV_PIX_FMT_VIDEOTOOLBOX as i32
142 || x == AVPixelFormat::AV_PIX_FMT_VAAPI as i32
143 || x == AVPixelFormat::AV_PIX_FMT_CUDA as i32
144 || x == AVPixelFormat::AV_PIX_FMT_D3D11 as i32
145 || x == AVPixelFormat::AV_PIX_FMT_DRM_PRIME as i32
146 || x == AVPixelFormat::AV_PIX_FMT_MEDIACODEC as i32
147 || x == AVPixelFormat::AV_PIX_FMT_VULKAN as i32
148 )
149}
150
151fn try_packet_copy(data: &[u8]) -> std::result::Result<Packet, ffmpeg_next::Error> {
168 if data.len() > c_int::MAX as usize {
172 return Err(ffmpeg_next::Error::Other {
173 errno: libc::EINVAL,
174 });
175 }
176 let mut pkt = Packet::new(data.len());
184 match pkt.data_mut() {
185 Some(slot) if slot.len() == data.len() => {
186 if !data.is_empty() {
190 unsafe {
191 core::ptr::copy_nonoverlapping(data.as_ptr(), slot.as_mut_ptr(), data.len());
192 }
193 }
194 Ok(pkt)
195 }
196 _ => Err(ffmpeg_next::Error::Other {
197 errno: libc::ENOMEM,
198 }),
199 }
200}
201
202fn map_md_flags_to_av(flags: MdPacketFlags) -> ffmpeg_next::packet::Flags {
205 let mut av_flags = ffmpeg_next::packet::Flags::empty();
206 if flags.contains(MdPacketFlags::KEY) {
207 av_flags |= ffmpeg_next::packet::Flags::KEY;
208 }
209 if flags.contains(MdPacketFlags::CORRUPT) {
210 av_flags |= ffmpeg_next::packet::Flags::CORRUPT;
211 }
212 av_flags
218}
219
220pub fn ffmpeg_packet_from_video_packet(
234 packet: &mediadecode::packet::VideoPacket<VideoPacketExtra, FfmpegBuffer>,
235) -> std::result::Result<Packet, ffmpeg_next::Error> {
236 let mut out = try_packet_copy(packet.data().as_ref())?;
237 if let Some(ts) = packet.pts() {
238 out.set_pts(Some(ts.pts()));
239 }
240 if let Some(ts) = packet.dts() {
241 out.set_dts(Some(ts.pts()));
242 }
243 if let Some(d) = packet.duration() {
244 out.set_duration(d.pts());
245 }
246 out.set_flags(map_md_flags_to_av(packet.flags()));
247 out.set_stream(packet.extra().stream_index() as usize);
248 Ok(out)
249}
250
251pub fn ffmpeg_packet_from_audio_packet(
256 packet: &mediadecode::packet::AudioPacket<AudioPacketExtra, FfmpegBuffer>,
257) -> std::result::Result<Packet, ffmpeg_next::Error> {
258 let mut out = try_packet_copy(packet.data().as_ref())?;
259 if let Some(ts) = packet.pts() {
260 out.set_pts(Some(ts.pts()));
261 }
262 if let Some(ts) = packet.dts() {
263 out.set_dts(Some(ts.pts()));
264 }
265 if let Some(d) = packet.duration() {
266 out.set_duration(d.pts());
267 }
268 out.set_flags(map_md_flags_to_av(packet.flags()));
269 out.set_stream(packet.extra().stream_index() as usize);
270 Ok(out)
271}
272
273pub fn ffmpeg_packet_from_subtitle_packet(
278 packet: &mediadecode::packet::SubtitlePacket<SubtitlePacketExtra, FfmpegBuffer>,
279) -> std::result::Result<Packet, ffmpeg_next::Error> {
280 let mut out = try_packet_copy(packet.data().as_ref())?;
281 if let Some(ts) = packet.pts() {
282 out.set_pts(Some(ts.pts()));
283 }
284 if let Some(d) = packet.duration() {
285 out.set_duration(d.pts());
286 }
287 out.set_flags(map_md_flags_to_av(packet.flags()));
288 out.set_stream(packet.extra().stream_index() as usize);
289 Ok(out)
290}
291
292pub fn video_packet_from_ffmpeg(
307 packet: &Packet,
308) -> Option<VideoPacket<VideoPacketExtra, FfmpegBuffer>> {
309 let buf = FfmpegBuffer::from_packet(packet)?;
310 let mut out = VideoPacket::new(buf, VideoPacketExtra::new(packet.stream() as i32))
311 .with_flags(md_flags_from_av(packet.flags()));
312 if let Some(p) = packet.pts() {
313 out = out.with_pts(Some(Timestamp::new(p, mediadecode::Timebase::default())));
314 }
315 if let Some(d) = packet.dts() {
316 out = out.with_dts(Some(Timestamp::new(d, mediadecode::Timebase::default())));
317 }
318 let dur = packet.duration();
319 if dur > 0 {
320 out = out.with_duration(Some(Timestamp::new(dur, mediadecode::Timebase::default())));
321 }
322 Some(out)
323}
324
325pub fn audio_packet_from_ffmpeg(
330 packet: &Packet,
331) -> Option<AudioPacket<AudioPacketExtra, FfmpegBuffer>> {
332 let buf = FfmpegBuffer::from_packet(packet)?;
333 let mut out = AudioPacket::new(buf, AudioPacketExtra::new(packet.stream() as i32))
334 .with_flags(md_flags_from_av(packet.flags()));
335 if let Some(p) = packet.pts() {
336 out = out.with_pts(Some(Timestamp::new(p, mediadecode::Timebase::default())));
337 }
338 if let Some(d) = packet.dts() {
339 out = out.with_dts(Some(Timestamp::new(d, mediadecode::Timebase::default())));
340 }
341 let dur = packet.duration();
342 if dur > 0 {
343 out = out.with_duration(Some(Timestamp::new(dur, mediadecode::Timebase::default())));
344 }
345 Some(out)
346}
347
348pub fn subtitle_packet_from_ffmpeg(
353 packet: &Packet,
354) -> Option<SubtitlePacket<SubtitlePacketExtra, FfmpegBuffer>> {
355 let buf = FfmpegBuffer::from_packet(packet)?;
356 let mut out = SubtitlePacket::new(buf, SubtitlePacketExtra::new(packet.stream() as i32))
357 .with_flags(md_flags_from_av(packet.flags()));
358 if let Some(p) = packet.pts() {
359 out = out.with_pts(Some(Timestamp::new(p, mediadecode::Timebase::default())));
360 }
361 let dur = packet.duration();
362 if dur > 0 {
363 out = out.with_duration(Some(Timestamp::new(dur, mediadecode::Timebase::default())));
364 }
365 Some(out)
366}
367
368fn md_flags_from_av(flags: ffmpeg_next::packet::Flags) -> MdPacketFlags {
369 let mut out = MdPacketFlags::empty();
370 if flags.contains(ffmpeg_next::packet::Flags::KEY) {
371 out |= MdPacketFlags::KEY;
372 }
373 if flags.contains(ffmpeg_next::packet::Flags::CORRUPT) {
374 out |= MdPacketFlags::CORRUPT;
375 }
376 out
377}
378
379pub fn empty_video_frame() -> VideoFrame<PixelFormat, VideoFrameExtra, FfmpegBuffer> {
399 try_empty_video_frame().expect("empty_video_frame: av_buffer_alloc returned null (OOM)")
400}
401
402pub fn try_empty_video_frame() -> Option<VideoFrame<PixelFormat, VideoFrameExtra, FfmpegBuffer>> {
405 let planes = [
406 Plane::new(FfmpegBuffer::try_empty()?, 0),
407 Plane::new(FfmpegBuffer::try_empty()?, 0),
408 Plane::new(FfmpegBuffer::try_empty()?, 0),
409 Plane::new(FfmpegBuffer::try_empty()?, 0),
410 ];
411 Some(VideoFrame::new(
412 Dimensions::new(0, 0),
413 PixelFormat::Unknown(0),
414 planes,
415 0,
416 VideoFrameExtra::default(),
417 ))
418}
419
420pub fn empty_audio_frame()
431-> AudioFrame<SampleFormat, AudioChannelLayout, AudioFrameExtra, FfmpegBuffer> {
432 try_empty_audio_frame().expect("empty_audio_frame: av_buffer_alloc returned null (OOM)")
433}
434
435pub fn try_empty_audio_frame()
438-> Option<AudioFrame<SampleFormat, AudioChannelLayout, AudioFrameExtra, FfmpegBuffer>> {
439 let planes = [
440 Plane::new(FfmpegBuffer::try_empty()?, 0),
441 Plane::new(FfmpegBuffer::try_empty()?, 0),
442 Plane::new(FfmpegBuffer::try_empty()?, 0),
443 Plane::new(FfmpegBuffer::try_empty()?, 0),
444 Plane::new(FfmpegBuffer::try_empty()?, 0),
445 Plane::new(FfmpegBuffer::try_empty()?, 0),
446 Plane::new(FfmpegBuffer::try_empty()?, 0),
447 Plane::new(FfmpegBuffer::try_empty()?, 0),
448 ];
449 Some(AudioFrame::new(
450 0,
451 0,
452 0,
453 SampleFormat::NONE,
454 AudioChannelLayout::default(),
455 planes,
456 0,
457 AudioFrameExtra::default(),
458 ))
459}
460
461pub fn empty_subtitle_frame() -> SubtitleFrame<SubtitleFrameExtra, FfmpegBuffer> {
472 try_empty_subtitle_frame().expect("empty_subtitle_frame: av_buffer_alloc returned null (OOM)")
473}
474
475pub fn try_empty_subtitle_frame() -> Option<SubtitleFrame<SubtitleFrameExtra, FfmpegBuffer>> {
478 let buf = FfmpegBuffer::copy_from_slice(&[]).or_else(FfmpegBuffer::try_empty)?;
479 Some(SubtitleFrame::new(
480 SubtitlePayload::Text {
481 text: buf,
482 language: None,
483 },
484 SubtitleFrameExtra::default(),
485 ))
486}
487
488#[cfg(test)]
489mod tests {
490 use super::*;
491
492 #[test]
493 fn nv12_round_trips() {
494 assert_eq!(
495 from_av_pixel_format(AVPixelFormat::AV_PIX_FMT_NV12 as i32),
496 PixelFormat::Nv12,
497 );
498 }
499
500 #[test]
501 fn p010be_maps_to_p010be() {
502 assert_eq!(
507 from_av_pixel_format(AVPixelFormat::AV_PIX_FMT_P010BE as i32),
508 PixelFormat::P010Be,
509 );
510 }
511
512 #[test]
513 fn unknown_for_garbage_value() {
514 assert!(matches!(
515 from_av_pixel_format(-99_999),
516 PixelFormat::Unknown(_)
517 ));
518 }
519
520 #[test]
521 fn hw_formats_detected() {
522 assert!(is_hardware_pix_fmt(
523 AVPixelFormat::AV_PIX_FMT_VIDEOTOOLBOX as i32
524 ));
525 assert!(is_hardware_pix_fmt(AVPixelFormat::AV_PIX_FMT_VAAPI as i32));
526 assert!(is_hardware_pix_fmt(AVPixelFormat::AV_PIX_FMT_CUDA as i32));
527 assert!(is_hardware_pix_fmt(AVPixelFormat::AV_PIX_FMT_D3D11 as i32));
528 }
529
530 #[test]
531 fn cpu_formats_not_detected_as_hw() {
532 assert!(!is_hardware_pix_fmt(AVPixelFormat::AV_PIX_FMT_NV12 as i32));
533 assert!(!is_hardware_pix_fmt(
534 AVPixelFormat::AV_PIX_FMT_YUV420P as i32
535 ));
536 assert!(!is_hardware_pix_fmt(AVPixelFormat::AV_PIX_FMT_NONE as i32));
537 }
538
539 #[test]
540 fn hw_formats_map_to_unknown_in_pixel_format() {
541 assert!(matches!(
544 from_av_pixel_format(AVPixelFormat::AV_PIX_FMT_VIDEOTOOLBOX as i32),
545 PixelFormat::Unknown(_)
546 ));
547 assert!(matches!(
548 from_av_pixel_format(AVPixelFormat::AV_PIX_FMT_VAAPI as i32),
549 PixelFormat::Unknown(_)
550 ));
551 }
552}