Commit f08ad558 by hoangtc Committed by Oliver Woodman

Supports seeking for MPEG PS Streams.

This CL adds support for seeking within PS streams by using binary search. For
any seek timestamp, it tries to find the location in the stream where SCR
timestamp is close to the target timestamp, and return this position as the
seek position.

Github: #4476.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=206787691
parent 377314a6
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
([#2565](https://github.com/google/ExoPlayer/issues/2565)). ([#2565](https://github.com/google/ExoPlayer/issues/2565)).
* Fix bug preventing SCTE-35 cues from being output * Fix bug preventing SCTE-35 cues from being output
([#4573](https://github.com/google/ExoPlayer/issues/4573)). ([#4573](https://github.com/google/ExoPlayer/issues/4573)).
* MPEG-PS: Support reading duration from MPEG-PS Streams * MPEG-PS: Support reading duration and seeking for MPEG-PS Streams
([#4476](https://github.com/google/ExoPlayer/issues/4476)). ([#4476](https://github.com/google/ExoPlayer/issues/4476)).
* MediaSession extension: * MediaSession extension:
* Allow apps to set custom errors. * Allow apps to set custom errors.
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
*/ */
package com.google.android.exoplayer2.ext.flac; package com.google.android.exoplayer2.ext.flac;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.BinarySearchSeeker; import com.google.android.exoplayer2.extractor.BinarySearchSeeker;
import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.SeekMap;
...@@ -75,7 +74,6 @@ import java.nio.ByteBuffer; ...@@ -75,7 +74,6 @@ import java.nio.ByteBuffer;
throws IOException, InterruptedException { throws IOException, InterruptedException {
ByteBuffer outputBuffer = outputFrameHolder.byteBuffer; ByteBuffer outputBuffer = outputFrameHolder.byteBuffer;
long searchPosition = input.getPosition(); long searchPosition = input.getPosition();
int searchRangeBytes = getTimestampSearchBytesRange();
decoderJni.reset(searchPosition); decoderJni.reset(searchPosition);
try { try {
decoderJni.decodeSampleWithBacktrackPosition( decoderJni.decodeSampleWithBacktrackPosition(
...@@ -107,13 +105,6 @@ import java.nio.ByteBuffer; ...@@ -107,13 +105,6 @@ import java.nio.ByteBuffer;
return TimestampSearchResult.overestimatedResult(lastFrameSampleIndex, searchPosition); return TimestampSearchResult.overestimatedResult(lastFrameSampleIndex, searchPosition);
} }
} }
@Override
public int getTimestampSearchBytesRange() {
// We rely on decoderJni to search for timestamp (sample index) from a given stream point, so
// we don't restrict the range at all.
return C.LENGTH_UNSET;
}
} }
/** /**
......
...@@ -72,14 +72,6 @@ public abstract class BinarySearchSeeker { ...@@ -72,14 +72,6 @@ public abstract class BinarySearchSeeker {
TimestampSearchResult searchForTimestamp( TimestampSearchResult searchForTimestamp(
ExtractorInput input, long targetTimestamp, OutputFrameHolder outputFrameHolder) ExtractorInput input, long targetTimestamp, OutputFrameHolder outputFrameHolder)
throws IOException, InterruptedException; throws IOException, InterruptedException;
/**
* The range of bytes from the current input position from which to search for the target
* timestamp. Uses {@link C#LENGTH_UNSET} to signal that there is no limit for the search range.
*
* @see #searchForTimestamp(ExtractorInput, long, OutputFrameHolder)
*/
int getTimestampSearchBytesRange();
} }
/** /**
...@@ -99,6 +91,18 @@ public abstract class BinarySearchSeeker { ...@@ -99,6 +91,18 @@ public abstract class BinarySearchSeeker {
} }
/** /**
* A {@link SeekTimestampConverter} implementation that returns the seek time itself as the
* timestamp for a seek time position.
*/
public static final class DefaultSeekTimestampConverter implements SeekTimestampConverter {
@Override
public long timeUsToTargetTime(long timeUs) {
return timeUs;
}
}
/**
* A converter that converts seek time in stream time into target timestamp for the {@link * A converter that converts seek time in stream time into target timestamp for the {@link
* BinarySearchSeeker}. * BinarySearchSeeker}.
*/ */
...@@ -566,16 +570,4 @@ public abstract class BinarySearchSeeker { ...@@ -566,16 +570,4 @@ public abstract class BinarySearchSeeker {
return seekTimestampConverter.timeUsToTargetTime(timeUs); return seekTimestampConverter.timeUsToTargetTime(timeUs);
} }
} }
/**
* A {@link SeekTimestampConverter} implementation that returns the seek time itself as the
* timestamp for a seek time position.
*/
private static final class DefaultSeekTimestampConverter implements SeekTimestampConverter {
@Override
public long timeUsToTargetTime(long timeUs) {
return timeUs;
}
}
} }
/*
* 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.BinarySearchSeeker;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import java.io.IOException;
/**
* A seeker that supports seeking within PS stream using binary search.
*
* <p>This seeker uses the first and last SCR values within the stream, as well as the stream
* duration to interpolate the SCR value of the seeking position. Then it performs binary search
* within the stream to find a packets whose SCR value is with in {@link #SEEK_TOLERANCE_US} from
* the target SCR.
*/
/* package */ final class PsBinarySearchSeeker extends BinarySearchSeeker {
private static final long SEEK_TOLERANCE_US = 100_000;
private static final int MINIMUM_SEARCH_RANGE_BYTES = 1000;
private static final int TIMESTAMP_SEARCH_BYTES = 20000;
public PsBinarySearchSeeker(
TimestampAdjuster scrTimestampAdjuster, long streamDurationUs, long inputLength) {
super(
new DefaultSeekTimestampConverter(),
new PsScrSeeker(scrTimestampAdjuster),
streamDurationUs,
/* floorTimePosition= */ 0,
/* ceilingTimePosition= */ streamDurationUs + 1,
/* floorBytePosition= */ 0,
/* ceilingBytePosition= */ inputLength,
/* approxBytesPerFrame= */ TsExtractor.TS_PACKET_SIZE,
MINIMUM_SEARCH_RANGE_BYTES);
}
/**
* A seeker that looks for a given SCR timestamp at a given position in a PS stream.
*
* <p>Given a SCR timestamp, and a position within a PS stream, this seeker will try to read a
* range of up to {@link #TIMESTAMP_SEARCH_BYTES} bytes from that stream position, look for all
* packs in that range, and then compare the SCR timestamps (if available) of these packets vs the
* target timestamp.
*/
private static final class PsScrSeeker implements TimestampSeeker {
private final TimestampAdjuster scrTimestampAdjuster;
private final ParsableByteArray packetBuffer;
private PsScrSeeker(TimestampAdjuster scrTimestampAdjuster) {
this.scrTimestampAdjuster = scrTimestampAdjuster;
packetBuffer = new ParsableByteArray(TIMESTAMP_SEARCH_BYTES);
}
@Override
public TimestampSearchResult searchForTimestamp(
ExtractorInput input, long targetTimestamp, OutputFrameHolder outputFrameHolder)
throws IOException, InterruptedException {
long inputPosition = input.getPosition();
int bytesToRead =
(int) Math.min(TIMESTAMP_SEARCH_BYTES, input.getLength() - input.getPosition());
packetBuffer.reset(bytesToRead);
input.peekFully(packetBuffer.data, /* offset= */ 0, bytesToRead);
return searchForScrValueInBuffer(packetBuffer, targetTimestamp, inputPosition);
}
private TimestampSearchResult searchForScrValueInBuffer(
ParsableByteArray packetBuffer, long targetScrTimeUs, long bufferStartOffset) {
int startOfLastPacketPosition = C.POSITION_UNSET;
int endOfLastPacketPosition = C.POSITION_UNSET;
long lastScrTimeUsInRange = C.TIME_UNSET;
while (packetBuffer.bytesLeft() >= 4) {
int nextStartCode = peekIntAtPosition(packetBuffer.data, packetBuffer.getPosition());
if (nextStartCode != PsExtractor.PACK_START_CODE) {
packetBuffer.skipBytes(1);
continue;
} else {
packetBuffer.skipBytes(4);
}
// We found a pack.
long scrValue = PsDurationReader.readScrValueFromPack(packetBuffer);
if (scrValue != C.TIME_UNSET) {
long scrTimeUs = scrTimestampAdjuster.adjustTsTimestamp(scrValue);
if (scrTimeUs > targetScrTimeUs) {
if (lastScrTimeUsInRange == C.TIME_UNSET) {
// First SCR timestamp is already over target.
return TimestampSearchResult.overestimatedResult(scrTimeUs, bufferStartOffset);
} else {
// Last SCR timestamp < target timestamp < this timestamp.
return TimestampSearchResult.targetFoundResult(
bufferStartOffset + startOfLastPacketPosition);
}
} else if (scrTimeUs + SEEK_TOLERANCE_US > targetScrTimeUs) {
long startOfPacketInStream = bufferStartOffset + packetBuffer.getPosition();
return TimestampSearchResult.targetFoundResult(startOfPacketInStream);
}
lastScrTimeUsInRange = scrTimeUs;
startOfLastPacketPosition = packetBuffer.getPosition();
}
skipToEndOfCurrentPack(packetBuffer);
endOfLastPacketPosition = packetBuffer.getPosition();
}
if (lastScrTimeUsInRange != C.TIME_UNSET) {
long endOfLastPacketPositionInStream = bufferStartOffset + endOfLastPacketPosition;
return TimestampSearchResult.underestimatedResult(
lastScrTimeUsInRange, endOfLastPacketPositionInStream);
} else {
return TimestampSearchResult.NO_TIMESTAMP_IN_RANGE_RESULT;
}
}
/**
* Skips the buffer position to the position after the end of the current PS pack in the buffer,
* given the byte position right after the {@link PsExtractor#PACK_START_CODE} of the pack in
* the buffer. If the pack ends after the end of the buffer, skips to the end of the buffer.
*/
private static void skipToEndOfCurrentPack(ParsableByteArray packetBuffer) {
int limit = packetBuffer.limit();
if (packetBuffer.bytesLeft() < 10) {
// We require at least 9 bytes for pack header to read SCR value + 1 byte for pack_stuffing
// length.
packetBuffer.setPosition(limit);
return;
}
packetBuffer.skipBytes(9);
int packStuffingLength = packetBuffer.readUnsignedByte() & 0x07;
if (packetBuffer.bytesLeft() < packStuffingLength) {
packetBuffer.setPosition(limit);
return;
}
packetBuffer.skipBytes(packStuffingLength);
if (packetBuffer.bytesLeft() < 4) {
packetBuffer.setPosition(limit);
return;
}
int nextStartCode = peekIntAtPosition(packetBuffer.data, packetBuffer.getPosition());
if (nextStartCode == PsExtractor.SYSTEM_HEADER_START_CODE) {
packetBuffer.skipBytes(4);
int systemHeaderLength = packetBuffer.readUnsignedShort();
if (packetBuffer.bytesLeft() < systemHeaderLength) {
packetBuffer.setPosition(limit);
return;
}
packetBuffer.skipBytes(systemHeaderLength);
}
// Find the position of the next PACK_START_CODE or MPEG_PROGRAM_END_CODE, which is right
// after the end position of this pack.
// If we couldn't find these codes within the buffer, return the buffer limit, or return
// the first position which PES packets pattern does not match (some malformed packets).
while (packetBuffer.bytesLeft() >= 4) {
nextStartCode = peekIntAtPosition(packetBuffer.data, packetBuffer.getPosition());
if (nextStartCode == PsExtractor.PACK_START_CODE
|| nextStartCode == PsExtractor.MPEG_PROGRAM_END_CODE) {
break;
}
if (nextStartCode >>> 8 != PsExtractor.PACKET_START_CODE_PREFIX) {
break;
}
packetBuffer.skipBytes(4);
if (packetBuffer.bytesLeft() < 2) {
// 2 bytes for PES_packet length.
packetBuffer.setPosition(limit);
return;
}
int pesPacketLength = packetBuffer.readUnsignedShort();
packetBuffer.setPosition(
Math.min(packetBuffer.limit(), packetBuffer.getPosition() + pesPacketLength));
}
}
}
private static int peekIntAtPosition(byte[] data, int position) {
return (data[position] & 0xFF) << 24
| (data[position + 1] & 0xFF) << 16
| (data[position + 2] & 0xFF) << 8
| (data[position + 3] & 0xFF);
}
}
...@@ -64,6 +64,10 @@ import java.io.IOException; ...@@ -64,6 +64,10 @@ import java.io.IOException;
return isDurationRead; return isDurationRead;
} }
public TimestampAdjuster getScrTimestampAdjuster() {
return scrTimestampAdjuster;
}
/** /**
* Reads a PS duration from the input. * Reads a PS duration from the input.
* *
...@@ -105,6 +109,25 @@ import java.io.IOException; ...@@ -105,6 +109,25 @@ import java.io.IOException;
return durationUs; return durationUs;
} }
/**
* Returns the SCR value read from the next pack in the stream, given the buffer at the pack
* header start position (just behind the pack start code).
*/
public static long readScrValueFromPack(ParsableByteArray packetBuffer) {
int originalPosition = packetBuffer.getPosition();
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);
packetBuffer.setPosition(originalPosition);
if (!checkMarkerBits(scrBytes)) {
return C.TIME_UNSET;
}
return readScrValueFromPackHeader(scrBytes);
}
private int finishReadDuration(ExtractorInput input) { private int finishReadDuration(ExtractorInput input) {
isDurationRead = true; isDurationRead = true;
input.resetPeekPosition(); input.resetPeekPosition();
...@@ -135,9 +158,10 @@ import java.io.IOException; ...@@ -135,9 +158,10 @@ import java.io.IOException;
for (int searchPosition = searchStartPosition; for (int searchPosition = searchStartPosition;
searchPosition < searchEndPosition - 3; searchPosition < searchEndPosition - 3;
searchPosition++) { searchPosition++) {
int nextStartCode = peakIntAtPosition(packetBuffer.data, searchPosition); int nextStartCode = peekIntAtPosition(packetBuffer.data, searchPosition);
if (nextStartCode == PsExtractor.PACK_START_CODE) { if (nextStartCode == PsExtractor.PACK_START_CODE) {
long scrValue = readScrValueFromPack(packetBuffer, searchPosition + 4); packetBuffer.setPosition(searchPosition + 4);
long scrValue = readScrValueFromPack(packetBuffer);
if (scrValue != C.TIME_UNSET) { if (scrValue != C.TIME_UNSET) {
return scrValue; return scrValue;
} }
...@@ -171,9 +195,10 @@ import java.io.IOException; ...@@ -171,9 +195,10 @@ import java.io.IOException;
for (int searchPosition = searchEndPosition - 4; for (int searchPosition = searchEndPosition - 4;
searchPosition >= searchStartPosition; searchPosition >= searchStartPosition;
searchPosition--) { searchPosition--) {
int nextStartCode = peakIntAtPosition(packetBuffer.data, searchPosition); int nextStartCode = peekIntAtPosition(packetBuffer.data, searchPosition);
if (nextStartCode == PsExtractor.PACK_START_CODE) { if (nextStartCode == PsExtractor.PACK_START_CODE) {
long scrValue = readScrValueFromPack(packetBuffer, searchPosition + 4); packetBuffer.setPosition(searchPosition + 4);
long scrValue = readScrValueFromPack(packetBuffer);
if (scrValue != C.TIME_UNSET) { if (scrValue != C.TIME_UNSET) {
return scrValue; return scrValue;
} }
...@@ -182,28 +207,14 @@ import java.io.IOException; ...@@ -182,28 +207,14 @@ import java.io.IOException;
return C.TIME_UNSET; return C.TIME_UNSET;
} }
private int peakIntAtPosition(byte[] data, int position) { private int peekIntAtPosition(byte[] data, int position) {
return (data[position] & 0xFF) << 24 return (data[position] & 0xFF) << 24
| (data[position + 1] & 0xFF) << 16 | (data[position + 1] & 0xFF) << 16
| (data[position + 2] & 0xFF) << 8 | (data[position + 2] & 0xFF) << 8
| (data[position + 3] & 0xFF); | (data[position + 3] & 0xFF);
} }
private long readScrValueFromPack(ParsableByteArray packetBuffer, int packHeaderStartPosition) { private static boolean checkMarkerBits(byte[] scrBytes) {
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 // Verify the 01xxx1xx marker on the 0th byte
if ((scrBytes[0] & 0xC4) != 0x44) { if ((scrBytes[0] & 0xC4) != 0x44) {
return false; return false;
......
...@@ -39,9 +39,9 @@ public final class PsExtractor implements Extractor { ...@@ -39,9 +39,9 @@ public final class PsExtractor implements Extractor {
public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new PsExtractor()}; public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new PsExtractor()};
/* package */ static final int PACK_START_CODE = 0x000001BA; /* package */ static final int PACK_START_CODE = 0x000001BA;
private static final int SYSTEM_HEADER_START_CODE = 0x000001BB; /* package */ static final int SYSTEM_HEADER_START_CODE = 0x000001BB;
private static final int PACKET_START_CODE_PREFIX = 0x000001; /* package */ static final int PACKET_START_CODE_PREFIX = 0x000001;
private static final int MPEG_PROGRAM_END_CODE = 0x000001B9; /* package */ static final int MPEG_PROGRAM_END_CODE = 0x000001B9;
private static final int MAX_STREAM_ID_PLUS_ONE = 0x100; private static final int MAX_STREAM_ID_PLUS_ONE = 0x100;
// Max search length for first audio and video track in input data. // Max search length for first audio and video track in input data.
...@@ -67,6 +67,7 @@ public final class PsExtractor implements Extractor { ...@@ -67,6 +67,7 @@ public final class PsExtractor implements Extractor {
private long lastTrackPosition; private long lastTrackPosition;
// Accessed only by the loading thread. // Accessed only by the loading thread.
private PsBinarySearchSeeker psBinarySearchSeeker;
private ExtractorOutput output; private ExtractorOutput output;
private boolean hasOutputSeekMap; private boolean hasOutputSeekMap;
...@@ -129,7 +130,23 @@ public final class PsExtractor implements Extractor { ...@@ -129,7 +130,23 @@ public final class PsExtractor implements Extractor {
@Override @Override
public void seek(long position, long timeUs) { public void seek(long position, long timeUs) {
timestampAdjuster.reset(); boolean hasNotEncounteredFirstTimestamp =
timestampAdjuster.getTimestampOffsetUs() == C.TIME_UNSET;
if (hasNotEncounteredFirstTimestamp
|| (timestampAdjuster.getFirstSampleTimestampUs() != 0
&& timestampAdjuster.getFirstSampleTimestampUs() != timeUs)) {
// - If the timestamp adjuster in the PS stream has not encountered any sample, it's going to
// treat the first timestamp encountered as sample time 0, which is incorrect. In this case,
// we have to set the first sample timestamp manually.
// - If the timestamp adjuster has its timestamp set manually before, and now we seek to a
// different position, we need to set the first sample timestamp manually again.
timestampAdjuster.reset();
timestampAdjuster.setFirstSampleTimestampUs(timeUs);
}
if (psBinarySearchSeeker != null) {
psBinarySearchSeeker.setSeekTargetUs(timeUs);
}
for (int i = 0; i < psPayloadReaders.size(); i++) { for (int i = 0; i < psPayloadReaders.size(); i++) {
psPayloadReaders.valueAt(i).seek(); psPayloadReaders.valueAt(i).seek();
} }
...@@ -144,12 +161,23 @@ public final class PsExtractor implements Extractor { ...@@ -144,12 +161,23 @@ public final class PsExtractor implements Extractor {
public int read(ExtractorInput input, PositionHolder seekPosition) public int read(ExtractorInput input, PositionHolder seekPosition)
throws IOException, InterruptedException { throws IOException, InterruptedException {
boolean canReadDuration = input.getLength() != C.LENGTH_UNSET; long inputLength = input.getLength();
boolean canReadDuration = inputLength != C.LENGTH_UNSET;
if (canReadDuration && !durationReader.isDurationReadFinished()) { if (canReadDuration && !durationReader.isDurationReadFinished()) {
return durationReader.readDuration(input, seekPosition); return durationReader.readDuration(input, seekPosition);
} }
maybeOutputSeekMap(); maybeOutputSeekMap(inputLength);
if (psBinarySearchSeeker != null && psBinarySearchSeeker.isSeeking()) {
return psBinarySearchSeeker.handlePendingSeek(
input, seekPosition, /* outputFrameHolder= */ null);
}
input.resetPeekPosition();
long peekBytesLeft =
inputLength != C.LENGTH_UNSET ? inputLength - input.getPeekPosition() : C.LENGTH_UNSET;
if (peekBytesLeft != C.LENGTH_UNSET && peekBytesLeft < 4) {
return RESULT_END_OF_INPUT;
}
// First peek and check what type of start code is next. // First peek and check what type of start code is next.
if (!input.peekFully(psPacketBuffer.data, 0, 4, true)) { if (!input.peekFully(psPacketBuffer.data, 0, 4, true)) {
return RESULT_END_OF_INPUT; return RESULT_END_OF_INPUT;
...@@ -251,10 +279,19 @@ public final class PsExtractor implements Extractor { ...@@ -251,10 +279,19 @@ public final class PsExtractor implements Extractor {
// Internals. // Internals.
private void maybeOutputSeekMap() { private void maybeOutputSeekMap(long inputLength) {
if (!hasOutputSeekMap) { if (!hasOutputSeekMap) {
hasOutputSeekMap = true; hasOutputSeekMap = true;
output.seekMap(new SeekMap.Unseekable(durationReader.getDurationUs())); if (durationReader.getDurationUs() != C.TIME_UNSET) {
psBinarySearchSeeker =
new PsBinarySearchSeeker(
durationReader.getScrTimestampAdjuster(),
durationReader.getDurationUs(),
inputLength);
output.seekMap(psBinarySearchSeeker.getSeekMap());
} else {
output.seekMap(new SeekMap.Unseekable(durationReader.getDurationUs()));
}
} }
} }
......
The file could not be displayed because it is too large.
seekMap: seekMap:
isSeekable = false isSeekable = true
duration = 766 duration = 766
getPosition(0) = [[timeUs=0, position=0]] getPosition(0) = [[timeUs=0, position=0]]
numberOfTracks = 2 numberOfTracks = 2
......
seekMap:
isSeekable = true
duration = 766
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 = 0
sample count = 0
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 = 33949
sample count = 1
sample 0:
time = 80000
flags = 0
data = length 17831, hash 5C5A57F5
tracksEnded = true
seekMap:
isSeekable = true
duration = 766
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 = 0
sample count = 0
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 = 19791
sample count = 0
tracksEnded = true
seekMap:
isSeekable = true
duration = 766
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 = 0
sample count = 0
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 = 1585
sample count = 0
tracksEnded = true
/*
* 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 com.google.common.truth.Truth.assertThat;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.testutil.FakeExtractorInput;
import com.google.android.exoplayer2.testutil.TestUtil;
import java.io.IOException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
/** Unit test for {@link PsDurationReader}. */
@RunWith(RobolectricTestRunner.class)
public final class PsDurationReaderTest {
private PsDurationReader tsDurationReader;
private PositionHolder seekPositionHolder;
@Before
public void setUp() {
tsDurationReader = new PsDurationReader();
seekPositionHolder = new PositionHolder();
}
@Test
public void testIsDurationReadPending_returnFalseByDefault() {
assertThat(tsDurationReader.isDurationReadFinished()).isFalse();
}
@Test
public void testReadDuration_returnsCorrectDuration() throws IOException, InterruptedException {
FakeExtractorInput input =
new FakeExtractorInput.Builder()
.setData(TestUtil.getByteArray(RuntimeEnvironment.application, "ts/sample.ps"))
.build();
int result = Extractor.RESULT_CONTINUE;
while (!tsDurationReader.isDurationReadFinished()) {
result = tsDurationReader.readDuration(input, seekPositionHolder);
if (result == Extractor.RESULT_SEEK) {
input.setPosition((int) seekPositionHolder.position);
}
}
assertThat(result).isNotEqualTo(Extractor.RESULT_END_OF_INPUT);
assertThat(tsDurationReader.getDurationUs()).isEqualTo(766);
}
@Test
public void testReadDuration_midStream_returnsCorrectDuration()
throws IOException, InterruptedException {
FakeExtractorInput input =
new FakeExtractorInput.Builder()
.setData(TestUtil.getByteArray(RuntimeEnvironment.application, "ts/sample.ps"))
.build();
input.setPosition(1234);
int result = Extractor.RESULT_CONTINUE;
while (!tsDurationReader.isDurationReadFinished()) {
result = tsDurationReader.readDuration(input, seekPositionHolder);
if (result == Extractor.RESULT_SEEK) {
input.setPosition((int) seekPositionHolder.position);
}
}
assertThat(result).isNotEqualTo(Extractor.RESULT_END_OF_INPUT);
assertThat(tsDurationReader.getDurationUs()).isEqualTo(766);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment