Add inband emsg-v1 support to FragmentedMp4Extractor

This also decouples EventMessageEncoder's serialization schema from the emesg spec (it happens to still match the emsg-v0 spec, but this is no longer required).

PiperOrigin-RevId: 261877918
This commit is contained in:
ibaker 2019-08-06 11:16:02 +01:00 committed by Oliver Woodman
parent 85c10b0256
commit 97183ef558
3 changed files with 66 additions and 41 deletions

View File

@ -71,9 +71,10 @@ public final class C {
/** Represents an unset or unknown percentage. */
public static final int PERCENTAGE_UNSET = -1;
/**
* The number of microseconds in one second.
*/
/** The number of milliseconds in one second. */
public static final long MILLIS_PER_SECOND = 1000L;
/** The number of microseconds in one second. */
public static final long MICROS_PER_SECOND = 1000000L;
/**

View File

@ -35,6 +35,8 @@ import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom;
import com.google.android.exoplayer2.extractor.mp4.Atom.LeafAtom;
import com.google.android.exoplayer2.metadata.emsg.EventMessage;
import com.google.android.exoplayer2.metadata.emsg.EventMessageEncoder;
import com.google.android.exoplayer2.text.cea.CeaUtil;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
@ -140,6 +142,8 @@ public class FragmentedMp4Extractor implements Extractor {
// Adjusts sample timestamps.
private final @Nullable TimestampAdjuster timestampAdjuster;
private final EventMessageEncoder eventMessageEncoder;
// Parser state.
private final ParsableByteArray atomHeader;
private final ArrayDeque<ContainerAtom> containerAtoms;
@ -253,6 +257,7 @@ public class FragmentedMp4Extractor implements Extractor {
this.sideloadedDrmInitData = sideloadedDrmInitData;
this.closedCaptionFormats = Collections.unmodifiableList(closedCaptionFormats);
this.additionalEmsgTrackOutput = additionalEmsgTrackOutput;
eventMessageEncoder = new EventMessageEncoder();
atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE);
nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
nalPrefix = new ParsableByteArray(5);
@ -590,39 +595,71 @@ public class FragmentedMp4Extractor implements Extractor {
}
}
/**
* Parses an emsg atom (defined in 23009-1).
*/
/** Handles an emsg atom (defined in 23009-1). */
private void onEmsgLeafAtomRead(ParsableByteArray atom) {
if (emsgTrackOutputs == null || emsgTrackOutputs.length == 0) {
return;
}
atom.setPosition(Atom.HEADER_SIZE);
int fullAtom = atom.readInt();
int version = Atom.parseFullAtomVersion(fullAtom);
String schemeIdUri;
String value;
long timescale;
long presentationTimeDeltaUs = C.TIME_UNSET; // Only set if version == 0
long sampleTimeUs = C.TIME_UNSET;
long durationMs;
long id;
switch (version) {
case 0:
schemeIdUri = Assertions.checkNotNull(atom.readNullTerminatedString());
value = Assertions.checkNotNull(atom.readNullTerminatedString());
timescale = atom.readUnsignedInt();
presentationTimeDeltaUs =
Util.scaleLargeTimestamp(atom.readUnsignedInt(), C.MICROS_PER_SECOND, timescale);
if (segmentIndexEarliestPresentationTimeUs != C.TIME_UNSET) {
sampleTimeUs = segmentIndexEarliestPresentationTimeUs + presentationTimeDeltaUs;
}
durationMs =
Util.scaleLargeTimestamp(atom.readUnsignedInt(), C.MILLIS_PER_SECOND, timescale);
id = atom.readUnsignedInt();
break;
case 1:
timescale = atom.readUnsignedInt();
sampleTimeUs =
Util.scaleLargeTimestamp(atom.readUnsignedLongToLong(), C.MICROS_PER_SECOND, timescale);
durationMs =
Util.scaleLargeTimestamp(atom.readUnsignedInt(), C.MILLIS_PER_SECOND, timescale);
id = atom.readUnsignedInt();
schemeIdUri = Assertions.checkNotNull(atom.readNullTerminatedString());
value = Assertions.checkNotNull(atom.readNullTerminatedString());
break;
default:
Log.w(TAG, "Skipping unsupported emsg version: " + version);
return;
}
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);
// The presentation_time_delta is accounted for by adjusting the sample timestamp, so we zero it
// in the sample data before writing it to the track outputs.
int position = atom.getPosition();
atom.data[position - 4] = 0;
atom.data[position - 3] = 0;
atom.data[position - 2] = 0;
atom.data[position - 1] = 0;
byte[] messageData = new byte[atom.bytesLeft()];
atom.readBytes(messageData, /*offset=*/ 0, atom.bytesLeft());
EventMessage eventMessage = new EventMessage(schemeIdUri, value, durationMs, id, messageData);
ParsableByteArray encodedEventMessage =
new ParsableByteArray(eventMessageEncoder.encode(eventMessage));
int sampleSize = encodedEventMessage.bytesLeft();
// Output the sample data.
for (TrackOutput emsgTrackOutput : emsgTrackOutputs) {
atom.setPosition(Atom.FULL_HEADER_SIZE);
emsgTrackOutput.sampleData(atom, sampleSize);
encodedEventMessage.setPosition(0);
emsgTrackOutput.sampleData(encodedEventMessage, sampleSize);
}
// Output the sample metadata.
if (segmentIndexEarliestPresentationTimeUs != C.TIME_UNSET) {
long sampleTimeUs = segmentIndexEarliestPresentationTimeUs + presentationTimeDeltaUs;
// Output the sample metadata. This is made a little complicated because emsg-v0 atoms
// have presentation time *delta* while v1 atoms have absolute presentation time.
if (sampleTimeUs == C.TIME_UNSET) {
// We need the first sample timestamp in the segment before we can output the metadata.
pendingMetadataSampleInfos.addLast(
new MetadataSampleInfo(presentationTimeDeltaUs, sampleSize));
pendingMetadataSampleBytes += sampleSize;
} else {
if (timestampAdjuster != null) {
sampleTimeUs = timestampAdjuster.adjustSampleTimestamp(sampleTimeUs);
}
@ -630,17 +667,10 @@ public class FragmentedMp4Extractor implements Extractor {
emsgTrackOutput.sampleMetadata(
sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, /* offset= */ 0, null);
}
} else {
// We need the first sample timestamp in the segment before we can output the metadata.
pendingMetadataSampleInfos.addLast(
new MetadataSampleInfo(presentationTimeDeltaUs, sampleSize));
pendingMetadataSampleBytes += sampleSize;
}
}
/**
* Parses a trex atom (defined in 14496-12).
*/
/** Parses a trex atom (defined in 14496-12). */
private static Pair<Integer, DefaultSampleValues> parseTrex(ParsableByteArray trex) {
trex.setPosition(Atom.FULL_HEADER_SIZE);
int trackId = trex.readInt();

View File

@ -25,13 +25,7 @@ import com.google.android.exoplayer2.util.Util;
import java.nio.ByteBuffer;
import java.util.Arrays;
/**
* Decodes Event Message (emsg) atoms, as defined in ISO/IEC 23009-1:2014, Section 5.10.3.3.
*
* <p>Atom data should be provided to the decoder without the full atom header (i.e. starting from
* the first byte of the scheme_id_uri field). It is expected that the presentation_time_delta field
* should be 0, having already been accounted for by adjusting the sample timestamp.
*/
/** Decodes data encoded by {@link EventMessageEncoder}. */
public final class EventMessageDecoder implements MetadataDecoder {
private static final String TAG = "EventMessageDecoder";