Skip to main content

ff_decode/audio/
decoder_inner.rs

1//! Internal audio decoder implementation using FFmpeg.
2//!
3//! This module contains the low-level decoder logic that directly interacts
4//! with FFmpeg's C API through the ff-sys crate. It is not exposed publicly.
5
6// Allow unsafe code in this module as it's necessary for FFmpeg FFI
7#![allow(unsafe_code)]
8// Allow specific clippy lints for FFmpeg FFI code
9#![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::match_same_arms)]
16#![allow(clippy::ptr_as_ptr)]
17#![allow(clippy::doc_markdown)]
18#![allow(clippy::unnecessary_cast)]
19#![allow(clippy::if_not_else)]
20#![allow(clippy::unnecessary_wraps)]
21#![allow(clippy::cast_precision_loss)]
22#![allow(clippy::if_same_then_else)]
23#![allow(clippy::cast_lossless)]
24
25use std::ffi::CStr;
26use std::path::Path;
27use std::ptr;
28use std::time::Duration;
29
30use ff_format::channel::ChannelLayout;
31use ff_format::codec::AudioCodec;
32use ff_format::time::{Rational, Timestamp};
33use ff_format::{AudioFrame, AudioStreamInfo, SampleFormat};
34use ff_sys::{
35    AVCodecContext, AVCodecID, AVFormatContext, AVFrame, AVMediaType_AVMEDIA_TYPE_AUDIO, AVPacket,
36    AVSampleFormat, SwrContext,
37};
38
39use crate::error::DecodeError;
40
41/// RAII guard for `AVFormatContext` to ensure proper cleanup.
42struct AvFormatContextGuard(*mut AVFormatContext);
43
44impl AvFormatContextGuard {
45    /// Creates a new guard by opening an input file.
46    ///
47    /// # Safety
48    ///
49    /// Caller must ensure FFmpeg is initialized and path is valid.
50    unsafe fn new(path: &Path) -> Result<Self, DecodeError> {
51        // SAFETY: Caller ensures FFmpeg is initialized and path is valid
52        let format_ctx = unsafe {
53            ff_sys::avformat::open_input(path).map_err(|e| DecodeError::Ffmpeg {
54                code: e,
55                message: format!("Failed to open file: {}", ff_sys::av_error_string(e)),
56            })?
57        };
58        Ok(Self(format_ctx))
59    }
60
61    /// Returns the raw pointer.
62    const fn as_ptr(&self) -> *mut AVFormatContext {
63        self.0
64    }
65
66    /// Consumes the guard and returns the raw pointer without dropping.
67    fn into_raw(self) -> *mut AVFormatContext {
68        let ptr = self.0;
69        std::mem::forget(self);
70        ptr
71    }
72}
73
74impl Drop for AvFormatContextGuard {
75    fn drop(&mut self) {
76        if !self.0.is_null() {
77            // SAFETY: self.0 is valid and owned by this guard
78            unsafe {
79                ff_sys::avformat::close_input(&mut (self.0 as *mut _));
80            }
81        }
82    }
83}
84
85/// RAII guard for `AVCodecContext` to ensure proper cleanup.
86struct AvCodecContextGuard(*mut AVCodecContext);
87
88impl AvCodecContextGuard {
89    /// Creates a new guard by allocating a codec context.
90    ///
91    /// # Safety
92    ///
93    /// Caller must ensure codec pointer is valid.
94    unsafe fn new(codec: *const ff_sys::AVCodec) -> Result<Self, DecodeError> {
95        // SAFETY: Caller ensures codec pointer is valid
96        let codec_ctx = unsafe {
97            ff_sys::avcodec::alloc_context3(codec).map_err(|e| DecodeError::Ffmpeg {
98                code: e,
99                message: format!("Failed to allocate codec context: {e}"),
100            })?
101        };
102        Ok(Self(codec_ctx))
103    }
104
105    /// Returns the raw pointer.
106    const fn as_ptr(&self) -> *mut AVCodecContext {
107        self.0
108    }
109
110    /// Consumes the guard and returns the raw pointer without dropping.
111    fn into_raw(self) -> *mut AVCodecContext {
112        let ptr = self.0;
113        std::mem::forget(self);
114        ptr
115    }
116}
117
118impl Drop for AvCodecContextGuard {
119    fn drop(&mut self) {
120        if !self.0.is_null() {
121            // SAFETY: self.0 is valid and owned by this guard
122            unsafe {
123                ff_sys::avcodec::free_context(&mut (self.0 as *mut _));
124            }
125        }
126    }
127}
128
129/// RAII guard for `AVPacket` to ensure proper cleanup.
130struct AvPacketGuard(*mut AVPacket);
131
132impl AvPacketGuard {
133    /// Creates a new guard by allocating a packet.
134    ///
135    /// # Safety
136    ///
137    /// Must be called after FFmpeg initialization.
138    unsafe fn new() -> Result<Self, DecodeError> {
139        // SAFETY: Caller ensures FFmpeg is initialized
140        let packet = unsafe { ff_sys::av_packet_alloc() };
141        if packet.is_null() {
142            return Err(DecodeError::Ffmpeg {
143                code: 0,
144                message: "Failed to allocate packet".to_string(),
145            });
146        }
147        Ok(Self(packet))
148    }
149
150    /// Consumes the guard and returns the raw pointer without dropping.
151    fn into_raw(self) -> *mut AVPacket {
152        let ptr = self.0;
153        std::mem::forget(self);
154        ptr
155    }
156}
157
158impl Drop for AvPacketGuard {
159    fn drop(&mut self) {
160        if !self.0.is_null() {
161            // SAFETY: self.0 is valid and owned by this guard
162            unsafe {
163                ff_sys::av_packet_free(&mut (self.0 as *mut _));
164            }
165        }
166    }
167}
168
169/// RAII guard for `AVFrame` to ensure proper cleanup.
170struct AvFrameGuard(*mut AVFrame);
171
172impl AvFrameGuard {
173    /// Creates a new guard by allocating a frame.
174    ///
175    /// # Safety
176    ///
177    /// Must be called after FFmpeg initialization.
178    unsafe fn new() -> Result<Self, DecodeError> {
179        // SAFETY: Caller ensures FFmpeg is initialized
180        let frame = unsafe { ff_sys::av_frame_alloc() };
181        if frame.is_null() {
182            return Err(DecodeError::Ffmpeg {
183                code: 0,
184                message: "Failed to allocate frame".to_string(),
185            });
186        }
187        Ok(Self(frame))
188    }
189
190    /// Consumes the guard and returns the raw pointer without dropping.
191    fn into_raw(self) -> *mut AVFrame {
192        let ptr = self.0;
193        std::mem::forget(self);
194        ptr
195    }
196}
197
198impl Drop for AvFrameGuard {
199    fn drop(&mut self) {
200        if !self.0.is_null() {
201            // SAFETY: self.0 is valid and owned by this guard
202            unsafe {
203                ff_sys::av_frame_free(&mut (self.0 as *mut _));
204            }
205        }
206    }
207}
208
209/// RAII guard for `SwrContext` to ensure proper cleanup.
210struct SwrContextGuard(*mut SwrContext);
211
212impl SwrContextGuard {
213    /// Consumes the guard and returns the raw pointer without dropping.
214    #[allow(dead_code)]
215    fn into_raw(self) -> *mut SwrContext {
216        let ptr = self.0;
217        std::mem::forget(self);
218        ptr
219    }
220}
221
222impl Drop for SwrContextGuard {
223    fn drop(&mut self) {
224        if !self.0.is_null() {
225            // SAFETY: self.0 is valid and owned by this guard
226            unsafe {
227                ff_sys::swr_free(&mut (self.0 as *mut _));
228            }
229        }
230    }
231}
232
233/// Internal decoder state holding FFmpeg contexts.
234///
235/// This structure manages the lifecycle of FFmpeg objects and is responsible
236/// for proper cleanup when dropped.
237pub(crate) struct AudioDecoderInner {
238    /// Format context for reading the media file
239    format_ctx: *mut AVFormatContext,
240    /// Codec context for decoding audio frames
241    codec_ctx: *mut AVCodecContext,
242    /// Audio stream index in the format context
243    stream_index: i32,
244    /// SwResample context for sample format conversion (optional)
245    swr_ctx: Option<*mut SwrContext>,
246    /// Target output sample format (if conversion is needed)
247    output_format: Option<SampleFormat>,
248    /// Target output sample rate (if resampling is needed)
249    output_sample_rate: Option<u32>,
250    /// Target output channel count (if remixing is needed)
251    output_channels: Option<u32>,
252    /// Whether end of file has been reached
253    eof: bool,
254    /// Current playback position
255    position: Duration,
256    /// Reusable packet for reading from file
257    packet: *mut AVPacket,
258    /// Reusable frame for decoding
259    frame: *mut AVFrame,
260}
261
262impl AudioDecoderInner {
263    /// Opens a media file and initializes the audio decoder.
264    ///
265    /// # Arguments
266    ///
267    /// * `path` - Path to the media file
268    /// * `output_format` - Optional target sample format for conversion
269    /// * `output_sample_rate` - Optional target sample rate for resampling
270    /// * `output_channels` - Optional target channel count for remixing
271    ///
272    /// # Errors
273    ///
274    /// Returns an error if:
275    /// - The file cannot be opened
276    /// - No audio stream is found
277    /// - The codec is not supported
278    /// - Decoder initialization fails
279    pub(crate) fn new(
280        path: &Path,
281        output_format: Option<SampleFormat>,
282        output_sample_rate: Option<u32>,
283        output_channels: Option<u32>,
284    ) -> Result<(Self, AudioStreamInfo), DecodeError> {
285        // Ensure FFmpeg is initialized (thread-safe and idempotent)
286        ff_sys::ensure_initialized();
287
288        // Open the input file (with RAII guard)
289        // SAFETY: Path is valid, AvFormatContextGuard ensures cleanup
290        let format_ctx_guard = unsafe { AvFormatContextGuard::new(path)? };
291        let format_ctx = format_ctx_guard.as_ptr();
292
293        // Read stream information
294        // SAFETY: format_ctx is valid and owned by guard
295        unsafe {
296            ff_sys::avformat::find_stream_info(format_ctx).map_err(|e| DecodeError::Ffmpeg {
297                code: e,
298                message: format!("Failed to find stream info: {}", ff_sys::av_error_string(e)),
299            })?;
300        }
301
302        // Find the audio stream
303        // SAFETY: format_ctx is valid
304        let (stream_index, codec_id) =
305            unsafe { Self::find_audio_stream(format_ctx) }.ok_or_else(|| {
306                DecodeError::NoAudioStream {
307                    path: path.to_path_buf(),
308                }
309            })?;
310
311        // Find the decoder for this codec
312        // SAFETY: codec_id is valid from FFmpeg
313        let codec_name = unsafe { Self::extract_codec_name(codec_id) };
314        let codec = unsafe {
315            ff_sys::avcodec::find_decoder(codec_id).ok_or_else(|| {
316                DecodeError::UnsupportedCodec {
317                    codec: format!("{codec_name} (codec_id={codec_id:?})"),
318                }
319            })?
320        };
321
322        // Allocate codec context (with RAII guard)
323        // SAFETY: codec pointer is valid, AvCodecContextGuard ensures cleanup
324        let codec_ctx_guard = unsafe { AvCodecContextGuard::new(codec)? };
325        let codec_ctx = codec_ctx_guard.as_ptr();
326
327        // Copy codec parameters from stream to context
328        // SAFETY: format_ctx and codec_ctx are valid, stream_index is valid
329        unsafe {
330            let stream = (*format_ctx).streams.add(stream_index as usize);
331            let codecpar = (*(*stream)).codecpar;
332            ff_sys::avcodec::parameters_to_context(codec_ctx, codecpar).map_err(|e| {
333                DecodeError::Ffmpeg {
334                    code: e,
335                    message: format!(
336                        "Failed to copy codec parameters: {}",
337                        ff_sys::av_error_string(e)
338                    ),
339                }
340            })?;
341        }
342
343        // Open the codec
344        // SAFETY: codec_ctx and codec are valid
345        unsafe {
346            ff_sys::avcodec::open2(codec_ctx, codec, ptr::null_mut()).map_err(|e| {
347                DecodeError::Ffmpeg {
348                    code: e,
349                    message: format!("Failed to open codec: {}", ff_sys::av_error_string(e)),
350                }
351            })?;
352        }
353
354        // Extract stream information
355        // SAFETY: All pointers are valid
356        let stream_info =
357            unsafe { Self::extract_stream_info(format_ctx, stream_index as i32, codec_ctx)? };
358
359        // Allocate packet and frame (with RAII guards)
360        // SAFETY: FFmpeg is initialized, guards ensure cleanup
361        let packet_guard = unsafe { AvPacketGuard::new()? };
362        let frame_guard = unsafe { AvFrameGuard::new()? };
363
364        // All initialization successful - transfer ownership to AudioDecoderInner
365        Ok((
366            Self {
367                format_ctx: format_ctx_guard.into_raw(),
368                codec_ctx: codec_ctx_guard.into_raw(),
369                stream_index: stream_index as i32,
370                swr_ctx: None,
371                output_format,
372                output_sample_rate,
373                output_channels,
374                eof: false,
375                position: Duration::ZERO,
376                packet: packet_guard.into_raw(),
377                frame: frame_guard.into_raw(),
378            },
379            stream_info,
380        ))
381    }
382
383    /// Finds the first audio stream in the format context.
384    ///
385    /// # Returns
386    ///
387    /// Returns `Some((index, codec_id))` if an audio stream is found, `None` otherwise.
388    ///
389    /// # Safety
390    ///
391    /// Caller must ensure `format_ctx` is valid and initialized.
392    unsafe fn find_audio_stream(format_ctx: *mut AVFormatContext) -> Option<(usize, AVCodecID)> {
393        // SAFETY: Caller ensures format_ctx is valid
394        unsafe {
395            let nb_streams = (*format_ctx).nb_streams as usize;
396
397            for i in 0..nb_streams {
398                let stream = (*format_ctx).streams.add(i);
399                let codecpar = (*(*stream)).codecpar;
400
401                if (*codecpar).codec_type == AVMediaType_AVMEDIA_TYPE_AUDIO {
402                    return Some((i, (*codecpar).codec_id));
403                }
404            }
405
406            None
407        }
408    }
409
410    /// Returns the human-readable codec name for a given `AVCodecID`.
411    unsafe fn extract_codec_name(codec_id: ff_sys::AVCodecID) -> String {
412        // SAFETY: avcodec_get_name is safe for any codec ID value
413        let name_ptr = unsafe { ff_sys::avcodec_get_name(codec_id) };
414        if name_ptr.is_null() {
415            return String::from("unknown");
416        }
417        // SAFETY: avcodec_get_name returns a valid C string with static lifetime
418        unsafe { CStr::from_ptr(name_ptr).to_string_lossy().into_owned() }
419    }
420
421    /// Extracts audio stream information from FFmpeg structures.
422    unsafe fn extract_stream_info(
423        format_ctx: *mut AVFormatContext,
424        stream_index: i32,
425        codec_ctx: *mut AVCodecContext,
426    ) -> Result<AudioStreamInfo, DecodeError> {
427        // SAFETY: Caller ensures all pointers are valid
428        let (sample_rate, channels, sample_fmt, duration_val, channel_layout, codec_id) = unsafe {
429            let stream = (*format_ctx).streams.add(stream_index as usize);
430            let codecpar = (*(*stream)).codecpar;
431
432            (
433                (*codecpar).sample_rate as u32,
434                (*codecpar).ch_layout.nb_channels as u32,
435                (*codec_ctx).sample_fmt,
436                (*format_ctx).duration,
437                (*codecpar).ch_layout,
438                (*codecpar).codec_id,
439            )
440        };
441
442        // Extract duration
443        let duration = if duration_val > 0 {
444            let duration_secs = duration_val as f64 / 1_000_000.0;
445            Some(Duration::from_secs_f64(duration_secs))
446        } else {
447            None
448        };
449
450        // Extract sample format
451        let sample_format = Self::convert_sample_format(sample_fmt);
452
453        // Extract channel layout
454        let channel_layout_enum = Self::convert_channel_layout(&channel_layout, channels);
455
456        // Extract codec
457        let codec = Self::convert_codec(codec_id);
458        let codec_name = unsafe { Self::extract_codec_name(codec_id) };
459
460        // Build stream info
461        let mut builder = AudioStreamInfo::builder()
462            .index(stream_index as u32)
463            .codec(codec)
464            .codec_name(codec_name)
465            .sample_rate(sample_rate)
466            .channels(channels)
467            .sample_format(sample_format)
468            .channel_layout(channel_layout_enum);
469
470        if let Some(d) = duration {
471            builder = builder.duration(d);
472        }
473
474        Ok(builder.build())
475    }
476
477    /// Converts FFmpeg sample format to our `SampleFormat` enum.
478    fn convert_sample_format(fmt: AVSampleFormat) -> SampleFormat {
479        if fmt == ff_sys::AVSampleFormat_AV_SAMPLE_FMT_U8 {
480            SampleFormat::U8
481        } else if fmt == ff_sys::AVSampleFormat_AV_SAMPLE_FMT_S16 {
482            SampleFormat::I16
483        } else if fmt == ff_sys::AVSampleFormat_AV_SAMPLE_FMT_S32 {
484            SampleFormat::I32
485        } else if fmt == ff_sys::AVSampleFormat_AV_SAMPLE_FMT_FLT {
486            SampleFormat::F32
487        } else if fmt == ff_sys::AVSampleFormat_AV_SAMPLE_FMT_DBL {
488            SampleFormat::F64
489        } else if fmt == ff_sys::AVSampleFormat_AV_SAMPLE_FMT_U8P {
490            SampleFormat::U8p
491        } else if fmt == ff_sys::AVSampleFormat_AV_SAMPLE_FMT_S16P {
492            SampleFormat::I16p
493        } else if fmt == ff_sys::AVSampleFormat_AV_SAMPLE_FMT_S32P {
494            SampleFormat::I32p
495        } else if fmt == ff_sys::AVSampleFormat_AV_SAMPLE_FMT_FLTP {
496            SampleFormat::F32p
497        } else if fmt == ff_sys::AVSampleFormat_AV_SAMPLE_FMT_DBLP {
498            SampleFormat::F64p
499        } else {
500            log::warn!(
501                "sample_format unsupported, falling back to F32 requested={fmt} fallback=F32"
502            );
503            SampleFormat::F32
504        }
505    }
506
507    /// Converts FFmpeg channel layout to our `ChannelLayout` enum.
508    fn convert_channel_layout(layout: &ff_sys::AVChannelLayout, channels: u32) -> ChannelLayout {
509        if layout.order == ff_sys::AVChannelOrder_AV_CHANNEL_ORDER_NATIVE {
510            // SAFETY: When order is AV_CHANNEL_ORDER_NATIVE, the mask field is valid
511            let mask = unsafe { layout.u.mask };
512            match mask {
513                0x4 => ChannelLayout::Mono,
514                0x3 => ChannelLayout::Stereo,
515                0x103 => ChannelLayout::Stereo2_1,
516                0x7 => ChannelLayout::Surround3_0,
517                0x33 => ChannelLayout::Quad,
518                0x37 => ChannelLayout::Surround5_0,
519                0x3F => ChannelLayout::Surround5_1,
520                0x13F => ChannelLayout::Surround6_1,
521                0x63F => ChannelLayout::Surround7_1,
522                _ => {
523                    log::warn!(
524                        "channel_layout mask has no mapping, deriving from channel count \
525                         mask={mask} channels={channels}"
526                    );
527                    ChannelLayout::from_channels(channels)
528                }
529            }
530        } else {
531            log::warn!(
532                "channel_layout order is not NATIVE, deriving from channel count \
533                 order={order} channels={channels}",
534                order = layout.order
535            );
536            ChannelLayout::from_channels(channels)
537        }
538    }
539
540    /// Creates an `AVChannelLayout` from channel count.
541    ///
542    /// # Safety
543    ///
544    /// The returned layout must be freed with `av_channel_layout_uninit`.
545    unsafe fn create_channel_layout(channels: u32) -> ff_sys::AVChannelLayout {
546        // SAFETY: Zeroing AVChannelLayout is safe
547        let mut layout = unsafe { std::mem::zeroed::<ff_sys::AVChannelLayout>() };
548        // SAFETY: Caller ensures proper cleanup
549        unsafe {
550            ff_sys::av_channel_layout_default(&raw mut layout, channels as i32);
551        }
552        layout
553    }
554
555    /// Converts FFmpeg codec ID to our `AudioCodec` enum.
556    fn convert_codec(codec_id: AVCodecID) -> AudioCodec {
557        if codec_id == ff_sys::AVCodecID_AV_CODEC_ID_AAC {
558            AudioCodec::Aac
559        } else if codec_id == ff_sys::AVCodecID_AV_CODEC_ID_MP3 {
560            AudioCodec::Mp3
561        } else if codec_id == ff_sys::AVCodecID_AV_CODEC_ID_OPUS {
562            AudioCodec::Opus
563        } else if codec_id == ff_sys::AVCodecID_AV_CODEC_ID_VORBIS {
564            AudioCodec::Vorbis
565        } else if codec_id == ff_sys::AVCodecID_AV_CODEC_ID_FLAC {
566            AudioCodec::Flac
567        } else if codec_id == ff_sys::AVCodecID_AV_CODEC_ID_PCM_S16LE {
568            AudioCodec::Pcm
569        } else {
570            log::warn!(
571                "audio codec unsupported, falling back to Aac codec_id={codec_id} fallback=Aac"
572            );
573            AudioCodec::Aac
574        }
575    }
576
577    /// Decodes the next audio frame.
578    ///
579    /// # Returns
580    ///
581    /// - `Ok(Some(frame))` - Successfully decoded a frame
582    /// - `Ok(None)` - End of stream reached
583    /// - `Err(_)` - Decoding error occurred
584    pub(crate) fn decode_one(&mut self) -> Result<Option<AudioFrame>, DecodeError> {
585        if self.eof {
586            return Ok(None);
587        }
588
589        unsafe {
590            loop {
591                // Try to receive a frame from the decoder
592                let ret = ff_sys::avcodec_receive_frame(self.codec_ctx, self.frame);
593
594                if ret == 0 {
595                    // Successfully received a frame
596                    let audio_frame = self.convert_frame_to_audio_frame()?;
597
598                    // Update position based on frame timestamp
599                    let pts = (*self.frame).pts;
600                    if pts != ff_sys::AV_NOPTS_VALUE {
601                        let stream = (*self.format_ctx).streams.add(self.stream_index as usize);
602                        let time_base = (*(*stream)).time_base;
603                        let timestamp_secs =
604                            pts as f64 * time_base.num as f64 / time_base.den as f64;
605                        self.position = Duration::from_secs_f64(timestamp_secs);
606                    }
607
608                    return Ok(Some(audio_frame));
609                } else if ret == ff_sys::error_codes::EAGAIN {
610                    // Need to send more packets to the decoder
611                    // Read a packet from the file
612                    let read_ret = ff_sys::av_read_frame(self.format_ctx, self.packet);
613
614                    if read_ret == ff_sys::error_codes::EOF {
615                        // End of file - flush the decoder
616                        ff_sys::avcodec_send_packet(self.codec_ctx, ptr::null());
617                        self.eof = true;
618                        continue;
619                    } else if read_ret < 0 {
620                        return Err(DecodeError::Ffmpeg {
621                            code: read_ret,
622                            message: format!(
623                                "Failed to read frame: {}",
624                                ff_sys::av_error_string(read_ret)
625                            ),
626                        });
627                    }
628
629                    // Check if this packet belongs to the audio stream
630                    if (*self.packet).stream_index == self.stream_index {
631                        // Send the packet to the decoder
632                        let send_ret = ff_sys::avcodec_send_packet(self.codec_ctx, self.packet);
633                        ff_sys::av_packet_unref(self.packet);
634
635                        if send_ret < 0 && send_ret != ff_sys::error_codes::EAGAIN {
636                            return Err(DecodeError::Ffmpeg {
637                                code: send_ret,
638                                message: format!(
639                                    "Failed to send packet: {}",
640                                    ff_sys::av_error_string(send_ret)
641                                ),
642                            });
643                        }
644                    } else {
645                        // Not our stream, unref and continue
646                        ff_sys::av_packet_unref(self.packet);
647                    }
648                } else if ret == ff_sys::error_codes::EOF {
649                    // Decoder has been fully flushed
650                    self.eof = true;
651                    return Ok(None);
652                } else {
653                    return Err(DecodeError::DecodingFailed {
654                        timestamp: Some(self.position),
655                        reason: ff_sys::av_error_string(ret),
656                    });
657                }
658            }
659        }
660    }
661
662    /// Converts an AVFrame to an AudioFrame, applying sample format conversion if needed.
663    unsafe fn convert_frame_to_audio_frame(&mut self) -> Result<AudioFrame, DecodeError> {
664        // SAFETY: Caller ensures self.frame is valid
665        unsafe {
666            let nb_samples = (*self.frame).nb_samples as usize;
667            let channels = (*self.frame).ch_layout.nb_channels as u32;
668            let sample_rate = (*self.frame).sample_rate as u32;
669            let src_format = (*self.frame).format;
670
671            // Determine if we need conversion
672            let needs_conversion = self.output_format.is_some()
673                || self.output_sample_rate.is_some()
674                || self.output_channels.is_some();
675
676            if needs_conversion {
677                self.convert_with_swr(nb_samples, channels, sample_rate, src_format)
678            } else {
679                self.av_frame_to_audio_frame(self.frame)
680            }
681        }
682    }
683
684    /// Converts sample format/rate/channels using SwResample.
685    unsafe fn convert_with_swr(
686        &mut self,
687        nb_samples: usize,
688        src_channels: u32,
689        src_sample_rate: u32,
690        src_format: i32,
691    ) -> Result<AudioFrame, DecodeError> {
692        // Determine target parameters
693        let dst_format = self
694            .output_format
695            .map_or(src_format, Self::sample_format_to_av);
696        let dst_sample_rate = self.output_sample_rate.unwrap_or(src_sample_rate);
697        let dst_channels = self.output_channels.unwrap_or(src_channels);
698
699        // If no conversion is needed, return the frame directly
700        if src_format == dst_format
701            && src_sample_rate == dst_sample_rate
702            && src_channels == dst_channels
703        {
704            return unsafe { self.av_frame_to_audio_frame(self.frame) };
705        }
706
707        // Create channel layouts for source and destination
708        // SAFETY: We'll properly clean up these layouts
709        let mut src_ch_layout = unsafe { Self::create_channel_layout(src_channels) };
710        let mut dst_ch_layout = unsafe { Self::create_channel_layout(dst_channels) };
711
712        // Create SwrContext using swr_alloc_set_opts2
713        let mut swr_ctx: *mut SwrContext = ptr::null_mut();
714
715        // SAFETY: FFmpeg API call with valid parameters
716        let ret = unsafe {
717            ff_sys::swr_alloc_set_opts2(
718                &raw mut swr_ctx,
719                &raw const dst_ch_layout,
720                dst_format,
721                dst_sample_rate as i32,
722                &raw const src_ch_layout,
723                src_format,
724                src_sample_rate as i32,
725                0,
726                ptr::null_mut(),
727            )
728        };
729
730        if ret < 0 {
731            // Clean up channel layouts
732            unsafe {
733                ff_sys::av_channel_layout_uninit(&raw mut src_ch_layout);
734                ff_sys::av_channel_layout_uninit(&raw mut dst_ch_layout);
735            }
736            return Err(DecodeError::Ffmpeg {
737                code: ret,
738                message: format!(
739                    "Failed to allocate SwrContext: {}",
740                    ff_sys::av_error_string(ret)
741                ),
742            });
743        }
744
745        // Wrap in RAII guard for automatic cleanup
746        let _swr_guard = SwrContextGuard(swr_ctx);
747
748        // Initialize the resampler
749        // SAFETY: swr_ctx is valid
750        let ret = unsafe { ff_sys::swr_init(swr_ctx) };
751        if ret < 0 {
752            // Clean up channel layouts
753            unsafe {
754                ff_sys::av_channel_layout_uninit(&raw mut src_ch_layout);
755                ff_sys::av_channel_layout_uninit(&raw mut dst_ch_layout);
756            }
757            return Err(DecodeError::Ffmpeg {
758                code: ret,
759                message: format!(
760                    "Failed to initialize SwrContext: {}",
761                    ff_sys::av_error_string(ret)
762                ),
763            });
764        }
765
766        // Calculate output sample count
767        // SAFETY: swr_ctx is valid and initialized
768        let out_samples = unsafe { ff_sys::swr_get_out_samples(swr_ctx, nb_samples as i32) };
769
770        if out_samples < 0 {
771            // Clean up channel layouts
772            unsafe {
773                ff_sys::av_channel_layout_uninit(&raw mut src_ch_layout);
774                ff_sys::av_channel_layout_uninit(&raw mut dst_ch_layout);
775            }
776            return Err(DecodeError::Ffmpeg {
777                code: 0,
778                message: "Failed to calculate output sample count".to_string(),
779            });
780        }
781
782        let out_samples = out_samples as usize;
783
784        // Calculate buffer size for output
785        let dst_sample_fmt = Self::convert_sample_format(dst_format);
786        let bytes_per_sample = dst_sample_fmt.bytes_per_sample();
787        let is_planar = dst_sample_fmt.is_planar();
788
789        // Allocate output buffer
790        let buffer_size = if is_planar {
791            // For planar formats, each plane has samples * bytes_per_sample
792            out_samples * bytes_per_sample * dst_channels as usize
793        } else {
794            // For packed formats, interleaved samples
795            out_samples * bytes_per_sample * dst_channels as usize
796        };
797
798        let mut out_buffer = vec![0u8; buffer_size];
799
800        // Prepare output pointers for swr_convert
801        let mut out_ptrs = if is_planar {
802            // For planar formats, create separate pointers for each channel
803            let plane_size = out_samples * bytes_per_sample;
804            (0..dst_channels)
805                .map(|i| {
806                    let offset = i as usize * plane_size;
807                    out_buffer[offset..].as_mut_ptr()
808                })
809                .collect::<Vec<_>>()
810        } else {
811            // For packed formats, single pointer
812            vec![out_buffer.as_mut_ptr()]
813        };
814
815        // Get input data pointers from frame
816        // SAFETY: self.frame is valid
817        let in_ptrs = unsafe { (*self.frame).data };
818
819        // Convert samples using SwResample
820        // SAFETY: All pointers are valid and buffers are properly sized
821        let converted_samples = unsafe {
822            ff_sys::swr_convert(
823                swr_ctx,
824                out_ptrs.as_mut_ptr(),
825                out_samples as i32,
826                in_ptrs.as_ptr() as *mut *const u8,
827                nb_samples as i32,
828            )
829        };
830
831        // Clean up channel layouts
832        unsafe {
833            ff_sys::av_channel_layout_uninit(&raw mut src_ch_layout);
834            ff_sys::av_channel_layout_uninit(&raw mut dst_ch_layout);
835        }
836
837        if converted_samples < 0 {
838            return Err(DecodeError::Ffmpeg {
839                code: converted_samples,
840                message: format!(
841                    "Failed to convert samples: {}",
842                    ff_sys::av_error_string(converted_samples)
843                ),
844            });
845        }
846
847        // Extract timestamp from original frame
848        // SAFETY: self.frame is valid
849        let timestamp = unsafe {
850            let pts = (*self.frame).pts;
851            if pts != ff_sys::AV_NOPTS_VALUE {
852                let stream = (*self.format_ctx).streams.add(self.stream_index as usize);
853                let time_base = (*(*stream)).time_base;
854                Timestamp::new(pts, Rational::new(time_base.num, time_base.den))
855            } else {
856                let stream = (*self.format_ctx).streams.add(self.stream_index as usize);
857                let time_base = (*(*stream)).time_base;
858                Timestamp::zero(Rational::new(time_base.num, time_base.den))
859            }
860        };
861
862        // Create planes for AudioFrame
863        let planes = if is_planar {
864            let plane_size = converted_samples as usize * bytes_per_sample;
865            (0..dst_channels)
866                .map(|i| {
867                    let offset = i as usize * plane_size;
868                    out_buffer[offset..offset + plane_size].to_vec()
869                })
870                .collect()
871        } else {
872            // For packed formats, single plane with all data
873            vec![
874                out_buffer[..converted_samples as usize * bytes_per_sample * dst_channels as usize]
875                    .to_vec(),
876            ]
877        };
878
879        AudioFrame::new(
880            planes,
881            converted_samples as usize,
882            dst_channels,
883            dst_sample_rate,
884            dst_sample_fmt,
885            timestamp,
886        )
887        .map_err(|e| DecodeError::Ffmpeg {
888            code: 0,
889            message: format!("Failed to create AudioFrame: {e}"),
890        })
891    }
892
893    /// Converts an AVFrame to an AudioFrame.
894    unsafe fn av_frame_to_audio_frame(
895        &self,
896        frame: *const AVFrame,
897    ) -> Result<AudioFrame, DecodeError> {
898        // SAFETY: Caller ensures frame and format_ctx are valid
899        unsafe {
900            let nb_samples = (*frame).nb_samples as usize;
901            let channels = (*frame).ch_layout.nb_channels as u32;
902            let sample_rate = (*frame).sample_rate as u32;
903            let format = Self::convert_sample_format((*frame).format);
904
905            // Extract timestamp
906            let pts = (*frame).pts;
907            let timestamp = if pts != ff_sys::AV_NOPTS_VALUE {
908                let stream = (*self.format_ctx).streams.add(self.stream_index as usize);
909                let time_base = (*(*stream)).time_base;
910                Timestamp::new(
911                    pts as i64,
912                    Rational::new(time_base.num as i32, time_base.den as i32),
913                )
914            } else {
915                Timestamp::default()
916            };
917
918            // Convert frame to planes
919            let planes = Self::extract_planes(frame, nb_samples, channels, format)?;
920
921            AudioFrame::new(planes, nb_samples, channels, sample_rate, format, timestamp).map_err(
922                |e| DecodeError::Ffmpeg {
923                    code: 0,
924                    message: format!("Failed to create AudioFrame: {e}"),
925                },
926            )
927        }
928    }
929
930    /// Extracts planes from an AVFrame.
931    unsafe fn extract_planes(
932        frame: *const AVFrame,
933        nb_samples: usize,
934        channels: u32,
935        format: SampleFormat,
936    ) -> Result<Vec<Vec<u8>>, DecodeError> {
937        // SAFETY: Caller ensures frame is valid and format matches actual frame format
938        unsafe {
939            let mut planes = Vec::new();
940            let bytes_per_sample = format.bytes_per_sample();
941
942            if format.is_planar() {
943                // Planar: one plane per channel
944                for ch in 0..channels as usize {
945                    let plane_size = nb_samples * bytes_per_sample;
946                    let mut plane_data = vec![0u8; plane_size];
947
948                    let src_ptr = (*frame).data[ch];
949                    std::ptr::copy_nonoverlapping(src_ptr, plane_data.as_mut_ptr(), plane_size);
950
951                    planes.push(plane_data);
952                }
953            } else {
954                // Packed: single plane with interleaved samples
955                let plane_size = nb_samples * channels as usize * bytes_per_sample;
956                let mut plane_data = vec![0u8; plane_size];
957
958                let src_ptr = (*frame).data[0];
959                std::ptr::copy_nonoverlapping(src_ptr, plane_data.as_mut_ptr(), plane_size);
960
961                planes.push(plane_data);
962            }
963
964            Ok(planes)
965        }
966    }
967
968    /// Converts our `SampleFormat` to FFmpeg `AVSampleFormat`.
969    fn sample_format_to_av(format: SampleFormat) -> AVSampleFormat {
970        match format {
971            SampleFormat::U8 => ff_sys::AVSampleFormat_AV_SAMPLE_FMT_U8,
972            SampleFormat::I16 => ff_sys::AVSampleFormat_AV_SAMPLE_FMT_S16,
973            SampleFormat::I32 => ff_sys::AVSampleFormat_AV_SAMPLE_FMT_S32,
974            SampleFormat::F32 => ff_sys::AVSampleFormat_AV_SAMPLE_FMT_FLT,
975            SampleFormat::F64 => ff_sys::AVSampleFormat_AV_SAMPLE_FMT_DBL,
976            SampleFormat::U8p => ff_sys::AVSampleFormat_AV_SAMPLE_FMT_U8P,
977            SampleFormat::I16p => ff_sys::AVSampleFormat_AV_SAMPLE_FMT_S16P,
978            SampleFormat::I32p => ff_sys::AVSampleFormat_AV_SAMPLE_FMT_S32P,
979            SampleFormat::F32p => ff_sys::AVSampleFormat_AV_SAMPLE_FMT_FLTP,
980            SampleFormat::F64p => ff_sys::AVSampleFormat_AV_SAMPLE_FMT_DBLP,
981            _ => {
982                log::warn!(
983                    "sample_format has no AV mapping, falling back to F32 format={format:?} fallback=AV_SAMPLE_FMT_FLT"
984                );
985                ff_sys::AVSampleFormat_AV_SAMPLE_FMT_FLT
986            }
987        }
988    }
989
990    /// Returns the current playback position.
991    pub(crate) fn position(&self) -> Duration {
992        self.position
993    }
994
995    /// Returns whether end of file has been reached.
996    pub(crate) fn is_eof(&self) -> bool {
997        self.eof
998    }
999
1000    /// Converts a `Duration` to a presentation timestamp (PTS) in stream time_base units.
1001    fn duration_to_pts(&self, duration: Duration) -> i64 {
1002        // SAFETY: format_ctx and stream_index are valid (owned by AudioDecoderInner)
1003        let time_base = unsafe {
1004            let stream = (*self.format_ctx).streams.add(self.stream_index as usize);
1005            (*(*stream)).time_base
1006        };
1007
1008        // Convert: duration (seconds) * (time_base.den / time_base.num) = PTS
1009        let time_base_f64 = time_base.den as f64 / time_base.num as f64;
1010        (duration.as_secs_f64() * time_base_f64) as i64
1011    }
1012
1013    /// Seeks to a specified position in the audio stream.
1014    ///
1015    /// # Arguments
1016    ///
1017    /// * `position` - Target position to seek to.
1018    /// * `mode` - Seek mode (Keyframe, Exact, or Backward).
1019    ///
1020    /// # Errors
1021    ///
1022    /// Returns [`DecodeError::SeekFailed`] if the seek operation fails.
1023    pub(crate) fn seek(
1024        &mut self,
1025        position: Duration,
1026        mode: crate::SeekMode,
1027    ) -> Result<(), DecodeError> {
1028        use crate::SeekMode;
1029
1030        let timestamp = self.duration_to_pts(position);
1031        let flags = ff_sys::avformat::seek_flags::BACKWARD;
1032
1033        // 1. Clear any pending packet and frame
1034        // SAFETY: packet and frame are valid (owned by AudioDecoderInner)
1035        unsafe {
1036            ff_sys::av_packet_unref(self.packet);
1037            ff_sys::av_frame_unref(self.frame);
1038        }
1039
1040        // 2. Seek in the format context
1041        // SAFETY: format_ctx and stream_index are valid
1042        unsafe {
1043            ff_sys::avformat::seek_frame(self.format_ctx, self.stream_index, timestamp, flags)
1044                .map_err(|e| DecodeError::SeekFailed {
1045                    target: position,
1046                    reason: ff_sys::av_error_string(e),
1047                })?;
1048        }
1049
1050        // 3. Flush decoder buffers
1051        // SAFETY: codec_ctx is valid (owned by AudioDecoderInner)
1052        unsafe {
1053            ff_sys::avcodec::flush_buffers(self.codec_ctx);
1054        }
1055
1056        // 4. Drain any remaining frames from the decoder after flush
1057        // SAFETY: codec_ctx and frame are valid
1058        unsafe {
1059            loop {
1060                let ret = ff_sys::avcodec_receive_frame(self.codec_ctx, self.frame);
1061                if ret == ff_sys::error_codes::EAGAIN || ret == ff_sys::error_codes::EOF {
1062                    break;
1063                } else if ret == 0 {
1064                    ff_sys::av_frame_unref(self.frame);
1065                } else {
1066                    break;
1067                }
1068            }
1069        }
1070
1071        // 5. Reset internal state
1072        self.eof = false;
1073
1074        // 6. For exact mode, skip frames to reach exact position
1075        if mode == SeekMode::Exact {
1076            self.skip_to_exact(position)?;
1077        }
1078        // For Keyframe/Backward modes, we're already at the keyframe after av_seek_frame
1079
1080        Ok(())
1081    }
1082
1083    /// Skips frames until reaching the exact target position.
1084    ///
1085    /// This is used by [`Self::seek`] when `SeekMode::Exact` is specified.
1086    ///
1087    /// # Arguments
1088    ///
1089    /// * `target` - The exact target position.
1090    fn skip_to_exact(&mut self, target: Duration) -> Result<(), DecodeError> {
1091        // Decode frames until we reach or pass the target
1092        while let Some(frame) = self.decode_one()? {
1093            let frame_time = frame.timestamp().as_duration();
1094            if frame_time >= target {
1095                // We've reached the target position
1096                break;
1097            }
1098            // Continue decoding to get closer (frames are automatically dropped)
1099        }
1100        Ok(())
1101    }
1102
1103    /// Flushes the decoder's internal buffers.
1104    pub(crate) fn flush(&mut self) {
1105        // SAFETY: codec_ctx is valid and owned by this instance
1106        unsafe {
1107            ff_sys::avcodec::flush_buffers(self.codec_ctx);
1108        }
1109        self.eof = false;
1110    }
1111}
1112
1113impl Drop for AudioDecoderInner {
1114    fn drop(&mut self) {
1115        // Free SwResample context if allocated
1116        if let Some(swr_ctx) = self.swr_ctx {
1117            // SAFETY: swr_ctx is valid and owned by this instance
1118            unsafe {
1119                // swr_free frees a SwrContext
1120                ff_sys::swr_free(&mut (swr_ctx as *mut _));
1121            }
1122        }
1123
1124        // Free frame and packet
1125        if !self.frame.is_null() {
1126            // SAFETY: self.frame is valid and owned by this instance
1127            unsafe {
1128                ff_sys::av_frame_free(&mut (self.frame as *mut _));
1129            }
1130        }
1131
1132        if !self.packet.is_null() {
1133            // SAFETY: self.packet is valid and owned by this instance
1134            unsafe {
1135                ff_sys::av_packet_free(&mut (self.packet as *mut _));
1136            }
1137        }
1138
1139        // Free codec context
1140        if !self.codec_ctx.is_null() {
1141            // SAFETY: self.codec_ctx is valid and owned by this instance
1142            unsafe {
1143                ff_sys::avcodec::free_context(&mut (self.codec_ctx as *mut _));
1144            }
1145        }
1146
1147        // Close format context
1148        if !self.format_ctx.is_null() {
1149            // SAFETY: self.format_ctx is valid and owned by this instance
1150            unsafe {
1151                ff_sys::avformat::close_input(&mut (self.format_ctx as *mut _));
1152            }
1153        }
1154    }
1155}
1156
1157// SAFETY: AudioDecoderInner manages FFmpeg contexts which are thread-safe when not shared.
1158// We don't expose mutable access across threads, so Send is safe.
1159unsafe impl Send for AudioDecoderInner {}
1160
1161#[cfg(test)]
1162#[allow(unsafe_code)]
1163mod tests {
1164    use ff_format::channel::ChannelLayout;
1165
1166    use super::AudioDecoderInner;
1167
1168    /// Constructs an `AVChannelLayout` with `AV_CHANNEL_ORDER_NATIVE` and the given mask.
1169    fn native_layout(mask: u64, nb_channels: i32) -> ff_sys::AVChannelLayout {
1170        ff_sys::AVChannelLayout {
1171            order: ff_sys::AVChannelOrder_AV_CHANNEL_ORDER_NATIVE,
1172            nb_channels,
1173            u: ff_sys::AVChannelLayout__bindgen_ty_1 { mask },
1174            opaque: std::ptr::null_mut(),
1175        }
1176    }
1177
1178    /// Constructs an `AVChannelLayout` with `AV_CHANNEL_ORDER_UNSPEC`.
1179    fn unspec_layout(nb_channels: i32) -> ff_sys::AVChannelLayout {
1180        ff_sys::AVChannelLayout {
1181            order: ff_sys::AVChannelOrder_AV_CHANNEL_ORDER_UNSPEC,
1182            nb_channels,
1183            u: ff_sys::AVChannelLayout__bindgen_ty_1 { mask: 0 },
1184            opaque: std::ptr::null_mut(),
1185        }
1186    }
1187
1188    #[test]
1189    fn native_mask_mono() {
1190        let layout = native_layout(0x4, 1);
1191        assert_eq!(
1192            AudioDecoderInner::convert_channel_layout(&layout, 1),
1193            ChannelLayout::Mono
1194        );
1195    }
1196
1197    #[test]
1198    fn native_mask_stereo() {
1199        let layout = native_layout(0x3, 2);
1200        assert_eq!(
1201            AudioDecoderInner::convert_channel_layout(&layout, 2),
1202            ChannelLayout::Stereo
1203        );
1204    }
1205
1206    #[test]
1207    fn native_mask_stereo2_1() {
1208        let layout = native_layout(0x103, 3);
1209        assert_eq!(
1210            AudioDecoderInner::convert_channel_layout(&layout, 3),
1211            ChannelLayout::Stereo2_1
1212        );
1213    }
1214
1215    #[test]
1216    fn native_mask_surround3_0() {
1217        let layout = native_layout(0x7, 3);
1218        assert_eq!(
1219            AudioDecoderInner::convert_channel_layout(&layout, 3),
1220            ChannelLayout::Surround3_0
1221        );
1222    }
1223
1224    #[test]
1225    fn native_mask_quad() {
1226        let layout = native_layout(0x33, 4);
1227        assert_eq!(
1228            AudioDecoderInner::convert_channel_layout(&layout, 4),
1229            ChannelLayout::Quad
1230        );
1231    }
1232
1233    #[test]
1234    fn native_mask_surround5_0() {
1235        let layout = native_layout(0x37, 5);
1236        assert_eq!(
1237            AudioDecoderInner::convert_channel_layout(&layout, 5),
1238            ChannelLayout::Surround5_0
1239        );
1240    }
1241
1242    #[test]
1243    fn native_mask_surround5_1() {
1244        let layout = native_layout(0x3F, 6);
1245        assert_eq!(
1246            AudioDecoderInner::convert_channel_layout(&layout, 6),
1247            ChannelLayout::Surround5_1
1248        );
1249    }
1250
1251    #[test]
1252    fn native_mask_surround6_1() {
1253        let layout = native_layout(0x13F, 7);
1254        assert_eq!(
1255            AudioDecoderInner::convert_channel_layout(&layout, 7),
1256            ChannelLayout::Surround6_1
1257        );
1258    }
1259
1260    #[test]
1261    fn native_mask_surround7_1() {
1262        let layout = native_layout(0x63F, 8);
1263        assert_eq!(
1264            AudioDecoderInner::convert_channel_layout(&layout, 8),
1265            ChannelLayout::Surround7_1
1266        );
1267    }
1268
1269    #[test]
1270    fn native_mask_unknown_falls_back_to_from_channels() {
1271        // mask=0x1 is not a standard layout; should fall back to from_channels(2)
1272        let layout = native_layout(0x1, 2);
1273        assert_eq!(
1274            AudioDecoderInner::convert_channel_layout(&layout, 2),
1275            ChannelLayout::from_channels(2)
1276        );
1277    }
1278
1279    #[test]
1280    fn non_native_order_falls_back_to_from_channels() {
1281        let layout = unspec_layout(6);
1282        assert_eq!(
1283            AudioDecoderInner::convert_channel_layout(&layout, 6),
1284            ChannelLayout::from_channels(6)
1285        );
1286    }
1287
1288    // -------------------------------------------------------------------------
1289    // extract_codec_name
1290    // -------------------------------------------------------------------------
1291
1292    #[test]
1293    fn codec_name_should_return_h264_for_h264_codec_id() {
1294        let name =
1295            unsafe { AudioDecoderInner::extract_codec_name(ff_sys::AVCodecID_AV_CODEC_ID_H264) };
1296        assert_eq!(name, "h264");
1297    }
1298
1299    #[test]
1300    fn codec_name_should_return_none_for_none_codec_id() {
1301        let name =
1302            unsafe { AudioDecoderInner::extract_codec_name(ff_sys::AVCodecID_AV_CODEC_ID_NONE) };
1303        assert_eq!(name, "none");
1304    }
1305
1306    #[test]
1307    fn unsupported_codec_error_should_include_codec_name() {
1308        let codec_id = ff_sys::AVCodecID_AV_CODEC_ID_MP3;
1309        let codec_name = unsafe { AudioDecoderInner::extract_codec_name(codec_id) };
1310        let error = crate::error::DecodeError::UnsupportedCodec {
1311            codec: format!("{codec_name} (codec_id={codec_id:?})"),
1312        };
1313        let msg = error.to_string();
1314        assert!(msg.contains("mp3"), "expected codec name in error: {msg}");
1315        assert!(
1316            msg.contains("codec_id="),
1317            "expected codec_id in error: {msg}"
1318        );
1319    }
1320}