Skip to content

Commit 184c310

Browse files
g-lassglass
authored andcommitted
Add MP4 extraction of Dolby TrueHD samples
Extract 16 access units per readSample call to align with what's done in MKV extraction. Signed-off-by: glass <glass@dolby.com>
1 parent 2138bfb commit 184c310

File tree

4 files changed

+156
-10
lines changed

4 files changed

+156
-10
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright (C) 2021 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.google.android.exoplayer2.audio;
17+
18+
import androidx.annotation.Nullable;
19+
import com.google.android.exoplayer2.C;
20+
import com.google.android.exoplayer2.Format;
21+
import com.google.android.exoplayer2.drm.DrmInitData;
22+
import com.google.android.exoplayer2.util.MimeTypes;
23+
import com.google.android.exoplayer2.util.ParsableBitArray;
24+
import com.google.android.exoplayer2.util.ParsableByteArray;
25+
import java.nio.ByteBuffer;
26+
27+
/** Utility methods for parsing MLP frames, which are access units in MLP bitstreams. */
28+
public final class MlpUtil {
29+
30+
/** The channel count of MLP stream. */
31+
// TODO: Parse MLP stream channel count.
32+
private static final int CHANNEL_COUNT_2 = 2;
33+
34+
35+
/**
36+
* Returns the MLP format given {@code data} containing the MLPSpecificBox according to
37+
* dolbytruehdbitstreamswithintheisobasemediafileformat.pdf
38+
* The reading position of {@code data} will be modified.
39+
*
40+
* @param data The MLPSpecificBox to parse.
41+
* @param trackId The track identifier to set on the format.
42+
* @param sampleRate The sample rate to be included in the format.
43+
* @param language The language to set on the format.
44+
* @param drmInitData {@link DrmInitData} to be included in the format.
45+
* @return The MLP format parsed from data in the header.
46+
*/
47+
public static Format parseMlpFormat(
48+
ParsableByteArray data, String trackId, int sampleRate,
49+
String language, @Nullable DrmInitData drmInitData) {
50+
51+
return new Format.Builder()
52+
.setId(trackId)
53+
.setSampleMimeType(MimeTypes.AUDIO_TRUEHD)
54+
.setChannelCount(CHANNEL_COUNT_2)
55+
.setSampleRate(sampleRate)
56+
.setDrmInitData(drmInitData)
57+
.setLanguage(language)
58+
.build();
59+
}
60+
61+
private MlpUtil() {}
62+
}

library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@
152152
@SuppressWarnings("ConstantCaseForConstants")
153153
public static final int TYPE_dac4 = 0x64616334;
154154

155+
@SuppressWarnings("ConstantCaseForConstants")
156+
public static final int TYPE_mlpa = 0x6d6c7061;
157+
158+
@SuppressWarnings("ConstantCaseForConstants")
159+
public static final int TYPE_dmlp = 0x646d6c70;
160+
155161
@SuppressWarnings("ConstantCaseForConstants")
156162
public static final int TYPE_dtsc = 0x64747363;
157163

