diff --git a/library/core/src/main/java/com/google/android/exoplayer2/C.java b/library/core/src/main/java/com/google/android/exoplayer2/C.java index 0120451bc1..cf0f97ea76 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/C.java @@ -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; /** 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 4f45e85762..373fd3f14e 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 @@ -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 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 parseTrex(ParsableByteArray trex) { trex.setPosition(Atom.FULL_HEADER_SIZE); int trackId = trex.readInt(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java index 33d79917eb..87d0491a7b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java @@ -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. - * - *

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";