use of com.google.android.exoplayer2.extractor.ExtractorInput in project ExoPlayer by google.
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 com.google.android.exoplayer2.extractor.ExtractorInput in project ExoPlayer by google.
the class Mp3Extractor method synchronize.
private boolean synchronize(ExtractorInput input, boolean sniffing) throws IOException {
int validFrameCount = 0;
int candidateSynchronizedHeaderData = 0;
int peekedId3Bytes = 0;
int searchedBytes = 0;
int searchLimitBytes = sniffing ? MAX_SNIFF_BYTES : MAX_SYNC_BYTES;
input.resetPeekPosition();
if (input.getPosition() == 0) {
// We need to parse enough ID3 metadata to retrieve any gapless/seeking playback information
// even if ID3 metadata parsing is disabled.
boolean parseAllId3Frames = (flags & FLAG_DISABLE_ID3_METADATA) == 0;
Id3Decoder.FramePredicate id3FramePredicate = parseAllId3Frames ? null : REQUIRED_ID3_FRAME_PREDICATE;
metadata = id3Peeker.peekId3Data(input, id3FramePredicate);
if (metadata != null) {
gaplessInfoHolder.setFromMetadata(metadata);
}
peekedId3Bytes = (int) input.getPeekPosition();
if (!sniffing) {
input.skipFully(peekedId3Bytes);
}
}
while (true) {
if (peekEndOfStreamOrHeader(input)) {
if (validFrameCount > 0) {
// We reached the end of the stream but found at least one valid frame.
break;
}
throw new EOFException();
}
scratch.setPosition(0);
int headerData = scratch.readInt();
int frameSize;
if ((candidateSynchronizedHeaderData != 0 && !headersMatch(headerData, candidateSynchronizedHeaderData)) || (frameSize = MpegAudioUtil.getFrameSize(headerData)) == C.LENGTH_UNSET) {
// The header doesn't match the candidate header or is invalid. Try the next byte offset.
if (searchedBytes++ == searchLimitBytes) {
if (!sniffing) {
throw ParserException.createForMalformedContainer("Searched too many bytes.", /* cause= */
null);
}
return false;
}
validFrameCount = 0;
candidateSynchronizedHeaderData = 0;
if (sniffing) {
input.resetPeekPosition();
input.advancePeekPosition(peekedId3Bytes + searchedBytes);
} else {
input.skipFully(1);
}
} else {
// The header matches the candidate header and/or is valid.
validFrameCount++;
if (validFrameCount == 1) {
synchronizedHeader.setForHeaderData(headerData);
candidateSynchronizedHeaderData = headerData;
} else if (validFrameCount == 4) {
break;
}
input.advancePeekPosition(frameSize - 4);
}
}
// Prepare to read the synchronized frame.
if (sniffing) {
input.skipFully(peekedId3Bytes + searchedBytes);
} else {
input.resetPeekPosition();
}
synchronizedHeaderData = candidateSynchronizedHeaderData;
return true;
}
use of com.google.android.exoplayer2.extractor.ExtractorInput in project ExoPlayer by google.
the class Mp3Extractor method computeSeeker.
private Seeker computeSeeker(ExtractorInput input) throws IOException {
// Read past any seek frame and set the seeker based on metadata or a seek frame. Metadata
// takes priority as it can provide greater precision.
Seeker seekFrameSeeker = maybeReadSeekFrame(input);
Seeker metadataSeeker = maybeHandleSeekMetadata(metadata, input.getPosition());
if (disableSeeking) {
return new UnseekableSeeker();
}
@Nullable Seeker resultSeeker = null;
if ((flags & FLAG_ENABLE_INDEX_SEEKING) != 0) {
long durationUs;
long dataEndPosition = C.POSITION_UNSET;
if (metadataSeeker != null) {
durationUs = metadataSeeker.getDurationUs();
dataEndPosition = metadataSeeker.getDataEndPosition();
} else if (seekFrameSeeker != null) {
durationUs = seekFrameSeeker.getDurationUs();
dataEndPosition = seekFrameSeeker.getDataEndPosition();
} else {
durationUs = getId3TlenUs(metadata);
}
resultSeeker = new IndexSeeker(durationUs, /* dataStartPosition= */
input.getPosition(), dataEndPosition);
} else if (metadataSeeker != null) {
resultSeeker = metadataSeeker;
} else if (seekFrameSeeker != null) {
resultSeeker = seekFrameSeeker;
}
if (resultSeeker == null || (!resultSeeker.isSeekable() && (flags & FLAG_ENABLE_CONSTANT_BITRATE_SEEKING) != 0)) {
resultSeeker = getConstantBitrateSeeker(input, (flags & FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS) != 0);
}
return resultSeeker;
}
use of com.google.android.exoplayer2.extractor.ExtractorInput in project ExoPlayer by google.
the class Mp4Extractor method readAtomHeader.
private boolean readAtomHeader(ExtractorInput input) throws IOException {
if (atomHeaderBytesRead == 0) {
// Read the standard length atom header.
if (!input.readFully(atomHeader.getData(), 0, Atom.HEADER_SIZE, true)) {
processEndOfStreamReadingAtomHeader();
return false;
}
atomHeaderBytesRead = Atom.HEADER_SIZE;
atomHeader.setPosition(0);
atomSize = atomHeader.readUnsignedInt();
atomType = atomHeader.readInt();
}
if (atomSize == Atom.DEFINES_LARGE_SIZE) {
// Read the large size.
int headerBytesRemaining = Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE;
input.readFully(atomHeader.getData(), Atom.HEADER_SIZE, headerBytesRemaining);
atomHeaderBytesRead += headerBytesRemaining;
atomSize = atomHeader.readUnsignedLongToLong();
} else if (atomSize == Atom.EXTENDS_TO_END_SIZE) {
// The atom extends to the end of the file. Note that if the atom is within a container we can
// work out its size even if the input length is unknown.
long endPosition = input.getLength();
if (endPosition == C.LENGTH_UNSET) {
@Nullable ContainerAtom containerAtom = containerAtoms.peek();
if (containerAtom != null) {
endPosition = containerAtom.endPosition;
}
}
if (endPosition != C.LENGTH_UNSET) {
atomSize = endPosition - input.getPosition() + atomHeaderBytesRead;
}
}
if (atomSize < atomHeaderBytesRead) {
throw ParserException.createForUnsupportedContainerFeature("Atom size less than header length (unsupported).");
}
if (shouldParseContainerAtom(atomType)) {
long endPosition = input.getPosition() + atomSize - atomHeaderBytesRead;
if (atomSize != atomHeaderBytesRead && atomType == Atom.TYPE_meta) {
maybeSkipRemainingMetaAtomHeaderBytes(input);
}
containerAtoms.push(new ContainerAtom(atomType, endPosition));
if (atomSize == atomHeaderBytesRead) {
processAtomEnded(endPosition);
} else {
// Start reading the first child atom.
enterReadingAtomHeaderState();
}
} else if (shouldParseLeafAtom(atomType)) {
// We don't support parsing of leaf atoms that define extended atom sizes, or that have
// lengths greater than Integer.MAX_VALUE.
Assertions.checkState(atomHeaderBytesRead == Atom.HEADER_SIZE);
Assertions.checkState(atomSize <= Integer.MAX_VALUE);
ParsableByteArray atomData = new ParsableByteArray((int) atomSize);
System.arraycopy(atomHeader.getData(), 0, atomData.getData(), 0, Atom.HEADER_SIZE);
this.atomData = atomData;
parserState = STATE_READING_ATOM_PAYLOAD;
} else {
processUnparsedAtom(input.getPosition() - atomHeaderBytesRead);
atomData = null;
parserState = STATE_READING_ATOM_PAYLOAD;
}
return true;
}
use of com.google.android.exoplayer2.extractor.ExtractorInput in project ExoPlayer by google.
the class SefReader method readSefData.
private void readSefData(ExtractorInput input, List<Metadata.Entry> slowMotionMetadataEntries) throws IOException {
long dataStartOffset = input.getPosition();
int totalDataLength = (int) (input.getLength() - input.getPosition() - tailLength);
ParsableByteArray data = new ParsableByteArray(/* limit= */
totalDataLength);
input.readFully(data.getData(), 0, totalDataLength);
for (int i = 0; i < dataReferences.size(); i++) {
DataReference dataReference = dataReferences.get(i);
int intendedPosition = (int) (dataReference.startOffset - dataStartOffset);
data.setPosition(intendedPosition);
// The data type is derived from the name because the SEF format has inconsistent data type
// values.
// data type (2), data sub info (2).
data.skipBytes(4);
int nameLength = data.readLittleEndianInt();
String name = data.readString(nameLength);
@DataType int dataType = nameToDataType(name);
int remainingDataLength = dataReference.size - (8 + nameLength);
switch(dataType) {
case TYPE_SLOW_MOTION_DATA:
slowMotionMetadataEntries.add(readSlowMotionData(data, remainingDataLength));
break;
case TYPE_SUPER_SLOW_MOTION_DATA:
case TYPE_SUPER_SLOW_MOTION_BGM:
case TYPE_SUPER_SLOW_MOTION_EDIT_DATA:
case TYPE_SUPER_SLOW_DEFLICKERING_ON:
break;
default:
throw new IllegalStateException();
}
}
}
Aggregations