1#![allow(unsafe_code)]
8#![allow(clippy::similar_names)]
10#![allow(clippy::too_many_lines)]
11#![allow(clippy::cast_sign_loss)]
12#![allow(clippy::cast_possible_truncation)]
13#![allow(clippy::cast_possible_wrap)]
14#![allow(clippy::module_name_repetitions)]
15#![allow(clippy::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::container::ContainerInfo;
33use ff_format::{AudioFrame, AudioStreamInfo, NetworkOptions, SampleFormat};
34use ff_sys::{
35 AVCodecContext, AVCodecID, AVFormatContext, AVFrame, AVMediaType_AVMEDIA_TYPE_AUDIO, AVPacket,
36};
37
38use super::resample_inner;
39
40use crate::error::DecodeError;
41
42struct AvFormatContextGuard(*mut AVFormatContext);
44
45impl AvFormatContextGuard {
46 unsafe fn new(path: &Path) -> Result<Self, DecodeError> {
52 let format_ctx = unsafe {
54 ff_sys::avformat::open_input(path).map_err(|e| DecodeError::Ffmpeg {
55 code: e,
56 message: format!("Failed to open file: {}", ff_sys::av_error_string(e)),
57 })?
58 };
59 Ok(Self(format_ctx))
60 }
61
62 unsafe fn new_url(url: &str, network: &NetworkOptions) -> Result<Self, DecodeError> {
68 let format_ctx = unsafe {
70 ff_sys::avformat::open_input_url(url, network.connect_timeout, network.read_timeout)
71 .map_err(|e| {
72 crate::network::map_network_error(e, crate::network::sanitize_url(url))
73 })?
74 };
75 Ok(Self(format_ctx))
76 }
77
78 const fn as_ptr(&self) -> *mut AVFormatContext {
80 self.0
81 }
82
83 fn into_raw(self) -> *mut AVFormatContext {
85 let ptr = self.0;
86 std::mem::forget(self);
87 ptr
88 }
89}
90
91impl Drop for AvFormatContextGuard {
92 fn drop(&mut self) {
93 if !self.0.is_null() {
94 unsafe {
96 ff_sys::avformat::close_input(&mut (self.0 as *mut _));
97 }
98 }
99 }
100}
101
102struct AvCodecContextGuard(*mut AVCodecContext);
104
105impl AvCodecContextGuard {
106 unsafe fn new(codec: *const ff_sys::AVCodec) -> Result<Self, DecodeError> {
112 let codec_ctx = unsafe {
114 ff_sys::avcodec::alloc_context3(codec).map_err(|e| DecodeError::Ffmpeg {
115 code: e,
116 message: format!("Failed to allocate codec context: {e}"),
117 })?
118 };
119 Ok(Self(codec_ctx))
120 }
121
122 const fn as_ptr(&self) -> *mut AVCodecContext {
124 self.0
125 }
126
127 fn into_raw(self) -> *mut AVCodecContext {
129 let ptr = self.0;
130 std::mem::forget(self);
131 ptr
132 }
133}
134
135impl Drop for AvCodecContextGuard {
136 fn drop(&mut self) {
137 if !self.0.is_null() {
138 unsafe {
140 ff_sys::avcodec::free_context(&mut (self.0 as *mut _));
141 }
142 }
143 }
144}
145
146struct AvPacketGuard(*mut AVPacket);
148
149impl AvPacketGuard {
150 unsafe fn new() -> Result<Self, DecodeError> {
156 let packet = unsafe { ff_sys::av_packet_alloc() };
158 if packet.is_null() {
159 return Err(DecodeError::Ffmpeg {
160 code: 0,
161 message: "Failed to allocate packet".to_string(),
162 });
163 }
164 Ok(Self(packet))
165 }
166
167 fn into_raw(self) -> *mut AVPacket {
169 let ptr = self.0;
170 std::mem::forget(self);
171 ptr
172 }
173}
174
175impl Drop for AvPacketGuard {
176 fn drop(&mut self) {
177 if !self.0.is_null() {
178 unsafe {
180 ff_sys::av_packet_free(&mut (self.0 as *mut _));
181 }
182 }
183 }
184}
185
186struct AvFrameGuard(*mut AVFrame);
188
189impl AvFrameGuard {
190 unsafe fn new() -> Result<Self, DecodeError> {
196 let frame = unsafe { ff_sys::av_frame_alloc() };
198 if frame.is_null() {
199 return Err(DecodeError::Ffmpeg {
200 code: 0,
201 message: "Failed to allocate frame".to_string(),
202 });
203 }
204 Ok(Self(frame))
205 }
206
207 fn into_raw(self) -> *mut AVFrame {
209 let ptr = self.0;
210 std::mem::forget(self);
211 ptr
212 }
213}
214
215impl Drop for AvFrameGuard {
216 fn drop(&mut self) {
217 if !self.0.is_null() {
218 unsafe {
220 ff_sys::av_frame_free(&mut (self.0 as *mut _));
221 }
222 }
223 }
224}
225
226pub(crate) struct AudioDecoderInner {
231 format_ctx: *mut AVFormatContext,
233 codec_ctx: *mut AVCodecContext,
235 stream_index: i32,
237 output_format: Option<SampleFormat>,
239 output_sample_rate: Option<u32>,
241 output_channels: Option<u32>,
243 is_live: bool,
245 eof: bool,
247 position: Duration,
249 packet: *mut AVPacket,
251 frame: *mut AVFrame,
253 url: Option<String>,
255 network_opts: NetworkOptions,
257 reconnect_count: u32,
259}
260
261impl AudioDecoderInner {
262 #[allow(clippy::too_many_arguments)]
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 network_opts: Option<NetworkOptions>,
285 ) -> Result<(Self, AudioStreamInfo, ContainerInfo), DecodeError> {
286 ff_sys::ensure_initialized();
288
289 let path_str = path.to_str().unwrap_or("");
290 let is_network_url = crate::network::is_url(path_str);
291
292 let url = if is_network_url {
293 Some(path_str.to_owned())
294 } else {
295 None
296 };
297 let stored_network_opts = network_opts.clone().unwrap_or_default();
298
299 if is_network_url {
301 crate::network::check_srt_url(path_str)?;
302 }
303
304 let format_ctx_guard = unsafe {
307 if is_network_url {
308 let network = network_opts.unwrap_or_default();
309 log::info!(
310 "opening network audio source url={} connect_timeout_ms={} read_timeout_ms={}",
311 crate::network::sanitize_url(path_str),
312 network.connect_timeout.as_millis(),
313 network.read_timeout.as_millis()
314 );
315 AvFormatContextGuard::new_url(path_str, &network)?
316 } else {
317 AvFormatContextGuard::new(path)?
318 }
319 };
320 let format_ctx = format_ctx_guard.as_ptr();
321
322 unsafe {
325 ff_sys::avformat::find_stream_info(format_ctx).map_err(|e| DecodeError::Ffmpeg {
326 code: e,
327 message: format!("Failed to find stream info: {}", ff_sys::av_error_string(e)),
328 })?;
329 }
330
331 let is_live = unsafe {
335 let iformat = (*format_ctx).iformat;
336 !iformat.is_null() && ((*iformat).flags & ff_sys::AVFMT_TS_DISCONT) != 0
337 };
338
339 let (stream_index, codec_id) =
342 unsafe { Self::find_audio_stream(format_ctx) }.ok_or_else(|| {
343 DecodeError::NoAudioStream {
344 path: path.to_path_buf(),
345 }
346 })?;
347
348 let codec_name = unsafe { Self::extract_codec_name(codec_id) };
351 let codec = unsafe {
352 ff_sys::avcodec::find_decoder(codec_id).ok_or_else(|| {
353 DecodeError::UnsupportedCodec {
354 codec: format!("{codec_name} (codec_id={codec_id:?})"),
355 }
356 })?
357 };
358
359 let codec_ctx_guard = unsafe { AvCodecContextGuard::new(codec)? };
362 let codec_ctx = codec_ctx_guard.as_ptr();
363
364 unsafe {
367 let stream = (*format_ctx).streams.add(stream_index as usize);
368 let codecpar = (*(*stream)).codecpar;
369 ff_sys::avcodec::parameters_to_context(codec_ctx, codecpar).map_err(|e| {
370 DecodeError::Ffmpeg {
371 code: e,
372 message: format!(
373 "Failed to copy codec parameters: {}",
374 ff_sys::av_error_string(e)
375 ),
376 }
377 })?;
378 }
379
380 unsafe {
383 ff_sys::avcodec::open2(codec_ctx, codec, ptr::null_mut()).map_err(|e| {
384 DecodeError::Ffmpeg {
385 code: e,
386 message: format!("Failed to open codec: {}", ff_sys::av_error_string(e)),
387 }
388 })?;
389 }
390
391 let stream_info =
394 unsafe { Self::extract_stream_info(format_ctx, stream_index as i32, codec_ctx)? };
395
396 let container_info = unsafe { Self::extract_container_info(format_ctx) };
399
400 let packet_guard = unsafe { AvPacketGuard::new()? };
403 let frame_guard = unsafe { AvFrameGuard::new()? };
404
405 Ok((
407 Self {
408 format_ctx: format_ctx_guard.into_raw(),
409 codec_ctx: codec_ctx_guard.into_raw(),
410 stream_index: stream_index as i32,
411 output_format,
412 output_sample_rate,
413 output_channels,
414 is_live,
415 eof: false,
416 position: Duration::ZERO,
417 packet: packet_guard.into_raw(),
418 frame: frame_guard.into_raw(),
419 url,
420 network_opts: stored_network_opts,
421 reconnect_count: 0,
422 },
423 stream_info,
424 container_info,
425 ))
426 }
427
428 unsafe fn find_audio_stream(format_ctx: *mut AVFormatContext) -> Option<(usize, AVCodecID)> {
438 unsafe {
440 let nb_streams = (*format_ctx).nb_streams as usize;
441
442 for i in 0..nb_streams {
443 let stream = (*format_ctx).streams.add(i);
444 let codecpar = (*(*stream)).codecpar;
445
446 if (*codecpar).codec_type == AVMediaType_AVMEDIA_TYPE_AUDIO {
447 return Some((i, (*codecpar).codec_id));
448 }
449 }
450
451 None
452 }
453 }
454
455 unsafe fn extract_codec_name(codec_id: ff_sys::AVCodecID) -> String {
457 let name_ptr = unsafe { ff_sys::avcodec_get_name(codec_id) };
459 if name_ptr.is_null() {
460 return String::from("unknown");
461 }
462 unsafe { CStr::from_ptr(name_ptr).to_string_lossy().into_owned() }
464 }
465
466 unsafe fn extract_stream_info(
468 format_ctx: *mut AVFormatContext,
469 stream_index: i32,
470 codec_ctx: *mut AVCodecContext,
471 ) -> Result<AudioStreamInfo, DecodeError> {
472 let (sample_rate, channels, sample_fmt, duration_val, channel_layout, codec_id) = unsafe {
474 let stream = (*format_ctx).streams.add(stream_index as usize);
475 let codecpar = (*(*stream)).codecpar;
476
477 (
478 (*codecpar).sample_rate as u32,
479 (*codecpar).ch_layout.nb_channels as u32,
480 (*codec_ctx).sample_fmt,
481 (*format_ctx).duration,
482 (*codecpar).ch_layout,
483 (*codecpar).codec_id,
484 )
485 };
486
487 let duration = if duration_val > 0 {
489 let duration_secs = duration_val as f64 / 1_000_000.0;
490 Some(Duration::from_secs_f64(duration_secs))
491 } else {
492 None
493 };
494
495 let sample_format = resample_inner::convert_sample_format(sample_fmt);
497
498 let channel_layout_enum = Self::convert_channel_layout(&channel_layout, channels);
500
501 let codec = Self::convert_codec(codec_id);
503 let codec_name = unsafe { Self::extract_codec_name(codec_id) };
504
505 let mut builder = AudioStreamInfo::builder()
507 .index(stream_index as u32)
508 .codec(codec)
509 .codec_name(codec_name)
510 .sample_rate(sample_rate)
511 .channels(channels)
512 .sample_format(sample_format)
513 .channel_layout(channel_layout_enum);
514
515 if let Some(d) = duration {
516 builder = builder.duration(d);
517 }
518
519 Ok(builder.build())
520 }
521
522 unsafe fn extract_container_info(format_ctx: *mut AVFormatContext) -> ContainerInfo {
528 unsafe {
530 let format_name = if (*format_ctx).iformat.is_null() {
531 String::new()
532 } else {
533 let ptr = (*(*format_ctx).iformat).name;
534 if ptr.is_null() {
535 String::new()
536 } else {
537 CStr::from_ptr(ptr).to_string_lossy().into_owned()
538 }
539 };
540
541 let bit_rate = {
542 let br = (*format_ctx).bit_rate;
543 if br > 0 { Some(br as u64) } else { None }
544 };
545
546 let nb_streams = (*format_ctx).nb_streams as u32;
547
548 let mut builder = ContainerInfo::builder()
549 .format_name(format_name)
550 .nb_streams(nb_streams);
551 if let Some(br) = bit_rate {
552 builder = builder.bit_rate(br);
553 }
554 builder.build()
555 }
556 }
557
558 fn convert_channel_layout(layout: &ff_sys::AVChannelLayout, channels: u32) -> ChannelLayout {
560 if layout.order == ff_sys::AVChannelOrder_AV_CHANNEL_ORDER_NATIVE {
561 let mask = unsafe { layout.u.mask };
563 match mask {
564 0x4 => ChannelLayout::Mono,
565 0x3 => ChannelLayout::Stereo,
566 0x103 => ChannelLayout::Stereo2_1,
567 0x7 => ChannelLayout::Surround3_0,
568 0x33 => ChannelLayout::Quad,
569 0x37 => ChannelLayout::Surround5_0,
570 0x3F => ChannelLayout::Surround5_1,
571 0x13F => ChannelLayout::Surround6_1,
572 0x63F => ChannelLayout::Surround7_1,
573 _ => {
574 log::warn!(
575 "channel_layout mask has no mapping, deriving from channel count \
576 mask={mask} channels={channels}"
577 );
578 ChannelLayout::from_channels(channels)
579 }
580 }
581 } else {
582 log::warn!(
583 "channel_layout order is not NATIVE, deriving from channel count \
584 order={order} channels={channels}",
585 order = layout.order
586 );
587 ChannelLayout::from_channels(channels)
588 }
589 }
590
591 fn convert_codec(codec_id: AVCodecID) -> AudioCodec {
593 if codec_id == ff_sys::AVCodecID_AV_CODEC_ID_AAC {
594 AudioCodec::Aac
595 } else if codec_id == ff_sys::AVCodecID_AV_CODEC_ID_MP3 {
596 AudioCodec::Mp3
597 } else if codec_id == ff_sys::AVCodecID_AV_CODEC_ID_OPUS {
598 AudioCodec::Opus
599 } else if codec_id == ff_sys::AVCodecID_AV_CODEC_ID_VORBIS {
600 AudioCodec::Vorbis
601 } else if codec_id == ff_sys::AVCodecID_AV_CODEC_ID_FLAC {
602 AudioCodec::Flac
603 } else if codec_id == ff_sys::AVCodecID_AV_CODEC_ID_PCM_S16LE {
604 AudioCodec::Pcm
605 } else {
606 log::warn!(
607 "audio codec unsupported, falling back to Aac codec_id={codec_id} fallback=Aac"
608 );
609 AudioCodec::Aac
610 }
611 }
612
613 pub(crate) fn decode_one(&mut self) -> Result<Option<AudioFrame>, DecodeError> {
624 loop {
625 match self.decode_one_inner() {
626 Ok(frame) => return Ok(frame),
627 Err(DecodeError::StreamInterrupted { .. })
628 if self.url.is_some() && self.network_opts.reconnect_on_error =>
629 {
630 self.attempt_reconnect()?;
631 }
632 Err(e) => return Err(e),
633 }
634 }
635 }
636
637 fn decode_one_inner(&mut self) -> Result<Option<AudioFrame>, DecodeError> {
638 if self.eof {
639 return Ok(None);
640 }
641
642 unsafe {
643 loop {
644 let ret = ff_sys::avcodec_receive_frame(self.codec_ctx, self.frame);
646
647 if ret == 0 {
648 let audio_frame = resample_inner::convert_frame_to_audio_frame(
650 self.frame,
651 self.format_ctx,
652 self.stream_index,
653 self.output_format,
654 self.output_sample_rate,
655 self.output_channels,
656 )?;
657
658 let pts = (*self.frame).pts;
660 if pts != ff_sys::AV_NOPTS_VALUE {
661 let stream = (*self.format_ctx).streams.add(self.stream_index as usize);
662 let time_base = (*(*stream)).time_base;
663 let timestamp_secs =
664 pts as f64 * time_base.num as f64 / time_base.den as f64;
665 self.position = Duration::from_secs_f64(timestamp_secs);
666 }
667
668 return Ok(Some(audio_frame));
669 } else if ret == ff_sys::error_codes::EAGAIN {
670 let read_ret = ff_sys::av_read_frame(self.format_ctx, self.packet);
673
674 if read_ret == ff_sys::error_codes::EOF {
675 ff_sys::avcodec_send_packet(self.codec_ctx, ptr::null());
677 self.eof = true;
678 continue;
679 } else if read_ret < 0 {
680 return Err(if let Some(url) = &self.url {
681 crate::network::map_network_error(
683 read_ret,
684 crate::network::sanitize_url(url),
685 )
686 } else {
687 DecodeError::Ffmpeg {
688 code: read_ret,
689 message: format!(
690 "Failed to read frame: {}",
691 ff_sys::av_error_string(read_ret)
692 ),
693 }
694 });
695 }
696
697 if (*self.packet).stream_index == self.stream_index {
699 let send_ret = ff_sys::avcodec_send_packet(self.codec_ctx, self.packet);
701 ff_sys::av_packet_unref(self.packet);
702
703 if send_ret < 0 && send_ret != ff_sys::error_codes::EAGAIN {
704 return Err(DecodeError::Ffmpeg {
705 code: send_ret,
706 message: format!(
707 "Failed to send packet: {}",
708 ff_sys::av_error_string(send_ret)
709 ),
710 });
711 }
712 } else {
713 ff_sys::av_packet_unref(self.packet);
715 }
716 } else if ret == ff_sys::error_codes::EOF {
717 self.eof = true;
719 return Ok(None);
720 } else {
721 return Err(DecodeError::DecodingFailed {
722 timestamp: Some(self.position),
723 reason: ff_sys::av_error_string(ret),
724 });
725 }
726 }
727 }
728 }
729
730 pub(crate) fn position(&self) -> Duration {
732 self.position
733 }
734
735 pub(crate) fn is_eof(&self) -> bool {
737 self.eof
738 }
739
740 pub(crate) fn is_live(&self) -> bool {
745 self.is_live
746 }
747
748 fn duration_to_pts(&self, duration: Duration) -> i64 {
750 let time_base = unsafe {
752 let stream = (*self.format_ctx).streams.add(self.stream_index as usize);
753 (*(*stream)).time_base
754 };
755
756 let time_base_f64 = time_base.den as f64 / time_base.num as f64;
758 (duration.as_secs_f64() * time_base_f64) as i64
759 }
760
761 pub(crate) fn seek(
772 &mut self,
773 position: Duration,
774 mode: crate::SeekMode,
775 ) -> Result<(), DecodeError> {
776 use crate::SeekMode;
777
778 let timestamp = self.duration_to_pts(position);
779 let flags = ff_sys::avformat::seek_flags::BACKWARD;
780
781 unsafe {
784 ff_sys::av_packet_unref(self.packet);
785 ff_sys::av_frame_unref(self.frame);
786 }
787
788 unsafe {
791 ff_sys::avformat::seek_frame(self.format_ctx, self.stream_index, timestamp, flags)
792 .map_err(|e| DecodeError::SeekFailed {
793 target: position,
794 reason: ff_sys::av_error_string(e),
795 })?;
796 }
797
798 unsafe {
801 ff_sys::avcodec::flush_buffers(self.codec_ctx);
802 }
803
804 unsafe {
807 loop {
808 let ret = ff_sys::avcodec_receive_frame(self.codec_ctx, self.frame);
809 if ret == ff_sys::error_codes::EAGAIN || ret == ff_sys::error_codes::EOF {
810 break;
811 } else if ret == 0 {
812 ff_sys::av_frame_unref(self.frame);
813 } else {
814 break;
815 }
816 }
817 }
818
819 self.eof = false;
821
822 if mode == SeekMode::Exact {
824 self.skip_to_exact(position)?;
825 }
826 Ok(())
829 }
830
831 fn skip_to_exact(&mut self, target: Duration) -> Result<(), DecodeError> {
839 while let Some(frame) = self.decode_one()? {
841 let frame_time = frame.timestamp().as_duration();
842 if frame_time >= target {
843 break;
845 }
846 }
848 Ok(())
849 }
850
851 pub(crate) fn flush(&mut self) {
853 unsafe {
855 ff_sys::avcodec::flush_buffers(self.codec_ctx);
856 }
857 self.eof = false;
858 }
859
860 fn attempt_reconnect(&mut self) -> Result<(), DecodeError> {
868 let url = match self.url.as_deref() {
869 Some(u) => u.to_owned(),
870 None => return Ok(()), };
872 let max = self.network_opts.max_reconnect_attempts;
873
874 for attempt in 1..=max {
875 let backoff_ms = 100u64 * (1u64 << (attempt - 1).min(10));
876 log::warn!(
877 "reconnecting attempt={attempt} url={} backoff_ms={backoff_ms}",
878 crate::network::sanitize_url(&url)
879 );
880 std::thread::sleep(Duration::from_millis(backoff_ms));
881 match self.reopen(&url) {
882 Ok(()) => {
883 self.reconnect_count += 1;
884 log::info!(
885 "reconnected attempt={attempt} url={} total_reconnects={}",
886 crate::network::sanitize_url(&url),
887 self.reconnect_count
888 );
889 return Ok(());
890 }
891 Err(e) => log::warn!("reconnect attempt={attempt} failed err={e}"),
892 }
893 }
894
895 Err(DecodeError::StreamInterrupted {
896 code: 0,
897 endpoint: crate::network::sanitize_url(&url),
898 message: format!("stream did not recover after {max} attempts"),
899 })
900 }
901
902 fn reopen(&mut self, url: &str) -> Result<(), DecodeError> {
905 unsafe {
909 ff_sys::avformat::close_input(std::ptr::addr_of_mut!(self.format_ctx));
910 }
911
912 self.format_ctx = unsafe {
915 ff_sys::avformat::open_input_url(
916 url,
917 self.network_opts.connect_timeout,
918 self.network_opts.read_timeout,
919 )
920 .map_err(|e| crate::network::map_network_error(e, crate::network::sanitize_url(url)))?
921 };
922
923 unsafe {
926 ff_sys::avformat::find_stream_info(self.format_ctx).map_err(|e| {
927 DecodeError::Ffmpeg {
928 code: e,
929 message: format!(
930 "reconnect find_stream_info failed: {}",
931 ff_sys::av_error_string(e)
932 ),
933 }
934 })?;
935 }
936
937 let (stream_index, _) = unsafe { Self::find_audio_stream(self.format_ctx) }
940 .ok_or_else(|| DecodeError::NoAudioStream { path: url.into() })?;
941 self.stream_index = stream_index as i32;
942
943 unsafe {
946 ff_sys::avcodec::flush_buffers(self.codec_ctx);
947 }
948
949 self.eof = false;
950 Ok(())
951 }
952}
953
954impl Drop for AudioDecoderInner {
955 fn drop(&mut self) {
956 if !self.frame.is_null() {
958 unsafe {
960 ff_sys::av_frame_free(&mut (self.frame as *mut _));
961 }
962 }
963
964 if !self.packet.is_null() {
965 unsafe {
967 ff_sys::av_packet_free(&mut (self.packet as *mut _));
968 }
969 }
970
971 if !self.codec_ctx.is_null() {
973 unsafe {
975 ff_sys::avcodec::free_context(&mut (self.codec_ctx as *mut _));
976 }
977 }
978
979 if !self.format_ctx.is_null() {
981 unsafe {
983 ff_sys::avformat::close_input(&mut (self.format_ctx as *mut _));
984 }
985 }
986 }
987}
988
989unsafe impl Send for AudioDecoderInner {}
992
993#[cfg(test)]
994#[allow(unsafe_code)]
995mod tests {
996 use ff_format::channel::ChannelLayout;
997
998 use super::AudioDecoderInner;
999
1000 fn native_layout(mask: u64, nb_channels: i32) -> ff_sys::AVChannelLayout {
1002 ff_sys::AVChannelLayout {
1003 order: ff_sys::AVChannelOrder_AV_CHANNEL_ORDER_NATIVE,
1004 nb_channels,
1005 u: ff_sys::AVChannelLayout__bindgen_ty_1 { mask },
1006 opaque: std::ptr::null_mut(),
1007 }
1008 }
1009
1010 fn unspec_layout(nb_channels: i32) -> ff_sys::AVChannelLayout {
1012 ff_sys::AVChannelLayout {
1013 order: ff_sys::AVChannelOrder_AV_CHANNEL_ORDER_UNSPEC,
1014 nb_channels,
1015 u: ff_sys::AVChannelLayout__bindgen_ty_1 { mask: 0 },
1016 opaque: std::ptr::null_mut(),
1017 }
1018 }
1019
1020 #[test]
1021 fn native_mask_mono() {
1022 let layout = native_layout(0x4, 1);
1023 assert_eq!(
1024 AudioDecoderInner::convert_channel_layout(&layout, 1),
1025 ChannelLayout::Mono
1026 );
1027 }
1028
1029 #[test]
1030 fn native_mask_stereo() {
1031 let layout = native_layout(0x3, 2);
1032 assert_eq!(
1033 AudioDecoderInner::convert_channel_layout(&layout, 2),
1034 ChannelLayout::Stereo
1035 );
1036 }
1037
1038 #[test]
1039 fn native_mask_stereo2_1() {
1040 let layout = native_layout(0x103, 3);
1041 assert_eq!(
1042 AudioDecoderInner::convert_channel_layout(&layout, 3),
1043 ChannelLayout::Stereo2_1
1044 );
1045 }
1046
1047 #[test]
1048 fn native_mask_surround3_0() {
1049 let layout = native_layout(0x7, 3);
1050 assert_eq!(
1051 AudioDecoderInner::convert_channel_layout(&layout, 3),
1052 ChannelLayout::Surround3_0
1053 );
1054 }
1055
1056 #[test]
1057 fn native_mask_quad() {
1058 let layout = native_layout(0x33, 4);
1059 assert_eq!(
1060 AudioDecoderInner::convert_channel_layout(&layout, 4),
1061 ChannelLayout::Quad
1062 );
1063 }
1064
1065 #[test]
1066 fn native_mask_surround5_0() {
1067 let layout = native_layout(0x37, 5);
1068 assert_eq!(
1069 AudioDecoderInner::convert_channel_layout(&layout, 5),
1070 ChannelLayout::Surround5_0
1071 );
1072 }
1073
1074 #[test]
1075 fn native_mask_surround5_1() {
1076 let layout = native_layout(0x3F, 6);
1077 assert_eq!(
1078 AudioDecoderInner::convert_channel_layout(&layout, 6),
1079 ChannelLayout::Surround5_1
1080 );
1081 }
1082
1083 #[test]
1084 fn native_mask_surround6_1() {
1085 let layout = native_layout(0x13F, 7);
1086 assert_eq!(
1087 AudioDecoderInner::convert_channel_layout(&layout, 7),
1088 ChannelLayout::Surround6_1
1089 );
1090 }
1091
1092 #[test]
1093 fn native_mask_surround7_1() {
1094 let layout = native_layout(0x63F, 8);
1095 assert_eq!(
1096 AudioDecoderInner::convert_channel_layout(&layout, 8),
1097 ChannelLayout::Surround7_1
1098 );
1099 }
1100
1101 #[test]
1102 fn native_mask_unknown_falls_back_to_from_channels() {
1103 let layout = native_layout(0x1, 2);
1105 assert_eq!(
1106 AudioDecoderInner::convert_channel_layout(&layout, 2),
1107 ChannelLayout::from_channels(2)
1108 );
1109 }
1110
1111 #[test]
1112 fn non_native_order_falls_back_to_from_channels() {
1113 let layout = unspec_layout(6);
1114 assert_eq!(
1115 AudioDecoderInner::convert_channel_layout(&layout, 6),
1116 ChannelLayout::from_channels(6)
1117 );
1118 }
1119
1120 #[test]
1125 fn codec_name_should_return_h264_for_h264_codec_id() {
1126 let name =
1127 unsafe { AudioDecoderInner::extract_codec_name(ff_sys::AVCodecID_AV_CODEC_ID_H264) };
1128 assert_eq!(name, "h264");
1129 }
1130
1131 #[test]
1132 fn codec_name_should_return_none_for_none_codec_id() {
1133 let name =
1134 unsafe { AudioDecoderInner::extract_codec_name(ff_sys::AVCodecID_AV_CODEC_ID_NONE) };
1135 assert_eq!(name, "none");
1136 }
1137
1138 #[test]
1139 fn unsupported_codec_error_should_include_codec_name() {
1140 let codec_id = ff_sys::AVCodecID_AV_CODEC_ID_MP3;
1141 let codec_name = unsafe { AudioDecoderInner::extract_codec_name(codec_id) };
1142 let error = crate::error::DecodeError::UnsupportedCodec {
1143 codec: format!("{codec_name} (codec_id={codec_id:?})"),
1144 };
1145 let msg = error.to_string();
1146 assert!(msg.contains("mp3"), "expected codec name in error: {msg}");
1147 assert!(
1148 msg.contains("codec_id="),
1149 "expected codec_id in error: {msg}"
1150 );
1151 }
1152}