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::time::Duration;
28
29use ff_format::channel::ChannelLayout;
30use ff_format::codec::AudioCodec;
31use ff_format::container::ContainerInfo;
32use ff_format::{AudioFrame, AudioStreamInfo, NetworkOptions, SampleFormat};
33use ff_sys::{AVCodecID, AVMediaType_AVMEDIA_TYPE_AUDIO, Frame, InputFormatContext, Packet};
34
35use super::resample_inner;
36
37use crate::error::DecodeError;
38use crate::shared::guards_inner::{open_input_ctx, open_url_ctx};
39
40pub(crate) struct AudioDecoderInner {
45 format_ctx: InputFormatContext,
47 codec_ctx: ff_sys::CodecContext,
49 stream_index: i32,
51 output_format: Option<SampleFormat>,
53 output_sample_rate: Option<u32>,
55 output_channels: Option<u32>,
57 swr_ctx: Option<ff_sys::ResampleContext>,
59 swr_key: Option<resample_inner::SwrKey>,
61 is_live: bool,
63 eof: bool,
65 position: Duration,
67 packet: Packet,
69 frame: Frame,
71 url: Option<String>,
73 network_opts: NetworkOptions,
75 reconnect_count: u32,
77}
78
79impl AudioDecoderInner {
80 #[allow(clippy::too_many_arguments)]
97 pub(crate) fn new(
98 path: &Path,
99 output_format: Option<SampleFormat>,
100 output_sample_rate: Option<u32>,
101 output_channels: Option<u32>,
102 network_opts: Option<NetworkOptions>,
103 ) -> Result<(Self, AudioStreamInfo, ContainerInfo), DecodeError> {
104 ff_sys::ensure_initialized();
106
107 let path_str = path.to_str().unwrap_or("");
108 let is_network_url = crate::network::is_url(path_str);
109
110 let url = if is_network_url {
111 Some(path_str.to_owned())
112 } else {
113 None
114 };
115 let stored_network_opts = network_opts.clone().unwrap_or_default();
116
117 if is_network_url {
119 crate::network::check_srt_url(path_str)?;
120 }
121
122 let mut ctx = if is_network_url {
124 let network = network_opts.unwrap_or_default();
125 log::info!(
126 "opening network audio source url={} connect_timeout_ms={} read_timeout_ms={}",
127 crate::network::sanitize_url(path_str),
128 network.connect_timeout.as_millis(),
129 network.read_timeout.as_millis()
130 );
131 open_url_ctx(path_str, &network)?
132 } else {
133 open_input_ctx(path)?
134 };
135
136 ctx.find_stream_info().map_err(|e| DecodeError::Ffmpeg {
138 code: e.code(),
139 message: format!(
140 "Failed to find stream info: {}",
141 ff_sys::av_error_string(e.code())
142 ),
143 })?;
144
145 let is_live = (ctx.iformat_flags() & ff_sys::AVFMT_TS_DISCONT) != 0;
147
148 let (stream_index, codec_id) =
150 Self::find_audio_stream(&ctx).ok_or_else(|| DecodeError::NoAudioStream {
151 path: path.to_path_buf(),
152 })?;
153
154 let codec_name = unsafe { Self::extract_codec_name(codec_id) };
157 let codec =
158 ff_sys::Codec::find_decoder(codec_id).ok_or_else(|| DecodeError::UnsupportedCodec {
159 codec: format!("{codec_name} (codec_id={codec_id:?})"),
160 })?;
161
162 let mut codec_ctx =
164 ff_sys::CodecContext::new(Some(codec)).map_err(|e| DecodeError::Ffmpeg {
165 code: e.code(),
166 message: format!(
167 "Failed to allocate codec context: {}",
168 ff_sys::av_error_string(e.code())
169 ),
170 })?;
171
172 let codecpar = ctx
174 .stream(stream_index)
175 .ok_or_else(|| DecodeError::NoAudioStream {
176 path: path.to_path_buf(),
177 })?
178 .codecpar();
179 codec_ctx
180 .apply_parameters(&codecpar)
181 .map_err(|e| DecodeError::Ffmpeg {
182 code: e.code(),
183 message: format!(
184 "Failed to copy codec parameters: {}",
185 ff_sys::av_error_string(e.code())
186 ),
187 })?;
188
189 codec_ctx
191 .open_codec(codec)
192 .map_err(|e| DecodeError::Ffmpeg {
193 code: e.code(),
194 message: format!(
195 "Failed to open codec: {}",
196 ff_sys::av_error_string(e.code())
197 ),
198 })?;
199
200 let duration_val = ctx.duration();
203 let stream = ctx
204 .stream(stream_index)
205 .ok_or_else(|| DecodeError::NoAudioStream {
206 path: path.to_path_buf(),
207 })?;
208 let stream_info = Self::extract_stream_info(stream, &codec_ctx, duration_val)?;
209
210 let container_info = Self::extract_container_info(&ctx);
212
213 let packet = Packet::new().map_err(|e| DecodeError::Ffmpeg {
216 code: e.code(),
217 message: format!(
218 "Failed to allocate packet: {}",
219 ff_sys::av_error_string(e.code())
220 ),
221 })?;
222 let frame = Frame::new().map_err(|e| DecodeError::Ffmpeg {
223 code: e.code(),
224 message: format!(
225 "Failed to allocate frame: {}",
226 ff_sys::av_error_string(e.code())
227 ),
228 })?;
229
230 Ok((
232 Self {
233 format_ctx: ctx,
234 codec_ctx,
235 stream_index: stream_index as i32,
236 output_format,
237 output_sample_rate,
238 output_channels,
239 swr_ctx: None,
240 swr_key: None,
241 is_live,
242 eof: false,
243 position: Duration::ZERO,
244 packet,
245 frame,
246 url,
247 network_opts: stored_network_opts,
248 reconnect_count: 0,
249 },
250 stream_info,
251 container_info,
252 ))
253 }
254
255 fn find_audio_stream(format_ctx: &InputFormatContext) -> Option<(usize, AVCodecID)> {
259 for stream in format_ctx.streams() {
260 let codecpar = stream.codecpar();
261 if codecpar.codec_type() == AVMediaType_AVMEDIA_TYPE_AUDIO {
262 return Some((stream.index() as usize, codecpar.codec_id()));
263 }
264 }
265 None
266 }
267
268 unsafe fn extract_codec_name(codec_id: ff_sys::AVCodecID) -> String {
270 let name_ptr = unsafe { ff_sys::avcodec_get_name(codec_id) };
272 if name_ptr.is_null() {
273 return String::from("unknown");
274 }
275 unsafe { CStr::from_ptr(name_ptr).to_string_lossy().into_owned() }
277 }
278
279 fn extract_stream_info(
282 stream: ff_sys::StreamRef<'_>,
283 codec_ctx: &ff_sys::CodecContext,
284 duration_val: i64,
285 ) -> Result<AudioStreamInfo, DecodeError> {
286 let codecpar = stream.codecpar();
287 let stream_index = stream.index();
288 let channel_layout = codecpar.ch_layout();
289 let sample_rate = codecpar.sample_rate() as u32;
290 let channels = channel_layout.nb_channels as u32;
291 let sample_fmt = codec_ctx.sample_fmt();
292 let codec_id = codecpar.codec_id();
293
294 let duration = if duration_val > 0 {
296 let duration_secs = duration_val as f64 / 1_000_000.0;
297 Some(Duration::from_secs_f64(duration_secs))
298 } else {
299 None
300 };
301
302 let sample_format = resample_inner::convert_sample_format(sample_fmt);
304
305 let channel_layout_enum = Self::convert_channel_layout(&channel_layout, channels);
307
308 let codec = Self::convert_codec(codec_id);
310 let codec_name = unsafe { Self::extract_codec_name(codec_id) };
311
312 let mut builder = AudioStreamInfo::builder()
314 .index(stream_index as u32)
315 .codec(codec)
316 .codec_name(codec_name)
317 .sample_rate(sample_rate)
318 .channels(channels)
319 .sample_format(sample_format)
320 .channel_layout(channel_layout_enum);
321
322 if let Some(d) = duration {
323 builder = builder.duration(d);
324 }
325
326 Ok(builder.build())
327 }
328
329 fn extract_container_info(format_ctx: &InputFormatContext) -> ContainerInfo {
331 let format_name = format_ctx.iformat_name().unwrap_or_default();
332
333 let bit_rate = {
334 let br = format_ctx.bit_rate();
335 if br > 0 { Some(br as u64) } else { None }
336 };
337
338 let nb_streams = format_ctx.nb_streams();
339
340 let mut builder = ContainerInfo::builder()
341 .format_name(format_name)
342 .nb_streams(nb_streams);
343 if let Some(br) = bit_rate {
344 builder = builder.bit_rate(br);
345 }
346 builder.build()
347 }
348
349 fn convert_channel_layout(layout: &ff_sys::AVChannelLayout, channels: u32) -> ChannelLayout {
351 if layout.order == ff_sys::AVChannelOrder_AV_CHANNEL_ORDER_NATIVE {
352 let mask = unsafe { layout.u.mask };
354 match mask {
355 0x4 => ChannelLayout::Mono,
356 0x3 => ChannelLayout::Stereo,
357 0x103 => ChannelLayout::Stereo2_1,
358 0x7 => ChannelLayout::Surround3_0,
359 0x33 => ChannelLayout::Quad,
360 0x37 => ChannelLayout::Surround5_0,
361 0x3F => ChannelLayout::Surround5_1,
362 0x13F => ChannelLayout::Surround6_1,
363 0x63F => ChannelLayout::Surround7_1,
364 _ => {
365 log::warn!(
366 "channel_layout mask has no mapping, deriving from channel count \
367 mask={mask} channels={channels}"
368 );
369 ChannelLayout::from_channels(channels)
370 }
371 }
372 } else {
373 log::warn!(
374 "channel_layout order is not NATIVE, deriving from channel count \
375 order={order} channels={channels}",
376 order = layout.order
377 );
378 ChannelLayout::from_channels(channels)
379 }
380 }
381
382 fn convert_codec(codec_id: AVCodecID) -> AudioCodec {
384 if codec_id == ff_sys::AVCodecID_AV_CODEC_ID_AAC {
385 AudioCodec::Aac
386 } else if codec_id == ff_sys::AVCodecID_AV_CODEC_ID_MP3 {
387 AudioCodec::Mp3
388 } else if codec_id == ff_sys::AVCodecID_AV_CODEC_ID_OPUS {
389 AudioCodec::Opus
390 } else if codec_id == ff_sys::AVCodecID_AV_CODEC_ID_VORBIS {
391 AudioCodec::Vorbis
392 } else if codec_id == ff_sys::AVCodecID_AV_CODEC_ID_FLAC {
393 AudioCodec::Flac
394 } else if codec_id == ff_sys::AVCodecID_AV_CODEC_ID_PCM_S16LE {
395 AudioCodec::Pcm
396 } else {
397 log::warn!(
398 "audio codec unsupported, falling back to Aac codec_id={codec_id} fallback=Aac"
399 );
400 AudioCodec::Aac
401 }
402 }
403
404 pub(crate) fn decode_one(&mut self) -> Result<Option<AudioFrame>, DecodeError> {
415 loop {
416 match self.decode_one_inner() {
417 Ok(frame) => return Ok(frame),
418 Err(DecodeError::StreamInterrupted { .. })
419 if self.url.is_some() && self.network_opts.reconnect_on_error =>
420 {
421 self.attempt_reconnect()?;
422 }
423 Err(e) => return Err(e),
424 }
425 }
426 }
427
428 fn decode_one_inner(&mut self) -> Result<Option<AudioFrame>, DecodeError> {
429 if self.eof {
430 return Ok(None);
431 }
432
433 unsafe {
434 loop {
435 match self.codec_ctx.receive_frame(&mut self.frame).map_err(|e| {
437 DecodeError::DecodingFailed {
438 timestamp: Some(self.position),
439 reason: ff_sys::av_error_string(e.code()),
440 }
441 })? {
442 ff_sys::ReceiveOutcome::Frame => {
443 let audio_frame = resample_inner::convert_frame_to_audio_frame(
447 &self.frame,
448 &self.format_ctx,
449 self.stream_index,
450 self.output_format,
451 self.output_sample_rate,
452 self.output_channels,
453 &mut self.swr_ctx,
454 &mut self.swr_key,
455 )?;
456
457 let pts = self.frame.pts();
459 if pts != ff_sys::AV_NOPTS_VALUE
460 && let Some(stream) = self.format_ctx.stream(self.stream_index as usize)
461 {
462 let time_base = stream.time_base();
463 let timestamp_secs =
464 pts as f64 * time_base.num as f64 / time_base.den as f64;
465 self.position = Duration::from_secs_f64(timestamp_secs);
466 }
467
468 return Ok(Some(audio_frame));
469 }
470 ff_sys::ReceiveOutcome::NeedInput => {
471 match self.format_ctx.read_frame(&mut self.packet) {
474 Ok(()) => {}
475 Err(e) if e.is_eof() => {
476 let _ = self.codec_ctx.send_eof();
478 self.eof = true;
479 continue;
480 }
481 Err(e) => {
482 let read_ret = e.code();
483 return Err(if let Some(url) = &self.url {
484 crate::network::map_network_error(
486 read_ret,
487 crate::network::sanitize_url(url),
488 )
489 } else {
490 DecodeError::Ffmpeg {
491 code: read_ret,
492 message: format!(
493 "Failed to read frame: {}",
494 ff_sys::av_error_string(read_ret)
495 ),
496 }
497 });
498 }
499 }
500
501 if self.packet.stream_index() == self.stream_index {
503 let send_result = self.codec_ctx.send_packet(&self.packet);
505 self.packet.unref();
506
507 if let Err(se) = send_result
508 && !se.is_eagain()
509 {
510 return Err(DecodeError::Ffmpeg {
511 code: se.code(),
512 message: format!(
513 "Failed to send packet: {}",
514 ff_sys::av_error_string(se.code())
515 ),
516 });
517 }
518 } else {
519 self.packet.unref();
521 }
522 }
523 ff_sys::ReceiveOutcome::Drained => {
524 self.eof = true;
526 return Ok(None);
527 }
528 }
529 }
530 }
531 }
532
533 pub(crate) fn position(&self) -> Duration {
535 self.position
536 }
537
538 pub(crate) fn is_eof(&self) -> bool {
540 self.eof
541 }
542
543 pub(crate) fn is_live(&self) -> bool {
548 self.is_live
549 }
550
551 fn duration_to_pts(&self, duration: Duration) -> i64 {
553 let time_base = self
556 .format_ctx
557 .stream(self.stream_index as usize)
558 .map_or(ff_sys::AVRational { num: 1, den: 1 }, |s| s.time_base());
559
560 let time_base_f64 = time_base.den as f64 / time_base.num as f64;
562 (duration.as_secs_f64() * time_base_f64) as i64
563 }
564
565 pub(crate) fn seek(
576 &mut self,
577 position: Duration,
578 mode: crate::SeekMode,
579 ) -> Result<(), DecodeError> {
580 use crate::SeekMode;
581
582 let timestamp = self.duration_to_pts(position);
583 let flags = ff_sys::avformat::seek_flags::BACKWARD;
584
585 self.packet.unref();
587 self.frame.unref();
588
589 self.format_ctx
591 .seek_frame(self.stream_index, timestamp, flags)
592 .map_err(|e| DecodeError::SeekFailed {
593 target: position,
594 reason: ff_sys::av_error_string(e.code()),
595 })?;
596
597 unsafe { self.codec_ctx.flush_buffers() };
601 self.swr_ctx = None;
602 self.swr_key = None;
603
604 while let Ok(ff_sys::ReceiveOutcome::Frame) = self.codec_ctx.receive_frame(&mut self.frame)
609 {
610 self.frame.unref();
611 }
612
613 self.eof = false;
615
616 if mode == SeekMode::Exact {
618 self.skip_to_exact(position)?;
619 }
620 Ok(())
623 }
624
625 fn skip_to_exact(&mut self, target: Duration) -> Result<(), DecodeError> {
633 while let Some(frame) = self.decode_one()? {
635 let frame_time = frame.timestamp().as_duration();
636 if frame_time >= target {
637 break;
639 }
640 }
642 Ok(())
643 }
644
645 pub(crate) fn flush(&mut self) {
647 unsafe { self.codec_ctx.flush_buffers() };
649 self.eof = false;
650 }
651
652 fn attempt_reconnect(&mut self) -> Result<(), DecodeError> {
660 let url = match self.url.as_deref() {
661 Some(u) => u.to_owned(),
662 None => return Ok(()), };
664 let max = self.network_opts.max_reconnect_attempts;
665
666 for attempt in 1..=max {
667 let backoff_ms = 100u64 * (1u64 << (attempt - 1).min(10));
668 log::warn!(
669 "reconnecting attempt={attempt} url={} backoff_ms={backoff_ms}",
670 crate::network::sanitize_url(&url)
671 );
672 std::thread::sleep(Duration::from_millis(backoff_ms));
673 match self.reopen(&url) {
674 Ok(()) => {
675 self.reconnect_count += 1;
676 log::info!(
677 "reconnected attempt={attempt} url={} total_reconnects={}",
678 crate::network::sanitize_url(&url),
679 self.reconnect_count
680 );
681 return Ok(());
682 }
683 Err(e) => log::warn!("reconnect attempt={attempt} failed err={e}"),
684 }
685 }
686
687 Err(DecodeError::StreamInterrupted {
688 code: 0,
689 endpoint: crate::network::sanitize_url(&url),
690 message: format!("stream did not recover after {max} attempts"),
691 })
692 }
693
694 fn reopen(&mut self, url: &str) -> Result<(), DecodeError> {
697 self.format_ctx = open_url_ctx(url, &self.network_opts)?;
700
701 self.format_ctx
703 .find_stream_info()
704 .map_err(|e| DecodeError::Ffmpeg {
705 code: e.code(),
706 message: format!(
707 "reconnect find_stream_info failed: {}",
708 ff_sys::av_error_string(e.code())
709 ),
710 })?;
711
712 let (stream_index, _) = Self::find_audio_stream(&self.format_ctx)
714 .ok_or_else(|| DecodeError::NoAudioStream { path: url.into() })?;
715 self.stream_index = stream_index as i32;
716
717 unsafe { self.codec_ctx.flush_buffers() };
720
721 self.eof = false;
722 Ok(())
723 }
724}
725
726unsafe impl Send for AudioDecoderInner {}
733
734#[cfg(test)]
735#[allow(unsafe_code)]
736mod tests {
737 use ff_format::channel::ChannelLayout;
738
739 use super::AudioDecoderInner;
740
741 fn native_layout(mask: u64, nb_channels: i32) -> ff_sys::AVChannelLayout {
743 ff_sys::AVChannelLayout {
744 order: ff_sys::AVChannelOrder_AV_CHANNEL_ORDER_NATIVE,
745 nb_channels,
746 u: ff_sys::AVChannelLayout__bindgen_ty_1 { mask },
747 opaque: std::ptr::null_mut(),
748 }
749 }
750
751 fn unspec_layout(nb_channels: i32) -> ff_sys::AVChannelLayout {
753 ff_sys::AVChannelLayout {
754 order: ff_sys::AVChannelOrder_AV_CHANNEL_ORDER_UNSPEC,
755 nb_channels,
756 u: ff_sys::AVChannelLayout__bindgen_ty_1 { mask: 0 },
757 opaque: std::ptr::null_mut(),
758 }
759 }
760
761 #[test]
762 fn native_mask_mono() {
763 let layout = native_layout(0x4, 1);
764 assert_eq!(
765 AudioDecoderInner::convert_channel_layout(&layout, 1),
766 ChannelLayout::Mono
767 );
768 }
769
770 #[test]
771 fn native_mask_stereo() {
772 let layout = native_layout(0x3, 2);
773 assert_eq!(
774 AudioDecoderInner::convert_channel_layout(&layout, 2),
775 ChannelLayout::Stereo
776 );
777 }
778
779 #[test]
780 fn native_mask_stereo2_1() {
781 let layout = native_layout(0x103, 3);
782 assert_eq!(
783 AudioDecoderInner::convert_channel_layout(&layout, 3),
784 ChannelLayout::Stereo2_1
785 );
786 }
787
788 #[test]
789 fn native_mask_surround3_0() {
790 let layout = native_layout(0x7, 3);
791 assert_eq!(
792 AudioDecoderInner::convert_channel_layout(&layout, 3),
793 ChannelLayout::Surround3_0
794 );
795 }
796
797 #[test]
798 fn native_mask_quad() {
799 let layout = native_layout(0x33, 4);
800 assert_eq!(
801 AudioDecoderInner::convert_channel_layout(&layout, 4),
802 ChannelLayout::Quad
803 );
804 }
805
806 #[test]
807 fn native_mask_surround5_0() {
808 let layout = native_layout(0x37, 5);
809 assert_eq!(
810 AudioDecoderInner::convert_channel_layout(&layout, 5),
811 ChannelLayout::Surround5_0
812 );
813 }
814
815 #[test]
816 fn native_mask_surround5_1() {
817 let layout = native_layout(0x3F, 6);
818 assert_eq!(
819 AudioDecoderInner::convert_channel_layout(&layout, 6),
820 ChannelLayout::Surround5_1
821 );
822 }
823
824 #[test]
825 fn native_mask_surround6_1() {
826 let layout = native_layout(0x13F, 7);
827 assert_eq!(
828 AudioDecoderInner::convert_channel_layout(&layout, 7),
829 ChannelLayout::Surround6_1
830 );
831 }
832
833 #[test]
834 fn native_mask_surround7_1() {
835 let layout = native_layout(0x63F, 8);
836 assert_eq!(
837 AudioDecoderInner::convert_channel_layout(&layout, 8),
838 ChannelLayout::Surround7_1
839 );
840 }
841
842 #[test]
843 fn native_mask_unknown_falls_back_to_from_channels() {
844 let layout = native_layout(0x1, 2);
846 assert_eq!(
847 AudioDecoderInner::convert_channel_layout(&layout, 2),
848 ChannelLayout::from_channels(2)
849 );
850 }
851
852 #[test]
853 fn non_native_order_falls_back_to_from_channels() {
854 let layout = unspec_layout(6);
855 assert_eq!(
856 AudioDecoderInner::convert_channel_layout(&layout, 6),
857 ChannelLayout::from_channels(6)
858 );
859 }
860
861 #[test]
866 fn codec_name_should_return_h264_for_h264_codec_id() {
867 let name =
868 unsafe { AudioDecoderInner::extract_codec_name(ff_sys::AVCodecID_AV_CODEC_ID_H264) };
869 assert_eq!(name, "h264");
870 }
871
872 #[test]
873 fn codec_name_should_return_none_for_none_codec_id() {
874 let name =
875 unsafe { AudioDecoderInner::extract_codec_name(ff_sys::AVCodecID_AV_CODEC_ID_NONE) };
876 assert_eq!(name, "none");
877 }
878
879 #[test]
880 fn unsupported_codec_error_should_include_codec_name() {
881 let codec_id = ff_sys::AVCodecID_AV_CODEC_ID_MP3;
882 let codec_name = unsafe { AudioDecoderInner::extract_codec_name(codec_id) };
883 let error = crate::error::DecodeError::UnsupportedCodec {
884 codec: format!("{codec_name} (codec_id={codec_id:?})"),
885 };
886 let msg = error.to_string();
887 assert!(msg.contains("mp3"), "expected codec name in error: {msg}");
888 assert!(
889 msg.contains("codec_id="),
890 "expected codec_id in error: {msg}"
891 );
892 }
893}