mirror of
https://github.com/samsonjs/media.git
synced 2026-04-04 11:05:47 +00:00
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
This commit is contained in:
parent
68822c0c41
commit
39b812298d
5 changed files with 347 additions and 3 deletions
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
### dev-v2 (not yet released) ###
|
||||
|
||||
* MPEG-PS: Support reading duration from MPEG-PS Streams
|
||||
([#4476](https://github.com/google/ExoPlayer/issues/4476)).
|
||||
* MediaSession extension:
|
||||
* Allow apps to set custom errors.
|
||||
* Audio:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -47,7 +47,7 @@ public final class PsExtractor implements Extractor {
|
|||
|
||||
};
|
||||
|
||||
private static final int PACK_START_CODE = 0x000001BA;
|
||||
/* package */ static final int PACK_START_CODE = 0x000001BA;
|
||||
private static final int SYSTEM_HEADER_START_CODE = 0x000001BB;
|
||||
private static final int PACKET_START_CODE_PREFIX = 0x000001;
|
||||
private static final int MPEG_PROGRAM_END_CODE = 0x000001B9;
|
||||
|
|
@ -68,6 +68,8 @@ public final class PsExtractor implements Extractor {
|
|||
private final TimestampAdjuster timestampAdjuster;
|
||||
private final SparseArray<PesReader> psPayloadReaders; // Indexed by pid
|
||||
private final ParsableByteArray psPacketBuffer;
|
||||
private final PsDurationReader durationReader;
|
||||
|
||||
private boolean foundAllTracks;
|
||||
private boolean foundAudioTrack;
|
||||
private boolean foundVideoTrack;
|
||||
|
|
@ -75,6 +77,7 @@ public final class PsExtractor implements Extractor {
|
|||
|
||||
// Accessed only by the loading thread.
|
||||
private ExtractorOutput output;
|
||||
private boolean hasOutputSeekMap;
|
||||
|
||||
public PsExtractor() {
|
||||
this(new TimestampAdjuster(0));
|
||||
|
|
@ -84,6 +87,7 @@ public final class PsExtractor implements Extractor {
|
|||
this.timestampAdjuster = timestampAdjuster;
|
||||
psPacketBuffer = new ParsableByteArray(4096);
|
||||
psPayloadReaders = new SparseArray<>();
|
||||
durationReader = new PsDurationReader();
|
||||
}
|
||||
|
||||
// Extractor implementation.
|
||||
|
|
@ -130,7 +134,6 @@ public final class PsExtractor implements Extractor {
|
|||
@Override
|
||||
public void init(ExtractorOutput output) {
|
||||
this.output = output;
|
||||
output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -149,6 +152,13 @@ public final class PsExtractor implements Extractor {
|
|||
@Override
|
||||
public int read(ExtractorInput input, PositionHolder seekPosition)
|
||||
throws IOException, InterruptedException {
|
||||
|
||||
boolean canReadDuration = input.getLength() != C.LENGTH_UNSET;
|
||||
if (canReadDuration && !durationReader.isDurationReadFinished()) {
|
||||
return durationReader.readDuration(input, seekPosition);
|
||||
}
|
||||
maybeOutputSeekMap();
|
||||
|
||||
// First peek and check what type of start code is next.
|
||||
if (!input.peekFully(psPacketBuffer.data, 0, 4, true)) {
|
||||
return RESULT_END_OF_INPUT;
|
||||
|
|
@ -250,6 +260,13 @@ public final class PsExtractor implements Extractor {
|
|||
|
||||
// Internals.
|
||||
|
||||
private void maybeOutputSeekMap() {
|
||||
if (!hasOutputSeekMap) {
|
||||
hasOutputSeekMap = true;
|
||||
output.seekMap(new SeekMap.Unseekable(durationReader.getDurationUs()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses PES packet data and extracts samples.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
seekMap:
|
||||
isSeekable = false
|
||||
duration = UNSET TIME
|
||||
duration = 766
|
||||
getPosition(0) = [[timeUs=0, position=0]]
|
||||
numberOfTracks = 2
|
||||
track 192:
|
||||
|
|
|
|||
79
library/core/src/test/assets/ts/sample.ps.unklen.dump
Normal file
79
library/core/src/test/assets/ts/sample.ps.unklen.dump
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
seekMap:
|
||||
isSeekable = false
|
||||
duration = UNSET TIME
|
||||
getPosition(0) = [[timeUs=0, position=0]]
|
||||
numberOfTracks = 2
|
||||
track 192:
|
||||
format:
|
||||
bitrate = -1
|
||||
id = 192
|
||||
containerMimeType = null
|
||||
sampleMimeType = audio/mpeg-L2
|
||||
maxInputSize = 4096
|
||||
width = -1
|
||||
height = -1
|
||||
frameRate = -1.0
|
||||
rotationDegrees = 0
|
||||
pixelWidthHeightRatio = 1.0
|
||||
channelCount = 1
|
||||
sampleRate = 44100
|
||||
pcmEncoding = -1
|
||||
encoderDelay = 0
|
||||
encoderPadding = 0
|
||||
subsampleOffsetUs = 9223372036854775807
|
||||
selectionFlags = 0
|
||||
language = null
|
||||
drmInitData = -
|
||||
initializationData:
|
||||
total output bytes = 1671
|
||||
sample count = 4
|
||||
sample 0:
|
||||
time = 29088
|
||||
flags = 1
|
||||
data = length 417, hash 5C710F78
|
||||
sample 1:
|
||||
time = 55210
|
||||
flags = 1
|
||||
data = length 418, hash 79CF71F8
|
||||
sample 2:
|
||||
time = 81332
|
||||
flags = 1
|
||||
data = length 418, hash 79CF71F8
|
||||
sample 3:
|
||||
time = 107454
|
||||
flags = 1
|
||||
data = length 418, hash 79CF71F8
|
||||
track 224:
|
||||
format:
|
||||
bitrate = -1
|
||||
id = 224
|
||||
containerMimeType = null
|
||||
sampleMimeType = video/mpeg2
|
||||
maxInputSize = -1
|
||||
width = 640
|
||||
height = 426
|
||||
frameRate = -1.0
|
||||
rotationDegrees = 0
|
||||
pixelWidthHeightRatio = 1.0
|
||||
channelCount = -1
|
||||
sampleRate = -1
|
||||
pcmEncoding = -1
|
||||
encoderDelay = 0
|
||||
encoderPadding = 0
|
||||
subsampleOffsetUs = 9223372036854775807
|
||||
selectionFlags = 0
|
||||
language = null
|
||||
drmInitData = -
|
||||
initializationData:
|
||||
data = length 22, hash 743CC6F8
|
||||
total output bytes = 44056
|
||||
sample count = 2
|
||||
sample 0:
|
||||
time = 40000
|
||||
flags = 1
|
||||
data = length 20646, hash 576390B
|
||||
sample 1:
|
||||
time = 80000
|
||||
flags = 0
|
||||
data = length 17831, hash 5C5A57F5
|
||||
tracksEnded = true
|
||||
Loading…
Reference in a new issue