From 3afdb24f25d7a224e5963384d0955e3393548527 Mon Sep 17 00:00:00 2001 From: hoangtc Date: Wed, 29 Nov 2017 09:23:57 -0800 Subject: [PATCH] Add support for outputing Emsg messages to a sideloaded track output. Currently FragmentedMp4Extractor only parses and outputs emsg messages if the flag FLAG_ENABLE_EMSG_TRACK is set (when there's a metadata renderer that handles emsg messages). Since there are emsg messages that only targets the player, which we want to handle independently from MetadateRenderer, this CL adds the ability for FragmentedMp4Extractor to output emsg messages to an additional TrackOutput if provided, independently from FLAG_ENABLED_EMSG_TRACK. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=177318983 --- .../extractor/mp4/FragmentedMp4Extractor.java | 98 ++++++++++++++----- 1 file changed, 73 insertions(+), 25 deletions(-) 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.