-
Notifications
You must be signed in to change notification settings - Fork 6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add supports for reading duration for a PS stream.
Add supports for reading duration for a PS stream by reading SCR values from the header of packs at the start and at the end of the stream, calculating the difference, and converting that into stream duration. Github: #4476 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=203954752
- Loading branch information
1 parent
68822c0
commit 39b8122
Showing
5 changed files
with
347 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
246 changes: 246 additions & 0 deletions
246
library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PsDurationReader.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,246 @@ | ||
/* | ||
* 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 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.ParsableByteArray; | ||
import com.google.android.exoplayer2.util.TimestampAdjuster; | ||
import java.io.IOException; | ||
|
||
/** | ||
* A reader that can extract the approximate duration from a given MPEG program stream (PS). | ||
* | ||
* <p>This reader extracts the duration by reading system clock reference (SCR) values from the | ||
* header of a pack 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 SCR | ||
* wraparound takes place within the stream, which can make SCR values at the beginning of the | ||
* stream larger than SCR 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. | ||
* | ||
* <p>Note: See ISO/IEC 13818-1, Table 2-33 for details of the SCR field in pack_header. | ||
*/ | ||
/* package */ final class PsDurationReader { | ||
|
||
private static final int DURATION_READ_BYTES = 20000; | ||
|
||
private final TimestampAdjuster scrTimestampAdjuster; | ||
private final ParsableByteArray packetBuffer; | ||
|
||
private boolean isDurationRead; | ||
private boolean isFirstScrValueRead; | ||
private boolean isLastScrValueRead; | ||
|
||
private long firstScrValue; | ||
private long lastScrValue; | ||
private long durationUs; | ||
|
||
/* package */ PsDurationReader() { | ||
scrTimestampAdjuster = new TimestampAdjuster(/* firstSampleTimestampUs= */ 0); | ||
firstScrValue = C.TIME_UNSET; | ||
lastScrValue = C.TIME_UNSET; | ||
durationUs = C.TIME_UNSET; | ||
packetBuffer = new ParsableByteArray(DURATION_READ_BYTES); | ||
} | ||
|
||
/** Returns true if a PS duration has been read. */ | ||
public boolean isDurationReadFinished() { | ||
return isDurationRead; | ||
} | ||
|
||
/** | ||
* Reads a PS duration from the input. | ||
* | ||
* <p>This reader reads the duration by reading SCR values from the header of a pack 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. | ||
* @return One of the {@code RESULT_} values defined in {@link Extractor}. | ||
* @throws IOException If an error occurred reading from the input. | ||
* @throws InterruptedException If the thread was interrupted. | ||
*/ | ||
public @Extractor.ReadResult int readDuration( | ||
ExtractorInput input, PositionHolder seekPositionHolder) | ||
throws IOException, InterruptedException { | ||
if (!isLastScrValueRead) { | ||
return readLastScrValue(input, seekPositionHolder); | ||
} | ||
if (lastScrValue == C.TIME_UNSET) { | ||
return finishReadDuration(input); | ||
} | ||
if (!isFirstScrValueRead) { | ||
return readFirstScrValue(input, seekPositionHolder); | ||
} | ||
if (firstScrValue == C.TIME_UNSET) { | ||
return finishReadDuration(input); | ||
} | ||
|
||
long minScrPositionUs = scrTimestampAdjuster.adjustTsTimestamp(firstScrValue); | ||
long maxScrPositionUs = scrTimestampAdjuster.adjustTsTimestamp(lastScrValue); | ||
durationUs = maxScrPositionUs - minScrPositionUs; | ||
return finishReadDuration(input); | ||
} | ||
|
||
/** Returns the duration last read from {@link #readDuration(ExtractorInput, PositionHolder)}. */ | ||
public long getDurationUs() { | ||
return durationUs; | ||
} | ||
|
||
private int finishReadDuration(ExtractorInput input) { | ||
isDurationRead = true; | ||
input.resetPeekPosition(); | ||
return Extractor.RESULT_CONTINUE; | ||
} | ||
|
||
private int readFirstScrValue(ExtractorInput input, PositionHolder seekPositionHolder) | ||
throws IOException, InterruptedException { | ||
if (input.getPosition() != 0) { | ||
seekPositionHolder.position = 0; | ||
return Extractor.RESULT_SEEK; | ||
} | ||
|
||
int bytesToRead = (int) Math.min(DURATION_READ_BYTES, input.getLength()); | ||
input.resetPeekPosition(); | ||
input.peekFully(packetBuffer.data, /* offset= */ 0, bytesToRead); | ||
packetBuffer.setPosition(0); | ||
packetBuffer.setLimit(bytesToRead); | ||
|
||
firstScrValue = readFirstScrValueFromBuffer(packetBuffer); | ||
isFirstScrValueRead = true; | ||
return Extractor.RESULT_CONTINUE; | ||
} | ||
|
||
private long readFirstScrValueFromBuffer(ParsableByteArray packetBuffer) { | ||
int searchStartPosition = packetBuffer.getPosition(); | ||
int searchEndPosition = packetBuffer.limit(); | ||
for (int searchPosition = searchStartPosition; | ||
searchPosition < searchEndPosition - 3; | ||
searchPosition++) { | ||
int nextStartCode = peakIntAtPosition(packetBuffer.data, searchPosition); | ||
if (nextStartCode == PsExtractor.PACK_START_CODE) { | ||
long scrValue = readScrValueFromPack(packetBuffer, searchPosition + 4); | ||
if (scrValue != C.TIME_UNSET) { | ||
return scrValue; | ||
} | ||
} | ||
} | ||
return C.TIME_UNSET; | ||
} | ||
|
||
private int readLastScrValue(ExtractorInput input, PositionHolder seekPositionHolder) | ||
throws IOException, InterruptedException { | ||
int bytesToRead = (int) Math.min(DURATION_READ_BYTES, input.getLength()); | ||
long bufferStartStreamPosition = input.getLength() - bytesToRead; | ||
if (input.getPosition() != bufferStartStreamPosition) { | ||
seekPositionHolder.position = bufferStartStreamPosition; | ||
return Extractor.RESULT_SEEK; | ||
} | ||
|
||
input.resetPeekPosition(); | ||
input.peekFully(packetBuffer.data, /* offset= */ 0, bytesToRead); | ||
packetBuffer.setPosition(0); | ||
packetBuffer.setLimit(bytesToRead); | ||
|
||
lastScrValue = readLastScrValueFromBuffer(packetBuffer); | ||
isLastScrValueRead = true; | ||
return Extractor.RESULT_CONTINUE; | ||
} | ||
|
||
private long readLastScrValueFromBuffer(ParsableByteArray packetBuffer) { | ||
int searchStartPosition = packetBuffer.getPosition(); | ||
int searchEndPosition = packetBuffer.limit(); | ||
for (int searchPosition = searchEndPosition - 4; | ||
searchPosition >= searchStartPosition; | ||
searchPosition--) { | ||
int nextStartCode = peakIntAtPosition(packetBuffer.data, searchPosition); | ||
if (nextStartCode == PsExtractor.PACK_START_CODE) { | ||
long scrValue = readScrValueFromPack(packetBuffer, searchPosition + 4); | ||
if (scrValue != C.TIME_UNSET) { | ||
return scrValue; | ||
} | ||
} | ||
} | ||
return C.TIME_UNSET; | ||
} | ||
|
||
private int peakIntAtPosition(byte[] data, int position) { | ||
return (data[position] & 0xFF) << 24 | ||
| (data[position + 1] & 0xFF) << 16 | ||
| (data[position + 2] & 0xFF) << 8 | ||
| (data[position + 3] & 0xFF); | ||
} | ||
|
||
private long readScrValueFromPack(ParsableByteArray packetBuffer, int packHeaderStartPosition) { | ||
packetBuffer.setPosition(packHeaderStartPosition); | ||
if (packetBuffer.bytesLeft() < 9) { | ||
// We require at 9 bytes for pack header to read scr value | ||
return C.TIME_UNSET; | ||
} | ||
byte[] scrBytes = new byte[9]; | ||
packetBuffer.readBytes(scrBytes, /* offset= */ 0, scrBytes.length); | ||
if (!checkMarkerBits(scrBytes)) { | ||
return C.TIME_UNSET; | ||
} | ||
return readScrValueFromPackHeader(scrBytes); | ||
} | ||
|
||
private boolean checkMarkerBits(byte[] scrBytes) { | ||
// Verify the 01xxx1xx marker on the 0th byte | ||
if ((scrBytes[0] & 0xC4) != 0x44) { | ||
return false; | ||
} | ||
// 1st byte belongs to scr field. | ||
// Verify the xxxxx1xx marker on the 2nd byte | ||
if ((scrBytes[2] & 0x04) != 0x04) { | ||
return false; | ||
} | ||
// 3rd byte belongs to scr field. | ||
// Verify the xxxxx1xx marker on the 4rd byte | ||
if ((scrBytes[4] & 0x04) != 0x04) { | ||
return false; | ||
} | ||
// Verify the xxxxxxx1 marker on the 5th byte | ||
if ((scrBytes[5] & 0x01) != 0x01) { | ||
return false; | ||
} | ||
// 6th and 7th bytes belongs to program_max_rate field. | ||
// Verify the xxxxxx11 marker on the 8th byte | ||
return (scrBytes[8] & 0x03) == 0x03; | ||
} | ||
|
||
/** | ||
* Returns the value of SCR base - 33 bits in big endian order from the PS pack header, ignoring | ||
* the marker bits. Note: See ISO/IEC 13818-1, Table 2-33 for details of the SCR field in | ||
* pack_header. | ||
* | ||
* <p>We ignore SCR Ext, because it's too small to have any significance. | ||
*/ | ||
private static long readScrValueFromPackHeader(byte[] scrBytes) { | ||
return ((scrBytes[0] & 0b00111000L) >> 3) << 30 | ||
| (scrBytes[0] & 0b00000011L) << 28 | ||
| (scrBytes[1] & 0xFFL) << 20 | ||
| ((scrBytes[2] & 0b11111000L) >> 3) << 15 | ||
| (scrBytes[2] & 0b00000011L) << 13 | ||
| (scrBytes[3] & 0xFFL) << 5 | ||
| (scrBytes[4] & 0b11111000L) >> 3; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.