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
This commit is contained in:
hoangtc 2017-11-29 09:23:57 -08:00 committed by Oliver Woodman
parent a99ef01d3a
commit 3afdb24f25

View File

@ -16,6 +16,7 @@
package com.google.android.exoplayer2.extractor.mp4; package com.google.android.exoplayer2.extractor.mp4;
import android.support.annotation.IntDef; import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
import android.util.SparseArray; import android.util.SparseArray;
@ -44,10 +45,10 @@ import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Stack; import java.util.Stack;
import java.util.UUID; 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 int SAMPLE_GROUP_TYPE_seig = Util.getIntegerCodeForString("seig");
private static final byte[] PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE = 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}; 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. // Parser states.
private static final int STATE_READING_ATOM_HEADER = 0; 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 ParsableByteArray atomHeader;
private final byte[] extendedTypeScratch; private final byte[] extendedTypeScratch;
private final Stack<ContainerAtom> containerAtoms; private final Stack<ContainerAtom> containerAtoms;
private final LinkedList<MetadataSampleInfo> pendingMetadataSampleInfos; private final ArrayDeque<MetadataSampleInfo> pendingMetadataSampleInfos;
private final @Nullable TrackOutput additionalEmsgTrackOutput;
private int parserState; private int parserState;
private int atomType; private int atomType;
@ -161,7 +165,7 @@ public final class FragmentedMp4Extractor implements Extractor {
// Extractor output. // Extractor output.
private ExtractorOutput extractorOutput; private ExtractorOutput extractorOutput;
private TrackOutput eventMessageTrackOutput; private TrackOutput[] emsgTrackOutputs;
private TrackOutput[] cea608TrackOutputs; private TrackOutput[] cea608TrackOutputs;
// Whether extractorOutput.seekMap has been called. // Whether extractorOutput.seekMap has been called.
@ -212,11 +216,32 @@ public final class FragmentedMp4Extractor implements Extractor {
*/ */
public FragmentedMp4Extractor(@Flags int flags, TimestampAdjuster timestampAdjuster, public FragmentedMp4Extractor(@Flags int flags, TimestampAdjuster timestampAdjuster,
Track sideloadedTrack, DrmInitData sideloadedDrmInitData, List<Format> closedCaptionFormats) { Track sideloadedTrack, DrmInitData sideloadedDrmInitData, List<Format> 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<Format> closedCaptionFormats,
@Nullable TrackOutput additionalEmsgTrackOutput) {
this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0); this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0);
this.timestampAdjuster = timestampAdjuster; this.timestampAdjuster = timestampAdjuster;
this.sideloadedTrack = sideloadedTrack; this.sideloadedTrack = sideloadedTrack;
this.sideloadedDrmInitData = sideloadedDrmInitData; this.sideloadedDrmInitData = sideloadedDrmInitData;
this.closedCaptionFormats = Collections.unmodifiableList(closedCaptionFormats); this.closedCaptionFormats = Collections.unmodifiableList(closedCaptionFormats);
this.additionalEmsgTrackOutput = additionalEmsgTrackOutput;
atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE); atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE);
nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
nalPrefix = new ParsableByteArray(5); nalPrefix = new ParsableByteArray(5);
@ -225,7 +250,7 @@ public final class FragmentedMp4Extractor implements Extractor {
defaultInitializationVector = new ParsableByteArray(); defaultInitializationVector = new ParsableByteArray();
extendedTypeScratch = new byte[16]; extendedTypeScratch = new byte[16];
containerAtoms = new Stack<>(); containerAtoms = new Stack<>();
pendingMetadataSampleInfos = new LinkedList<>(); pendingMetadataSampleInfos = new ArrayDeque<>();
trackBundles = new SparseArray<>(); trackBundles = new SparseArray<>();
durationUs = C.TIME_UNSET; durationUs = C.TIME_UNSET;
segmentIndexEarliestPresentationTimeUs = C.TIME_UNSET; segmentIndexEarliestPresentationTimeUs = C.TIME_UNSET;
@ -494,10 +519,21 @@ public final class FragmentedMp4Extractor implements Extractor {
} }
private void maybeInitExtraTracks() { private void maybeInitExtraTracks() {
if ((flags & FLAG_ENABLE_EMSG_TRACK) != 0 && eventMessageTrackOutput == null) { if (emsgTrackOutputs == null) {
eventMessageTrackOutput = extractorOutput.track(trackBundles.size(), C.TRACK_TYPE_METADATA); emsgTrackOutputs = new TrackOutput[2];
eventMessageTrackOutput.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_EMSG, int emsgTrackOutputCount = 0;
Format.OFFSET_SAMPLE_RELATIVE)); 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) { if (cea608TrackOutputs == null) {
cea608TrackOutputs = new TrackOutput[closedCaptionFormats.size()]; 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) { private void onEmsgLeafAtomRead(ParsableByteArray atom) {
if (eventMessageTrackOutput == null) { if (emsgTrackOutputs.length == 0) {
return; return;
} }
// Parse the event's presentation time delta.
atom.setPosition(Atom.FULL_HEADER_SIZE); atom.setPosition(Atom.FULL_HEADER_SIZE);
int sampleSize = atom.bytesLeft();
atom.readNullTerminatedString(); // schemeIdUri atom.readNullTerminatedString(); // schemeIdUri
atom.readNullTerminatedString(); // value atom.readNullTerminatedString(); // value
long timescale = atom.readUnsignedInt(); long timescale = atom.readUnsignedInt();
long presentationTimeDeltaUs = long presentationTimeDeltaUs =
Util.scaleLargeTimestamp(atom.readUnsignedInt(), C.MICROS_PER_SECOND, timescale); Util.scaleLargeTimestamp(atom.readUnsignedInt(), C.MICROS_PER_SECOND, timescale);
// Output the sample data. // Output the sample data.
for (TrackOutput emsgTrackOutput : emsgTrackOutputs) {
atom.setPosition(Atom.FULL_HEADER_SIZE); atom.setPosition(Atom.FULL_HEADER_SIZE);
int sampleSize = atom.bytesLeft(); emsgTrackOutput.sampleData(atom, sampleSize);
eventMessageTrackOutput.sampleData(atom, sampleSize); }
// Output the sample metadata. // Output the sample metadata.
if (segmentIndexEarliestPresentationTimeUs != C.TIME_UNSET) { if (segmentIndexEarliestPresentationTimeUs != C.TIME_UNSET) {
// We can output the sample metadata immediately. for (TrackOutput emsgTrackOutput : emsgTrackOutputs) {
eventMessageTrackOutput.sampleMetadata( emsgTrackOutput.sampleMetadata(
segmentIndexEarliestPresentationTimeUs + presentationTimeDeltaUs, segmentIndexEarliestPresentationTimeUs + presentationTimeDeltaUs,
C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0 /* offset */, null); C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0 /* offset */, null);
}
} else { } else {
// We need the first sample timestamp in the segment before we can output the metadata. // We need the first sample timestamp in the segment before we can output the metadata.
pendingMetadataSampleInfos.addLast( pendingMetadataSampleInfos.addLast(
@ -1194,13 +1235,8 @@ public final class FragmentedMp4Extractor implements Extractor {
output.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, cryptoData); output.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, cryptoData);
while (!pendingMetadataSampleInfos.isEmpty()) { // After we have the sampleTimeUs, we can commit all the pending metadata samples
MetadataSampleInfo sampleInfo = pendingMetadataSampleInfos.removeFirst(); outputPendingMetadataSamples(sampleTimeUs);
pendingMetadataSampleBytes -= sampleInfo.size;
eventMessageTrackOutput.sampleMetadata(
sampleTimeUs + sampleInfo.presentationTimeDeltaUs,
C.BUFFER_FLAG_KEY_FRAME, sampleInfo.size, pendingMetadataSampleBytes, null);
}
currentTrackBundle.currentSampleIndex++; currentTrackBundle.currentSampleIndex++;
currentTrackBundle.currentSampleInTrackRun++; currentTrackBundle.currentSampleInTrackRun++;
@ -1214,6 +1250,18 @@ public final class FragmentedMp4Extractor implements Extractor {
return true; 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 * 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. * yet to be consumed, or null if all have been consumed.