Skip to content

PsExtractor does not strip the private_stream_1 sub-stream header, corrupting AC-3/DTS/LPCM audio in MPEG-PS #3327

Description

@TsaiHao

Version

Media3 main branch

More version details

Reproduced on main (HEAD as of 2026-07-09) and confirmed present in all releases through 1.10.1. The relevant code (PsExtractor.PesReader.consume()) is unchanged across these versions.

Devices that reproduce the issue

Reproduced on main (HEAD as of 2026-07-09) and confirmed present in all releases through 1.10.1. The relevant code (PsExtractor.PesReader.consume()) is unchanged across these versions.

Devices that do not reproduce the issue

No response

Reproducible in the demo app?

Yes

Reproduction steps

Root cause: In an MPEG Program Stream, AC-3 (also DTS and LPCM) audio is carried in private_stream_1 (PES stream id 0xBD). In DVD-style content, each such PES packet begins with a 4-byte sub-stream header immediately after the PES header:

sub_stream_id (1 byte)              // 0x80–0x87 = AC-3, 0x88–0x8F = DTS, 0xA0–0xA7 = LPCM
number_of_frame_headers (1 byte)
first_access_unit_pointer (2 bytes)

PsExtractor.PesReader.consume() strips the PES header + PTS/DTS extension but does not strip this 4-byte sub-stream header — it forwards it straight into Ac3Reader as if it were AC-3 elementary data:

https://github.com/androidx/media/blob/main/libraries/extractor/src/main/java/androidx/media3/extractor/ts/PsExtractor.java#L362-L370

Because AC-3 sync frames do not align to PES packet boundaries, the 4 stray bytes land inside the AC-3 frame that spans each PES boundary, corrupting that frame's bitstream. Ac3Reader re-syncs on the next 0x0B77 syncword, so the corruption is invisible to a frame-count/size check (every emitted frame still starts with a valid syncword) but every frame straddling a boundary is corrupt. ffmpeg's mpegps demuxer strips these bytes, which is why VLC/ffmpeg play the same content correctly. first_access_unit_pointer (which a correct demuxer uses to realign to the first frame that starts within the packet) is also unused.

Minimal desktop repro (no device needed) — feed the attached sample_ac3_ps.ps through PsExtractor with FakeExtractorInput/FakeExtractorOutput, concatenate the AC-3 track's samples, and decode with ffmpeg:

byte[] data = TestUtil.getByteArrayFromFilePath(".../sample_ac3_ps.ps");
FakeExtractorInput input = new FakeExtractorInput.Builder().setData(data).build();
FakeExtractorOutput output = new FakeExtractorOutput();
Extractor extractor = new PsExtractor();
extractor.init(output);
PositionHolder seek = new PositionHolder();
int result = Extractor.RESULT_CONTINUE;
while (result != Extractor.RESULT_END_OF_INPUT) {
  result = extractor.read(input, seek);
  if (result == Extractor.RESULT_SEEK) input.setPosition((int) seek.position);
}
FakeTrackOutput ac3 = output.trackOutputs.get(0xBD); // AC-3 track id = stream id
try (FileOutputStream fos = new FileOutputStream("/tmp/out.ac3")) {
  for (int i = 0; i < ac3.getSampleCount(); i++) fos.write(ac3.getSampleData(i));
}
// then: ffmpeg -v error -i /tmp/out.ac3 -f null -   ->  many "error decoding" / BSI errors

The first private_stream_1 PES payload of the sample starts with 80 02 00 01 (the sub-stream header), then the AC-3 sync 0b 77 ....

Expected result

The AC-3 elementary stream extracted from private_stream_1 should be byte-for-byte the audio bitstream only (matching ffmpeg's -c:a copy output), decoding without errors.

Actual result

The extracted AC-3 stream is corrupted at every PES boundary by the un-stripped 4-byte sub-stream header, producing AC-3 decode errors and audibly garbled audio.

Measured on the attached 580 KB sample (PsExtractor output → decoded with ffmpeg's AC-3 decoder):

AC-3 stream source AC-3 decode errors
PsExtractor (current main) 111
ffmpeg -c:a copy reference 0
PsExtractor with sub-stream header stripped 0

Media

sample_ac3_ps.mpeg.zip

Bug Report

Metadata

Metadata

Assignees

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions