use of com.google.android.exoplayer2.metadata.Metadata in project react-native-video by react-native-community.
the class VideoEventEmitter method timedMetadata.
void timedMetadata(Metadata metadata) {
WritableArray metadataArray = Arguments.createArray();
for (int i = 0; i < metadata.length(); i++) {
Id3Frame frame = (Id3Frame) metadata.get(i);
String value = "";
if (frame instanceof TxxxFrame) {
TxxxFrame txxxFrame = (TxxxFrame) frame;
value = txxxFrame.value;
}
String identifier = frame.id;
WritableMap map = Arguments.createMap();
map.putString("identifier", identifier);
map.putString("value", value);
metadataArray.pushMap(map);
}
WritableMap event = Arguments.createMap();
event.putArray(EVENT_PROP_TIMED_METADATA, metadataArray);
receiveEvent(EVENT_TIMED_METADATA, event);
}
use of com.google.android.exoplayer2.metadata.Metadata in project ExoPlayer by google.
the class AtomParsers method parseStbl.
/**
* Parses an stbl atom (defined in 14496-12).
*
* @param track Track to which this sample table corresponds.
* @param stblAtom stbl (sample table) atom to decode.
* @param gaplessInfoHolder Holder to populate with gapless playback information.
* @return Sample table described by the stbl atom.
* @throws ParserException If the resulting sample sequence does not contain a sync sample.
*/
public static TrackSampleTable parseStbl(Track track, Atom.ContainerAtom stblAtom, GaplessInfoHolder gaplessInfoHolder) throws ParserException {
SampleSizeBox sampleSizeBox;
Atom.LeafAtom stszAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stsz);
if (stszAtom != null) {
sampleSizeBox = new StszSampleSizeBox(stszAtom);
} else {
Atom.LeafAtom stz2Atom = stblAtom.getLeafAtomOfType(Atom.TYPE_stz2);
if (stz2Atom == null) {
throw new ParserException("Track has no sample table size information");
}
sampleSizeBox = new Stz2SampleSizeBox(stz2Atom);
}
int sampleCount = sampleSizeBox.getSampleCount();
if (sampleCount == 0) {
return new TrackSampleTable(new long[0], new int[0], 0, new long[0], new int[0]);
}
// Entries are byte offsets of chunks.
boolean chunkOffsetsAreLongs = false;
Atom.LeafAtom chunkOffsetsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stco);
if (chunkOffsetsAtom == null) {
chunkOffsetsAreLongs = true;
chunkOffsetsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_co64);
}
ParsableByteArray chunkOffsets = chunkOffsetsAtom.data;
// Entries are (chunk number, number of samples per chunk, sample description index).
ParsableByteArray stsc = stblAtom.getLeafAtomOfType(Atom.TYPE_stsc).data;
// Entries are (number of samples, timestamp delta between those samples).
ParsableByteArray stts = stblAtom.getLeafAtomOfType(Atom.TYPE_stts).data;
// Entries are the indices of samples that are synchronization samples.
Atom.LeafAtom stssAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stss);
ParsableByteArray stss = stssAtom != null ? stssAtom.data : null;
// Entries are (number of samples, timestamp offset).
Atom.LeafAtom cttsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_ctts);
ParsableByteArray ctts = cttsAtom != null ? cttsAtom.data : null;
// Prepare to read chunk information.
ChunkIterator chunkIterator = new ChunkIterator(stsc, chunkOffsets, chunkOffsetsAreLongs);
// Prepare to read sample timestamps.
stts.setPosition(Atom.FULL_HEADER_SIZE);
int remainingTimestampDeltaChanges = stts.readUnsignedIntToInt() - 1;
int remainingSamplesAtTimestampDelta = stts.readUnsignedIntToInt();
int timestampDeltaInTimeUnits = stts.readUnsignedIntToInt();
// Prepare to read sample timestamp offsets, if ctts is present.
int remainingSamplesAtTimestampOffset = 0;
int remainingTimestampOffsetChanges = 0;
int timestampOffset = 0;
if (ctts != null) {
ctts.setPosition(Atom.FULL_HEADER_SIZE);
remainingTimestampOffsetChanges = ctts.readUnsignedIntToInt();
}
int nextSynchronizationSampleIndex = C.INDEX_UNSET;
int remainingSynchronizationSamples = 0;
if (stss != null) {
stss.setPosition(Atom.FULL_HEADER_SIZE);
remainingSynchronizationSamples = stss.readUnsignedIntToInt();
if (remainingSynchronizationSamples > 0) {
nextSynchronizationSampleIndex = stss.readUnsignedIntToInt() - 1;
} else {
// Ignore empty stss boxes, which causes all samples to be treated as sync samples.
stss = null;
}
}
// True if we can rechunk fixed-sample-size data. Note that we only rechunk raw audio.
boolean isRechunkable = sampleSizeBox.isFixedSampleSize() && MimeTypes.AUDIO_RAW.equals(track.format.sampleMimeType) && remainingTimestampDeltaChanges == 0 && remainingTimestampOffsetChanges == 0 && remainingSynchronizationSamples == 0;
long[] offsets;
int[] sizes;
int maximumSize = 0;
long[] timestamps;
int[] flags;
long timestampTimeUnits = 0;
if (!isRechunkable) {
offsets = new long[sampleCount];
sizes = new int[sampleCount];
timestamps = new long[sampleCount];
flags = new int[sampleCount];
long offset = 0;
int remainingSamplesInChunk = 0;
for (int i = 0; i < sampleCount; i++) {
// Advance to the next chunk if necessary.
while (remainingSamplesInChunk == 0) {
Assertions.checkState(chunkIterator.moveNext());
offset = chunkIterator.offset;
remainingSamplesInChunk = chunkIterator.numSamples;
}
// Add on the timestamp offset if ctts is present.
if (ctts != null) {
while (remainingSamplesAtTimestampOffset == 0 && remainingTimestampOffsetChanges > 0) {
remainingSamplesAtTimestampOffset = ctts.readUnsignedIntToInt();
// The BMFF spec (ISO 14496-12) states that sample offsets should be unsigned integers
// in version 0 ctts boxes, however some streams violate the spec and use signed
// integers instead. It's safe to always decode sample offsets as signed integers here,
// because unsigned integers will still be parsed correctly (unless their top bit is
// set, which is never true in practice because sample offsets are always small).
timestampOffset = ctts.readInt();
remainingTimestampOffsetChanges--;
}
remainingSamplesAtTimestampOffset--;
}
offsets[i] = offset;
sizes[i] = sampleSizeBox.readNextSampleSize();
if (sizes[i] > maximumSize) {
maximumSize = sizes[i];
}
timestamps[i] = timestampTimeUnits + timestampOffset;
// All samples are synchronization samples if the stss is not present.
flags[i] = stss == null ? C.BUFFER_FLAG_KEY_FRAME : 0;
if (i == nextSynchronizationSampleIndex) {
flags[i] = C.BUFFER_FLAG_KEY_FRAME;
remainingSynchronizationSamples--;
if (remainingSynchronizationSamples > 0) {
nextSynchronizationSampleIndex = stss.readUnsignedIntToInt() - 1;
}
}
// Add on the duration of this sample.
timestampTimeUnits += timestampDeltaInTimeUnits;
remainingSamplesAtTimestampDelta--;
if (remainingSamplesAtTimestampDelta == 0 && remainingTimestampDeltaChanges > 0) {
remainingSamplesAtTimestampDelta = stts.readUnsignedIntToInt();
timestampDeltaInTimeUnits = stts.readUnsignedIntToInt();
remainingTimestampDeltaChanges--;
}
offset += sizes[i];
remainingSamplesInChunk--;
}
Assertions.checkArgument(remainingSamplesAtTimestampOffset == 0);
// Remove trailing ctts entries with 0-valued sample counts.
while (remainingTimestampOffsetChanges > 0) {
Assertions.checkArgument(ctts.readUnsignedIntToInt() == 0);
// Ignore offset.
ctts.readInt();
remainingTimestampOffsetChanges--;
}
// still be playable.
if (remainingSynchronizationSamples != 0 || remainingSamplesAtTimestampDelta != 0 || remainingSamplesInChunk != 0 || remainingTimestampDeltaChanges != 0) {
Log.w(TAG, "Inconsistent stbl box for track " + track.id + ": remainingSynchronizationSamples " + remainingSynchronizationSamples + ", remainingSamplesAtTimestampDelta " + remainingSamplesAtTimestampDelta + ", remainingSamplesInChunk " + remainingSamplesInChunk + ", remainingTimestampDeltaChanges " + remainingTimestampDeltaChanges);
}
} else {
long[] chunkOffsetsBytes = new long[chunkIterator.length];
int[] chunkSampleCounts = new int[chunkIterator.length];
while (chunkIterator.moveNext()) {
chunkOffsetsBytes[chunkIterator.index] = chunkIterator.offset;
chunkSampleCounts[chunkIterator.index] = chunkIterator.numSamples;
}
int fixedSampleSize = sampleSizeBox.readNextSampleSize();
FixedSampleSizeRechunker.Results rechunkedResults = FixedSampleSizeRechunker.rechunk(fixedSampleSize, chunkOffsetsBytes, chunkSampleCounts, timestampDeltaInTimeUnits);
offsets = rechunkedResults.offsets;
sizes = rechunkedResults.sizes;
maximumSize = rechunkedResults.maximumSize;
timestamps = rechunkedResults.timestamps;
flags = rechunkedResults.flags;
}
if (track.editListDurations == null || gaplessInfoHolder.hasGaplessInfo()) {
// There is no edit list, or we are ignoring it as we already have gapless metadata to apply.
// This implementation does not support applying both gapless metadata and an edit list.
Util.scaleLargeTimestampsInPlace(timestamps, C.MICROS_PER_SECOND, track.timescale);
return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags);
}
if (track.editListDurations.length == 1 && track.type == C.TRACK_TYPE_AUDIO && timestamps.length >= 2) {
// Handle the edit by setting gapless playback metadata, if possible. This implementation
// assumes that only one "roll" sample is needed, which is the case for AAC, so the start/end
// points of the edit must lie within the first/last samples respectively.
long editStartTime = track.editListMediaTimes[0];
long editEndTime = editStartTime + Util.scaleLargeTimestamp(track.editListDurations[0], track.timescale, track.movieTimescale);
long lastSampleEndTime = timestampTimeUnits;
if (timestamps[0] <= editStartTime && editStartTime < timestamps[1] && timestamps[timestamps.length - 1] < editEndTime && editEndTime <= lastSampleEndTime) {
long paddingTimeUnits = lastSampleEndTime - editEndTime;
long encoderDelay = Util.scaleLargeTimestamp(editStartTime - timestamps[0], track.format.sampleRate, track.timescale);
long encoderPadding = Util.scaleLargeTimestamp(paddingTimeUnits, track.format.sampleRate, track.timescale);
if ((encoderDelay != 0 || encoderPadding != 0) && encoderDelay <= Integer.MAX_VALUE && encoderPadding <= Integer.MAX_VALUE) {
gaplessInfoHolder.encoderDelay = (int) encoderDelay;
gaplessInfoHolder.encoderPadding = (int) encoderPadding;
Util.scaleLargeTimestampsInPlace(timestamps, C.MICROS_PER_SECOND, track.timescale);
return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags);
}
}
}
if (track.editListDurations.length == 1 && track.editListDurations[0] == 0) {
// samples in the edit.
for (int i = 0; i < timestamps.length; i++) {
timestamps[i] = Util.scaleLargeTimestamp(timestamps[i] - track.editListMediaTimes[0], C.MICROS_PER_SECOND, track.timescale);
}
return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags);
}
// Omit any sample at the end point of an edit for audio tracks.
boolean omitClippedSample = track.type == C.TRACK_TYPE_AUDIO;
// Count the number of samples after applying edits.
int editedSampleCount = 0;
int nextSampleIndex = 0;
boolean copyMetadata = false;
for (int i = 0; i < track.editListDurations.length; i++) {
long mediaTime = track.editListMediaTimes[i];
if (mediaTime != -1) {
long duration = Util.scaleLargeTimestamp(track.editListDurations[i], track.timescale, track.movieTimescale);
int startIndex = Util.binarySearchCeil(timestamps, mediaTime, true, true);
int endIndex = Util.binarySearchCeil(timestamps, mediaTime + duration, omitClippedSample, false);
editedSampleCount += endIndex - startIndex;
copyMetadata |= nextSampleIndex != startIndex;
nextSampleIndex = endIndex;
}
}
copyMetadata |= editedSampleCount != sampleCount;
// Calculate edited sample timestamps and update the corresponding metadata arrays.
long[] editedOffsets = copyMetadata ? new long[editedSampleCount] : offsets;
int[] editedSizes = copyMetadata ? new int[editedSampleCount] : sizes;
int editedMaximumSize = copyMetadata ? 0 : maximumSize;
int[] editedFlags = copyMetadata ? new int[editedSampleCount] : flags;
long[] editedTimestamps = new long[editedSampleCount];
long pts = 0;
int sampleIndex = 0;
for (int i = 0; i < track.editListDurations.length; i++) {
long mediaTime = track.editListMediaTimes[i];
long duration = track.editListDurations[i];
if (mediaTime != -1) {
long endMediaTime = mediaTime + Util.scaleLargeTimestamp(duration, track.timescale, track.movieTimescale);
int startIndex = Util.binarySearchCeil(timestamps, mediaTime, true, true);
int endIndex = Util.binarySearchCeil(timestamps, endMediaTime, omitClippedSample, false);
if (copyMetadata) {
int count = endIndex - startIndex;
System.arraycopy(offsets, startIndex, editedOffsets, sampleIndex, count);
System.arraycopy(sizes, startIndex, editedSizes, sampleIndex, count);
System.arraycopy(flags, startIndex, editedFlags, sampleIndex, count);
}
for (int j = startIndex; j < endIndex; j++) {
long ptsUs = Util.scaleLargeTimestamp(pts, C.MICROS_PER_SECOND, track.movieTimescale);
long timeInSegmentUs = Util.scaleLargeTimestamp(timestamps[j] - mediaTime, C.MICROS_PER_SECOND, track.timescale);
editedTimestamps[sampleIndex] = ptsUs + timeInSegmentUs;
if (copyMetadata && editedSizes[sampleIndex] > editedMaximumSize) {
editedMaximumSize = sizes[j];
}
sampleIndex++;
}
}
pts += duration;
}
boolean hasSyncSample = false;
for (int i = 0; i < editedFlags.length && !hasSyncSample; i++) {
hasSyncSample |= (editedFlags[i] & C.BUFFER_FLAG_KEY_FRAME) != 0;
}
if (!hasSyncSample) {
throw new ParserException("The edited sample sequence does not contain a sync sample.");
}
return new TrackSampleTable(editedOffsets, editedSizes, editedMaximumSize, editedTimestamps, editedFlags);
}
use of com.google.android.exoplayer2.metadata.Metadata in project ExoPlayer by google.
the class SpliceInfoDecoder method decode.
@Override
public Metadata decode(MetadataInputBuffer inputBuffer) throws MetadataDecoderException {
// Internal timestamps adjustment.
if (timestampAdjuster == null || inputBuffer.subsampleOffsetUs != timestampAdjuster.getTimestampOffsetUs()) {
timestampAdjuster = new TimestampAdjuster(inputBuffer.timeUs);
timestampAdjuster.adjustSampleTimestamp(inputBuffer.timeUs - inputBuffer.subsampleOffsetUs);
}
ByteBuffer buffer = inputBuffer.data;
byte[] data = buffer.array();
int size = buffer.limit();
sectionData.reset(data, size);
sectionHeader.reset(data, size);
// table_id(8), section_syntax_indicator(1), private_indicator(1), reserved(2),
// section_length(12), protocol_version(8), encrypted_packet(1), encryption_algorithm(6).
sectionHeader.skipBits(39);
long ptsAdjustment = sectionHeader.readBits(1);
ptsAdjustment = (ptsAdjustment << 32) | sectionHeader.readBits(32);
// cw_index(8), tier(12).
sectionHeader.skipBits(20);
int spliceCommandLength = sectionHeader.readBits(12);
int spliceCommandType = sectionHeader.readBits(8);
SpliceCommand command = null;
// Go to the start of the command by skipping all fields up to command_type.
sectionData.skipBytes(14);
switch(spliceCommandType) {
case TYPE_SPLICE_NULL:
command = new SpliceNullCommand();
break;
case TYPE_SPLICE_SCHEDULE:
command = SpliceScheduleCommand.parseFromSection(sectionData);
break;
case TYPE_SPLICE_INSERT:
command = SpliceInsertCommand.parseFromSection(sectionData, ptsAdjustment, timestampAdjuster);
break;
case TYPE_TIME_SIGNAL:
command = TimeSignalCommand.parseFromSection(sectionData, ptsAdjustment, timestampAdjuster);
break;
case TYPE_PRIVATE_COMMAND:
command = PrivateCommand.parseFromSection(sectionData, spliceCommandLength, ptsAdjustment);
break;
default:
// Do nothing.
break;
}
return command == null ? new Metadata() : new Metadata(command);
}
use of com.google.android.exoplayer2.metadata.Metadata in project ExoPlayer by google.
the class Mp4Extractor method processMoovAtom.
/**
* Updates the stored track metadata to reflect the contents of the specified moov atom.
*/
private void processMoovAtom(ContainerAtom moov) throws ParserException {
long durationUs = C.TIME_UNSET;
List<Mp4Track> tracks = new ArrayList<>();
long earliestSampleOffset = Long.MAX_VALUE;
Metadata metadata = null;
GaplessInfoHolder gaplessInfoHolder = new GaplessInfoHolder();
Atom.LeafAtom udta = moov.getLeafAtomOfType(Atom.TYPE_udta);
if (udta != null) {
metadata = AtomParsers.parseUdta(udta, isQuickTime);
if (metadata != null) {
gaplessInfoHolder.setFromMetadata(metadata);
}
}
for (int i = 0; i < moov.containerChildren.size(); i++) {
Atom.ContainerAtom atom = moov.containerChildren.get(i);
if (atom.type != Atom.TYPE_trak) {
continue;
}
Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd), C.TIME_UNSET, null, isQuickTime);
if (track == null) {
continue;
}
Atom.ContainerAtom stblAtom = atom.getContainerAtomOfType(Atom.TYPE_mdia).getContainerAtomOfType(Atom.TYPE_minf).getContainerAtomOfType(Atom.TYPE_stbl);
TrackSampleTable trackSampleTable = AtomParsers.parseStbl(track, stblAtom, gaplessInfoHolder);
if (trackSampleTable.sampleCount == 0) {
continue;
}
Mp4Track mp4Track = new Mp4Track(track, trackSampleTable, extractorOutput.track(i, track.type));
// Each sample has up to three bytes of overhead for the start code that replaces its length.
// Allow ten source samples per output sample, like the platform extractor.
int maxInputSize = trackSampleTable.maximumSize + 3 * 10;
Format format = track.format.copyWithMaxInputSize(maxInputSize);
if (track.type == C.TRACK_TYPE_AUDIO) {
if (gaplessInfoHolder.hasGaplessInfo()) {
format = format.copyWithGaplessInfo(gaplessInfoHolder.encoderDelay, gaplessInfoHolder.encoderPadding);
}
if (metadata != null) {
format = format.copyWithMetadata(metadata);
}
}
mp4Track.trackOutput.format(format);
durationUs = Math.max(durationUs, track.durationUs);
tracks.add(mp4Track);
long firstSampleOffset = trackSampleTable.offsets[0];
if (firstSampleOffset < earliestSampleOffset) {
earliestSampleOffset = firstSampleOffset;
}
}
this.durationUs = durationUs;
this.tracks = tracks.toArray(new Mp4Track[tracks.size()]);
extractorOutput.endTracks();
extractorOutput.seekMap(this);
}
use of com.google.android.exoplayer2.metadata.Metadata in project ExoPlayer by google.
the class EventMessageDecoderTest method testDecodeEventMessage.
public void testDecodeEventMessage() {
byte[] rawEmsgBody = new byte[] { // scheme_id_uri = "urn:test"
117, // scheme_id_uri = "urn:test"
114, // scheme_id_uri = "urn:test"
110, // scheme_id_uri = "urn:test"
58, // scheme_id_uri = "urn:test"
116, // scheme_id_uri = "urn:test"
101, // scheme_id_uri = "urn:test"
115, // scheme_id_uri = "urn:test"
116, // scheme_id_uri = "urn:test"
0, // value = "123"
49, // value = "123"
50, // value = "123"
51, // value = "123"
0, // timescale = 48000
0, // timescale = 48000
0, // timescale = 48000
-69, // timescale = 48000
-128, // presentation_time_delta (ignored) = 0
0, // presentation_time_delta (ignored) = 0
0, // presentation_time_delta (ignored) = 0
0, // presentation_time_delta (ignored) = 0
0, // event_duration = 144000
0, // event_duration = 144000
2, // event_duration = 144000
50, // event_duration = 144000
-128, // id = 1000403
0, // id = 1000403
15, // id = 1000403
67, // id = 1000403
-45, 0, 1, 2, 3, // message_data = {0, 1, 2, 3, 4}
4 };
EventMessageDecoder decoder = new EventMessageDecoder();
MetadataInputBuffer buffer = new MetadataInputBuffer();
buffer.data = ByteBuffer.allocate(rawEmsgBody.length).put(rawEmsgBody);
Metadata metadata = decoder.decode(buffer);
assertEquals(1, metadata.length());
EventMessage eventMessage = (EventMessage) metadata.get(0);
assertEquals("urn:test", eventMessage.schemeIdUri);
assertEquals("123", eventMessage.value);
assertEquals(3000, eventMessage.durationMs);
assertEquals(1000403, eventMessage.id);
MoreAsserts.assertEquals(new byte[] { 0, 1, 2, 3, 4 }, eventMessage.messageData);
}
Aggregations