blob: 5934be9653f5b07fb36700089c1037e6c868e2b0 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.extractor.ts;
import static java.lang.Math.min;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
/**
* A reader that can extract the approximate duration from a given MPEG transport stream (TS).
*
* <p>This reader extracts the duration by reading PCR values of the PCR PID packets at the start
* and at the end of the stream, calculating the difference, and converting that into stream
* duration. This reader also handles the case when a single PCR wraparound takes place within the
* stream, which can make PCR values at the beginning of the stream larger than PCR values at the
* end. This class can only be used once to read duration from a given stream, and the usage of the
* class is not thread-safe, so all calls should be made from the same thread.
*/
/* package */ final class TsDurationReader {
private static final String TAG = "TsDurationReader";
private final int timestampSearchBytes;
private final TimestampAdjuster pcrTimestampAdjuster;
private final ParsableByteArray packetBuffer;
private boolean isDurationRead;
private boolean isFirstPcrValueRead;
private boolean isLastPcrValueRead;
private long firstPcrValue;
private long lastPcrValue;
private long durationUs;
/* package */ TsDurationReader(int timestampSearchBytes) {
this.timestampSearchBytes = timestampSearchBytes;
pcrTimestampAdjuster = new TimestampAdjuster(/* firstSampleTimestampUs= */ 0);
firstPcrValue = C.TIME_UNSET;
lastPcrValue = C.TIME_UNSET;
durationUs = C.TIME_UNSET;
packetBuffer = new ParsableByteArray();
}
/** Returns true if a TS duration has been read. */
public boolean isDurationReadFinished() {
return isDurationRead;
}
/**
* Reads a TS duration from the input, using the given PCR PID.
*
* <p>This reader reads the duration by reading PCR values of the PCR PID packets at the start and
* at the end of the stream, calculating the difference, and converting that into stream duration.
*
* @param input The {@link ExtractorInput} from which data should be read.
* @param seekPositionHolder If {@link Extractor#RESULT_SEEK} is returned, this holder is updated
* to hold the position of the required seek.
* @param pcrPid The PID of the packet stream within this TS stream that contains PCR values.
* @return One of the {@code RESULT_} values defined in {@link Extractor}.
* @throws IOException If an error occurred reading from the input.
*/
public @Extractor.ReadResult int readDuration(
ExtractorInput input, PositionHolder seekPositionHolder, int pcrPid) throws IOException {
if (pcrPid <= 0) {
return finishReadDuration(input);
}
if (!isLastPcrValueRead) {
return readLastPcrValue(input, seekPositionHolder, pcrPid);
}
if (lastPcrValue == C.TIME_UNSET) {
return finishReadDuration(input);
}
if (!isFirstPcrValueRead) {
return readFirstPcrValue(input, seekPositionHolder, pcrPid);
}
if (firstPcrValue == C.TIME_UNSET) {
return finishReadDuration(input);
}
long minPcrPositionUs = pcrTimestampAdjuster.adjustTsTimestamp(firstPcrValue);
long maxPcrPositionUs = pcrTimestampAdjuster.adjustTsTimestamp(lastPcrValue);
durationUs = maxPcrPositionUs - minPcrPositionUs;
if (durationUs < 0) {
Log.w(TAG, "Invalid duration: " + durationUs + ". Using TIME_UNSET instead.");
durationUs = C.TIME_UNSET;
}
return finishReadDuration(input);
}
/**
* Returns the duration last read from {@link #readDuration(ExtractorInput, PositionHolder, int)}.
*/
public long getDurationUs() {
return durationUs;
}
/**
* Returns the {@link TimestampAdjuster} that this class uses to adjust timestamps read from the
* input TS stream.
*/
public TimestampAdjuster getPcrTimestampAdjuster() {
return pcrTimestampAdjuster;
}
private int finishReadDuration(ExtractorInput input) {
packetBuffer.reset(Util.EMPTY_BYTE_ARRAY);
isDurationRead = true;
input.resetPeekPosition();
return Extractor.RESULT_CONTINUE;
}
private int readFirstPcrValue(ExtractorInput input, PositionHolder seekPositionHolder, int pcrPid)
throws IOException {
int bytesToSearch = (int) min(timestampSearchBytes, input.getLength());
int searchStartPosition = 0;
if (input.getPosition() != searchStartPosition) {
seekPositionHolder.position = searchStartPosition;
return Extractor.RESULT_SEEK;
}
packetBuffer.reset(bytesToSearch);
input.resetPeekPosition();
input.peekFully(packetBuffer.getData(), /* offset= */ 0, bytesToSearch);
firstPcrValue = readFirstPcrValueFromBuffer(packetBuffer, pcrPid);
isFirstPcrValueRead = true;
return Extractor.RESULT_CONTINUE;
}
private long readFirstPcrValueFromBuffer(ParsableByteArray packetBuffer, int pcrPid) {
int searchStartPosition = packetBuffer.getPosition();
int searchEndPosition = packetBuffer.limit();
for (int searchPosition = searchStartPosition;
searchPosition < searchEndPosition;
searchPosition++) {
if (packetBuffer.getData()[searchPosition] != TsExtractor.TS_SYNC_BYTE) {
continue;
}
long pcrValue = TsUtil.readPcrFromPacket(packetBuffer, searchPosition, pcrPid);
if (pcrValue != C.TIME_UNSET) {
return pcrValue;
}
}
return C.TIME_UNSET;
}
private int readLastPcrValue(ExtractorInput input, PositionHolder seekPositionHolder, int pcrPid)
throws IOException {
long inputLength = input.getLength();
int bytesToSearch = (int) min(timestampSearchBytes, inputLength);
long searchStartPosition = inputLength - bytesToSearch;
if (input.getPosition() != searchStartPosition) {
seekPositionHolder.position = searchStartPosition;
return Extractor.RESULT_SEEK;
}
packetBuffer.reset(bytesToSearch);
input.resetPeekPosition();
input.peekFully(packetBuffer.getData(), /* offset= */ 0, bytesToSearch);
lastPcrValue = readLastPcrValueFromBuffer(packetBuffer, pcrPid);
isLastPcrValueRead = true;
return Extractor.RESULT_CONTINUE;
}
private long readLastPcrValueFromBuffer(ParsableByteArray packetBuffer, int pcrPid) {
int searchStartPosition = packetBuffer.getPosition();
int searchEndPosition = packetBuffer.limit();
// We start searching 'TsExtractor.TS_PACKET_SIZE' bytes from the end to prevent trying to read
// from an incomplete TS packet.
for (int searchPosition = searchEndPosition - TsExtractor.TS_PACKET_SIZE;
searchPosition >= searchStartPosition;
searchPosition--) {
if (!TsUtil.isStartOfTsPacket(
packetBuffer.getData(), searchStartPosition, searchEndPosition, searchPosition)) {
continue;
}
long pcrValue = TsUtil.readPcrFromPacket(packetBuffer, searchPosition, pcrPid);
if (pcrValue != C.TIME_UNSET) {
return pcrValue;
}
}
return C.TIME_UNSET;
}
}