use of com.google.android.exoplayer2.util.TimestampAdjuster in project ExoPlayer by google.
the class FragmentedMp4Extractor method readSample.
/**
* Attempts to extract the next sample in the current mdat atom.
* <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 extracted in part in the case that an exception is thrown. In
* this case the method can be called again to extract the remainder of the sample.
*
* @param input The {@link ExtractorInput} from which to read data.
* @return Whether a sample was extracted.
* @throws IOException If an error occurs reading from the input.
* @throws InterruptedException If the thread is interrupted.
*/
private boolean readSample(ExtractorInput input) throws IOException, InterruptedException {
if (parserState == STATE_READING_SAMPLE_START) {
if (currentTrackBundle == null) {
TrackBundle currentTrackBundle = getNextFragmentRun(trackBundles);
if (currentTrackBundle == 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 new ParserException("Offset to end of mdat was negative.");
}
input.skipFully(bytesToSkip);
enterReadingAtomHeaderState();
return false;
}
long nextDataPosition = currentTrackBundle.fragment.trunDataPosition[currentTrackBundle.currentTrackRunIndex];
// 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);
this.currentTrackBundle = currentTrackBundle;
}
sampleSize = currentTrackBundle.fragment.sampleSizeTable[currentTrackBundle.currentSampleIndex];
if (currentTrackBundle.fragment.definesEncryptionData) {
sampleBytesWritten = appendSampleEncryptionData(currentTrackBundle);
sampleSize += sampleBytesWritten;
} else {
sampleBytesWritten = 0;
}
if (currentTrackBundle.track.sampleTransformation == Track.TRANSFORMATION_CEA608_CDAT) {
sampleSize -= Atom.HEADER_SIZE;
input.skipFully(Atom.HEADER_SIZE);
}
parserState = STATE_READING_SAMPLE_CONTINUE;
sampleCurrentNalBytesRemaining = 0;
}
TrackFragment fragment = currentTrackBundle.fragment;
Track track = currentTrackBundle.track;
TrackOutput output = currentTrackBundle.output;
int sampleIndex = currentTrackBundle.currentSampleIndex;
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.data;
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);
sampleCurrentNalBytesRemaining = nalPrefix.readUnsignedIntToInt() - 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 = cea608TrackOutputs != null && 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.data, 0, sampleCurrentNalBytesRemaining);
output.sampleData(nalBuffer, sampleCurrentNalBytesRemaining);
writtenBytes = sampleCurrentNalBytesRemaining;
// Unescape and process the SEI NAL unit.
int unescapedLength = NalUnitUtil.unescapeStream(nalBuffer.data, 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(fragment.getSamplePresentationTime(sampleIndex) * 1000L, nalBuffer, cea608TrackOutputs);
} 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;
}
}
long sampleTimeUs = fragment.getSamplePresentationTime(sampleIndex) * 1000L;
@C.BufferFlags int sampleFlags = (fragment.definesEncryptionData ? C.BUFFER_FLAG_ENCRYPTED : 0) | (fragment.sampleIsSyncFrameTable[sampleIndex] ? C.BUFFER_FLAG_KEY_FRAME : 0);
int sampleDescriptionIndex = fragment.header.sampleDescriptionIndex;
byte[] encryptionKey = null;
if (fragment.definesEncryptionData) {
encryptionKey = fragment.trackEncryptionBox != null ? fragment.trackEncryptionBox.keyId : track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex].keyId;
}
if (timestampAdjuster != null) {
sampleTimeUs = timestampAdjuster.adjustSampleTimestamp(sampleTimeUs);
}
output.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, encryptionKey);
while (!pendingMetadataSampleInfos.isEmpty()) {
MetadataSampleInfo sampleInfo = pendingMetadataSampleInfos.removeFirst();
pendingMetadataSampleBytes -= sampleInfo.size;
eventMessageTrackOutput.sampleMetadata(sampleTimeUs + sampleInfo.presentationTimeDeltaUs, C.BUFFER_FLAG_KEY_FRAME, sampleInfo.size, pendingMetadataSampleBytes, null);
}
currentTrackBundle.currentSampleIndex++;
currentTrackBundle.currentSampleInTrackRun++;
if (currentTrackBundle.currentSampleInTrackRun == fragment.trunLength[currentTrackBundle.currentTrackRunIndex]) {
currentTrackBundle.currentTrackRunIndex++;
currentTrackBundle.currentSampleInTrackRun = 0;
currentTrackBundle = null;
}
parserState = STATE_READING_SAMPLE_START;
return true;
}
use of com.google.android.exoplayer2.util.TimestampAdjuster 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.util.TimestampAdjuster in project ExoPlayer by google.
the class PsExtractor method read.
@Override
public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException, InterruptedException {
// First peek and check what type of start code is next.
if (!input.peekFully(psPacketBuffer.data, 0, 4, true)) {
return RESULT_END_OF_INPUT;
}
psPacketBuffer.setPosition(0);
int nextStartCode = psPacketBuffer.readInt();
if (nextStartCode == MPEG_PROGRAM_END_CODE) {
return RESULT_END_OF_INPUT;
} else if (nextStartCode == PACK_START_CODE) {
// Now peek the rest of the pack_header.
input.peekFully(psPacketBuffer.data, 0, 10);
// We only care about the pack_stuffing_length in here, skip the first 77 bits.
psPacketBuffer.setPosition(9);
// Last 3 bits is the length.
int packStuffingLength = psPacketBuffer.readUnsignedByte() & 0x07;
// Now skip the stuffing and the pack header.
input.skipFully(packStuffingLength + 14);
return RESULT_CONTINUE;
} else if (nextStartCode == SYSTEM_HEADER_START_CODE) {
// We just skip all this, but we need to get the length first.
input.peekFully(psPacketBuffer.data, 0, 2);
// Length is the next 2 bytes.
psPacketBuffer.setPosition(0);
int systemHeaderLength = psPacketBuffer.readUnsignedShort();
input.skipFully(systemHeaderLength + 6);
return RESULT_CONTINUE;
} else if (((nextStartCode & 0xFFFFFF00) >> 8) != PACKET_START_CODE_PREFIX) {
// Skip bytes until we see a valid start code again.
input.skipFully(1);
return RESULT_CONTINUE;
}
// We're at the start of a regular PES packet now.
// Get the stream ID off the last byte of the start code.
int streamId = nextStartCode & 0xFF;
// Check to see if we have this one in our map yet, and if not, then add it.
PesReader payloadReader = psPayloadReaders.get(streamId);
if (!foundAllTracks) {
if (payloadReader == null) {
ElementaryStreamReader elementaryStreamReader = null;
if (!foundAudioTrack && streamId == PRIVATE_STREAM_1) {
// Private stream, used for AC3 audio.
// NOTE: This may need further parsing to determine if its DTS, but that's likely only
// valid for DVDs.
elementaryStreamReader = new Ac3Reader();
foundAudioTrack = true;
} else if (!foundAudioTrack && (streamId & AUDIO_STREAM_MASK) == AUDIO_STREAM) {
elementaryStreamReader = new MpegAudioReader();
foundAudioTrack = true;
} else if (!foundVideoTrack && (streamId & VIDEO_STREAM_MASK) == VIDEO_STREAM) {
elementaryStreamReader = new H262Reader();
foundVideoTrack = true;
}
if (elementaryStreamReader != null) {
TrackIdGenerator idGenerator = new TrackIdGenerator(streamId, MAX_STREAM_ID_PLUS_ONE);
elementaryStreamReader.createTracks(output, idGenerator);
payloadReader = new PesReader(elementaryStreamReader, timestampAdjuster);
psPayloadReaders.put(streamId, payloadReader);
}
}
if ((foundAudioTrack && foundVideoTrack) || input.getPosition() > MAX_SEARCH_LENGTH) {
foundAllTracks = true;
output.endTracks();
}
}
// The next 2 bytes are the length. Once we have that we can consume the complete packet.
input.peekFully(psPacketBuffer.data, 0, 2);
psPacketBuffer.setPosition(0);
int payloadLength = psPacketBuffer.readUnsignedShort();
int pesLength = payloadLength + 6;
if (payloadReader == null) {
// Just skip this data.
input.skipFully(pesLength);
} else {
psPacketBuffer.reset(pesLength);
// Read the whole packet and the header for consumption.
input.readFully(psPacketBuffer.data, 0, pesLength);
psPacketBuffer.setPosition(6);
payloadReader.consume(psPacketBuffer);
psPacketBuffer.setLimit(psPacketBuffer.capacity());
}
return RESULT_CONTINUE;
}
use of com.google.android.exoplayer2.util.TimestampAdjuster in project ExoPlayer by google.
the class SectionReaderTest method setUp.
@Override
public void setUp() {
packetPayload = new byte[512];
Arrays.fill(packetPayload, (byte) 0xFF);
payloadReader = new CustomSectionPayloadReader();
reader = new SectionReader(payloadReader);
reader.init(new TimestampAdjuster(0), new FakeExtractorOutput(), new TsPayloadReader.TrackIdGenerator(0, 1));
}
use of com.google.android.exoplayer2.util.TimestampAdjuster in project ExoPlayer by google.
the class TsExtractorTest method testCustomInitialSectionReader.
public void testCustomInitialSectionReader() throws Exception {
CustomTsPayloadReaderFactory factory = new CustomTsPayloadReaderFactory(false, true);
TsExtractor tsExtractor = new TsExtractor(TsExtractor.MODE_NORMAL, new TimestampAdjuster(0), factory);
FakeExtractorInput input = new FakeExtractorInput.Builder().setData(TestUtil.getByteArray(getInstrumentation(), "ts/sample_with_sdt.ts")).setSimulateIOErrors(false).setSimulateUnknownLength(false).setSimulatePartialReads(false).build();
tsExtractor.init(new FakeExtractorOutput());
PositionHolder seekPositionHolder = new PositionHolder();
int readResult = Extractor.RESULT_CONTINUE;
while (readResult != Extractor.RESULT_END_OF_INPUT) {
readResult = tsExtractor.read(input, seekPositionHolder);
}
assertEquals(1, factory.sdtReader.consumedSdts);
}
Aggregations