library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import com.google.android.exoplayer2.audio.AacUtil;
2929
import com.google.android.exoplayer2.audio.Ac3Util;
3030
import com.google.android.exoplayer2.audio.Ac4Util;
31+
import com.google.android.exoplayer2.audio.MlpUtil;
3132
import com.google.android.exoplayer2.audio.OpusUtil;
3233
import com.google.android.exoplayer2.drm.DrmInitData;
3334
import com.google.android.exoplayer2.extractor.ExtractorUtil;
@@ -962,6 +963,7 @@ private static StsdData parseStsd(
962963
|| childAtomType == Atom.TYPE_ac_3
963964
|| childAtomType == Atom.TYPE_ec_3
964965
|| childAtomType == Atom.TYPE_ac_4
966+
|| childAtomType == Atom.TYPE_mlpa
965967
|| childAtomType == Atom.TYPE_dtsc
966968
|| childAtomType == Atom.TYPE_dtse
967969
|| childAtomType == Atom.TYPE_dtsh
@@ -1312,14 +1314,20 @@ private static void parseAudioSampleEntry(
13121314
parent.skipBytes(8);
13131315
}
13141316

1315-
int channelCount;
1316-
int sampleRate;
1317+
int channelCount;
1318+
int sampleRate;
1319+
int sampleRate32 = 0;
13171320
@C.PcmEncoding int pcmEncoding = Format.NO_VALUE;
13181321
@Nullable String codecs = null;
13191322

13201323
if (quickTimeSoundDescriptionVersion == 0 || quickTimeSoundDescriptionVersion == 1) {
13211324
channelCount = parent.readUnsignedShort();
1322-
parent.skipBytes(6); // sampleSize, compressionId, packetSize.
1325+
parent.skipBytes(6); // sampleSize, compressionId, packetSize.
1326+
1327+
int pos = parent.getPosition();
1328+
sampleRate32 = (int) parent.readUnsignedInt();
1329+
1330+
parent.setPosition(pos);
13231331
sampleRate = parent.readUnsignedFixedPoint1616();
13241332

13251333
if (quickTimeSoundDescriptionVersion == 1) {
@@ -1401,6 +1409,8 @@ private static void parseAudioSampleEntry(
14011409
mimeType = MimeTypes.AUDIO_OPUS;
14021410
} else if (atomType == Atom.TYPE_fLaC) {
14031411
mimeType = MimeTypes.AUDIO_FLAC;
1412+
} else if (atomType == Atom.TYPE_mlpa) {
1413+
mimeType = MimeTypes.AUDIO_TRUEHD;
14041414
}
14051415

14061416
@Nullable List<byte[]> initializationData = null;
@@ -1442,6 +1452,10 @@ private static void parseAudioSampleEntry(
14421452
initializationData = ImmutableList.of(initializationDataBytes);
14431453
}
14441454
}
1455+
} else if (childAtomType == Atom.TYPE_dmlp) {
1456+
parent.setPosition(Atom.HEADER_SIZE + childPosition);
1457+
out.format = MlpUtil.parseMlpFormat(parent, Integer.toString(trackId),
1458+
sampleRate32, language, drmInitData);
14451459
} else if (childAtomType == Atom.TYPE_dac3) {
14461460
parent.setPosition(Atom.HEADER_SIZE + childPosition);
14471461
out.format =

library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package com.google.android.exoplayer2.extractor.mp4;
1717

18+
import static com.google.android.exoplayer2.audio.Ac3Util.TRUEHD_RECHUNK_SAMPLE_COUNT;
1819
import static com.google.android.exoplayer2.extractor.mp4.AtomParsers.parseTraks;
1920
import static com.google.android.exoplayer2.extractor.mp4.Sniffer.BRAND_HEIC;
2021
import static com.google.android.exoplayer2.extractor.mp4.Sniffer.BRAND_QUICKTIME;
@@ -44,6 +45,7 @@
4445
import com.google.android.exoplayer2.metadata.mp4.MotionPhotoMetadata;
4546
import com.google.android.exoplayer2.metadata.mp4.SlowMotionData;
4647
import com.google.android.exoplayer2.util.Assertions;
48+
import com.google.android.exoplayer2.util.Log;
4749
import com.google.android.exoplayer2.util.MimeTypes;
4850
import com.google.android.exoplayer2.util.NalUnitUtil;
4951
import com.google.android.exoplayer2.util.ParsableByteArray;
@@ -155,6 +157,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
155157
private int sampleBytesRead;
156158
private int sampleBytesWritten;
157159
private int sampleCurrentNalBytesRemaining;
160+
private int lastReadSampleSize;
158161

159162
// Extractor outputs.
160163
private @MonotonicNonNull ExtractorOutput extractorOutput;
@@ -506,6 +509,12 @@ private void processMoovAtom(ContainerAtom moov) throws ParserException {
506509
// Each sample has up to three bytes of overhead for the start code that replaces its length.
507510
// Allow ten source samples per output sample, like the platform extractor.
508511
int maxInputSize = trackSampleTable.maximumSize + 3 * 10;
512+
513+
if (track.format.sampleMimeType.equals(MimeTypes.AUDIO_TRUEHD)) {
514+
// TrueHD collates 16 source samples per output
515+
maxInputSize = trackSampleTable.maximumSize * TRUEHD_RECHUNK_SAMPLE_COUNT;
516+
}
517+
509518
Format.Builder formatBuilder = track.format.buildUpon();
510519
formatBuilder.setMaxInputSize(maxInputSize);
511520
if (track.type == C.TRACK_TYPE_VIDEO
@@ -554,7 +563,7 @@ private void processMoovAtom(ContainerAtom moov) throws ParserException {
554563
* @return One of the {@code RESULT_*} flags in {@link Extractor}.
555564
* @throws IOException If an error occurs reading from the input.
556565
*/
557-
private int readSample(ExtractorInput input, PositionHolder positionHolder) throws IOException {
566+
private int readSample1(ExtractorInput input, PositionHolder positionHolder) throws IOException {
558567
long inputPosition = input.getPosition();
559568
if (sampleTrackIndex == C.INDEX_UNSET) {
560569
sampleTrackIndex = getTrackIndexOfNextReadSample(inputPosition);
@@ -632,20 +641,75 @@ private int readSample(ExtractorInput input, PositionHolder positionHolder) thro
632641
sampleCurrentNalBytesRemaining -= writtenBytes;
633642
}
634643
}
635-
trackOutput.sampleMetadata(
636-
track.sampleTable.timestampsUs[sampleIndex],
637-
track.sampleTable.flags[sampleIndex],
638-
sampleSize,
639-
0,
640-
null);
644+
641645
track.sampleIndex++;
642646
sampleTrackIndex = C.INDEX_UNSET;
643647
sampleBytesRead = 0;
644648
sampleBytesWritten = 0;
645649
sampleCurrentNalBytesRemaining = 0;
650+
lastReadSampleSize = sampleSize;
646651
return RESULT_CONTINUE;
647652
}
648653

654+
/**
655+
* Attempts to extract the next sample or the next 16 samples in case of Dolby TrueHD audio
656+
* in the current mdat atom for the specified track.
657+
*
658+
* <p>Returns {@link #RESULT_SEEK} if the source should be reloaded from the position in {@code
659+
* positionHolder}.
660+
*
661+
* <p>Returns {@link #RESULT_END_OF_INPUT} if no samples are left. Otherwise, returns {@link
662+
* #RESULT_CONTINUE}.
663+
*
664+
* @param input The {@link ExtractorInput} from which to read data.
665+
* @param positionHolder If {@link #RESULT_SEEK} is returned, this holder is updated to hold the
666+
* position of the required data.
667+
* @return One of the {@code RESULT_*} flags in {@link Extractor}.
668+
* @throws IOException If an error occurs reading from the input.
669+
*/
670+
private int readSample(ExtractorInput input, PositionHolder positionHolder) throws IOException {
671+
long inputPosition = input.getPosition();
672+
if (sampleTrackIndex == C.INDEX_UNSET) {
673+
sampleTrackIndex = getTrackIndexOfNextReadSample(inputPosition);
674+
if (sampleTrackIndex == C.INDEX_UNSET) {
675+
return RESULT_END_OF_INPUT;
676+
}
677+
}
678+
679+
Mp4Track track = castNonNull(tracks)[sampleTrackIndex];
680+
TrackOutput trackOutput = track.trackOutput;
681+
int sampleIndex = track.sampleIndex;
682+
int result = readSample1(input, positionHolder);
683+
684+
if (result != RESULT_CONTINUE) {
685+
return result;
686+
}
687+
688+
int sampleSize = lastReadSampleSize;
689+
690+
if (MimeTypes.AUDIO_TRUEHD.equals(track.track.format.sampleMimeType)) {
691+
int untilIdx = sampleIndex + TRUEHD_RECHUNK_SAMPLE_COUNT;
692+
int lastIdx = track.sampleTable.sizes.length;
693+
694+
untilIdx = min(untilIdx, lastIdx);
695+
696+
while (result == RESULT_CONTINUE && track.sampleIndex < untilIdx) {
697+
int i = track.sampleIndex;
698+
result = readSample1(input, positionHolder);
699+
sampleSize += result == RESULT_CONTINUE ? lastReadSampleSize : 0;
700+
}
701+
}
702+
703+
trackOutput.sampleMetadata(
704+
track.sampleTable.timestampsUs[sampleIndex],
705+
track.sampleTable.flags[sampleIndex],
706+
sampleSize,
707+
0,
708+
null);
709+
710+
return result;
711+
}
712+
649713
/**
650714
* Returns the index of the track that contains the next sample to be read, or {@link
651715
* C#INDEX_UNSET} if no samples remain.

0 commit comments

Comments
 (0)