diff --git a/mp4parse/src/lib.rs b/mp4parse/src/lib.rs index 04a81886..3c126d2f 100644 --- a/mp4parse/src/lib.rs +++ b/mp4parse/src/lib.rs @@ -2050,6 +2050,7 @@ pub enum CodecType { Unknown, MP3, AAC, + XHEAAC, // xHE-AAC (Extended High Efficiency AAC) FLAC, Opus, H264, // 14496-10 @@ -5118,7 +5119,7 @@ fn read_ds_descriptor( }; match audio_object_type { - 1..=4 | 6 | 7 | 17 | 19..=23 => { + 1..=4 | 6 | 7 | 17 | 19..=23 | 42 => { if sample_frequency.is_none() { return Err(Error::Unsupported("unknown frequency")); } @@ -5205,6 +5206,12 @@ fn read_ds_descriptor( esds.extended_audio_object_type = extended_audio_object_type; esds.audio_sample_rate = Some(sample_frequency_value); esds.audio_channel_count = Some(channel_counts); + + // Update codec type for xHE-AAC if audio object type 42 is detected + if audio_object_type == 42 { + esds.audio_codec = CodecType::XHEAAC; + } + if !esds.decoder_specific_data.is_empty() { fail_with_status_if( strictness == ParseStrictness::Strict, @@ -5257,11 +5264,14 @@ fn read_dc_descriptor( )?; } - esds.audio_codec = match object_profile { - 0x40 | 0x66 | 0x67 => CodecType::AAC, - 0x69 | 0x6B => CodecType::MP3, - _ => CodecType::Unknown, - }; + // Only set codec type if it hasn't been set to a more specific type (e.g., XHEAAC) + if esds.audio_codec == CodecType::Unknown { + esds.audio_codec = match object_profile { + 0x40 | 0x66 | 0x67 => CodecType::AAC, + 0x69 | 0x6B => CodecType::MP3, + _ => CodecType::Unknown, + }; + } debug!( "read_dc_descriptor: esds.audio_codec = {:?}", diff --git a/mp4parse/tests/public.rs b/mp4parse/tests/public.rs index 3e9628c2..98b4678d 100644 --- a/mp4parse/tests/public.rs +++ b/mp4parse/tests/public.rs @@ -218,6 +218,8 @@ static VIDEO_H263_3GP: &str = "tests/bbb_sunflower_QCIF_30fps_h263_noaudio_1f.3g // The 1 frame hevc mp4 file generated by ffmpeg with command // "ffmpeg -f lavfi -i color=c=white:s=640x480 -c:v libx265 -frames:v 1 -pix_fmt yuv420p hevc_white_frame.mp4" static VIDEO_HEVC_MP4: &str = "tests/hevc_white_frame.mp4"; +// xHE-AAC test file generated by exhale encoder - 3 seconds, 44.1kHz mono, ~14.6kbps +static AUDIO_XHE_AAC_MP4: &str = "tests/sine-3s-xhe-aac-44khz-mono.mp4"; // The 1 frame AMR-NB 3gp file can be generated by ffmpeg with command // "ffmpeg -i [input file] -f 3gp -acodec amr_nb -ar 8000 -ac 1 -frames:a 1 -vn output.3gp" #[cfg(feature = "3gpp")] @@ -1580,3 +1582,80 @@ fn public_video_mp4v() { }; } } + +#[test] +fn public_audio_xhe_aac() { + let mut fd = File::open(AUDIO_XHE_AAC_MP4).expect("Unknown file"); + let mut buf = Vec::new(); + fd.read_to_end(&mut buf).expect("File error"); + + let mut c = Cursor::new(&buf); + let context = mp4::read_mp4(&mut c, ParseStrictness::Normal).expect("read_mp4 failed"); + + println!("xHE-AAC MP4 file parsed successfully"); + println!("Number of tracks: {}", context.tracks.len()); + + // This file contains a single xHE-AAC audio track at 44.1kHz mono, ~14.6kbps, 3 seconds + assert_eq!(context.tracks.len(), 1, "Expected exactly one track"); + + let track = &context.tracks[0]; + assert_eq!( + track.track_type, + mp4::TrackType::Audio, + "Expected audio track" + ); + + // Check sample description + let stsd = track.stsd.as_ref().expect("expected an stsd"); + assert_eq!( + stsd.descriptions.len(), + 1, + "Expected one sample description" + ); + + let a = match stsd.descriptions.first().expect("expected a SampleEntry") { + mp4::SampleEntry::Audio(ref a) => a, + _ => panic!("expected an AudioSampleEntry"), + }; + + println!("Audio track details:"); + println!(" Codec type: {:?}", a.codec_type); + println!(" Sample rate: {}", a.samplerate); + println!(" Channel count: {}", a.channelcount); + + // The parser should detect this as xHE-AAC + assert_eq!(a.codec_type, mp4::CodecType::XHEAAC); + + // Based on ffprobe: 44.1kHz, 1 channel (mono) + assert_eq!(a.samplerate, 44100.0); + assert_eq!(a.channelcount, 1); + + // Check codec-specific data + match &a.codec_specific { + mp4::AudioCodecSpecific::ES_Descriptor(ref esds) => { + println!(" ESDS present"); + println!(" Audio object type: {:?}", esds.audio_object_type); + println!( + " Extended audio object type: {:?}", + esds.extended_audio_object_type + ); + println!(" Audio sample rate: {:?}", esds.audio_sample_rate); + println!(" Audio channel count: {:?}", esds.audio_channel_count); + + // Should be xHE-AAC with audio object type 42 + assert_eq!(esds.audio_codec, mp4::CodecType::XHEAAC); + assert_eq!(esds.audio_object_type, Some(42)); + + // Verify ESDS matches the container info + if let Some(sample_rate) = esds.audio_sample_rate { + assert_eq!(sample_rate, 44100); + } + if let Some(channel_count) = esds.audio_channel_count { + assert_eq!(channel_count, 1); + } + } + _ => panic!("Expected ES descriptor for xHE-AAC audio"), + } + + println!("xHE-AAC file parsing test completed successfully"); +} diff --git a/mp4parse/tests/sine-3s-xhe-aac-44khz-mono.mp4 b/mp4parse/tests/sine-3s-xhe-aac-44khz-mono.mp4 new file mode 100644 index 00000000..44c74bab Binary files /dev/null and b/mp4parse/tests/sine-3s-xhe-aac-44khz-mono.mp4 differ