Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev v2 truehd #9496

Merged
merged 2 commits into from Oct 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -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;
kim-vde marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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;
kim-vde marked this conversation as resolved.
Show resolved Hide resolved

/**
* Rechunks TrueHD sample data into groups of {@link #TRUEHD_RECHUNK_SAMPLE_COUNT} samples.
*/
public static class TrueHdSampleRechunker {
kim-vde marked this conversation as resolved.
Show resolved Hide resolved

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;
}
}
}
Expand Up @@ -152,6 +152,12 @@
@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;

Expand Down
Expand Up @@ -28,6 +28,7 @@
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;
Expand Down Expand Up @@ -962,6 +963,7 @@ private static StsdData parseStsd(
|| 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
Expand Down Expand Up @@ -1312,14 +1314,20 @@ private static void parseAudioSampleEntry(
parent.skipBytes(8);
}

int channelCount;
int sampleRate;
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.
parent.skipBytes(6); // sampleSize, compressionId, packetSize.

int pos = parent.getPosition();
sampleRate32 = (int) parent.readUnsignedInt();

parent.setPosition(pos);
sampleRate = parent.readUnsignedFixedPoint1616();

if (quickTimeSoundDescriptionVersion == 1) {
Expand Down Expand Up @@ -1401,6 +1409,8 @@ private static void parseAudioSampleEntry(
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;
Expand Down Expand Up @@ -1442,6 +1452,10 @@ private static void parseAudioSampleEntry(
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 =
Expand Down
Expand Up @@ -30,6 +30,7 @@
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;
Expand Down Expand Up @@ -501,11 +502,17 @@ private void processMoovAtom(ContainerAtom moov) throws ParserException {
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
Expand Down Expand Up @@ -540,7 +547,8 @@ private void processMoovAtom(ContainerAtom moov) throws ParserException {
}

/**
* 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}.
Expand Down Expand Up @@ -632,12 +640,9 @@ private int readSample(ExtractorInput input, PositionHolder positionHolder) thro
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;
Expand Down Expand Up @@ -904,11 +909,40 @@ private static final class Mp4Track {
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);
}
}
}

}
Expand Up @@ -102,4 +102,10 @@ public void mp4SampleWithColorInfo() throws Exception {
ExtractorAsserts.assertBehavior(
Mp4Extractor::new, "media/mp4/sample_with_color_info.mp4", simulationConfig);
}

@Test
public void mp4SampleWithDolbyTrueHDTrack() throws Exception {
ExtractorAsserts.assertBehavior(
Mp4Extractor::new, "media/mp4/sample_dthd.mp4", simulationConfig);
kim-vde marked this conversation as resolved.
Show resolved Hide resolved
}
}