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