diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java index 28a1ffaa7b..9a70dfbf90 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.extractor.mp4; import android.support.annotation.IntDef; +import android.support.annotation.Nullable; import android.util.Log; import android.util.Pair; import android.util.SparseArray; @@ -44,10 +45,10 @@ import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.LinkedList; import java.util.List; import java.util.Stack; import java.util.UUID; @@ -108,6 +109,8 @@ public final class FragmentedMp4Extractor implements Extractor { private static final int SAMPLE_GROUP_TYPE_seig = Util.getIntegerCodeForString("seig"); private static final byte[] PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE = new byte[] {-94, 57, 79, 82, 90, -101, 79, 20, -94, 68, 108, 66, 124, 100, -115, -12}; + private static final Format EMSG_FORMAT = + Format.createSampleFormat(null, MimeTypes.APPLICATION_EMSG, Format.OFFSET_SAMPLE_RELATIVE); // Parser states. private static final int STATE_READING_ATOM_HEADER = 0; @@ -141,7 +144,8 @@ public final class FragmentedMp4Extractor implements Extractor { private final ParsableByteArray atomHeader; private final byte[] extendedTypeScratch; private final Stack containerAtoms; - private final LinkedList pendingMetadataSampleInfos; + private final ArrayDeque pendingMetadataSampleInfos; + private final @Nullable TrackOutput additionalEmsgTrackOutput; private int parserState; private int atomType; @@ -161,7 +165,7 @@ public final class FragmentedMp4Extractor implements Extractor { // Extractor output. private ExtractorOutput extractorOutput; - private TrackOutput eventMessageTrackOutput; + private TrackOutput[] emsgTrackOutputs; private TrackOutput[] cea608TrackOutputs; // Whether extractorOutput.seekMap has been called. @@ -212,11 +216,32 @@ public final class FragmentedMp4Extractor implements Extractor { */ public FragmentedMp4Extractor(@Flags int flags, TimestampAdjuster timestampAdjuster, Track sideloadedTrack, DrmInitData sideloadedDrmInitData, List closedCaptionFormats) { + this(flags, timestampAdjuster, sideloadedTrack, sideloadedDrmInitData, + closedCaptionFormats, null); + } + + /** + * @param flags Flags that control the extractor's behavior. + * @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed. + * @param sideloadedTrack Sideloaded track information, in the case that the extractor + * will not receive a moov box in the input data. Null if a moov box is expected. + * @param sideloadedDrmInitData The {@link DrmInitData} to use for encrypted tracks. If null, the + * pssh boxes (if present) will be used. + * @param closedCaptionFormats For tracks that contain SEI messages, the formats of the closed + * caption channels to expose. + * @param additionalEmsgTrackOutput An extra track output that will receive all emsg messages + * targeting the player, even if {@link #FLAG_ENABLE_EMSG_TRACK} is not set. Null if special + * handling of emsg messages for players is not required. + */ + public FragmentedMp4Extractor(@Flags int flags, TimestampAdjuster timestampAdjuster, + Track sideloadedTrack, DrmInitData sideloadedDrmInitData, List closedCaptionFormats, + @Nullable TrackOutput additionalEmsgTrackOutput) { this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0); this.timestampAdjuster = timestampAdjuster; this.sideloadedTrack = sideloadedTrack; this.sideloadedDrmInitData = sideloadedDrmInitData; this.closedCaptionFormats = Collections.unmodifiableList(closedCaptionFormats); + this.additionalEmsgTrackOutput = additionalEmsgTrackOutput; atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE); nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); nalPrefix = new ParsableByteArray(5); @@ -225,7 +250,7 @@ public final class FragmentedMp4Extractor implements Extractor { defaultInitializationVector = new ParsableByteArray(); extendedTypeScratch = new byte[16]; containerAtoms = new Stack<>(); - pendingMetadataSampleInfos = new LinkedList<>(); + pendingMetadataSampleInfos = new ArrayDeque<>(); trackBundles = new SparseArray<>(); durationUs = C.TIME_UNSET; segmentIndexEarliestPresentationTimeUs = C.TIME_UNSET; @@ -494,10 +519,21 @@ public final class FragmentedMp4Extractor implements Extractor { } private void maybeInitExtraTracks() { - if ((flags & FLAG_ENABLE_EMSG_TRACK) != 0 && eventMessageTrackOutput == null) { - eventMessageTrackOutput = extractorOutput.track(trackBundles.size(), C.TRACK_TYPE_METADATA); - eventMessageTrackOutput.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_EMSG, - Format.OFFSET_SAMPLE_RELATIVE)); + if (emsgTrackOutputs == null) { + emsgTrackOutputs = new TrackOutput[2]; + int emsgTrackOutputCount = 0; + if (additionalEmsgTrackOutput != null) { + emsgTrackOutputs[emsgTrackOutputCount++] = additionalEmsgTrackOutput; + } + if ((flags & FLAG_ENABLE_EMSG_TRACK) != 0) { + emsgTrackOutputs[emsgTrackOutputCount++] = + extractorOutput.track(trackBundles.size(), C.TRACK_TYPE_METADATA); + } + emsgTrackOutputs = Arrays.copyOf(emsgTrackOutputs, emsgTrackOutputCount); + + for (TrackOutput eventMessageTrackOutput : emsgTrackOutputs) { + eventMessageTrackOutput.format(EMSG_FORMAT); + } } if (cea608TrackOutputs == null) { cea608TrackOutputs = new TrackOutput[closedCaptionFormats.size()]; @@ -510,29 +546,34 @@ public final class FragmentedMp4Extractor implements Extractor { } /** - * Handles an emsg atom (defined in 23009-1). + * Parses an emsg atom (defined in 23009-1). */ private void onEmsgLeafAtomRead(ParsableByteArray atom) { - if (eventMessageTrackOutput == null) { + if (emsgTrackOutputs.length == 0) { return; } - // Parse the event's presentation time delta. + atom.setPosition(Atom.FULL_HEADER_SIZE); + int sampleSize = atom.bytesLeft(); atom.readNullTerminatedString(); // schemeIdUri atom.readNullTerminatedString(); // value long timescale = atom.readUnsignedInt(); long presentationTimeDeltaUs = Util.scaleLargeTimestamp(atom.readUnsignedInt(), C.MICROS_PER_SECOND, timescale); + // Output the sample data. - atom.setPosition(Atom.FULL_HEADER_SIZE); - int sampleSize = atom.bytesLeft(); - eventMessageTrackOutput.sampleData(atom, sampleSize); + for (TrackOutput emsgTrackOutput : emsgTrackOutputs) { + atom.setPosition(Atom.FULL_HEADER_SIZE); + emsgTrackOutput.sampleData(atom, sampleSize); + } + // Output the sample metadata. if (segmentIndexEarliestPresentationTimeUs != C.TIME_UNSET) { - // We can output the sample metadata immediately. - eventMessageTrackOutput.sampleMetadata( - segmentIndexEarliestPresentationTimeUs + presentationTimeDeltaUs, - C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0 /* offset */, null); + for (TrackOutput emsgTrackOutput : emsgTrackOutputs) { + emsgTrackOutput.sampleMetadata( + segmentIndexEarliestPresentationTimeUs + presentationTimeDeltaUs, + C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0 /* offset */, null); + } } else { // We need the first sample timestamp in the segment before we can output the metadata. pendingMetadataSampleInfos.addLast( @@ -1194,13 +1235,8 @@ public final class FragmentedMp4Extractor implements Extractor { output.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, cryptoData); - while (!pendingMetadataSampleInfos.isEmpty()) { - MetadataSampleInfo sampleInfo = pendingMetadataSampleInfos.removeFirst(); - pendingMetadataSampleBytes -= sampleInfo.size; - eventMessageTrackOutput.sampleMetadata( - sampleTimeUs + sampleInfo.presentationTimeDeltaUs, - C.BUFFER_FLAG_KEY_FRAME, sampleInfo.size, pendingMetadataSampleBytes, null); - } + // After we have the sampleTimeUs, we can commit all the pending metadata samples + outputPendingMetadataSamples(sampleTimeUs); currentTrackBundle.currentSampleIndex++; currentTrackBundle.currentSampleInTrackRun++; @@ -1214,6 +1250,18 @@ public final class FragmentedMp4Extractor implements Extractor { return true; } + private void outputPendingMetadataSamples(long sampleTimeUs) { + while (!pendingMetadataSampleInfos.isEmpty()) { + MetadataSampleInfo sampleInfo = pendingMetadataSampleInfos.removeFirst(); + pendingMetadataSampleBytes -= sampleInfo.size; + for (TrackOutput emsgTrackOutput : emsgTrackOutputs) { + emsgTrackOutput.sampleMetadata( + sampleTimeUs + sampleInfo.presentationTimeDeltaUs, + C.BUFFER_FLAG_KEY_FRAME, sampleInfo.size, pendingMetadataSampleBytes, null); + } + } + } + /** * Returns the {@link TrackBundle} whose fragment run has the earliest file position out of those * yet to be consumed, or null if all have been consumed.