@@ -48,8 +48,23 @@ use crate::video::DEFAULT_VIDEO_FRAME_DURATION_US;
4848// Constants
4949// ---------------------------------------------------------------------------
5050
51- /// Default audio frame duration when metadata is missing (20 ms Opus frame).
52- const DEFAULT_AUDIO_FRAME_DURATION_US : u64 = 20_000 ;
51+ /// Default audio frame duration when metadata is missing.
52+ ///
53+ /// The correct value depends on the codec:
54+ /// - Opus: 20 ms (960 samples at 48 kHz)
55+ /// - AAC-LC: ~21.333 ms (1024 samples at 48 kHz)
56+ ///
57+ /// Use [`default_audio_frame_duration_us`] to get the codec-aware value.
58+ const DEFAULT_AUDIO_FRAME_DURATION_US_OPUS : u64 = 20_000 ;
59+ const DEFAULT_AUDIO_FRAME_DURATION_US_AAC : u64 = 21_333 ;
60+
61+ /// Return the default audio frame duration for the given codec.
62+ const fn default_audio_frame_duration_us ( codec : AudioCodec ) -> u64 {
63+ match codec {
64+ AudioCodec :: Aac => DEFAULT_AUDIO_FRAME_DURATION_US_AAC ,
65+ _ => DEFAULT_AUDIO_FRAME_DURATION_US_OPUS ,
66+ }
67+ }
5368
5469/// Default video timescale (90 kHz — standard for MPEG transport streams / MP4).
5570const DEFAULT_VIDEO_TIMESCALE : NonZeroU32 = match NonZeroU32 :: new ( 90_000 ) {
@@ -608,21 +623,10 @@ fn classify_packet(packet: Packet) -> Option<MuxFrame> {
608623
609624/// Parse an optional audio codec config string into an [`AudioCodec`].
610625///
611- /// Accepted values (case-insensitive): `"opus"`, `"aac"`.
612- /// Returns `AudioCodec:: Opus` when the input is `None` or unrecognised.
626+ /// Delegates to the shared [`crate::transport::moq::parse_audio_codec_config`]
627+ /// parser and defaults to ` Opus` when the input is `None` or unrecognised.
613628fn parse_mp4_audio_codec_config ( s : Option < & str > ) -> AudioCodec {
614- match s {
615- Some ( v) if v. eq_ignore_ascii_case ( "aac" ) => AudioCodec :: Aac ,
616- Some ( v) if v. eq_ignore_ascii_case ( "opus" ) => AudioCodec :: Opus ,
617- Some ( other) => {
618- tracing:: warn!(
619- audio_codec = other,
620- "unrecognised audio_codec config value, defaulting to Opus"
621- ) ;
622- AudioCodec :: Opus
623- } ,
624- None => AudioCodec :: Opus ,
625- }
629+ s. and_then ( crate :: transport:: moq:: parse_audio_codec_config) . unwrap_or ( AudioCodec :: Opus )
626630}
627631
628632/// Determine the MP4 MIME content-type string from optional codec info.
@@ -653,10 +657,19 @@ fn mp4_content_type(audio: Option<AudioCodec>, video: Option<VideoCodec>) -> &'s
653657 ( Some ( AudioCodec :: Aac ) , Some ( VideoCodec :: H264 ) ) => "video/mp4; codecs=\" avc1,mp4a\" " ,
654658 // Audio + unknown/future video codec
655659 ( Some ( AudioCodec :: Opus ) , Some ( _) ) => "video/mp4; codecs=\" opus\" " ,
656- ( Some ( AudioCodec :: Aac | _ ) , Some ( _) ) => "video/mp4; codecs=\" mp4a\" " ,
660+ ( Some ( AudioCodec :: Aac ) , Some ( _) ) => "video/mp4; codecs=\" mp4a\" " ,
657661 // Audio-only
658662 ( Some ( AudioCodec :: Opus ) , None ) => "audio/mp4; codecs=\" opus\" " ,
659- ( Some ( AudioCodec :: Aac | _) , None ) => "audio/mp4; codecs=\" mp4a\" " ,
663+ ( Some ( AudioCodec :: Aac ) , None ) => "audio/mp4; codecs=\" mp4a\" " ,
664+ // Future audio codec — warn and omit codecs param.
665+ ( Some ( _) , Some ( _) ) => {
666+ tracing:: warn!( "mp4_content_type: unrecognised audio codec — omitting codecs param" ) ;
667+ "video/mp4"
668+ } ,
669+ ( Some ( _) , None ) => {
670+ tracing:: warn!( "mp4_content_type: unrecognised audio codec — omitting codecs param" ) ;
671+ "audio/mp4"
672+ } ,
660673 // Video-only
661674 ( None , Some ( VideoCodec :: Av1 ) ) => "video/mp4; codecs=\" av01\" " ,
662675 ( None , Some ( VideoCodec :: H264 ) ) => "video/mp4; codecs=\" avc1\" " ,
@@ -1213,7 +1226,7 @@ async fn run_stream_mode(
12131226 let duration_us = metadata
12141227 . as_ref ( )
12151228 . and_then ( |m| m. duration_us )
1216- . unwrap_or ( DEFAULT_AUDIO_FRAME_DURATION_US ) ;
1229+ . unwrap_or_else ( || default_audio_frame_duration_us ( session . audio_codec ) ) ;
12171230 let duration_ticks = us_to_ticks ( duration_us, audio_timescale. get ( ) ) ;
12181231
12191232 let data_size = data. len ( ) ;
@@ -1504,7 +1517,13 @@ async fn run_file_mode(
15041517 ) ?;
15051518 } ,
15061519 MuxFrame :: Audio ( data, metadata) => {
1507- process_file_audio_frame ( & data, metadata. as_ref ( ) , & mut state, stats_tracker) ?;
1520+ process_file_audio_frame (
1521+ & data,
1522+ metadata. as_ref ( ) ,
1523+ session. audio_codec ,
1524+ & mut state,
1525+ stats_tracker,
1526+ ) ?;
15081527 } ,
15091528 }
15101529 }
@@ -1608,14 +1627,16 @@ fn process_file_video_frame(
16081627fn process_file_audio_frame (
16091628 data : & Bytes ,
16101629 metadata : Option < & PacketMetadata > ,
1630+ audio_codec : AudioCodec ,
16111631 state : & mut FileMuxState ,
16121632 stats_tracker : & mut NodeStatsTracker ,
16131633) -> Result < ( ) , StreamKitError > {
16141634 state. packet_count += 1 ;
16151635 stats_tracker. received ( ) ;
16161636
1617- let duration_us =
1618- metadata. and_then ( |m| m. duration_us ) . unwrap_or ( DEFAULT_AUDIO_FRAME_DURATION_US ) ;
1637+ let duration_us = metadata
1638+ . and_then ( |m| m. duration_us )
1639+ . unwrap_or_else ( || default_audio_frame_duration_us ( audio_codec) ) ;
16191640 let duration_ticks = us_to_ticks ( duration_us, state. audio_timescale . get ( ) ) ;
16201641
16211642 let data_offset = state
0 commit comments