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>
This commit is contained in:
parent
2138bfb396
commit
13f4c832da
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.audio;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.drm.DrmInitData;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
|
||||
/** Utility methods for parsing MLP frames, which are access units in MLP bitstreams. */
|
||||
public final class MlpUtil {
|
||||
|
||||
/** a MLP stream can carry simultaneously multiple representations of the same audio :
|
||||
* stereo as well as multichannel and object based immersive audio,
|
||||
* so just consider stereo by default */
|
||||
private static final int CHANNEL_COUNT_2 = 2;
|
||||
|
||||
/**
|
||||
* Returns the MLP format given {@code data} containing the MLPSpecificBox according to
|
||||
* dolbytruehdbitstreamswithintheisobasemediafileformat.pdf
|
||||
* The reading position of {@code data} will be modified.
|
||||
*
|
||||
* @param data The MLPSpecificBox to parse.
|
||||
* @param trackId The track identifier to set on the format.
|
||||
* @param sampleRate The sample rate to be included in the format.
|
||||
* @param language The language to set on the format.
|
||||
* @param drmInitData {@link DrmInitData} to be included in the format.
|
||||
* @return The MLP format parsed from data in the header.
|
||||
*/
|
||||
public static Format parseMlpFormat(
|
||||
ParsableByteArray data, String trackId, int sampleRate,
|
||||
String language, @Nullable DrmInitData drmInitData) {
|
||||
|
||||
return new Format.Builder()
|
||||
.setId(trackId)
|
||||
.setSampleMimeType(MimeTypes.AUDIO_TRUEHD)
|
||||
.setChannelCount(CHANNEL_COUNT_2)
|
||||
.setSampleRate(sampleRate)
|
||||
.setDrmInitData(drmInitData)
|
||||
.setLanguage(language)
|
||||
.build();
|
||||
}
|
||||
|
||||
private MlpUtil() {}
|
||||
|
||||
/**
|
||||
* The number of samples to store in each output chunk when rechunking TrueHD streams. The number
|
||||
* of samples extracted from the container corresponding to one syncframe must be an integer
|
||||
* multiple of this value.
|
||||
*/
|
||||
public static final int TRUEHD_RECHUNK_SAMPLE_COUNT = 16;
|
||||
|
||||
/**
|
||||
* Rechunks TrueHD sample data into groups of {@link #TRUEHD_RECHUNK_SAMPLE_COUNT} samples.
|
||||
*/
|
||||
public static class TrueHdSampleRechunker {
|
||||
|
||||
private int sampleCount;
|
||||
public long timeUs;
|
||||
public @C.BufferFlags int flags;
|
||||
public int sampleSize;
|
||||
|
||||
public TrueHdSampleRechunker() {
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
sampleCount = 0;
|
||||
sampleSize = 0;
|
||||
}
|
||||
|
||||
/** Returns true when enough samples have been appended. */
|
||||
public boolean appendSampleMetadata(long timeUs, @C.BufferFlags int flags, int size) {
|
||||
|
||||
if (sampleCount++ == 0) {
|
||||
// This is the first sample in the chunk.
|
||||
this.timeUs = timeUs;
|
||||
this.flags = flags;
|
||||
this.sampleSize = 0;
|
||||
}
|
||||
this.sampleSize += size;
|
||||
if (sampleCount >= TRUEHD_RECHUNK_SAMPLE_COUNT) {
|
||||
sampleCount = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -152,6 +152,12 @@ import java.util.List;
|
||||
@SuppressWarnings("ConstantCaseForConstants")
|
||||
public static final int TYPE_dac4 = 0x64616334;
|
||||
|
||||
@SuppressWarnings("ConstantCaseForConstants")
|
||||
public static final int TYPE_mlpa = 0x6d6c7061;
|
||||
|
||||
@SuppressWarnings("ConstantCaseForConstants")
|
||||
public static final int TYPE_dmlp = 0x646d6c70;
|
||||
|
||||
@SuppressWarnings("ConstantCaseForConstants")
|
||||
public static final int TYPE_dtsc = 0x64747363;
|
||||
|
||||
|
@ -28,6 +28,7 @@ import com.google.android.exoplayer2.ParserException;
|
||||
import com.google.android.exoplayer2.audio.AacUtil;
|
||||
import com.google.android.exoplayer2.audio.Ac3Util;
|
||||
import com.google.android.exoplayer2.audio.Ac4Util;
|
||||
import com.google.android.exoplayer2.audio.MlpUtil;
|
||||
import com.google.android.exoplayer2.audio.OpusUtil;
|
||||
import com.google.android.exoplayer2.drm.DrmInitData;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorUtil;
|
||||
@ -962,6 +963,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
|| childAtomType == Atom.TYPE_ac_3
|
||||
|| childAtomType == Atom.TYPE_ec_3
|
||||
|| childAtomType == Atom.TYPE_ac_4
|
||||
|| childAtomType == Atom.TYPE_mlpa
|
||||
|| childAtomType == Atom.TYPE_dtsc
|
||||
|| childAtomType == Atom.TYPE_dtse
|
||||
|| childAtomType == Atom.TYPE_dtsh
|
||||
@ -1314,12 +1316,18 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
|
||||
int channelCount;
|
||||
int sampleRate;
|
||||
int sampleRate32 = 0;
|
||||
@C.PcmEncoding int pcmEncoding = Format.NO_VALUE;
|
||||
@Nullable String codecs = null;
|
||||
|
||||
if (quickTimeSoundDescriptionVersion == 0 || quickTimeSoundDescriptionVersion == 1) {
|
||||
channelCount = parent.readUnsignedShort();
|
||||
parent.skipBytes(6); // sampleSize, compressionId, packetSize.
|
||||
|
||||
int pos = parent.getPosition();
|
||||
sampleRate32 = (int) parent.readUnsignedInt();
|
||||
|
||||
parent.setPosition(pos);
|
||||
sampleRate = parent.readUnsignedFixedPoint1616();
|
||||
|
||||
if (quickTimeSoundDescriptionVersion == 1) {
|
||||
@ -1401,6 +1409,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
mimeType = MimeTypes.AUDIO_OPUS;
|
||||
} else if (atomType == Atom.TYPE_fLaC) {
|
||||
mimeType = MimeTypes.AUDIO_FLAC;
|
||||
} else if (atomType == Atom.TYPE_mlpa) {
|
||||
mimeType = MimeTypes.AUDIO_TRUEHD;
|
||||
}
|
||||
|
||||
@Nullable List<byte[]> initializationData = null;
|
||||
@ -1442,6 +1452,10 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
initializationData = ImmutableList.of(initializationDataBytes);
|
||||
}
|
||||
}
|
||||
} else if (childAtomType == Atom.TYPE_dmlp) {
|
||||
parent.setPosition(Atom.HEADER_SIZE + childPosition);
|
||||
out.format = MlpUtil.parseMlpFormat(parent, Integer.toString(trackId),
|
||||
sampleRate32, language, drmInitData);
|
||||
} else if (childAtomType == Atom.TYPE_dac3) {
|
||||
parent.setPosition(Atom.HEADER_SIZE + childPosition);
|
||||
out.format =
|
||||
|
@ -30,6 +30,7 @@ import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.ParserException;
|
||||
import com.google.android.exoplayer2.audio.Ac4Util;
|
||||
import com.google.android.exoplayer2.audio.MlpUtil;
|
||||
import com.google.android.exoplayer2.extractor.Extractor;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||
@ -501,11 +502,17 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||
track.durationUs != C.TIME_UNSET ? track.durationUs : trackSampleTable.durationUs;
|
||||
durationUs = max(durationUs, trackDurationUs);
|
||||
Mp4Track mp4Track =
|
||||
new Mp4Track(track, trackSampleTable, extractorOutput.track(i, track.type));
|
||||
new Mp4Track(track, trackSampleTable, extractorOutput.track(i, track.type), track.format.sampleMimeType);
|
||||
|
||||
// Each sample has up to three bytes of overhead for the start code that replaces its length.
|
||||
// Allow ten source samples per output sample, like the platform extractor.
|
||||
int maxInputSize = trackSampleTable.maximumSize + 3 * 10;
|
||||
|
||||
if ((track.format.sampleMimeType != null) && (track.format.sampleMimeType.equals(MimeTypes.AUDIO_TRUEHD))) {
|
||||
// TrueHD collates 16 source samples per output
|
||||
maxInputSize = trackSampleTable.maximumSize * MlpUtil.TRUEHD_RECHUNK_SAMPLE_COUNT;
|
||||
}
|
||||
|
||||
Format.Builder formatBuilder = track.format.buildUpon();
|
||||
formatBuilder.setMaxInputSize(maxInputSize);
|
||||
if (track.type == C.TRACK_TYPE_VIDEO
|
||||
@ -540,7 +547,8 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to extract the next sample in the current mdat atom for the specified track.
|
||||
* Attempts to extract the next sample or the next 16 samples in case of Dolby TrueHD audio
|
||||
* in the current mdat atom for the specified track.
|
||||
*
|
||||
* <p>Returns {@link #RESULT_SEEK} if the source should be reloaded from the position in {@code
|
||||
* positionHolder}.
|
||||
@ -632,12 +640,9 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||
sampleCurrentNalBytesRemaining -= writtenBytes;
|
||||
}
|
||||
}
|
||||
trackOutput.sampleMetadata(
|
||||
track.sampleTable.timestampsUs[sampleIndex],
|
||||
track.sampleTable.flags[sampleIndex],
|
||||
sampleSize,
|
||||
0,
|
||||
null);
|
||||
|
||||
track.sampleMetadata(sampleIndex, sampleSize, 0, null);
|
||||
|
||||
track.sampleIndex++;
|
||||
sampleTrackIndex = C.INDEX_UNSET;
|
||||
sampleBytesRead = 0;
|
||||
@ -904,11 +909,40 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||
public final TrackOutput trackOutput;
|
||||
|
||||
public int sampleIndex;
|
||||
@Nullable public MlpUtil.TrueHdSampleRechunker trueHdSampleRechunker;
|
||||
|
||||
public Mp4Track(Track track, TrackSampleTable sampleTable, TrackOutput trackOutput) {
|
||||
public Mp4Track(Track track, TrackSampleTable sampleTable, TrackOutput trackOutput, @Nullable String mimeType) {
|
||||
this.track = track;
|
||||
this.sampleTable = sampleTable;
|
||||
this.trackOutput = trackOutput;
|
||||
this.trueHdSampleRechunker = null;
|
||||
|
||||
if ((mimeType != null) && mimeType.equals(MimeTypes.AUDIO_TRUEHD)) {
|
||||
this.trueHdSampleRechunker = new MlpUtil.TrueHdSampleRechunker();
|
||||
}
|
||||
}
|
||||
|
||||
public void sampleMetadata( int sampleIndex, int sampleSize, int offset,
|
||||
@Nullable TrackOutput.CryptoData cryptoData) {
|
||||
|
||||
long timeUs = sampleTable.timestampsUs[sampleIndex];
|
||||
@C.BufferFlags int flags = sampleTable.flags[sampleIndex];
|
||||
|
||||
if (trueHdSampleRechunker != null) {
|
||||
boolean fullChunk = trueHdSampleRechunker.appendSampleMetadata(timeUs,flags,sampleSize);
|
||||
|
||||
if (fullChunk || (sampleIndex+1 == sampleTable.sampleCount)) {
|
||||
timeUs = trueHdSampleRechunker.timeUs;
|
||||
flags = trueHdSampleRechunker.flags;
|
||||
sampleSize = trueHdSampleRechunker.sampleSize;
|
||||
|
||||
trackOutput.sampleMetadata( timeUs, flags, sampleSize, offset, cryptoData);
|
||||
trueHdSampleRechunker.reset();
|
||||
}
|
||||
} else {
|
||||
trackOutput.sampleMetadata( timeUs, flags, sampleSize, offset, cryptoData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user