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 swr_ctx: Option<resample_inner::SwrContextGuard>,
245 swr_key: Option<resample_inner::SwrKey>,
247 is_live: bool,
249 eof: bool,
251 position: Duration,
253 packet: *mut AVPacket,
255 frame: *mut AVFrame,
257 url: Option<String>,
259 network_opts: NetworkOptions,
261 reconnect_count: u32,
263}
264
265impl AudioDecoderInner {
266 #[allow(clippy::too_many_arguments)]
283 pub(crate) fn new(
284 path: &Path,
285 output_format: Option<SampleFormat>,
286 output_sample_rate: Option<u32>,
287 output_channels: Option<u32>,
288 network_opts: Option<NetworkOptions>,
289 ) -> Result<(Self, AudioStreamInfo, ContainerInfo), DecodeError> {
290 ff_sys::ensure_initialized();
292
293 let path_str = path.to_str().unwrap_or("");
294 let is_network_url = crate::network::is_url(path_str);
295
296 let url = if is_network_url {
297 Some(path_str.to_owned())
298 } else {
299 None
300 };
301 let stored_network_opts = network_opts.clone().unwrap_or_default();
302
303 if is_network_url {
305 crate::network::check_srt_url(path_str)?;
306 }
307
308 let format_ctx_guard = unsafe {
311 if is_network_url {
312 let network = network_opts.unwrap_or_default();
313 log::info!(
314 "opening network audio source url={} connect_timeout_ms={} read_timeout_ms={}",
315 crate::network::sanitize_url(path_str),
316 network.connect_timeout.as_millis(),
317 network.read_timeout.as_millis()
318 );
319 AvFormatContextGuard::new_url(path_str, &network)?
320 } else {
321 AvFormatContextGuard::new(path)?
322 }
323 };
324 let format_ctx = format_ctx_guard.as_ptr();
325
326 unsafe {
329 ff_sys::avformat::find_stream_info(format_ctx).map_err(|e| DecodeError::Ffmpeg {
330 code: e,
331 message: format!("Failed to find stream info: {}", ff_sys::av_error_string(e)),
332 })?;
333 }
334
335 let is_live = unsafe {
339 let iformat = (*format_ctx).iformat;
340 !iformat.is_null() && ((*iformat).flags & ff_sys::AVFMT_TS_DISCONT) != 0
341 };
342
343 let (stream_index, codec_id) =
346 unsafe { Self::find_audio_stream(format_ctx) }.ok_or_else(|| {
347 DecodeError::NoAudioStream {
348 path: path.to_path_buf(),
349 }
350 })?;
351
352 let codec_name = unsafe { Self::extract_codec_name(codec_id) };
355 let codec = unsafe {
356 ff_sys::avcodec::find_decoder(codec_id).ok_or_else(|| {
357 DecodeError::UnsupportedCodec {
358 codec: format!("{codec_name} (codec_id={codec_id:?})"),
359 }
360 })?
361 };
362
363 let codec_ctx_guard = unsafe { AvCodecContextGuard::new(codec)? };
366 let codec_ctx = codec_ctx_guard.as_ptr();
367
368 unsafe {
371 let stream = (*format_ctx).streams.add(stream_index as usize);
372 let codecpar = (*(*stream)).codecpar;
373 ff_sys::avcodec::parameters_to_context(codec_ctx, codecpar).map_err(|e| {
374 DecodeError::Ffmpeg {
375 code: e,
376 message: format!(
377 "Failed to copy codec parameters: {}",
378 ff_sys::av_error_string(e)
379 ),
380 }
381 })?;
382 }
383
384 unsafe {
387 ff_sys::avcodec::open2(codec_ctx, codec, ptr::null_mut()).map_err(|e| {
388 DecodeError::Ffmpeg {
389 code: e,
390 message: format!("Failed to open codec: {}", ff_sys::av_error_string(e)),
391 }
392 })?;
393 }
394
395 let stream_info =
398 unsafe { Self::extract_stream_info(format_ctx, stream_index as i32, codec_ctx)? };
399
400 let container_info = unsafe { Self::extract_container_info(format_ctx) };
403
404 let packet_guard = unsafe { AvPacketGuard::new()? };
407 let frame_guard = unsafe { AvFrameGuard::new()? };
408
409 Ok((
411 Self {
412 format_ctx: format_ctx_guard.into_raw(),
413 codec_ctx: codec_ctx_guard.into_raw(),
414 stream_index: stream_index as i32,
415 output_format,
416 output_sample_rate,
417 output_channels,
418 swr_ctx: None,
419 swr_key: None,
420 is_live,
421 eof: false,
422 position: Duration::ZERO,
423 packet: packet_guard.into_raw(),
424 frame: frame_guard.into_raw(),
425 url,
426 network_opts: stored_network_opts,
427 reconnect_count: 0,
428 },
429 stream_info,
430 container_info,
431 ))
432 }
433
434 unsafe fn find_audio_stream(format_ctx: *mut AVFormatContext) -> Option<(usize, AVCodecID)> {
444 unsafe {
446 let nb_streams = (*format_ctx).nb_streams as usize;
447
448 for i in 0..nb_streams {
449 let stream = (*format_ctx).streams.add(i);
450 let codecpar = (*(*stream)).codecpar;
451
452 if (*codecpar).codec_type == AVMediaType_AVMEDIA_TYPE_AUDIO {
453 return Some((i, (*codecpar).codec_id));
454 }
455 }
456
457 None
458 }
459 }
460
461 unsafe fn extract_codec_name(codec_id: ff_sys::AVCodecID) -> String {
463 let name_ptr = unsafe { ff_sys::avcodec_get_name(codec_id) };
465 if name_ptr.is_null() {
466 return String::from("unknown");
467 }
468 unsafe { CStr::from_ptr(name_ptr).to_string_lossy().into_owned() }
470 }
471
472 unsafe fn extract_stream_info(
474 format_ctx: *mut AVFormatContext,
475 stream_index: i32,
476 codec_ctx: *mut AVCodecContext,
477 ) -> Result<AudioStreamInfo, DecodeError> {
478 let (sample_rate, channels, sample_fmt, duration_val, channel_layout, codec_id) = unsafe {
480 let stream = (*format_ctx).streams.add(stream_index as usize);
481 let codecpar = (*(*stream)).codecpar;
482
483 (
484 (*codecpar).sample_rate as u32,
485 (*codecpar).ch_layout.nb_channels as u32,
486 (*codec_ctx).sample_fmt,
487 (*format_ctx).duration,
488 (*codecpar).ch_layout,
489 (*codecpar).codec_id,
490 )
491 };
492
493 let duration = if duration_val > 0 {
495 let duration_secs = duration_val as f64 / 1_000_000.0;
496 Some(Duration::from_secs_f64(duration_secs))
497 } else {
498 None
499 };
500
501 let sample_format = resample_inner::convert_sample_format(sample_fmt);
503
504 let channel_layout_enum = Self::convert_channel_layout(&channel_layout, channels);
506
507 let codec = Self::convert_codec(codec_id);
509 let codec_name = unsafe { Self::extract_codec_name(codec_id) };
510
511 let mut builder = AudioStreamInfo::builder()
513 .index(stream_index as u32)
514 .codec(codec)
515 .codec_name(codec_name)
516 .sample_rate(sample_rate)
517 .channels(channels)
518 .sample_format(sample_format)
519 .channel_layout(channel_layout_enum);
520
521 if let Some(d) = duration {
522 builder = builder.duration(d);
523 }
524
525 Ok(builder.build())
526 }
527
528 unsafe fn extract_container_info(format_ctx: *mut AVFormatContext) -> ContainerInfo {
534 unsafe {
536 let format_name = if (*format_ctx).iformat.is_null() {
537 String::new()
538 } else {
539 let ptr = (*(*format_ctx).iformat).name;
540 if ptr.is_null() {
541 String::new()
542 } else {
543 CStr::from_ptr(ptr).to_string_lossy().into_owned()
544 }
545 };
546
547 let bit_rate = {
548 let br = (*format_ctx).bit_rate;
549 if br > 0 { Some(br as u64) } else { None }
550 };
551
552 let nb_streams = (*format_ctx).nb_streams as u32;
553
554 let mut builder = ContainerInfo::builder()
555 .format_name(format_name)
556 .nb_streams(nb_streams);
557 if let Some(br) = bit_rate {
558 builder = builder.bit_rate(br);
559 }
560 builder.build()
561 }
562 }
563
564 fn convert_channel_layout(layout: &ff_sys::AVChannelLayout, channels: u32) -> ChannelLayout {
566 if layout.order == ff_sys::AVChannelOrder_AV_CHANNEL_ORDER_NATIVE {
567 let mask = unsafe { layout.u.mask };
569 match mask {
570 0x4 => ChannelLayout::Mono,
571 0x3 => ChannelLayout::Stereo,
572 0x103 => ChannelLayout::Stereo2_1,
573 0x7 => ChannelLayout::Surround3_0,
574 0x33 => ChannelLayout::Quad,
575 0x37 => ChannelLayout::Surround5_0,
576 0x3F => ChannelLayout::Surround5_1,
577 0x13F => ChannelLayout::Surround6_1,
578 0x63F => ChannelLayout::Surround7_1,
579 _ => {
580 log::warn!(
581 "channel_layout mask has no mapping, deriving from channel count \
582 mask={mask} channels={channels}"
583 );
584 ChannelLayout::from_channels(channels)
585 }
586 }
587 } else {
588 log::warn!(
589 "channel_layout order is not NATIVE, deriving from channel count \
590 order={order} channels={channels}",
591 order = layout.order
592 );
593 ChannelLayout::from_channels(channels)
594 }
595 }
596
597 fn convert_codec(codec_id: AVCodecID) -> AudioCodec {
599 if codec_id == ff_sys::AVCodecID_AV_CODEC_ID_AAC {
600 AudioCodec::Aac
601 } else if codec_id == ff_sys::AVCodecID_AV_CODEC_ID_MP3 {
602 AudioCodec::Mp3
603 } else if codec_id == ff_sys::AVCodecID_AV_CODEC_ID_OPUS {
604 AudioCodec::Opus
605 } else if codec_id == ff_sys::AVCodecID_AV_CODEC_ID_VORBIS {
606 AudioCodec::Vorbis
607 } else if codec_id == ff_sys::AVCodecID_AV_CODEC_ID_FLAC {
608 AudioCodec::Flac
609 } else if codec_id == ff_sys::AVCodecID_AV_CODEC_ID_PCM_S16LE {
610 AudioCodec::Pcm
611 } else {
612 log::warn!(
613 "audio codec unsupported, falling back to Aac codec_id={codec_id} fallback=Aac"
614 );
615 AudioCodec::Aac
616 }
617 }
618
619 pub(crate) fn decode_one(&mut self) -> Result<Option<AudioFrame>, DecodeError> {
630 loop {
631 match self.decode_one_inner() {
632 Ok(frame) => return Ok(frame),
633 Err(DecodeError::StreamInterrupted { .. })
634 if self.url.is_some() && self.network_opts.reconnect_on_error =>
635 {
636 self.attempt_reconnect()?;
637 }
638 Err(e) => return Err(e),
639 }
640 }
641 }
642
643 fn decode_one_inner(&mut self) -> Result<Option<AudioFrame>, DecodeError> {
644 if self.eof {
645 return Ok(None);
646 }
647
648 unsafe {
649 loop {
650 let ret = ff_sys::avcodec_receive_frame(self.codec_ctx, self.frame);
652
653 if ret == 0 {
654 let audio_frame = resample_inner::convert_frame_to_audio_frame(
656 self.frame,
657 self.format_ctx,
658 self.stream_index,
659 self.output_format,
660 self.output_sample_rate,
661 self.output_channels,
662 &mut self.swr_ctx,
663 &mut self.swr_key,
664 )?;
665
666 let pts = (*self.frame).pts;
668 if pts != ff_sys::AV_NOPTS_VALUE {
669 let stream = (*self.format_ctx).streams.add(self.stream_index as usize);
670 let time_base = (*(*stream)).time_base;
671 let timestamp_secs =
672 pts as f64 * time_base.num as f64 / time_base.den as f64;
673 self.position = Duration::from_secs_f64(timestamp_secs);
674 }
675
676 return Ok(Some(audio_frame));
677 } else if ret == ff_sys::error_codes::EAGAIN {
678 let read_ret = ff_sys::av_read_frame(self.format_ctx, self.packet);
681
682 if read_ret == ff_sys::error_codes::EOF {
683 ff_sys::avcodec_send_packet(self.codec_ctx, ptr::null());
685 self.eof = true;
686 continue;
687 } else if read_ret < 0 {
688 return Err(if let Some(url) = &self.url {
689 crate::network::map_network_error(
691 read_ret,
692 crate::network::sanitize_url(url),
693 )
694 } else {
695 DecodeError::Ffmpeg {
696 code: read_ret,
697 message: format!(
698 "Failed to read frame: {}",
699 ff_sys::av_error_string(read_ret)
700 ),
701 }
702 });
703 }
704
705 if (*self.packet).stream_index == self.stream_index {
707 let send_ret = ff_sys::avcodec_send_packet(self.codec_ctx, self.packet);
709 ff_sys::av_packet_unref(self.packet);
710
711 if send_ret < 0 && send_ret != ff_sys::error_codes::EAGAIN {
712 return Err(DecodeError::Ffmpeg {
713 code: send_ret,
714 message: format!(
715 "Failed to send packet: {}",
716 ff_sys::av_error_string(send_ret)
717 ),
718 });
719 }
720 } else {
721 ff_sys::av_packet_unref(self.packet);
723 }
724 } else if ret == ff_sys::error_codes::EOF {
725 self.eof = true;
727 return Ok(None);
728 } else {
729 return Err(DecodeError::DecodingFailed {
730 timestamp: Some(self.position),
731 reason: ff_sys::av_error_string(ret),
732 });
733 }
734 }
735 }
736 }
737
738 pub(crate) fn position(&self) -> Duration {
740 self.position
741 }
742
743 pub(crate) fn is_eof(&self) -> bool {
745 self.eof
746 }
747
748 pub(crate) fn is_live(&self) -> bool {
753 self.is_live
754 }
755
756 fn duration_to_pts(&self, duration: Duration) -> i64 {
758 let time_base = unsafe {
760 let stream = (*self.format_ctx).streams.add(self.stream_index as usize);
761 (*(*stream)).time_base
762 };
763
764 let time_base_f64 = time_base.den as f64 / time_base.num as f64;
766 (duration.as_secs_f64() * time_base_f64) as i64
767 }
768
769 pub(crate) fn seek(
780 &mut self,
781 position: Duration,
782 mode: crate::SeekMode,
783 ) -> Result<(), DecodeError> {
784 use crate::SeekMode;
785
786 let timestamp = self.duration_to_pts(position);
787 let flags = ff_sys::avformat::seek_flags::BACKWARD;
788
789 unsafe {
792 ff_sys::av_packet_unref(self.packet);
793 ff_sys::av_frame_unref(self.frame);
794 }
795
796 unsafe {
799 ff_sys::avformat::seek_frame(self.format_ctx, self.stream_index, timestamp, flags)
800 .map_err(|e| DecodeError::SeekFailed {
801 target: position,
802 reason: ff_sys::av_error_string(e),
803 })?;
804 }
805
806 unsafe {
810 ff_sys::avcodec::flush_buffers(self.codec_ctx);
811 }
812 self.swr_ctx = None;
813 self.swr_key = None;
814
815 unsafe {
818 loop {
819 let ret = ff_sys::avcodec_receive_frame(self.codec_ctx, self.frame);
820 if ret == ff_sys::error_codes::EAGAIN || ret == ff_sys::error_codes::EOF {
821 break;
822 } else if ret == 0 {
823 ff_sys::av_frame_unref(self.frame);
824 } else {
825 break;
826 }
827 }
828 }
829
830 self.eof = false;
832
833 if mode == SeekMode::Exact {
835 self.skip_to_exact(position)?;
836 }
837 Ok(())
840 }
841
842 fn skip_to_exact(&mut self, target: Duration) -> Result<(), DecodeError> {
850 while let Some(frame) = self.decode_one()? {
852 let frame_time = frame.timestamp().as_duration();
853 if frame_time >= target {
854 break;
856 }
857 }
859 Ok(())
860 }
861
862 pub(crate) fn flush(&mut self) {
864 unsafe {
866 ff_sys::avcodec::flush_buffers(self.codec_ctx);
867 }
868 self.eof = false;
869 }
870
871 fn attempt_reconnect(&mut self) -> Result<(), DecodeError> {
879 let url = match self.url.as_deref() {
880 Some(u) => u.to_owned(),
881 None => return Ok(()), };
883 let max = self.network_opts.max_reconnect_attempts;
884
885 for attempt in 1..=max {
886 let backoff_ms = 100u64 * (1u64 << (attempt - 1).min(10));
887 log::warn!(
888 "reconnecting attempt={attempt} url={} backoff_ms={backoff_ms}",
889 crate::network::sanitize_url(&url)
890 );
891 std::thread::sleep(Duration::from_millis(backoff_ms));
892 match self.reopen(&url) {
893 Ok(()) => {
894 self.reconnect_count += 1;
895 log::info!(
896 "reconnected attempt={attempt} url={} total_reconnects={}",
897 crate::network::sanitize_url(&url),
898 self.reconnect_count
899 );
900 return Ok(());
901 }
902 Err(e) => log::warn!("reconnect attempt={attempt} failed err={e}"),
903 }
904 }
905
906 Err(DecodeError::StreamInterrupted {
907 code: 0,
908 endpoint: crate::network::sanitize_url(&url),
909 message: format!("stream did not recover after {max} attempts"),
910 })
911 }
912
913 fn reopen(&mut self, url: &str) -> Result<(), DecodeError> {
916 unsafe {
920 ff_sys::avformat::close_input(std::ptr::addr_of_mut!(self.format_ctx));
921 }
922
923 self.format_ctx = unsafe {
926 ff_sys::avformat::open_input_url(
927 url,
928 self.network_opts.connect_timeout,
929 self.network_opts.read_timeout,
930 )
931 .map_err(|e| crate::network::map_network_error(e, crate::network::sanitize_url(url)))?
932 };
933
934 unsafe {
937 ff_sys::avformat::find_stream_info(self.format_ctx).map_err(|e| {
938 DecodeError::Ffmpeg {
939 code: e,
940 message: format!(
941 "reconnect find_stream_info failed: {}",
942 ff_sys::av_error_string(e)
943 ),
944 }
945 })?;
946 }
947
948 let (stream_index, _) = unsafe { Self::find_audio_stream(self.format_ctx) }
951 .ok_or_else(|| DecodeError::NoAudioStream { path: url.into() })?;
952 self.stream_index = stream_index as i32;
953
954 unsafe {
957 ff_sys::avcodec::flush_buffers(self.codec_ctx);
958 }
959
960 self.eof = false;
961 Ok(())
962 }
963}
964
965impl Drop for AudioDecoderInner {
966 fn drop(&mut self) {
967 if !self.frame.is_null() {
969 unsafe {
971 ff_sys::av_frame_free(&mut (self.frame as *mut _));
972 }
973 }
974
975 if !self.packet.is_null() {
976 unsafe {
978 ff_sys::av_packet_free(&mut (self.packet as *mut _));
979 }
980 }
981
982 if !self.codec_ctx.is_null() {
984 unsafe {
986 ff_sys::avcodec::free_context(&mut (self.codec_ctx as *mut _));
987 }
988 }
989
990 if !self.format_ctx.is_null() {
992 unsafe {
994 ff_sys::avformat::close_input(&mut (self.format_ctx as *mut _));
995 }
996 }
997 }
998}
999
1000unsafe impl Send for AudioDecoderInner {}
1003
1004#[cfg(test)]
1005#[allow(unsafe_code)]
1006mod tests {
1007 use ff_format::channel::ChannelLayout;
1008
1009 use super::AudioDecoderInner;
1010
1011 fn native_layout(mask: u64, nb_channels: i32) -> ff_sys::AVChannelLayout {
1013 ff_sys::AVChannelLayout {
1014 order: ff_sys::AVChannelOrder_AV_CHANNEL_ORDER_NATIVE,
1015 nb_channels,
1016 u: ff_sys::AVChannelLayout__bindgen_ty_1 { mask },
1017 opaque: std::ptr::null_mut(),
1018 }
1019 }
1020
1021 fn unspec_layout(nb_channels: i32) -> ff_sys::AVChannelLayout {
1023 ff_sys::AVChannelLayout {
1024 order: ff_sys::AVChannelOrder_AV_CHANNEL_ORDER_UNSPEC,
1025 nb_channels,
1026 u: ff_sys::AVChannelLayout__bindgen_ty_1 { mask: 0 },
1027 opaque: std::ptr::null_mut(),
1028 }
1029 }
1030
1031 #[test]
1032 fn native_mask_mono() {
1033 let layout = native_layout(0x4, 1);
1034 assert_eq!(
1035 AudioDecoderInner::convert_channel_layout(&layout, 1),
1036 ChannelLayout::Mono
1037 );
1038 }
1039
1040 #[test]
1041 fn native_mask_stereo() {
1042 let layout = native_layout(0x3, 2);
1043 assert_eq!(
1044 AudioDecoderInner::convert_channel_layout(&layout, 2),
1045 ChannelLayout::Stereo
1046 );
1047 }
1048
1049 #[test]
1050 fn native_mask_stereo2_1() {
1051 let layout = native_layout(0x103, 3);
1052 assert_eq!(
1053 AudioDecoderInner::convert_channel_layout(&layout, 3),
1054 ChannelLayout::Stereo2_1
1055 );
1056 }
1057
1058 #[test]
1059 fn native_mask_surround3_0() {
1060 let layout = native_layout(0x7, 3);
1061 assert_eq!(
1062 AudioDecoderInner::convert_channel_layout(&layout, 3),
1063 ChannelLayout::Surround3_0
1064 );
1065 }
1066
1067 #[test]
1068 fn native_mask_quad() {
1069 let layout = native_layout(0x33, 4);
1070 assert_eq!(
1071 AudioDecoderInner::convert_channel_layout(&layout, 4),
1072 ChannelLayout::Quad
1073 );
1074 }
1075
1076 #[test]
1077 fn native_mask_surround5_0() {
1078 let layout = native_layout(0x37, 5);
1079 assert_eq!(
1080 AudioDecoderInner::convert_channel_layout(&layout, 5),
1081 ChannelLayout::Surround5_0
1082 );
1083 }
1084
1085 #[test]
1086 fn native_mask_surround5_1() {
1087 let layout = native_layout(0x3F, 6);
1088 assert_eq!(
1089 AudioDecoderInner::convert_channel_layout(&layout, 6),
1090 ChannelLayout::Surround5_1
1091 );
1092 }
1093
1094 #[test]
1095 fn native_mask_surround6_1() {
1096 let layout = native_layout(0x13F, 7);
1097 assert_eq!(
1098 AudioDecoderInner::convert_channel_layout(&layout, 7),
1099 ChannelLayout::Surround6_1
1100 );
1101 }
1102
1103 #[test]
1104 fn native_mask_surround7_1() {
1105 let layout = native_layout(0x63F, 8);
1106 assert_eq!(
1107 AudioDecoderInner::convert_channel_layout(&layout, 8),
1108 ChannelLayout::Surround7_1
1109 );
1110 }
1111
1112 #[test]
1113 fn native_mask_unknown_falls_back_to_from_channels() {
1114 let layout = native_layout(0x1, 2);
1116 assert_eq!(
1117 AudioDecoderInner::convert_channel_layout(&layout, 2),
1118 ChannelLayout::from_channels(2)
1119 );
1120 }
1121
1122 #[test]
1123 fn non_native_order_falls_back_to_from_channels() {
1124 let layout = unspec_layout(6);
1125 assert_eq!(
1126 AudioDecoderInner::convert_channel_layout(&layout, 6),
1127 ChannelLayout::from_channels(6)
1128 );
1129 }
1130
1131 #[test]
1136 fn codec_name_should_return_h264_for_h264_codec_id() {
1137 let name =
1138 unsafe { AudioDecoderInner::extract_codec_name(ff_sys::AVCodecID_AV_CODEC_ID_H264) };
1139 assert_eq!(name, "h264");
1140 }
1141
1142 #[test]
1143 fn codec_name_should_return_none_for_none_codec_id() {
1144 let name =
1145 unsafe { AudioDecoderInner::extract_codec_name(ff_sys::AVCodecID_AV_CODEC_ID_NONE) };
1146 assert_eq!(name, "none");
1147 }
1148
1149 #[test]
1150 fn unsupported_codec_error_should_include_codec_name() {
1151 let codec_id = ff_sys::AVCodecID_AV_CODEC_ID_MP3;
1152 let codec_name = unsafe { AudioDecoderInner::extract_codec_name(codec_id) };
1153 let error = crate::error::DecodeError::UnsupportedCodec {
1154 codec: format!("{codec_name} (codec_id={codec_id:?})"),
1155 };
1156 let msg = error.to_string();
1157 assert!(msg.contains("mp3"), "expected codec name in error: {msg}");
1158 assert!(
1159 msg.contains("codec_id="),
1160 "expected codec_id in error: {msg}"
1161 );
1162 }
1163}