Skip to content

Commit ac4ed53

Browse files
committed
Fix esds DSI parsing for truncated and duplicate DecSpecificInfo.
Record AOT, sample rate and channel count before the GASpecificConfig bit reads that can hit EOF on a truncated DSI. find_descriptor swallows BitReaderError in non-strict mode, so storing the fields afterwards left a playable-looking AAC track with no usable config and silent playback for HE-AAC streams with explicit SBR signalling. Also keep the first DecSpecificInfo and ignore subsequent ones in non-strict mode, matching Chromium and ffmpeg. The previous code concatenated DSI bytes and overwrote the parsed audio fields from the second DSI, leaving the ES_Descriptor inconsistent when the two disagreed. Strict mode still rejects duplicates with EsdsDecSpecificInfoTagQuantity.
1 parent 73423e1 commit ac4ed53

2 files changed

Lines changed: 273 additions & 52 deletions

File tree

mp4parse/src/lib.rs

Lines changed: 74 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5160,6 +5160,20 @@ fn read_ds_descriptor(
51605160
esds: &mut ES_Descriptor,
51615161
strictness: ParseStrictness,
51625162
) -> Result<()> {
5163+
// Keep the first DecSpecificInfo and ignore subsequent entries in
5164+
// non-strict mode. Concatenating raw bytes and/or overwriting the parsed
5165+
// fields from a second DSI leaves the ES_Descriptor inconsistent (e.g.
5166+
// sample-rate/channel-count mismatched with the stored DSI bytes) and
5167+
// produces silent-audio playback. A single well-formed DSI is always
5168+
// sufficient.
5169+
if !esds.decoder_specific_data.is_empty() {
5170+
fail_with_status_if(
5171+
strictness == ParseStrictness::Strict,
5172+
Status::EsdsDecSpecificInfoTagQuantity,
5173+
)?;
5174+
return Ok(());
5175+
}
5176+
51635177
#[cfg(feature = "mp4v")]
51645178
// Check if we are in a Visual esda Box.
51655179
if esds.video_codec != CodecType::Unknown {
@@ -5262,70 +5276,78 @@ fn read_ds_descriptor(
52625276
_ => 96000,
52635277
};
52645278

5279+
// Map channel_configuration to channel count. Zero indicates PCE
5280+
// (program_config_element) signalling, which requires parsing the
5281+
// trailing GASpecificConfig fields below and so can't be resolved
5282+
// yet.
5283+
let channel_count_from_config = match channel_configuration {
5284+
0 => None,
5285+
1..=7 => Some(channel_configuration),
5286+
11 => Some(7), // 6.1, AAC Amendment 4 (2013)
5287+
12 | 14 => Some(8), // 7.1 (a/d), ITU BS.2159
5288+
_ => return Err(Error::Unsupported("invalid channel configuration")),
5289+
};
5290+
5291+
// Record the essential audio fields now, before any further bit
5292+
// reads that may hit EOF on a truncated DSI. find_descriptor
5293+
// swallows BitReaderError in non-strict mode; without recording
5294+
// here we'd return a playable-looking track with no usable config
5295+
// (see band-orion.de, where HE-AAC with explicit SBR signalling
5296+
// omits the GASpecificConfig tail).
5297+
esds.audio_object_type = Some(audio_object_type);
5298+
esds.extended_audio_object_type = extended_audio_object_type;
5299+
esds.audio_sample_rate = Some(sample_frequency_value);
5300+
if let Some(cc) = channel_count_from_config {
5301+
esds.audio_channel_count = Some(cc);
5302+
esds.decoder_specific_data.extend_from_slice(data)?;
5303+
}
5304+
52655305
bit_reader.skip(1)?; // frameLengthFlag
52665306
let depend_on_core_order: u8 = ReadInto::read(bit_reader, 1)?;
52675307
if depend_on_core_order > 0 {
52685308
bit_reader.skip(14)?; // codeCoderDelay
52695309
}
52705310
bit_reader.skip(1)?; // extensionFlag
52715311

5272-
let channel_counts = match channel_configuration {
5273-
0 => {
5274-
debug!("Parsing program_config_element for channel counts");
5275-
5276-
bit_reader.skip(4)?; // element_instance_tag
5277-
bit_reader.skip(2)?; // object_type
5278-
bit_reader.skip(4)?; // sampling_frequency_index
5279-
let num_front_channel: u8 = ReadInto::read(bit_reader, 4)?;
5280-
let num_side_channel: u8 = ReadInto::read(bit_reader, 4)?;
5281-
let num_back_channel: u8 = ReadInto::read(bit_reader, 4)?;
5282-
let num_lfe_channel: u8 = ReadInto::read(bit_reader, 2)?;
5283-
bit_reader.skip(3)?; // num_assoc_data
5284-
bit_reader.skip(4)?; // num_valid_cc
5285-
5286-
let mono_mixdown_present: bool = ReadInto::read(bit_reader, 1)?;
5287-
if mono_mixdown_present {
5288-
bit_reader.skip(4)?; // mono_mixdown_element_number
5289-
}
5290-
5291-
let stereo_mixdown_present: bool = ReadInto::read(bit_reader, 1)?;
5292-
if stereo_mixdown_present {
5293-
bit_reader.skip(4)?; // stereo_mixdown_element_number
5294-
}
5295-
5296-
let matrix_mixdown_idx_present: bool = ReadInto::read(bit_reader, 1)?;
5297-
if matrix_mixdown_idx_present {
5298-
bit_reader.skip(2)?; // matrix_mixdown_idx
5299-
bit_reader.skip(1)?; // pseudo_surround_enable
5300-
}
5301-
let mut _channel_counts = 0;
5302-
_channel_counts += read_surround_channel_count(bit_reader, num_front_channel)?;
5303-
_channel_counts += read_surround_channel_count(bit_reader, num_side_channel)?;
5304-
_channel_counts += read_surround_channel_count(bit_reader, num_back_channel)?;
5305-
_channel_counts += read_surround_channel_count(bit_reader, num_lfe_channel)?;
5306-
_channel_counts
5312+
if channel_count_from_config.is_none() {
5313+
// channel_configuration == 0: derive channel count from the
5314+
// program_config_element.
5315+
debug!("Parsing program_config_element for channel counts");
5316+
5317+
bit_reader.skip(4)?; // element_instance_tag
5318+
bit_reader.skip(2)?; // object_type
5319+
bit_reader.skip(4)?; // sampling_frequency_index
5320+
let num_front_channel: u8 = ReadInto::read(bit_reader, 4)?;
5321+
let num_side_channel: u8 = ReadInto::read(bit_reader, 4)?;
5322+
let num_back_channel: u8 = ReadInto::read(bit_reader, 4)?;
5323+
let num_lfe_channel: u8 = ReadInto::read(bit_reader, 2)?;
5324+
bit_reader.skip(3)?; // num_assoc_data
5325+
bit_reader.skip(4)?; // num_valid_cc
5326+
5327+
let mono_mixdown_present: bool = ReadInto::read(bit_reader, 1)?;
5328+
if mono_mixdown_present {
5329+
bit_reader.skip(4)?; // mono_mixdown_element_number
53075330
}
5308-
1..=7 => channel_configuration,
5309-
// Amendment 4 of the AAC standard in 2013 below
5310-
11 => 7, // 6.1 Amendment 4 of the AAC standard in 2013
5311-
12 | 14 => 8, // 7.1 (a/d) of ITU BS.2159
5312-
_ => {
5313-
return Err(Error::Unsupported("invalid channel configuration"));
5331+
5332+
let stereo_mixdown_present: bool = ReadInto::read(bit_reader, 1)?;
5333+
if stereo_mixdown_present {
5334+
bit_reader.skip(4)?; // stereo_mixdown_element_number
53145335
}
5315-
};
53165336

5317-
esds.audio_object_type = Some(audio_object_type);
5318-
esds.extended_audio_object_type = extended_audio_object_type;
5319-
esds.audio_sample_rate = Some(sample_frequency_value);
5320-
esds.audio_channel_count = Some(channel_counts);
5337+
let matrix_mixdown_idx_present: bool = ReadInto::read(bit_reader, 1)?;
5338+
if matrix_mixdown_idx_present {
5339+
bit_reader.skip(2)?; // matrix_mixdown_idx
5340+
bit_reader.skip(1)?; // pseudo_surround_enable
5341+
}
5342+
let mut channel_counts = 0;
5343+
channel_counts += read_surround_channel_count(bit_reader, num_front_channel)?;
5344+
channel_counts += read_surround_channel_count(bit_reader, num_side_channel)?;
5345+
channel_counts += read_surround_channel_count(bit_reader, num_back_channel)?;
5346+
channel_counts += read_surround_channel_count(bit_reader, num_lfe_channel)?;
53215347

5322-
if !esds.decoder_specific_data.is_empty() {
5323-
fail_with_status_if(
5324-
strictness == ParseStrictness::Strict,
5325-
Status::EsdsDecSpecificInfoTagQuantity,
5326-
)?;
5348+
esds.audio_channel_count = Some(channel_counts);
5349+
esds.decoder_specific_data.extend_from_slice(data)?;
53275350
}
5328-
esds.decoder_specific_data.extend_from_slice(data)?;
53295351

53305352
Ok(())
53315353
}

mp4parse/src/tests.rs

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1120,6 +1120,205 @@ fn read_esds_mpeg2_aac_lc() {
11201120
assert_eq!(es.decoder_specific_data, aac_dc_descriptor);
11211121
}
11221122

1123+
#[test]
1124+
fn read_esds_truncated_dsi() {
1125+
// HE-AAC DSI truncated so the GASpecificConfig tail (frameLengthFlag /
1126+
// dependsOnCoreCoder / extensionFlag) runs off the end of the 24-bit
1127+
// payload, causing the bit reader to hit EOF. Reduced from band-orion.de
1128+
// (Firefox bug 2011644).
1129+
//
1130+
// DSI `2b 92 08` decodes as:
1131+
// AOT=5 (SBR, explicit signalling)
1132+
// samplingFrequencyIndex=7 (22050 Hz base)
1133+
// channelConfiguration=2 (stereo)
1134+
// extensionSamplingFrequencyIndex=4 (44100 Hz extended)
1135+
// inner AOT=2 (AAC-LC)
1136+
// which leaves 2 bits remaining, one short of the extensionFlag.
1137+
//
1138+
// Before our fix read_ds_descriptor stored the parsed fields only after
1139+
// the failing reads, so find_descriptor would swallow the BitReaderError
1140+
// in non-strict mode and leave the ES_Descriptor with audio_sample_rate,
1141+
// audio_channel_count and decoder_specific_data all unset — a "playable"
1142+
// AAC track with no config, producing silent audio.
1143+
let aac_esds = vec![
1144+
0x03, 0x1a, // ES_Descriptor tag, len=26
1145+
0x00, 0x00, 0x00, // ES_ID + flags
1146+
0x04, 0x12, // DC_Descriptor tag, len=18
1147+
0x40, 0x15, 0x00, 0x00, 0x00, // objType=AAC, streamType, bufferSize
1148+
0x00, 0x00, 0x00, 0x00, // maxBitrate
1149+
0x00, 0x00, 0x00, 0x00, // avgBitrate
1150+
0x05, 0x03, 0x2b, 0x92, 0x08, // DSI: truncated HE-AAC
1151+
0x06, 0x01, 0x02, // SL_Descriptor
1152+
];
1153+
let aac_dc_descriptor = &aac_esds[22..25];
1154+
1155+
let mut stream = make_box(BoxSize::Auto, b"esds", |s| {
1156+
s.B32(0) // reserved
1157+
.append_bytes(aac_esds.as_slice())
1158+
});
1159+
let mut iter = super::BoxIter::new(&mut stream);
1160+
let mut stream = iter.next_box().unwrap().unwrap();
1161+
1162+
let es = super::read_esds(&mut stream, ParseStrictness::Normal).unwrap();
1163+
1164+
assert_eq!(es.audio_codec, super::CodecType::AAC);
1165+
assert_eq!(es.audio_object_type, Some(2));
1166+
assert_eq!(es.extended_audio_object_type, Some(5));
1167+
assert_eq!(es.audio_sample_rate, Some(22050));
1168+
assert_eq!(es.audio_channel_count, Some(2));
1169+
assert_eq!(es.codec_esds, aac_esds);
1170+
assert_eq!(es.decoder_specific_data, aac_dc_descriptor);
1171+
}
1172+
1173+
#[test]
1174+
fn read_esds_duplicate_dsi() {
1175+
// Two DecSpecificInfo descriptors with identical AAC-LC 48 kHz stereo
1176+
// payload (`11 90`). Reduced from Firefox bug 1906838.
1177+
//
1178+
// In non-strict mode the parser must keep the first DSI and ignore the
1179+
// second — matching what Chromium (media/formats/mp4/es_descriptor.cc)
1180+
// and ffmpeg (libavformat/isom.c ff_mp4_read_dec_config_descr) do.
1181+
// Before our fix the parser concatenated both DSI payloads and
1182+
// overwrote audio_object_type / audio_sample_rate / audio_channel_count
1183+
// with the second DSI's parsed values, which left the ES_Descriptor in
1184+
// an inconsistent state when the two DSIs disagreed.
1185+
let aac_esds = vec![
1186+
0x03, 0x1d, // ES_Descriptor tag, len=29
1187+
0x00, 0x00, 0x00, // ES_ID + flags
1188+
0x04, 0x15, // DC_Descriptor tag, len=21
1189+
0x40, 0x15, 0x00, 0x00, 0x00, // objType=AAC, streamType, bufferSize
1190+
0x00, 0x00, 0x01, 0xf4, // maxBitrate
1191+
0x00, 0x00, 0x01, 0xf4, // avgBitrate
1192+
0x05, 0x02, 0x11, 0x90, // DSI #1 (kept)
1193+
0x05, 0x02, 0x11, 0x90, // DSI #2 (ignored in non-strict)
1194+
0x06, 0x01, 0x02, // SL_Descriptor
1195+
];
1196+
let aac_dc_descriptor = &aac_esds[22..24];
1197+
1198+
let mut stream = make_box(BoxSize::Auto, b"esds", |s| {
1199+
s.B32(0) // reserved
1200+
.append_bytes(aac_esds.as_slice())
1201+
});
1202+
let mut iter = super::BoxIter::new(&mut stream);
1203+
let mut stream = iter.next_box().unwrap().unwrap();
1204+
1205+
let es = super::read_esds(&mut stream, ParseStrictness::Normal).unwrap();
1206+
1207+
assert_eq!(es.audio_codec, super::CodecType::AAC);
1208+
assert_eq!(es.audio_object_type, Some(2));
1209+
assert_eq!(es.extended_audio_object_type, None);
1210+
assert_eq!(es.audio_sample_rate, Some(48000));
1211+
assert_eq!(es.audio_channel_count, Some(2));
1212+
assert_eq!(es.codec_esds, aac_esds);
1213+
// First DSI's bytes only — not the concatenation of both.
1214+
assert_eq!(es.decoder_specific_data, aac_dc_descriptor);
1215+
}
1216+
1217+
#[test]
1218+
fn read_esds_duplicate_dsi_strict() {
1219+
// In strict mode, the duplicate-DSI case is rejected with the
1220+
// corresponding status rather than silently kept as "first wins".
1221+
let aac_esds = vec![
1222+
0x03, 0x1d, 0x00, 0x00, 0x00, 0x04, 0x15, 0x40, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
1223+
0xf4, 0x00, 0x00, 0x01, 0xf4, 0x05, 0x02, 0x11, 0x90, 0x05, 0x02, 0x11, 0x90, 0x06, 0x01,
1224+
0x02,
1225+
];
1226+
1227+
let mut stream = make_box(BoxSize::Auto, b"esds", |s| {
1228+
s.B32(0) // reserved
1229+
.append_bytes(aac_esds.as_slice())
1230+
});
1231+
let mut iter = super::BoxIter::new(&mut stream);
1232+
let mut stream = iter.next_box().unwrap().unwrap();
1233+
1234+
match super::read_esds(&mut stream, ParseStrictness::Strict) {
1235+
Err(Error::InvalidData(Status::EsdsDecSpecificInfoTagQuantity)) => {}
1236+
other => panic!(
1237+
"expected EsdsDecSpecificInfoTagQuantity in strict mode, got {:?}",
1238+
other
1239+
),
1240+
}
1241+
}
1242+
1243+
#[test]
1244+
fn read_esds_duplicate_dsi_disagreeing() {
1245+
// Two DecSpecificInfo descriptors whose payloads disagree on sample rate
1246+
// and channel count. Locks in "first-wins" rather than "second-wins" or
1247+
// "concatenate":
1248+
// DSI #1 `11 90` → AOT=2 (AAC-LC), SFI=3 (48 kHz), chCfg=2 (stereo)
1249+
// DSI #2 `12 88` → AOT=2 (AAC-LC), SFI=5 (32 kHz), chCfg=1 (mono)
1250+
let aac_esds = vec![
1251+
0x03, 0x1d, // ES_Descriptor tag, len=29
1252+
0x00, 0x00, 0x00, // ES_ID + flags
1253+
0x04, 0x15, // DC_Descriptor tag, len=21
1254+
0x40, 0x15, 0x00, 0x00, 0x00, // objType=AAC, streamType, bufferSize
1255+
0x00, 0x00, 0x01, 0xf4, // maxBitrate
1256+
0x00, 0x00, 0x01, 0xf4, // avgBitrate
1257+
0x05, 0x02, 0x11, 0x90, // DSI #1: 48 kHz stereo (kept)
1258+
0x05, 0x02, 0x12, 0x88, // DSI #2: 32 kHz mono (ignored in non-strict)
1259+
0x06, 0x01, 0x02, // SL_Descriptor
1260+
];
1261+
let dsi1 = &aac_esds[22..24];
1262+
1263+
let mut stream = make_box(BoxSize::Auto, b"esds", |s| {
1264+
s.B32(0) // reserved
1265+
.append_bytes(aac_esds.as_slice())
1266+
});
1267+
let mut iter = super::BoxIter::new(&mut stream);
1268+
let mut stream = iter.next_box().unwrap().unwrap();
1269+
1270+
let es = super::read_esds(&mut stream, ParseStrictness::Normal).unwrap();
1271+
1272+
assert_eq!(es.audio_codec, super::CodecType::AAC);
1273+
assert_eq!(es.audio_object_type, Some(2));
1274+
assert_eq!(es.extended_audio_object_type, None);
1275+
// First DSI wins — not 32000 / 1 from DSI #2.
1276+
assert_eq!(es.audio_sample_rate, Some(48000));
1277+
assert_eq!(es.audio_channel_count, Some(2));
1278+
assert_eq!(es.decoder_specific_data, dsi1);
1279+
}
1280+
1281+
#[test]
1282+
fn read_esds_truncated_dsi_pce() {
1283+
// Truncated DSI with channel_configuration == 0 (program_config_element
1284+
// signalling). The PCE tail is needed to derive the channel count, so
1285+
// the fix's early-store cannot populate audio_channel_count or the DSI
1286+
// bytes — only AOT / extended_AOT / sample_rate.
1287+
//
1288+
// DSI `11 80` decodes as:
1289+
// AOT=2 (AAC-LC), SFI=3 (48 kHz), chCfg=0 (PCE)
1290+
// frameLengthFlag=1, dependsOnCoreCoder=0, extensionFlag=0
1291+
// after which the 4-bit element_instance_tag read runs off the end.
1292+
let aac_esds = vec![
1293+
0x03, 0x19, // ES_Descriptor tag, len=25
1294+
0x00, 0x00, 0x00, // ES_ID + flags
1295+
0x04, 0x11, // DC_Descriptor tag, len=17
1296+
0x40, 0x15, 0x00, 0x00, 0x00, // objType=AAC, streamType, bufferSize
1297+
0x00, 0x00, 0x01, 0xf4, // maxBitrate
1298+
0x00, 0x00, 0x01, 0xf4, // avgBitrate
1299+
0x05, 0x02, 0x11, 0x80, // DSI: AAC-LC 48 kHz PCE, truncated mid-PCE
1300+
0x06, 0x01, 0x02, // SL_Descriptor
1301+
];
1302+
1303+
let mut stream = make_box(BoxSize::Auto, b"esds", |s| {
1304+
s.B32(0) // reserved
1305+
.append_bytes(aac_esds.as_slice())
1306+
});
1307+
let mut iter = super::BoxIter::new(&mut stream);
1308+
let mut stream = iter.next_box().unwrap().unwrap();
1309+
1310+
let es = super::read_esds(&mut stream, ParseStrictness::Normal).unwrap();
1311+
1312+
assert_eq!(es.audio_codec, super::CodecType::AAC);
1313+
assert_eq!(es.audio_object_type, Some(2));
1314+
assert_eq!(es.extended_audio_object_type, None);
1315+
assert_eq!(es.audio_sample_rate, Some(48000));
1316+
// PCE parsing hits EOF before deriving the channel count, so these stay
1317+
// unset rather than guessing.
1318+
assert_eq!(es.audio_channel_count, None);
1319+
assert!(es.decoder_specific_data.is_empty());
1320+
}
1321+
11231322
#[test]
11241323
fn read_stsd_mp4v() {
11251324
let mp4v = vec![

0 commit comments

Comments
 (0)