Skip to content

Commit 9bbb873

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 fb7ed9e commit 9bbb873

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
@@ -5151,6 +5151,20 @@ fn read_ds_descriptor(
51515151
esds: &mut ES_Descriptor,
51525152
strictness: ParseStrictness,
51535153
) -> Result<()> {
5154+
// Keep the first DecSpecificInfo and ignore subsequent entries in
5155+
// non-strict mode. Concatenating raw bytes and/or overwriting the parsed
5156+
// fields from a second DSI leaves the ES_Descriptor inconsistent (e.g.
5157+
// sample-rate/channel-count mismatched with the stored DSI bytes) and
5158+
// produces silent-audio playback. A single well-formed DSI is always
5159+
// sufficient.
5160+
if !esds.decoder_specific_data.is_empty() {
5161+
fail_with_status_if(
5162+
strictness == ParseStrictness::Strict,
5163+
Status::EsdsDecSpecificInfoTagQuantity,
5164+
)?;
5165+
return Ok(());
5166+
}
5167+
51545168
#[cfg(feature = "mp4v")]
51555169
// Check if we are in a Visual esda Box.
51565170
if esds.video_codec != CodecType::Unknown {
@@ -5253,70 +5267,78 @@ fn read_ds_descriptor(
52535267
_ => 96000,
52545268
};
52555269

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

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

5308-
esds.audio_object_type = Some(audio_object_type);
5309-
esds.extended_audio_object_type = extended_audio_object_type;
5310-
esds.audio_sample_rate = Some(sample_frequency_value);
5311-
esds.audio_channel_count = Some(channel_counts);
5328+
let matrix_mixdown_idx_present: bool = ReadInto::read(bit_reader, 1)?;
5329+
if matrix_mixdown_idx_present {
5330+
bit_reader.skip(2)?; // matrix_mixdown_idx
5331+
bit_reader.skip(1)?; // pseudo_surround_enable
5332+
}
5333+
let mut channel_counts = 0;
5334+
channel_counts += read_surround_channel_count(bit_reader, num_front_channel)?;
5335+
channel_counts += read_surround_channel_count(bit_reader, num_side_channel)?;
5336+
channel_counts += read_surround_channel_count(bit_reader, num_back_channel)?;
5337+
channel_counts += read_surround_channel_count(bit_reader, num_lfe_channel)?;
53125338

5313-
if !esds.decoder_specific_data.is_empty() {
5314-
fail_with_status_if(
5315-
strictness == ParseStrictness::Strict,
5316-
Status::EsdsDecSpecificInfoTagQuantity,
5317-
)?;
5339+
esds.audio_channel_count = Some(channel_counts);
5340+
esds.decoder_specific_data.extend_from_slice(data)?;
53185341
}
5319-
esds.decoder_specific_data.extend_from_slice(data)?;
53205342

53215343
Ok(())
53225344
}

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)