mirror of
https://github.com/androidx/media.git
synced 2025-05-17 12:39:52 +08:00
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:
parent
85c10b0256
commit
97183ef558
@ -71,9 +71,10 @@ public final class C {
|
|||||||
/** Represents an unset or unknown percentage. */
|
/** Represents an unset or unknown percentage. */
|
||||||
public static final int PERCENTAGE_UNSET = -1;
|
public static final int PERCENTAGE_UNSET = -1;
|
||||||
|
|
||||||
/**
|
/** The number of milliseconds in one second. */
|
||||||
* The number of microseconds 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;
|
public static final long MICROS_PER_SECOND = 1000000L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,6 +35,8 @@ import com.google.android.exoplayer2.extractor.SeekMap;
|
|||||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom;
|
import com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom;
|
||||||
import com.google.android.exoplayer2.extractor.mp4.Atom.LeafAtom;
|
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.text.cea.CeaUtil;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.Log;
|
import com.google.android.exoplayer2.util.Log;
|
||||||
@ -140,6 +142,8 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
// Adjusts sample timestamps.
|
// Adjusts sample timestamps.
|
||||||
private final @Nullable TimestampAdjuster timestampAdjuster;
|
private final @Nullable TimestampAdjuster timestampAdjuster;
|
||||||
|
|
||||||
|
private final EventMessageEncoder eventMessageEncoder;
|
||||||
|
|
||||||
// Parser state.
|
// Parser state.
|
||||||
private final ParsableByteArray atomHeader;
|
private final ParsableByteArray atomHeader;
|
||||||
private final ArrayDeque<ContainerAtom> containerAtoms;
|
private final ArrayDeque<ContainerAtom> containerAtoms;
|
||||||
@ -253,6 +257,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
this.sideloadedDrmInitData = sideloadedDrmInitData;
|
this.sideloadedDrmInitData = sideloadedDrmInitData;
|
||||||
this.closedCaptionFormats = Collections.unmodifiableList(closedCaptionFormats);
|
this.closedCaptionFormats = Collections.unmodifiableList(closedCaptionFormats);
|
||||||
this.additionalEmsgTrackOutput = additionalEmsgTrackOutput;
|
this.additionalEmsgTrackOutput = additionalEmsgTrackOutput;
|
||||||
|
eventMessageEncoder = new EventMessageEncoder();
|
||||||
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);
|
||||||
@ -590,39 +595,71 @@ public 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 (emsgTrackOutputs == null || emsgTrackOutputs.length == 0) {
|
if (emsgTrackOutputs == null || emsgTrackOutputs.length == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
atom.setPosition(Atom.HEADER_SIZE);
|
||||||
atom.setPosition(Atom.FULL_HEADER_SIZE);
|
int fullAtom = atom.readInt();
|
||||||
int sampleSize = atom.bytesLeft();
|
int version = Atom.parseFullAtomVersion(fullAtom);
|
||||||
atom.readNullTerminatedString(); // schemeIdUri
|
String schemeIdUri;
|
||||||
atom.readNullTerminatedString(); // value
|
String value;
|
||||||
long timescale = atom.readUnsignedInt();
|
long timescale;
|
||||||
long presentationTimeDeltaUs =
|
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);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
// The presentation_time_delta is accounted for by adjusting the sample timestamp, so we zero it
|
byte[] messageData = new byte[atom.bytesLeft()];
|
||||||
// in the sample data before writing it to the track outputs.
|
atom.readBytes(messageData, /*offset=*/ 0, atom.bytesLeft());
|
||||||
int position = atom.getPosition();
|
EventMessage eventMessage = new EventMessage(schemeIdUri, value, durationMs, id, messageData);
|
||||||
atom.data[position - 4] = 0;
|
ParsableByteArray encodedEventMessage =
|
||||||
atom.data[position - 3] = 0;
|
new ParsableByteArray(eventMessageEncoder.encode(eventMessage));
|
||||||
atom.data[position - 2] = 0;
|
int sampleSize = encodedEventMessage.bytesLeft();
|
||||||
atom.data[position - 1] = 0;
|
|
||||||
|
|
||||||
// Output the sample data.
|
// Output the sample data.
|
||||||
for (TrackOutput emsgTrackOutput : emsgTrackOutputs) {
|
for (TrackOutput emsgTrackOutput : emsgTrackOutputs) {
|
||||||
atom.setPosition(Atom.FULL_HEADER_SIZE);
|
encodedEventMessage.setPosition(0);
|
||||||
emsgTrackOutput.sampleData(atom, sampleSize);
|
emsgTrackOutput.sampleData(encodedEventMessage, sampleSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output the sample metadata.
|
// Output the sample metadata. This is made a little complicated because emsg-v0 atoms
|
||||||
if (segmentIndexEarliestPresentationTimeUs != C.TIME_UNSET) {
|
// have presentation time *delta* while v1 atoms have absolute presentation time.
|
||||||
long sampleTimeUs = segmentIndexEarliestPresentationTimeUs + presentationTimeDeltaUs;
|
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) {
|
if (timestampAdjuster != null) {
|
||||||
sampleTimeUs = timestampAdjuster.adjustSampleTimestamp(sampleTimeUs);
|
sampleTimeUs = timestampAdjuster.adjustSampleTimestamp(sampleTimeUs);
|
||||||
}
|
}
|
||||||
@ -630,17 +667,10 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
emsgTrackOutput.sampleMetadata(
|
emsgTrackOutput.sampleMetadata(
|
||||||
sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, /* offset= */ 0, null);
|
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) {
|
private static Pair<Integer, DefaultSampleValues> parseTrex(ParsableByteArray trex) {
|
||||||
trex.setPosition(Atom.FULL_HEADER_SIZE);
|
trex.setPosition(Atom.FULL_HEADER_SIZE);
|
||||||
int trackId = trex.readInt();
|
int trackId = trex.readInt();
|
||||||
|
@ -25,13 +25,7 @@ import com.google.android.exoplayer2.util.Util;
|
|||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/** Decodes data encoded by {@link EventMessageEncoder}. */
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
public final class EventMessageDecoder implements MetadataDecoder {
|
public final class EventMessageDecoder implements MetadataDecoder {
|
||||||
|
|
||||||
private static final String TAG = "EventMessageDecoder";
|
private static final String TAG = "EventMessageDecoder";
|
||||||
|
Loading…
x
Reference in New Issue
Block a user