use of androidx.media3.common.Metadata in project media by androidx.
the class FlacMetadataReader method readId3Metadata.
/**
* Reads ID3 Data.
*
* <p>If no exception is thrown, the peek position of {@code input} is aligned with the read
* position.
*
* @param input Input stream to read the ID3 data from.
* @param parseData Whether to parse the ID3 frames.
* @return The parsed ID3 data, or {@code null} if there is no such data or if {@code parseData}
* is {@code false}.
* @throws IOException If reading from the input fails. In this case, the read position is left
* unchanged and there is no guarantee on the peek position.
*/
@Nullable
public static Metadata readId3Metadata(ExtractorInput input, boolean parseData) throws IOException {
input.resetPeekPosition();
long startingPeekPosition = input.getPeekPosition();
@Nullable Metadata id3Metadata = peekId3Metadata(input, parseData);
int peekedId3Bytes = (int) (input.getPeekPosition() - startingPeekPosition);
input.skipFully(peekedId3Bytes);
return id3Metadata;
}
use of androidx.media3.common.Metadata in project media by androidx.
the class AppInfoTableDecoder method parseAit.
@Nullable
private static Metadata parseAit(ParsableBitArray sectionData) {
// tableId, section_syntax_indication, reserved_future_use, reserved
sectionData.skipBits(12);
int sectionLength = sectionData.readBits(12);
int endOfSection = sectionData.getBytePosition() + sectionLength - 4;
// test_application_flag, application_type, reserved, version_number, current_next_indicator,
// section_number, last_section_number, reserved_future_use
sectionData.skipBits(44);
int commonDescriptorsLength = sectionData.readBits(12);
// Since we currently only keep URL and control code, which are unique per application,
// there is no useful information in common descriptor.
sectionData.skipBytes(commonDescriptorsLength);
// reserved_future_use, application_loop_length
sectionData.skipBits(16);
ArrayList<AppInfoTable> appInfoTables = new ArrayList<>();
while (sectionData.getBytePosition() < endOfSection) {
@Nullable String urlBase = null;
@Nullable String urlExtension = null;
// application_identifier
sectionData.skipBits(48);
int controlCode = sectionData.readBits(8);
// reserved_future_use
sectionData.skipBits(4);
int applicationDescriptorsLoopLength = sectionData.readBits(12);
int positionOfNextApplication = sectionData.getBytePosition() + applicationDescriptorsLoopLength;
while (sectionData.getBytePosition() < positionOfNextApplication) {
int descriptorTag = sectionData.readBits(8);
int descriptorLength = sectionData.readBits(8);
int positionOfNextDescriptor = sectionData.getBytePosition() + descriptorLength;
if (descriptorTag == DESCRIPTOR_TRANSPORT_PROTOCOL) {
// See section 5.3.6.
int protocolId = sectionData.readBits(16);
// label
sectionData.skipBits(8);
if (protocolId == TRANSPORT_PROTOCOL_HTTP) {
// See section 5.3.6.2.
while (sectionData.getBytePosition() < positionOfNextDescriptor) {
int urlBaseLength = sectionData.readBits(8);
urlBase = sectionData.readBytesAsString(urlBaseLength, Charsets.US_ASCII);
int extensionCount = sectionData.readBits(8);
for (int urlExtensionIndex = 0; urlExtensionIndex < extensionCount; urlExtensionIndex++) {
int urlExtensionLength = sectionData.readBits(8);
sectionData.skipBytes(urlExtensionLength);
}
}
}
} else if (descriptorTag == DESCRIPTOR_SIMPLE_APPLICATION_LOCATION) {
// See section 5.3.7.
urlExtension = sectionData.readBytesAsString(descriptorLength, Charsets.US_ASCII);
}
sectionData.setPosition(positionOfNextDescriptor * 8);
}
sectionData.setPosition(positionOfNextApplication * 8);
if (urlBase != null && urlExtension != null) {
appInfoTables.add(new AppInfoTable(controlCode, urlBase + urlExtension));
}
}
return appInfoTables.isEmpty() ? null : new Metadata(appInfoTables);
}
use of androidx.media3.common.Metadata in project media by androidx.
the class FragmentedMp4Extractor method readSample.
/**
* Attempts to read the next sample in the current mdat atom. The read sample may be output or
* skipped.
*
* <p>If there are no more samples in the current mdat atom then the parser state is transitioned
* to {@link #STATE_READING_ATOM_HEADER} and {@code false} is returned.
*
* <p>It is possible for a sample to be partially read in the case that an exception is thrown. In
* this case the method can be called again to read the remainder of the sample.
*
* @param input The {@link ExtractorInput} from which to read data.
* @return Whether a sample was read. The read sample may have been output or skipped. False
* indicates that there are no samples left to read in the current mdat.
* @throws IOException If an error occurs reading from the input.
*/
private boolean readSample(ExtractorInput input) throws IOException {
@Nullable TrackBundle trackBundle = currentTrackBundle;
if (trackBundle == null) {
trackBundle = getNextTrackBundle(trackBundles);
if (trackBundle == null) {
// We've run out of samples in the current mdat. Discard any trailing data and prepare to
// read the header of the next atom.
int bytesToSkip = (int) (endOfMdatPosition - input.getPosition());
if (bytesToSkip < 0) {
throw ParserException.createForMalformedContainer("Offset to end of mdat was negative.", /* cause= */
null);
}
input.skipFully(bytesToSkip);
enterReadingAtomHeaderState();
return false;
}
long nextDataPosition = trackBundle.getCurrentSampleOffset();
// We skip bytes preceding the next sample to read.
int bytesToSkip = (int) (nextDataPosition - input.getPosition());
if (bytesToSkip < 0) {
// Assume the sample data must be contiguous in the mdat with no preceding data.
Log.w(TAG, "Ignoring negative offset to sample data.");
bytesToSkip = 0;
}
input.skipFully(bytesToSkip);
currentTrackBundle = trackBundle;
}
if (parserState == STATE_READING_SAMPLE_START) {
sampleSize = trackBundle.getCurrentSampleSize();
if (trackBundle.currentSampleIndex < trackBundle.firstSampleToOutputIndex) {
input.skipFully(sampleSize);
trackBundle.skipSampleEncryptionData();
if (!trackBundle.next()) {
currentTrackBundle = null;
}
parserState = STATE_READING_SAMPLE_START;
return true;
}
if (trackBundle.moovSampleTable.track.sampleTransformation == Track.TRANSFORMATION_CEA608_CDAT) {
sampleSize -= Atom.HEADER_SIZE;
input.skipFully(Atom.HEADER_SIZE);
}
if (MimeTypes.AUDIO_AC4.equals(trackBundle.moovSampleTable.track.format.sampleMimeType)) {
// AC4 samples need to be prefixed with a clear sample header.
sampleBytesWritten = trackBundle.outputSampleEncryptionData(sampleSize, Ac4Util.SAMPLE_HEADER_SIZE);
Ac4Util.getAc4SampleHeader(sampleSize, scratch);
trackBundle.output.sampleData(scratch, Ac4Util.SAMPLE_HEADER_SIZE);
sampleBytesWritten += Ac4Util.SAMPLE_HEADER_SIZE;
} else {
sampleBytesWritten = trackBundle.outputSampleEncryptionData(sampleSize, /* clearHeaderSize= */
0);
}
sampleSize += sampleBytesWritten;
parserState = STATE_READING_SAMPLE_CONTINUE;
sampleCurrentNalBytesRemaining = 0;
}
Track track = trackBundle.moovSampleTable.track;
TrackOutput output = trackBundle.output;
long sampleTimeUs = trackBundle.getCurrentSamplePresentationTimeUs();
if (timestampAdjuster != null) {
sampleTimeUs = timestampAdjuster.adjustSampleTimestamp(sampleTimeUs);
}
if (track.nalUnitLengthFieldLength != 0) {
// Zero the top three bytes of the array that we'll use to decode nal unit lengths, in case
// they're only 1 or 2 bytes long.
byte[] nalPrefixData = nalPrefix.getData();
nalPrefixData[0] = 0;
nalPrefixData[1] = 0;
nalPrefixData[2] = 0;
int nalUnitPrefixLength = track.nalUnitLengthFieldLength + 1;
int nalUnitLengthFieldLengthDiff = 4 - track.nalUnitLengthFieldLength;
// start codes as we encounter them.
while (sampleBytesWritten < sampleSize) {
if (sampleCurrentNalBytesRemaining == 0) {
// Read the NAL length so that we know where we find the next one, and its type.
input.readFully(nalPrefixData, nalUnitLengthFieldLengthDiff, nalUnitPrefixLength);
nalPrefix.setPosition(0);
int nalLengthInt = nalPrefix.readInt();
if (nalLengthInt < 1) {
throw ParserException.createForMalformedContainer("Invalid NAL length", /* cause= */
null);
}
sampleCurrentNalBytesRemaining = nalLengthInt - 1;
// Write a start code for the current NAL unit.
nalStartCode.setPosition(0);
output.sampleData(nalStartCode, 4);
// Write the NAL unit type byte.
output.sampleData(nalPrefix, 1);
processSeiNalUnitPayload = ceaTrackOutputs.length > 0 && NalUnitUtil.isNalUnitSei(track.format.sampleMimeType, nalPrefixData[4]);
sampleBytesWritten += 5;
sampleSize += nalUnitLengthFieldLengthDiff;
} else {
int writtenBytes;
if (processSeiNalUnitPayload) {
// Read and write the payload of the SEI NAL unit.
nalBuffer.reset(sampleCurrentNalBytesRemaining);
input.readFully(nalBuffer.getData(), 0, sampleCurrentNalBytesRemaining);
output.sampleData(nalBuffer, sampleCurrentNalBytesRemaining);
writtenBytes = sampleCurrentNalBytesRemaining;
// Unescape and process the SEI NAL unit.
int unescapedLength = NalUnitUtil.unescapeStream(nalBuffer.getData(), nalBuffer.limit());
// If the format is H.265/HEVC the NAL unit header has two bytes so skip one more byte.
nalBuffer.setPosition(MimeTypes.VIDEO_H265.equals(track.format.sampleMimeType) ? 1 : 0);
nalBuffer.setLimit(unescapedLength);
CeaUtil.consume(sampleTimeUs, nalBuffer, ceaTrackOutputs);
} else {
// Write the payload of the NAL unit.
writtenBytes = output.sampleData(input, sampleCurrentNalBytesRemaining, false);
}
sampleBytesWritten += writtenBytes;
sampleCurrentNalBytesRemaining -= writtenBytes;
}
}
} else {
while (sampleBytesWritten < sampleSize) {
int writtenBytes = output.sampleData(input, sampleSize - sampleBytesWritten, false);
sampleBytesWritten += writtenBytes;
}
}
@C.BufferFlags int sampleFlags = trackBundle.getCurrentSampleFlags();
// Encryption data.
@Nullable TrackOutput.CryptoData cryptoData = null;
@Nullable TrackEncryptionBox encryptionBox = trackBundle.getEncryptionBoxIfEncrypted();
if (encryptionBox != null) {
cryptoData = encryptionBox.cryptoData;
}
output.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, cryptoData);
// After we have the sampleTimeUs, we can commit all the pending metadata samples
outputPendingMetadataSamples(sampleTimeUs);
if (!trackBundle.next()) {
currentTrackBundle = null;
}
parserState = STATE_READING_SAMPLE_START;
return true;
}
use of androidx.media3.common.Metadata in project media by androidx.
the class FragmentedMp4Extractor method onEmsgLeafAtomRead.
/**
* Handles an emsg atom (defined in 23009-1).
*/
private void onEmsgLeafAtomRead(ParsableByteArray atom) {
if (emsgTrackOutputs.length == 0) {
return;
}
atom.setPosition(Atom.HEADER_SIZE);
int fullAtom = atom.readInt();
int version = Atom.parseFullAtomVersion(fullAtom);
String schemeIdUri;
String value;
long timescale;
// Only set if version == 0
long presentationTimeDeltaUs = C.TIME_UNSET;
long sampleTimeUs = C.TIME_UNSET;
long durationMs;
long id;
switch(version) {
case 0:
schemeIdUri = checkNotNull(atom.readNullTerminatedString());
value = 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 = checkNotNull(atom.readNullTerminatedString());
value = checkNotNull(atom.readNullTerminatedString());
break;
default:
Log.w(TAG, "Skipping unsupported emsg version: " + version);
return;
}
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) {
encodedEventMessage.setPosition(0);
emsgTrackOutput.sampleData(encodedEventMessage, sampleSize);
}
// Output the sample metadata.
if (sampleTimeUs == C.TIME_UNSET) {
// We're processing a v0 emsg atom, which contains a presentation time delta, and cannot yet
// calculate its absolute sample timestamp. Defer outputting the metadata until we can.
pendingMetadataSampleInfos.addLast(new MetadataSampleInfo(presentationTimeDeltaUs, /* sampleTimeIsRelative= */
true, sampleSize));
pendingMetadataSampleBytes += sampleSize;
} else if (!pendingMetadataSampleInfos.isEmpty()) {
// We also need to defer outputting metadata if pendingMetadataSampleInfos is non-empty, else
// we will output metadata for samples in the wrong order. See:
// https://github.com/google/ExoPlayer/issues/9996.
pendingMetadataSampleInfos.addLast(new MetadataSampleInfo(sampleTimeUs, /* sampleTimeIsRelative= */
false, sampleSize));
pendingMetadataSampleBytes += sampleSize;
} else {
// We can output the sample metadata immediately.
if (timestampAdjuster != null) {
sampleTimeUs = timestampAdjuster.adjustSampleTimestamp(sampleTimeUs);
}
for (TrackOutput emsgTrackOutput : emsgTrackOutputs) {
emsgTrackOutput.sampleMetadata(sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, /* offset= */
0, null);
}
}
}
use of androidx.media3.common.Metadata in project media by androidx.
the class FragmentedMp4Extractor method outputPendingMetadataSamples.
/**
* Called immediately after outputting a non-metadata sample, to output any pending metadata
* samples.
*
* @param sampleTimeUs The timestamp of the non-metadata sample that was just output.
*/
private void outputPendingMetadataSamples(long sampleTimeUs) {
while (!pendingMetadataSampleInfos.isEmpty()) {
MetadataSampleInfo metadataSampleInfo = pendingMetadataSampleInfos.removeFirst();
pendingMetadataSampleBytes -= metadataSampleInfo.size;
long metadataSampleTimeUs = metadataSampleInfo.sampleTimeUs;
if (metadataSampleInfo.sampleTimeIsRelative) {
// The metadata sample timestamp is relative to the timestamp of the non-metadata sample
// that was just output. Make it absolute.
metadataSampleTimeUs += sampleTimeUs;
}
if (timestampAdjuster != null) {
metadataSampleTimeUs = timestampAdjuster.adjustSampleTimestamp(metadataSampleTimeUs);
}
for (TrackOutput emsgTrackOutput : emsgTrackOutputs) {
emsgTrackOutput.sampleMetadata(metadataSampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, metadataSampleInfo.size, pendingMetadataSampleBytes, null);
}
}
}
Aggregations