diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/mkv/MatroskaExtractor.java b/libraries/extractor/src/main/java/androidx/media3/extractor/mkv/MatroskaExtractor.java index fb251afdb96..58e7f55c305 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/mkv/MatroskaExtractor.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/mkv/MatroskaExtractor.java @@ -46,6 +46,7 @@ import androidx.media3.extractor.AacUtil; import androidx.media3.extractor.AvcConfig; import androidx.media3.extractor.ChunkIndex; +import androidx.media3.extractor.DtsUtil; import androidx.media3.extractor.Extractor; import androidx.media3.extractor.ExtractorInput; import androidx.media3.extractor.ExtractorOutput; @@ -444,6 +445,7 @@ public static ExtractorsFactory newFactory(SubtitleParser.Factory subtitleParser private long durationTimecode = C.TIME_UNSET; private long durationUs = C.TIME_UNSET; private boolean isWebm; + private boolean pendingEndTracks = true; // The track corresponding to the current TrackEntry element, or null. @Nullable private Track currentTrack; @@ -795,6 +797,23 @@ protected void startMasterElement(int id, long contentPosition, long contentSize } } + /** + * Ensures `extractorOutput.endTracks()` gets called only once, and only + * if we don't have any pending audio analysis. + */ + private void maybeEndTracks() { + if (!pendingEndTracks) { + return; + } + for (int i = 0; i < tracks.size(); i++) { + if (tracks.valueAt(i).waitingForDtsAnalysis) { + return; + } + } + extractorOutput.endTracks(); + pendingEndTracks = false; + } + /** * Called when the end of a master element is encountered. * @@ -905,7 +924,7 @@ protected void endMasterElement(int id) throws ParserException { throw ParserException.createForMalformedContainer( "No valid tracks were found", /* cause= */ null); } - extractorOutput.endTracks(); + maybeEndTracks(); break; default: break; @@ -1564,6 +1583,53 @@ private int writeSampleData(ExtractorInput input, Track track, int size, boolean return finishWriteSampleData(); } + if (track.waitingForDtsAnalysis) { + // The format for this DTS track as not been determined yet + long remaining = input.getLength() - input.getPosition(); + // Limit the peek ahead to be up to the max frame size (16383) plus the + // sync word of the second frame + int scanLength = (int)Math.min(16383 + 95, remaining); + byte[] buf = new byte[scanLength]; + + input.advancePeekPosition(0); + input.peekFully(buf, 0, buf.length); + input.resetPeekPosition(); + + final ByteBuffer bb = ByteBuffer.wrap(buf); + for (int idx = 0; idx + 4 <= buf.length; idx += 4) { + int word = bb.getInt(idx); + + if (DtsUtil.getFrameType(word) == DtsUtil.FRAME_TYPE_CORE) { + if (idx + 10 > buf.length) { + break; + } + + bb.mark(); + bb.position(idx); + byte[] header = new byte[10]; + bb.get(header); + bb.reset(); + int fsize = DtsUtil.getDtsFrameSize(header); + if (fsize <= 0 || idx + fsize + 4 > buf.length) { + break; + } + + word = bb.getInt(idx + fsize); + + if (DtsUtil.getFrameType(word) == DtsUtil.FRAME_TYPE_EXTENSION_SUBSTREAM) { + track.formatBuilder.setSampleMimeType(MimeTypes.AUDIO_DTS_HD); + track.output.format(track.formatBuilder.build()); + } + + // After finding a valid DTS core frame we can break the loop, there is no + // need to evaluate the rest of the buffer. + break; + } + } + track.waitingForDtsAnalysis = false; + maybeEndTracks(); + } + TrackOutput output = track.output; if (!sampleEncodingHandled) { if (track.hasContentEncryption) { @@ -2135,6 +2201,7 @@ protected static final class Track { public long codecDelayNs = 0; public long seekPreRollNs = 0; public @MonotonicNonNull TrueHdSampleRechunker trueHdSampleRechunker; + public boolean waitingForDtsAnalysis = false; // Text elements. public boolean flagForced; @@ -2143,6 +2210,7 @@ protected static final class Track { // Set when the output is initialized. nalUnitLengthFieldLength is only set for H264/H265. public @MonotonicNonNull TrackOutput output; + public Format.Builder formatBuilder; public int nalUnitLengthFieldLength; /** Initializes the track with an output. */ @@ -2246,7 +2314,8 @@ public void initializeOutput(ExtractorOutput output, int trackId) throws ParserE break; case CODEC_ID_DTS: case CODEC_ID_DTS_EXPRESS: - mimeType = MimeTypes.AUDIO_DTS; + mimeType = MimeTypes.AUDIO_DTS; // temporary + waitingForDtsAnalysis = true; break; case CODEC_ID_DTS_LOSSLESS: mimeType = MimeTypes.AUDIO_DTS_HD; @@ -2369,7 +2438,7 @@ public void initializeOutput(ExtractorOutput output, int trackId) throws ParserE selectionFlags |= flagForced ? C.SELECTION_FLAG_FORCED : 0; int type; - Format.Builder formatBuilder = new Format.Builder(); + formatBuilder = new Format.Builder(); // TODO: Consider reading the name elements of the tracks and, if present, incorporating them // into the trackId passed when creating the formats. if (MimeTypes.isAudio(mimeType)) {