use of com.google.android.exoplayer2.extractor.TrackOutput.CryptoData in project ExoPlayer by google.
the class SampleQueue method commitSample.
private synchronized void commitSample(long timeUs, @C.BufferFlags int sampleFlags, long offset, int size, @Nullable CryptoData cryptoData) {
if (length > 0) {
// Ensure sample data doesn't overlap.
int previousSampleRelativeIndex = getRelativeIndex(length - 1);
checkArgument(offsets[previousSampleRelativeIndex] + sizes[previousSampleRelativeIndex] <= offset);
}
isLastSampleQueued = (sampleFlags & C.BUFFER_FLAG_LAST_SAMPLE) != 0;
largestQueuedTimestampUs = max(largestQueuedTimestampUs, timeUs);
int relativeEndIndex = getRelativeIndex(length);
timesUs[relativeEndIndex] = timeUs;
offsets[relativeEndIndex] = offset;
sizes[relativeEndIndex] = size;
flags[relativeEndIndex] = sampleFlags;
cryptoDatas[relativeEndIndex] = cryptoData;
sourceIds[relativeEndIndex] = upstreamSourceId;
if (sharedSampleMetadata.isEmpty() || !sharedSampleMetadata.getEndValue().format.equals(upstreamFormat)) {
DrmSessionReference drmSessionReference = drmSessionManager != null ? drmSessionManager.preacquireSession(drmEventDispatcher, upstreamFormat) : DrmSessionReference.EMPTY;
sharedSampleMetadata.appendSpan(getWriteIndex(), new SharedSampleMetadata(checkNotNull(upstreamFormat), drmSessionReference));
}
length++;
if (length == capacity) {
// Increase the capacity.
int newCapacity = capacity + SAMPLE_CAPACITY_INCREMENT;
int[] newSourceIds = new int[newCapacity];
long[] newOffsets = new long[newCapacity];
long[] newTimesUs = new long[newCapacity];
int[] newFlags = new int[newCapacity];
int[] newSizes = new int[newCapacity];
CryptoData[] newCryptoDatas = new CryptoData[newCapacity];
int beforeWrap = capacity - relativeFirstIndex;
System.arraycopy(offsets, relativeFirstIndex, newOffsets, 0, beforeWrap);
System.arraycopy(timesUs, relativeFirstIndex, newTimesUs, 0, beforeWrap);
System.arraycopy(flags, relativeFirstIndex, newFlags, 0, beforeWrap);
System.arraycopy(sizes, relativeFirstIndex, newSizes, 0, beforeWrap);
System.arraycopy(cryptoDatas, relativeFirstIndex, newCryptoDatas, 0, beforeWrap);
System.arraycopy(sourceIds, relativeFirstIndex, newSourceIds, 0, beforeWrap);
int afterWrap = relativeFirstIndex;
System.arraycopy(offsets, 0, newOffsets, beforeWrap, afterWrap);
System.arraycopy(timesUs, 0, newTimesUs, beforeWrap, afterWrap);
System.arraycopy(flags, 0, newFlags, beforeWrap, afterWrap);
System.arraycopy(sizes, 0, newSizes, beforeWrap, afterWrap);
System.arraycopy(cryptoDatas, 0, newCryptoDatas, beforeWrap, afterWrap);
System.arraycopy(sourceIds, 0, newSourceIds, beforeWrap, afterWrap);
offsets = newOffsets;
timesUs = newTimesUs;
flags = newFlags;
sizes = newSizes;
cryptoDatas = newCryptoDatas;
sourceIds = newSourceIds;
relativeFirstIndex = 0;
capacity = newCapacity;
}
}
use of com.google.android.exoplayer2.extractor.TrackOutput.CryptoData in project ExoPlayer by google.
the class OutputConsumerAdapterV30 method toExoPlayerCryptoData.
@Nullable
private CryptoData toExoPlayerCryptoData(int trackIndex, @Nullable CryptoInfo cryptoInfo) {
if (cryptoInfo == null) {
return null;
}
@Nullable CryptoInfo lastReceivedCryptoInfo = lastReceivedCryptoInfos.get(trackIndex);
CryptoData cryptoDataToOutput;
// MediaParser keeps identity and value equality aligned for efficient comparison.
if (lastReceivedCryptoInfo == cryptoInfo) {
// They match, we can reuse the last one we created.
cryptoDataToOutput = Assertions.checkNotNull(lastOutputCryptoDatas.get(trackIndex));
} else {
// They don't match, we create a new CryptoData.
// TODO: Access pattern encryption info directly once the Android SDK makes it visible.
// See [Internal ref: b/154248283].
int encryptedBlocks;
int clearBlocks;
try {
Matcher matcher = REGEX_CRYPTO_INFO_PATTERN.matcher(cryptoInfo.toString());
matcher.find();
encryptedBlocks = Integer.parseInt(Util.castNonNull(matcher.group(1)));
clearBlocks = Integer.parseInt(Util.castNonNull(matcher.group(2)));
} catch (RuntimeException e) {
// Should never happen.
Log.e(TAG, "Unexpected error while parsing CryptoInfo: " + cryptoInfo, e);
// Assume no-pattern encryption.
encryptedBlocks = 0;
clearBlocks = 0;
}
cryptoDataToOutput = new CryptoData(cryptoInfo.mode, cryptoInfo.key, encryptedBlocks, clearBlocks);
lastReceivedCryptoInfos.set(trackIndex, cryptoInfo);
lastOutputCryptoDatas.set(trackIndex, cryptoDataToOutput);
}
return cryptoDataToOutput;
}
use of com.google.android.exoplayer2.extractor.TrackOutput.CryptoData in project ExoPlayer by google.
the class AdtsReaderTest method skipToNextSampleResetsState.
@Test
public void skipToNextSampleResetsState() throws Exception {
data = new ParsableByteArray(Bytes.concat(ADTS_HEADER, ADTS_CONTENT, ADTS_HEADER, ADTS_CONTENT, // The Reader should be able to read the next sample.
Arrays.copyOfRange(ADTS_HEADER, 1, ADTS_HEADER.length), ADTS_CONTENT, ADTS_HEADER, ADTS_CONTENT));
feed();
assertSampleCounts(0, 3);
for (int i = 0; i < 3; i++) {
adtsOutput.assertSample(/* index= */
i, /* data= */
ADTS_CONTENT, /* timeUs= */
ADTS_SAMPLE_DURATION * i, /* flags= */
C.BUFFER_FLAG_KEY_FRAME, /* cryptoData= */
null);
}
}
use of com.google.android.exoplayer2.extractor.TrackOutput.CryptoData 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.TrackOutput.CryptoData in project ExoPlayer by google.
the class FakeSampleStream method writeData.
/**
* Writes all not yet written {@link FakeSampleStreamItem sample stream items} to the sample queue
* starting at the given position.
*
* @param startPositionUs The start position, in microseconds.
*/
public void writeData(long startPositionUs) {
if (sampleStreamItemsWritePosition == 0) {
sampleQueue.setStartTimeUs(startPositionUs);
}
boolean writtenFirstFormat = false;
@Nullable Format pendingFirstFormat = null;
for (int i = 0; i < sampleStreamItems.size(); i++) {
FakeSampleStreamItem fakeSampleStreamItem = sampleStreamItems.get(i);
@Nullable FakeSampleStream.SampleInfo sampleInfo = fakeSampleStreamItem.sampleInfo;
if (sampleInfo == null) {
if (writtenFirstFormat) {
sampleQueue.format(checkNotNull(fakeSampleStreamItem.format));
} else {
pendingFirstFormat = checkNotNull(fakeSampleStreamItem.format);
}
continue;
}
if ((sampleInfo.flags & C.BUFFER_FLAG_END_OF_STREAM) != 0) {
loadingFinished = true;
break;
}
if (sampleInfo.timeUs >= startPositionUs && i >= sampleStreamItemsWritePosition) {
if (!writtenFirstFormat) {
sampleQueue.format(checkNotNull(pendingFirstFormat));
writtenFirstFormat = true;
}
sampleQueue.sampleData(new ParsableByteArray(sampleInfo.data), sampleInfo.data.length);
sampleQueue.sampleMetadata(sampleInfo.timeUs, sampleInfo.flags, sampleInfo.data.length, /* offset= */
0, /* cryptoData= */
null);
}
}
sampleStreamItemsWritePosition = sampleStreamItems.size();
}
Aggregations