Skip to content

Commit 438ae32

Browse files
committed
Add support for demuxing xhe-aac files
1 parent 13b8873 commit 438ae32

File tree

3 files changed

+95
-6
lines changed

3 files changed

+95
-6
lines changed

mp4parse/src/lib.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2050,6 +2050,7 @@ pub enum CodecType {
20502050
Unknown,
20512051
MP3,
20522052
AAC,
2053+
XHEAAC, // xHE-AAC (Extended High Efficiency AAC)
20532054
FLAC,
20542055
Opus,
20552056
H264, // 14496-10
@@ -5118,7 +5119,7 @@ fn read_ds_descriptor(
51185119
};
51195120

51205121
match audio_object_type {
5121-
1..=4 | 6 | 7 | 17 | 19..=23 => {
5122+
1..=4 | 6 | 7 | 17 | 19..=23 | 42 => {
51225123
if sample_frequency.is_none() {
51235124
return Err(Error::Unsupported("unknown frequency"));
51245125
}
@@ -5205,6 +5206,12 @@ fn read_ds_descriptor(
52055206
esds.extended_audio_object_type = extended_audio_object_type;
52065207
esds.audio_sample_rate = Some(sample_frequency_value);
52075208
esds.audio_channel_count = Some(channel_counts);
5209+
5210+
// Update codec type for xHE-AAC if audio object type 42 is detected
5211+
if audio_object_type == 42 {
5212+
esds.audio_codec = CodecType::XHEAAC;
5213+
}
5214+
52085215
if !esds.decoder_specific_data.is_empty() {
52095216
fail_with_status_if(
52105217
strictness == ParseStrictness::Strict,
@@ -5257,11 +5264,14 @@ fn read_dc_descriptor(
52575264
)?;
52585265
}
52595266

5260-
esds.audio_codec = match object_profile {
5261-
0x40 | 0x66 | 0x67 => CodecType::AAC,
5262-
0x69 | 0x6B => CodecType::MP3,
5263-
_ => CodecType::Unknown,
5264-
};
5267+
// Only set codec type if it hasn't been set to a more specific type (e.g., XHEAAC)
5268+
if esds.audio_codec == CodecType::Unknown {
5269+
esds.audio_codec = match object_profile {
5270+
0x40 | 0x66 | 0x67 => CodecType::AAC,
5271+
0x69 | 0x6B => CodecType::MP3,
5272+
_ => CodecType::Unknown,
5273+
};
5274+
}
52655275

52665276
debug!(
52675277
"read_dc_descriptor: esds.audio_codec = {:?}",

mp4parse/tests/public.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,8 @@ static VIDEO_H263_3GP: &str = "tests/bbb_sunflower_QCIF_30fps_h263_noaudio_1f.3g
218218
// The 1 frame hevc mp4 file generated by ffmpeg with command
219219
// "ffmpeg -f lavfi -i color=c=white:s=640x480 -c:v libx265 -frames:v 1 -pix_fmt yuv420p hevc_white_frame.mp4"
220220
static VIDEO_HEVC_MP4: &str = "tests/hevc_white_frame.mp4";
221+
// xHE-AAC test file generated by exhale encoder - 3 seconds, 44.1kHz mono, ~14.6kbps
222+
static AUDIO_XHE_AAC_MP4: &str = "tests/sine-3s-xhe-aac-44khz-mono.mp4";
221223
// The 1 frame AMR-NB 3gp file can be generated by ffmpeg with command
222224
// "ffmpeg -i [input file] -f 3gp -acodec amr_nb -ar 8000 -ac 1 -frames:a 1 -vn output.3gp"
223225
#[cfg(feature = "3gpp")]
@@ -1580,3 +1582,80 @@ fn public_video_mp4v() {
15801582
};
15811583
}
15821584
}
1585+
1586+
#[test]
1587+
fn public_audio_xhe_aac() {
1588+
let mut fd = File::open(AUDIO_XHE_AAC_MP4).expect("Unknown file");
1589+
let mut buf = Vec::new();
1590+
fd.read_to_end(&mut buf).expect("File error");
1591+
1592+
let mut c = Cursor::new(&buf);
1593+
let context = mp4::read_mp4(&mut c, ParseStrictness::Normal).expect("read_mp4 failed");
1594+
1595+
println!("xHE-AAC MP4 file parsed successfully");
1596+
println!("Number of tracks: {}", context.tracks.len());
1597+
1598+
// This file contains a single xHE-AAC audio track at 44.1kHz mono, ~14.6kbps, 3 seconds
1599+
assert_eq!(context.tracks.len(), 1, "Expected exactly one track");
1600+
1601+
let track = &context.tracks[0];
1602+
assert_eq!(
1603+
track.track_type,
1604+
mp4::TrackType::Audio,
1605+
"Expected audio track"
1606+
);
1607+
1608+
// Check sample description
1609+
let stsd = track.stsd.as_ref().expect("expected an stsd");
1610+
assert_eq!(
1611+
stsd.descriptions.len(),
1612+
1,
1613+
"Expected one sample description"
1614+
);
1615+
1616+
let a = match stsd.descriptions.first().expect("expected a SampleEntry") {
1617+
mp4::SampleEntry::Audio(ref a) => a,
1618+
_ => panic!("expected an AudioSampleEntry"),
1619+
};
1620+
1621+
println!("Audio track details:");
1622+
println!(" Codec type: {:?}", a.codec_type);
1623+
println!(" Sample rate: {}", a.samplerate);
1624+
println!(" Channel count: {}", a.channelcount);
1625+
1626+
// The parser should detect this as xHE-AAC
1627+
assert_eq!(a.codec_type, mp4::CodecType::XHEAAC);
1628+
1629+
// Based on ffprobe: 44.1kHz, 1 channel (mono)
1630+
assert_eq!(a.samplerate, 44100.0);
1631+
assert_eq!(a.channelcount, 1);
1632+
1633+
// Check codec-specific data
1634+
match &a.codec_specific {
1635+
mp4::AudioCodecSpecific::ES_Descriptor(ref esds) => {
1636+
println!(" ESDS present");
1637+
println!(" Audio object type: {:?}", esds.audio_object_type);
1638+
println!(
1639+
" Extended audio object type: {:?}",
1640+
esds.extended_audio_object_type
1641+
);
1642+
println!(" Audio sample rate: {:?}", esds.audio_sample_rate);
1643+
println!(" Audio channel count: {:?}", esds.audio_channel_count);
1644+
1645+
// Should be xHE-AAC with audio object type 42
1646+
assert_eq!(esds.audio_codec, mp4::CodecType::XHEAAC);
1647+
assert_eq!(esds.audio_object_type, Some(42));
1648+
1649+
// Verify ESDS matches the container info
1650+
if let Some(sample_rate) = esds.audio_sample_rate {
1651+
assert_eq!(sample_rate, 44100);
1652+
}
1653+
if let Some(channel_count) = esds.audio_channel_count {
1654+
assert_eq!(channel_count, 1);
1655+
}
1656+
}
1657+
_ => panic!("Expected ES descriptor for xHE-AAC audio"),
1658+
}
1659+
1660+
println!("xHE-AAC file parsing test completed successfully");
1661+
}
6.74 KB
Binary file not shown.

0 commit comments

Comments
 (0)