Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* 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.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.nio.ByteBuffer;

/** Utility methods for parsing MLP frames, which are access units in MLP bitstreams. */
public final class MlpUtil {

/** The channel count of MLP stream. */
// TODO: Parse MLP stream channel count.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this intended that this TODO has not been implemented?

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() {}
}
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.extractor.mp4;

import static com.google.android.exoplayer2.audio.Ac3Util.TRUEHD_RECHUNK_SAMPLE_COUNT;
import static com.google.android.exoplayer2.extractor.mp4.AtomParsers.parseTraks;
import static com.google.android.exoplayer2.extractor.mp4.Sniffer.BRAND_HEIC;
import static com.google.android.exoplayer2.extractor.mp4.Sniffer.BRAND_QUICKTIME;
Expand Down Expand Up @@ -44,6 +45,7 @@
import com.google.android.exoplayer2.metadata.mp4.MotionPhotoMetadata;
import com.google.android.exoplayer2.metadata.mp4.SlowMotionData;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.NalUnitUtil;
import com.google.android.exoplayer2.util.ParsableByteArray;
Expand Down Expand Up @@ -155,6 +157,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
private int sampleBytesRead;
private int sampleBytesWritten;
private int sampleCurrentNalBytesRemaining;
private int lastReadSampleSize;

// Extractor outputs.
private @MonotonicNonNull ExtractorOutput extractorOutput;
Expand Down Expand Up @@ -506,6 +509,12 @@ private void processMoovAtom(ContainerAtom moov) throws ParserException {
// 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.equals(MimeTypes.AUDIO_TRUEHD)) {
// TrueHD collates 16 source samples per output
maxInputSize = trackSampleTable.maximumSize * TRUEHD_RECHUNK_SAMPLE_COUNT;
}

Format.Builder formatBuilder = track.format.buildUpon();
formatBuilder.setMaxInputSize(maxInputSize);
if (track.type == C.TRACK_TYPE_VIDEO
Expand Down Expand Up @@ -554,7 +563,7 @@ private void processMoovAtom(ContainerAtom moov) throws ParserException {
* @return One of the {@code RESULT_*} flags in {@link Extractor}.
* @throws IOException If an error occurs reading from the input.
*/
private int readSample(ExtractorInput input, PositionHolder positionHolder) throws IOException {
private int readSample1(ExtractorInput input, PositionHolder positionHolder) throws IOException {
long inputPosition = input.getPosition();
if (sampleTrackIndex == C.INDEX_UNSET) {
sampleTrackIndex = getTrackIndexOfNextReadSample(inputPosition);
Expand Down Expand Up @@ -632,20 +641,75 @@ private int readSample(ExtractorInput input, PositionHolder positionHolder) thro
sampleCurrentNalBytesRemaining -= writtenBytes;
}
}
trackOutput.sampleMetadata(
track.sampleTable.timestampsUs[sampleIndex],
track.sampleTable.flags[sampleIndex],
sampleSize,
0,
null);

track.sampleIndex++;
sampleTrackIndex = C.INDEX_UNSET;
sampleBytesRead = 0;
sampleBytesWritten = 0;
sampleCurrentNalBytesRemaining = 0;
lastReadSampleSize = sampleSize;
return RESULT_CONTINUE;
}

/**
* 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}.
*
* <p>Returns {@link #RESULT_END_OF_INPUT} if no samples are left. Otherwise, returns {@link
* #RESULT_CONTINUE}.
*
* @param input The {@link ExtractorInput} from which to read data.
* @param positionHolder If {@link #RESULT_SEEK} is returned, this holder is updated to hold the
* position of the required data.
* @return One of the {@code RESULT_*} flags in {@link Extractor}.
* @throws IOException If an error occurs reading from the input.
*/
private int readSample(ExtractorInput input, PositionHolder positionHolder) throws IOException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method readSample should return as often as possible so it should ideally read only one sample before returning. To avoid that, you can use a rechunker as in the MatroskaExtractor that would check whether sampleMetadata should be called or not.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that I get it... you mean "don't change the readSample to read 16 samples a time but to issue the metadata only once every 16 samples for TrueHD" ...

I'll try to implement this.

long inputPosition = input.getPosition();
if (sampleTrackIndex == C.INDEX_UNSET) {
sampleTrackIndex = getTrackIndexOfNextReadSample(inputPosition);
if (sampleTrackIndex == C.INDEX_UNSET) {
return RESULT_END_OF_INPUT;
}
}

Mp4Track track = castNonNull(tracks)[sampleTrackIndex];
TrackOutput trackOutput = track.trackOutput;
int sampleIndex = track.sampleIndex;
int result = readSample1(input, positionHolder);

if (result != RESULT_CONTINUE) {
return result;
}

int sampleSize = lastReadSampleSize;

if (MimeTypes.AUDIO_TRUEHD.equals(track.track.format.sampleMimeType)) {
int untilIdx = sampleIndex + TRUEHD_RECHUNK_SAMPLE_COUNT;
int lastIdx = track.sampleTable.sizes.length;

untilIdx = min(untilIdx, lastIdx);

while (result == RESULT_CONTINUE && track.sampleIndex < untilIdx) {
int i = track.sampleIndex;
result = readSample1(input, positionHolder);
sampleSize += result == RESULT_CONTINUE ? lastReadSampleSize : 0;
}
}

trackOutput.sampleMetadata(
track.sampleTable.timestampsUs[sampleIndex],
track.sampleTable.flags[sampleIndex],
sampleSize,
0,
null);

return result;
}

/**
* Returns the index of the track that contains the next sample to be read, or {@link
* C#INDEX_UNSET} if no samples remain.
Expand Down
Original file line number Diff line number Diff line change
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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test doesn't pass. The changes also seem to have broken test mp4SampleWithAc4Track.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've messed up while rebasing. First a closing bracket } is missing on line 104.

As of the regression on AC4, I have indeed broken it while moving most of the readSample method code into a readSample1 method.
I have now fixed both issues in an updated set of commits.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mp4SampleWithDolbyTrueHDTrack() test still fails when simulating i/o errors but I am not quite sure how to debug this because don't quite understand how i/o errors get injected.

ExtractorAsserts.assertBehavior(
Mp4Extractor::new, "media/mp4/sample_dthd.mp4", simulationConfig);
}
}
Loading