Skip to content

Commit be79f52

Browse files
committed
Refactor logic and add tests
1 parent 3657ebc commit be79f52

22 files changed

+5022
-64
lines changed

RELEASENOTES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@
4949
* MP3: Allow gaps between (and before) ID3 tags at the beginning of MP3
5050
files ([#811](https://github.com/androidx/media/issues/811),
5151
[#5718](https://github.com/google/ExoPlayer/issues/5718)).
52+
* Matroska: Add support for DTS-HD detection
53+
([#6225](https://github.com/google/ExoPlayer/issues/6225)).
5254
* DataSource:
5355
* Audio:
5456
* Make `AudioProcessor` instances aware of seeking.

libraries/extractor/src/main/java/androidx/media3/extractor/DtsUtil.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@
2727
import androidx.media3.common.MimeTypes;
2828
import androidx.media3.common.ParserException;
2929
import androidx.media3.common.util.ParsableBitArray;
30+
import androidx.media3.common.util.ParsableByteArray;
3031
import androidx.media3.common.util.UnstableApi;
3132
import androidx.media3.common.util.Util;
33+
import java.io.IOException;
3234
import java.lang.annotation.Documented;
3335
import java.lang.annotation.Retention;
3436
import java.lang.annotation.Target;
@@ -681,6 +683,39 @@ public static int parseDtsUhdHeaderSize(byte[] headerPrefix) {
681683
+ 1;
682684
}
683685

686+
/** Returns whether the sample data at the current {@link ExtractorInput} is a DTS-HD sample. */
687+
public static boolean isSampleDtsHd(ExtractorInput input, int sampleSize) throws IOException {
688+
// Limit the peek ahead to be up to the sample size plus the sync word of the second frame.
689+
int scanLength = sampleSize + 95;
690+
ParsableByteArray sampleData = new ParsableByteArray(scanLength);
691+
if (!input.peekFully(
692+
sampleData.getData(), /* offset= */ 0, scanLength, /* allowEndOfInput= */ true)) {
693+
return false;
694+
}
695+
input.resetPeekPosition();
696+
697+
while (sampleData.bytesLeft() >= 4) {
698+
int word = sampleData.peekInt();
699+
if (DtsUtil.getFrameType(word) == DtsUtil.FRAME_TYPE_CORE) {
700+
if (sampleData.bytesLeft() < 10) {
701+
return false;
702+
}
703+
byte[] header = new byte[10];
704+
sampleData.readBytes(header, /* offset= */ 0, /* length= */ 10);
705+
sampleData.setPosition(0);
706+
int frameSize = DtsUtil.getDtsFrameSize(header);
707+
if (frameSize <= 0 || sampleData.bytesLeft() < frameSize + 4) {
708+
return false;
709+
}
710+
sampleData.skipBytes(frameSize);
711+
word = sampleData.readInt();
712+
return DtsUtil.getFrameType(word) == DtsUtil.FRAME_TYPE_EXTENSION_SUBSTREAM;
713+
}
714+
sampleData.skipBytes(4);
715+
}
716+
return false;
717+
}
718+
684719
/**
685720
* Check if calculated and extracted CRC-16 words match. See ETSI TS 103 491 V1.2.1, Table 6-8.
686721
*/

libraries/extractor/src/main/java/androidx/media3/extractor/mkv/MatroskaExtractor.java

Lines changed: 26 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ public static ExtractorsFactory newFactory(SubtitleParser.Factory subtitleParser
445445
private long durationTimecode = C.TIME_UNSET;
446446
private long durationUs = C.TIME_UNSET;
447447
private boolean isWebm;
448-
private boolean pendingEndTracks = true;
448+
private boolean pendingEndTracks;
449449

450450
// The track corresponding to the current TrackEntry element, or null.
451451
@Nullable private Track currentTrack;
@@ -557,6 +557,7 @@ public MatroskaExtractor(SubtitleParser.Factory subtitleParserFactory, @Flags in
557557
encryptionSubsampleData = new ParsableByteArray();
558558
supplementalData = new ParsableByteArray();
559559
blockSampleSizes = new int[1];
560+
pendingEndTracks = true;
560561
}
561562

562563
@Override
@@ -797,23 +798,6 @@ protected void startMasterElement(int id, long contentPosition, long contentSize
797798
}
798799
}
799800

800-
/**
801-
* Ensures `extractorOutput.endTracks()` gets called only once, and only
802-
* if we don't have any pending audio analysis.
803-
*/
804-
private void maybeEndTracks() {
805-
if (!pendingEndTracks) {
806-
return;
807-
}
808-
for (int i = 0; i < tracks.size(); i++) {
809-
if (tracks.valueAt(i).waitingForDtsAnalysis) {
810-
return;
811-
}
812-
}
813-
extractorOutput.endTracks();
814-
pendingEndTracks = false;
815-
}
816-
817801
/**
818802
* Called when the end of a master element is encountered.
819803
*
@@ -1584,51 +1568,14 @@ private int writeSampleData(ExtractorInput input, Track track, int size, boolean
15841568
}
15851569

15861570
if (track.waitingForDtsAnalysis) {
1587-
// The format for this DTS track as not been determined yet
1588-
long remaining = input.getLength() - input.getPosition();
1589-
// Limit the peek ahead to be up to the max frame size (16383) plus the
1590-
// sync word of the second frame
1591-
int scanLength = (int)Math.min(16383 + 95, remaining);
1592-
byte[] buf = new byte[scanLength];
1593-
1594-
input.advancePeekPosition(0);
1595-
input.peekFully(buf, 0, buf.length);
1596-
input.resetPeekPosition();
1597-
1598-
final ByteBuffer bb = ByteBuffer.wrap(buf);
1599-
for (int idx = 0; idx + 4 <= buf.length; idx += 4) {
1600-
int word = bb.getInt(idx);
1601-
1602-
if (DtsUtil.getFrameType(word) == DtsUtil.FRAME_TYPE_CORE) {
1603-
if (idx + 10 > buf.length) {
1604-
break;
1605-
}
1606-
1607-
bb.mark();
1608-
bb.position(idx);
1609-
byte[] header = new byte[10];
1610-
bb.get(header);
1611-
bb.reset();
1612-
int fsize = DtsUtil.getDtsFrameSize(header);
1613-
if (fsize <= 0 || idx + fsize + 4 > buf.length) {
1614-
break;
1615-
}
1616-
1617-
word = bb.getInt(idx + fsize);
1618-
1619-
if (DtsUtil.getFrameType(word) == DtsUtil.FRAME_TYPE_EXTENSION_SUBSTREAM) {
1620-
track.formatBuilder.setSampleMimeType(MimeTypes.AUDIO_DTS_HD);
1621-
track.output.format(track.formatBuilder.build());
1622-
}
1623-
1624-
// After finding a valid DTS core frame we can break the loop, there is no
1625-
// need to evaluate the rest of the buffer.
1626-
break;
1627-
}
1571+
checkNotNull(track.format);
1572+
if (DtsUtil.isSampleDtsHd(input, size)) {
1573+
track.format = track.format.buildUpon().setSampleMimeType(MimeTypes.AUDIO_DTS_HD).build();
16281574
}
1575+
track.output.format(track.format);
16291576
track.waitingForDtsAnalysis = false;
16301577
maybeEndTracks();
1631-
}
1578+
}
16321579

16331580
TrackOutput output = track.output;
16341581
if (!sampleEncodingHandled) {
@@ -2091,6 +2038,19 @@ private void assertInitialized() {
20912038
checkStateNotNull(extractorOutput);
20922039
}
20932040

2041+
private void maybeEndTracks() {
2042+
if (!pendingEndTracks) {
2043+
return;
2044+
}
2045+
for (int i = 0; i < tracks.size(); i++) {
2046+
if (tracks.valueAt(i).waitingForDtsAnalysis) {
2047+
return;
2048+
}
2049+
}
2050+
checkNotNull(extractorOutput).endTracks();
2051+
pendingEndTracks = false;
2052+
}
2053+
20942054
/** Passes events through to the outer {@link MatroskaExtractor}. */
20952055
private final class InnerEbmlProcessor implements EbmlProcessor {
20962056

@@ -2210,7 +2170,7 @@ protected static final class Track {
22102170

22112171
// Set when the output is initialized. nalUnitLengthFieldLength is only set for H264/H265.
22122172
public @MonotonicNonNull TrackOutput output;
2213-
public Format.Builder formatBuilder;
2173+
public @MonotonicNonNull Format format;
22142174
public int nalUnitLengthFieldLength;
22152175

22162176
/** Initializes the track with an output. */
@@ -2438,7 +2398,7 @@ public void initializeOutput(ExtractorOutput output, int trackId) throws ParserE
24382398
selectionFlags |= flagForced ? C.SELECTION_FLAG_FORCED : 0;
24392399

24402400
int type;
2441-
formatBuilder = new Format.Builder();
2401+
Format.Builder formatBuilder = new Format.Builder();
24422402
// TODO: Consider reading the name elements of the tracks and, if present, incorporating them
24432403
// into the trackId passed when creating the formats.
24442404
if (MimeTypes.isAudio(mimeType)) {
@@ -2514,7 +2474,7 @@ public void initializeOutput(ExtractorOutput output, int trackId) throws ParserE
25142474
formatBuilder.setLabel(name);
25152475
}
25162476

2517-
Format format =
2477+
format =
25182478
formatBuilder
25192479
.setId(trackId)
25202480
.setContainerMimeType(isWebm ? MimeTypes.VIDEO_WEBM : MimeTypes.VIDEO_MATROSKA)
@@ -2528,7 +2488,9 @@ public void initializeOutput(ExtractorOutput output, int trackId) throws ParserE
25282488
.build();
25292489

25302490
this.output = output.track(number, type);
2531-
this.output.format(format);
2491+
if (!waitingForDtsAnalysis) {
2492+
this.output.format(format);
2493+
}
25322494
}
25332495

25342496
/** Forces any pending sample metadata to be flushed to the output. */

libraries/extractor/src/test/java/androidx/media3/extractor/mkv/MatroskaExtractorTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,30 @@ public void webmSubsampleEncryptionWithAltrefFrames() throws Exception {
211211
simulationConfig);
212212
}
213213

214+
@Test
215+
public void mkvSample_withDts() throws Exception {
216+
ExtractorAsserts.assertBehavior(
217+
getExtractorFactory(subtitlesParsedDuringExtraction),
218+
"media/mkv/sample_with_dts.mkv",
219+
simulationConfig);
220+
}
221+
222+
@Test
223+
public void mkvSample_withDtsHdMa() throws Exception {
224+
ExtractorAsserts.assertBehavior(
225+
getExtractorFactory(subtitlesParsedDuringExtraction),
226+
"media/mkv/sample_with_dts_hd_ma.mkv",
227+
simulationConfig);
228+
}
229+
230+
@Test
231+
public void mkvSample_withDtsX() throws Exception {
232+
ExtractorAsserts.assertBehavior(
233+
getExtractorFactory(subtitlesParsedDuringExtraction),
234+
"media/mkv/sample_with_dts_x.mkv",
235+
simulationConfig);
236+
}
237+
214238
private static ExtractorAsserts.ExtractorFactory getExtractorFactory(
215239
boolean subtitlesParsedDuringExtraction) {
216240
SubtitleParser.Factory subtitleParserFactory;

0 commit comments

Comments
 (0)