1#![allow(unsafe_code)]
8#![allow(clippy::similar_names)]
10#![allow(clippy::too_many_lines)]
11#![allow(clippy::cast_sign_loss)]
12#![allow(clippy::cast_possible_truncation)]
13#![allow(clippy::cast_possible_wrap)]
14#![allow(clippy::module_name_repetitions)]
15#![allow(clippy::ptr_as_ptr)]
16#![allow(clippy::doc_markdown)]
17#![allow(clippy::unnecessary_cast)]
18#![allow(clippy::cast_precision_loss)]
19#![allow(clippy::cast_lossless)]
20
21use std::path::Path;
22use std::ptr;
23
24use ff_format::time::{Rational, Timestamp};
25use ff_format::{PixelFormat, PooledBuffer, VideoFrame};
26use ff_sys::{
27 AVCodecContext, AVCodecID, AVFormatContext, AVFrame, AVMediaType_AVMEDIA_TYPE_VIDEO, AVPacket,
28 AVPixelFormat,
29};
30
31use crate::error::DecodeError;
32
33struct AvFormatContextGuard(*mut AVFormatContext);
37
38impl AvFormatContextGuard {
39 unsafe fn new(path: &Path) -> Result<Self, DecodeError> {
40 let format_ctx = unsafe {
42 ff_sys::avformat::open_input(path).map_err(|e| DecodeError::Ffmpeg {
43 code: e,
44 message: format!("Failed to open file: {}", ff_sys::av_error_string(e)),
45 })?
46 };
47 Ok(Self(format_ctx))
48 }
49
50 const fn as_ptr(&self) -> *mut AVFormatContext {
51 self.0
52 }
53
54 fn into_raw(self) -> *mut AVFormatContext {
55 let ptr = self.0;
56 std::mem::forget(self);
57 ptr
58 }
59}
60
61impl Drop for AvFormatContextGuard {
62 fn drop(&mut self) {
63 if !self.0.is_null() {
64 unsafe {
66 ff_sys::avformat::close_input(&mut (self.0 as *mut _));
67 }
68 }
69 }
70}
71
72struct AvCodecContextGuard(*mut AVCodecContext);
74
75impl AvCodecContextGuard {
76 unsafe fn new(codec: *const ff_sys::AVCodec) -> Result<Self, DecodeError> {
77 let codec_ctx = unsafe {
79 ff_sys::avcodec::alloc_context3(codec).map_err(|e| DecodeError::Ffmpeg {
80 code: e,
81 message: format!("Failed to allocate codec context: {e}"),
82 })?
83 };
84 Ok(Self(codec_ctx))
85 }
86
87 const fn as_ptr(&self) -> *mut AVCodecContext {
88 self.0
89 }
90
91 fn into_raw(self) -> *mut AVCodecContext {
92 let ptr = self.0;
93 std::mem::forget(self);
94 ptr
95 }
96}
97
98impl Drop for AvCodecContextGuard {
99 fn drop(&mut self) {
100 if !self.0.is_null() {
101 unsafe {
103 ff_sys::avcodec::free_context(&mut (self.0 as *mut _));
104 }
105 }
106 }
107}
108
109pub(crate) struct ImageDecoderInner {
115 format_ctx: *mut AVFormatContext,
117 codec_ctx: *mut AVCodecContext,
119 stream_index: usize,
121 packet: *mut AVPacket,
123 frame: *mut AVFrame,
125}
126
127unsafe impl Send for ImageDecoderInner {}
131
132impl ImageDecoderInner {
133 pub(crate) fn new(path: &Path) -> Result<Self, DecodeError> {
144 ff_sys::ensure_initialized();
145
146 let format_ctx_guard = unsafe { AvFormatContextGuard::new(path)? };
149 let format_ctx = format_ctx_guard.as_ptr();
150
151 unsafe {
154 ff_sys::avformat::find_stream_info(format_ctx).map_err(|e| DecodeError::Ffmpeg {
155 code: e,
156 message: format!("Failed to find stream info: {}", ff_sys::av_error_string(e)),
157 })?;
158 }
159
160 let (stream_index, codec_id) =
163 unsafe { Self::find_video_stream(format_ctx) }.ok_or_else(|| {
164 DecodeError::NoVideoStream {
165 path: path.to_path_buf(),
166 }
167 })?;
168
169 let codec = unsafe {
172 ff_sys::avcodec::find_decoder(codec_id).ok_or_else(|| {
173 DecodeError::UnsupportedCodec {
174 codec: format!("codec_id={codec_id:?}"),
175 }
176 })?
177 };
178
179 let codec_ctx_guard = unsafe { AvCodecContextGuard::new(codec)? };
182 let codec_ctx = codec_ctx_guard.as_ptr();
183
184 unsafe {
187 let stream = (*format_ctx).streams.add(stream_index);
188 let codecpar = (*(*stream)).codecpar;
189 ff_sys::avcodec::parameters_to_context(codec_ctx, codecpar).map_err(|e| {
190 DecodeError::Ffmpeg {
191 code: e,
192 message: format!(
193 "Failed to copy codec parameters: {}",
194 ff_sys::av_error_string(e)
195 ),
196 }
197 })?;
198 }
199
200 unsafe {
203 ff_sys::avcodec::open2(codec_ctx, codec, ptr::null_mut()).map_err(|e| {
204 DecodeError::Ffmpeg {
205 code: e,
206 message: format!("Failed to open codec: {}", ff_sys::av_error_string(e)),
207 }
208 })?;
209 }
210
211 let packet = unsafe { ff_sys::av_packet_alloc() };
214 if packet.is_null() {
215 return Err(DecodeError::Ffmpeg {
216 code: 0,
217 message: "Failed to allocate packet".to_string(),
218 });
219 }
220 let frame = unsafe { ff_sys::av_frame_alloc() };
221 if frame.is_null() {
222 unsafe { ff_sys::av_packet_free(&mut (packet as *mut _)) };
223 return Err(DecodeError::Ffmpeg {
224 code: 0,
225 message: "Failed to allocate frame".to_string(),
226 });
227 }
228
229 Ok(Self {
230 format_ctx: format_ctx_guard.into_raw(),
231 codec_ctx: codec_ctx_guard.into_raw(),
232 stream_index,
233 packet,
234 frame,
235 })
236 }
237
238 pub(crate) fn width(&self) -> u32 {
240 unsafe { (*self.codec_ctx).width as u32 }
242 }
243
244 pub(crate) fn height(&self) -> u32 {
246 unsafe { (*self.codec_ctx).height as u32 }
248 }
249
250 pub(crate) fn decode(self) -> Result<VideoFrame, DecodeError> {
258 let ret = unsafe { ff_sys::av_read_frame(self.format_ctx, self.packet) };
261 if ret < 0 {
262 return Err(DecodeError::Ffmpeg {
263 code: ret,
264 message: format!("Failed to read frame: {}", ff_sys::av_error_string(ret)),
265 });
266 }
267
268 let ret = unsafe { ff_sys::avcodec_send_packet(self.codec_ctx, self.packet) };
271 unsafe { ff_sys::av_packet_unref(self.packet) };
272 if ret < 0 {
273 return Err(DecodeError::Ffmpeg {
274 code: ret,
275 message: format!(
276 "Failed to send packet to decoder: {}",
277 ff_sys::av_error_string(ret)
278 ),
279 });
280 }
281
282 let ret = unsafe { ff_sys::avcodec_receive_frame(self.codec_ctx, self.frame) };
285 if ret < 0 {
286 return Err(DecodeError::Ffmpeg {
287 code: ret,
288 message: format!(
289 "Failed to receive decoded frame: {}",
290 ff_sys::av_error_string(ret)
291 ),
292 });
293 }
294
295 let video_frame = unsafe { self.av_frame_to_video_frame(self.frame)? };
298 Ok(video_frame)
299 }
300
301 unsafe fn find_video_stream(format_ctx: *mut AVFormatContext) -> Option<(usize, AVCodecID)> {
307 unsafe {
309 let nb_streams = (*format_ctx).nb_streams as usize;
310 for i in 0..nb_streams {
311 let stream = (*format_ctx).streams.add(i);
312 let codecpar = (*(*stream)).codecpar;
313 if (*codecpar).codec_type == AVMediaType_AVMEDIA_TYPE_VIDEO {
314 return Some((i, (*codecpar).codec_id));
315 }
316 }
317 }
318 None
319 }
320
321 fn convert_pixel_format(fmt: AVPixelFormat) -> PixelFormat {
328 if fmt == ff_sys::AVPixelFormat_AV_PIX_FMT_YUV420P
329 || fmt == ff_sys::AVPixelFormat_AV_PIX_FMT_YUVJ420P
330 {
331 PixelFormat::Yuv420p
332 } else if fmt == ff_sys::AVPixelFormat_AV_PIX_FMT_YUV422P
333 || fmt == ff_sys::AVPixelFormat_AV_PIX_FMT_YUVJ422P
334 {
335 PixelFormat::Yuv422p
336 } else if fmt == ff_sys::AVPixelFormat_AV_PIX_FMT_YUV444P
337 || fmt == ff_sys::AVPixelFormat_AV_PIX_FMT_YUVJ444P
338 {
339 PixelFormat::Yuv444p
340 } else if fmt == ff_sys::AVPixelFormat_AV_PIX_FMT_RGB24 {
341 PixelFormat::Rgb24
342 } else if fmt == ff_sys::AVPixelFormat_AV_PIX_FMT_BGR24 {
343 PixelFormat::Bgr24
344 } else if fmt == ff_sys::AVPixelFormat_AV_PIX_FMT_RGBA {
345 PixelFormat::Rgba
346 } else if fmt == ff_sys::AVPixelFormat_AV_PIX_FMT_BGRA {
347 PixelFormat::Bgra
348 } else if fmt == ff_sys::AVPixelFormat_AV_PIX_FMT_GRAY8 {
349 PixelFormat::Gray8
350 } else {
351 log::warn!(
352 "pixel_format unsupported, falling back to Rgb24 requested={fmt} fallback=Rgb24"
353 );
354 PixelFormat::Rgb24
355 }
356 }
357
358 unsafe fn av_frame_to_video_frame(
364 &self,
365 frame: *const AVFrame,
366 ) -> Result<VideoFrame, DecodeError> {
367 unsafe {
369 let width = (*frame).width as u32;
370 let height = (*frame).height as u32;
371 let format = Self::convert_pixel_format((*frame).format);
372
373 let pts = (*frame).pts;
375 let timestamp = if pts == ff_sys::AV_NOPTS_VALUE {
376 Timestamp::default()
377 } else {
378 let stream = (*self.format_ctx).streams.add(self.stream_index);
379 let time_base = (*(*stream)).time_base;
380 Timestamp::new(
381 pts as i64,
382 Rational::new(time_base.num as i32, time_base.den as i32),
383 )
384 };
385
386 let (planes, strides) = Self::extract_planes_and_strides(frame, width, height, format)?;
387
388 VideoFrame::new(planes, strides, width, height, format, timestamp, true).map_err(|e| {
390 DecodeError::Ffmpeg {
391 code: 0,
392 message: format!("Failed to create VideoFrame: {e}"),
393 }
394 })
395 }
396 }
397
398 unsafe fn extract_planes_and_strides(
407 frame: *const AVFrame,
408 width: u32,
409 height: u32,
410 format: PixelFormat,
411 ) -> Result<(Vec<PooledBuffer>, Vec<usize>), DecodeError> {
412 unsafe {
414 let w = width as usize;
415 let h = height as usize;
416 let mut planes: Vec<PooledBuffer> = Vec::new();
417 let mut strides: Vec<usize> = Vec::new();
418
419 match format {
420 PixelFormat::Rgba | PixelFormat::Bgra => {
421 let bytes_per_pixel = 4_usize;
422 let stride = (*frame).linesize[0] as usize;
423 let row_w = w * bytes_per_pixel;
424 let mut buf = vec![0u8; row_w * h];
425 let src = (*frame).data[0];
426 if src.is_null() {
427 return Err(DecodeError::Ffmpeg {
428 code: 0,
429 message: "Null plane data for packed format".to_string(),
430 });
431 }
432 for row in 0..h {
433 ptr::copy_nonoverlapping(
434 src.add(row * stride),
435 buf[row * row_w..].as_mut_ptr(),
436 row_w,
437 );
438 }
439 planes.push(PooledBuffer::standalone(buf));
440 strides.push(row_w);
441 }
442 PixelFormat::Rgb24 | PixelFormat::Bgr24 => {
443 let bytes_per_pixel = 3_usize;
444 let stride = (*frame).linesize[0] as usize;
445 let row_w = w * bytes_per_pixel;
446 let mut buf = vec![0u8; row_w * h];
447 let src = (*frame).data[0];
448 if src.is_null() {
449 return Err(DecodeError::Ffmpeg {
450 code: 0,
451 message: "Null plane data for packed format".to_string(),
452 });
453 }
454 for row in 0..h {
455 ptr::copy_nonoverlapping(
456 src.add(row * stride),
457 buf[row * row_w..].as_mut_ptr(),
458 row_w,
459 );
460 }
461 planes.push(PooledBuffer::standalone(buf));
462 strides.push(row_w);
463 }
464 PixelFormat::Gray8 => {
465 let stride = (*frame).linesize[0] as usize;
466 let mut buf = vec![0u8; w * h];
467 let src = (*frame).data[0];
468 if src.is_null() {
469 return Err(DecodeError::Ffmpeg {
470 code: 0,
471 message: "Null plane data for Gray8".to_string(),
472 });
473 }
474 for row in 0..h {
475 ptr::copy_nonoverlapping(
476 src.add(row * stride),
477 buf[row * w..].as_mut_ptr(),
478 w,
479 );
480 }
481 planes.push(PooledBuffer::standalone(buf));
482 strides.push(w);
483 }
484 PixelFormat::Yuv420p | PixelFormat::Nv12 | PixelFormat::Nv21 => {
485 let y_stride = (*frame).linesize[0] as usize;
487 let mut y_buf = vec![0u8; w * h];
488 let y_src = (*frame).data[0];
489 if y_src.is_null() {
490 return Err(DecodeError::Ffmpeg {
491 code: 0,
492 message: "Null Y plane".to_string(),
493 });
494 }
495 for row in 0..h {
496 ptr::copy_nonoverlapping(
497 y_src.add(row * y_stride),
498 y_buf[row * w..].as_mut_ptr(),
499 w,
500 );
501 }
502 planes.push(PooledBuffer::standalone(y_buf));
503 strides.push(w);
504
505 if matches!(format, PixelFormat::Nv12 | PixelFormat::Nv21) {
506 let uv_h = h / 2;
508 let uv_stride = (*frame).linesize[1] as usize;
509 let mut uv_buf = vec![0u8; w * uv_h];
510 let uv_src = (*frame).data[1];
511 if !uv_src.is_null() {
512 for row in 0..uv_h {
513 ptr::copy_nonoverlapping(
514 uv_src.add(row * uv_stride),
515 uv_buf[row * w..].as_mut_ptr(),
516 w,
517 );
518 }
519 }
520 planes.push(PooledBuffer::standalone(uv_buf));
521 strides.push(w);
522 } else {
523 let uv_w = w / 2;
525 let uv_h = h / 2;
526 for plane_idx in 1..=2usize {
527 let uv_stride = (*frame).linesize[plane_idx] as usize;
528 let mut uv_buf = vec![0u8; uv_w * uv_h];
529 let uv_src = (*frame).data[plane_idx];
530 if !uv_src.is_null() {
531 for row in 0..uv_h {
532 ptr::copy_nonoverlapping(
533 uv_src.add(row * uv_stride),
534 uv_buf[row * uv_w..].as_mut_ptr(),
535 uv_w,
536 );
537 }
538 }
539 planes.push(PooledBuffer::standalone(uv_buf));
540 strides.push(uv_w);
541 }
542 }
543 }
544 PixelFormat::Yuv422p => {
545 let uv_w = w / 2;
547 let plane_dims = [(w, h), (uv_w, h), (uv_w, h)];
548 for (plane_idx, (pw, ph)) in plane_dims.iter().enumerate() {
549 let stride = (*frame).linesize[plane_idx] as usize;
550 let mut buf = vec![0u8; pw * ph];
551 let src = (*frame).data[plane_idx];
552 if !src.is_null() {
553 for row in 0..*ph {
554 ptr::copy_nonoverlapping(
555 src.add(row * stride),
556 buf[row * pw..].as_mut_ptr(),
557 *pw,
558 );
559 }
560 }
561 planes.push(PooledBuffer::standalone(buf));
562 strides.push(*pw);
563 }
564 }
565 PixelFormat::Yuv444p => {
566 for plane_idx in 0..3usize {
568 let stride = (*frame).linesize[plane_idx] as usize;
569 let mut buf = vec![0u8; w * h];
570 let src = (*frame).data[plane_idx];
571 if !src.is_null() {
572 for row in 0..h {
573 ptr::copy_nonoverlapping(
574 src.add(row * stride),
575 buf[row * w..].as_mut_ptr(),
576 w,
577 );
578 }
579 }
580 planes.push(PooledBuffer::standalone(buf));
581 strides.push(w);
582 }
583 }
584 _ => {
585 return Err(DecodeError::Ffmpeg {
586 code: 0,
587 message: format!("Unsupported pixel format for image decoding: {format:?}"),
588 });
589 }
590 }
591
592 Ok((planes, strides))
593 }
594 }
595}
596
597impl Drop for ImageDecoderInner {
598 fn drop(&mut self) {
599 unsafe {
602 if !self.frame.is_null() {
603 ff_sys::av_frame_free(&mut (self.frame as *mut _));
604 }
605 if !self.packet.is_null() {
606 ff_sys::av_packet_free(&mut (self.packet as *mut _));
607 }
608 if !self.codec_ctx.is_null() {
609 ff_sys::avcodec::free_context(&mut (self.codec_ctx as *mut _));
610 }
611 if !self.format_ctx.is_null() {
612 ff_sys::avformat::close_input(&mut (self.format_ctx as *mut _));
613 }
614 }
615 }
616}
617
618#[cfg(test)]
619mod tests {
620 use super::*;
621
622 #[test]
623 fn convert_pixel_format_yuv420p_should_map_to_yuv420p() {
624 assert_eq!(
625 ImageDecoderInner::convert_pixel_format(ff_sys::AVPixelFormat_AV_PIX_FMT_YUV420P),
626 PixelFormat::Yuv420p
627 );
628 }
629
630 #[test]
631 fn convert_pixel_format_yuvj420p_should_map_to_yuv420p() {
632 assert_eq!(
633 ImageDecoderInner::convert_pixel_format(ff_sys::AVPixelFormat_AV_PIX_FMT_YUVJ420P),
634 PixelFormat::Yuv420p
635 );
636 }
637
638 #[test]
639 fn convert_pixel_format_rgb24_should_map_to_rgb24() {
640 assert_eq!(
641 ImageDecoderInner::convert_pixel_format(ff_sys::AVPixelFormat_AV_PIX_FMT_RGB24),
642 PixelFormat::Rgb24
643 );
644 }
645
646 #[test]
647 fn convert_pixel_format_rgba_should_map_to_rgba() {
648 assert_eq!(
649 ImageDecoderInner::convert_pixel_format(ff_sys::AVPixelFormat_AV_PIX_FMT_RGBA),
650 PixelFormat::Rgba
651 );
652 }
653
654 #[test]
655 fn convert_pixel_format_gray8_should_map_to_gray8() {
656 assert_eq!(
657 ImageDecoderInner::convert_pixel_format(ff_sys::AVPixelFormat_AV_PIX_FMT_GRAY8),
658 PixelFormat::Gray8
659 );
660 }
661}