Commit 077f2c30 by hoangtc Committed by Oliver Woodman

Supports seeking for TS Streams.

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

Github: #966.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=207529906
parent 7fcd6b6d
...@@ -4,8 +4,11 @@ ...@@ -4,8 +4,11 @@
* Add `AudioListener` for listening to changes in audio configuration during * Add `AudioListener` for listening to changes in audio configuration during
playback ([#3994](https://github.com/google/ExoPlayer/issues/3994)). playback ([#3994](https://github.com/google/ExoPlayer/issues/3994)).
* MPEG-TS: Support CEA-608/708 in H262 * MPEG-TS:
([#2565](https://github.com/google/ExoPlayer/issues/2565)). * Support seeking for MPEG-TS Streams
([#966](https://github.com/google/ExoPlayer/issues/966)).
* Support CEA-608/708 in H262
([#2565](https://github.com/google/ExoPlayer/issues/2565)).
* MPEG-PS: Support reading duration and seeking for 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:
......
/*
* 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 TS stream using binary search.
*
* <p>This seeker uses the first and last PCR values within the stream, as well as the stream
* duration to interpolate the PCR value of the seeking position. Then it performs binary search
* within the stream to find a packets whose PCR value is within {@link #SEEK_TOLERANCE_US} from the
* target PCR.
*/
/* package */ final class TsBinarySearchSeeker extends BinarySearchSeeker {
private static final long SEEK_TOLERANCE_US = 100_000;
private static final int MINIMUM_SEARCH_RANGE_BYTES = TsExtractor.TS_PACKET_SIZE * 5;
private static final int TIMESTAMP_SEARCH_PACKETS = 200;
private static final int TIMESTAMP_SEARCH_BYTES =
TsExtractor.TS_PACKET_SIZE * TIMESTAMP_SEARCH_PACKETS;
public TsBinarySearchSeeker(
TimestampAdjuster pcrTimestampAdjuster, long streamDurationUs, long inputLength, int pcrPid) {
super(
new DefaultSeekTimestampConverter(),
new TsPcrSeeker(pcrPid, pcrTimestampAdjuster),
streamDurationUs,
/* floorTimePosition= */ 0,
/* ceilingTimePosition= */ streamDurationUs + 1,
/* floorBytePosition= */ 0,
/* ceilingBytePosition= */ inputLength,
/* approxBytesPerFrame= */ TsExtractor.TS_PACKET_SIZE,
MINIMUM_SEARCH_RANGE_BYTES);
}
/**
* A {@link TimestampSeeker} implementation that looks for a given PCR timestamp at a given
* position in a TS stream.
*
* <p>Given a PCR timestamp, and a position within a TS stream, this seeker will try to read up to
* {@link #TIMESTAMP_SEARCH_PACKETS} TS packets from that stream position, look for all packet
* with PID equals to PCR_PID, and then compare the PCR timestamps (if available) of these packets
* vs the target timestamp.
*/
private static final class TsPcrSeeker implements TimestampSeeker {
private final TimestampAdjuster pcrTimestampAdjuster;
private final ParsableByteArray packetBuffer;
private final int pcrPid;
public TsPcrSeeker(int pcrPid, TimestampAdjuster pcrTimestampAdjuster) {
this.pcrPid = pcrPid;
this.pcrTimestampAdjuster = pcrTimestampAdjuster;
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 searchForPcrValueInBuffer(packetBuffer, targetTimestamp, inputPosition);
}
private TimestampSearchResult searchForPcrValueInBuffer(
ParsableByteArray packetBuffer, long targetPcrTimeUs, long bufferStartOffset) {
int limit = packetBuffer.limit();
long startOfLastPacketPosition = C.POSITION_UNSET;
long endOfLastPacketPosition = C.POSITION_UNSET;
long lastPcrTimeUsInRange = C.TIME_UNSET;
while (packetBuffer.bytesLeft() >= TsExtractor.TS_PACKET_SIZE) {
int startOfPacket =
TsUtil.findSyncBytePosition(packetBuffer.data, packetBuffer.getPosition(), limit);
int endOfPacket = startOfPacket + TsExtractor.TS_PACKET_SIZE;
if (endOfPacket > limit) {
break;
}
long pcrValue = TsUtil.readPcrFromPacket(packetBuffer, startOfPacket, pcrPid);
if (pcrValue != C.TIME_UNSET) {
long pcrTimeUs = pcrTimestampAdjuster.adjustTsTimestamp(pcrValue);
if (pcrTimeUs > targetPcrTimeUs) {
if (lastPcrTimeUsInRange == C.TIME_UNSET) {
// First PCR timestamp is already over target.
return TimestampSearchResult.overestimatedResult(pcrTimeUs, bufferStartOffset);
} else {
// Last PCR timestamp < target timestamp < this timestamp.
return TimestampSearchResult.targetFoundResult(
bufferStartOffset + startOfLastPacketPosition);
}
} else if (pcrTimeUs + SEEK_TOLERANCE_US > targetPcrTimeUs) {
long startOfPacketInStream = bufferStartOffset + startOfPacket;
return TimestampSearchResult.targetFoundResult(startOfPacketInStream);
}
lastPcrTimeUsInRange = pcrTimeUs;
startOfLastPacketPosition = startOfPacket;
}
packetBuffer.setPosition(endOfPacket);
endOfLastPacketPosition = endOfPacket;
}
if (lastPcrTimeUsInRange != C.TIME_UNSET) {
long endOfLastPacketPositionInStream = bufferStartOffset + endOfLastPacketPosition;
return TimestampSearchResult.underestimatedResult(
lastPcrTimeUsInRange, endOfLastPacketPositionInStream);
} else {
return TimestampSearchResult.NO_TIMESTAMP_IN_RANGE_RESULT;
}
}
}
}
...@@ -108,6 +108,14 @@ import java.io.IOException; ...@@ -108,6 +108,14 @@ import java.io.IOException;
return durationUs; 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) { private int finishReadDuration(ExtractorInput input) {
isDurationRead = true; isDurationRead = true;
input.resetPeekPosition(); input.resetPeekPosition();
...@@ -141,7 +149,7 @@ import java.io.IOException; ...@@ -141,7 +149,7 @@ import java.io.IOException;
if (packetBuffer.data[searchPosition] != TsExtractor.TS_SYNC_BYTE) { if (packetBuffer.data[searchPosition] != TsExtractor.TS_SYNC_BYTE) {
continue; continue;
} }
long pcrValue = readPcrFromPacket(packetBuffer, searchPosition, pcrPid); long pcrValue = TsUtil.readPcrFromPacket(packetBuffer, searchPosition, pcrPid);
if (pcrValue != C.TIME_UNSET) { if (pcrValue != C.TIME_UNSET) {
return pcrValue; return pcrValue;
} }
...@@ -177,7 +185,7 @@ import java.io.IOException; ...@@ -177,7 +185,7 @@ import java.io.IOException;
if (packetBuffer.data[searchPosition] != TsExtractor.TS_SYNC_BYTE) { if (packetBuffer.data[searchPosition] != TsExtractor.TS_SYNC_BYTE) {
continue; continue;
} }
long pcrValue = readPcrFromPacket(packetBuffer, searchPosition, pcrPid); long pcrValue = TsUtil.readPcrFromPacket(packetBuffer, searchPosition, pcrPid);
if (pcrValue != C.TIME_UNSET) { if (pcrValue != C.TIME_UNSET) {
return pcrValue; return pcrValue;
} }
...@@ -185,51 +193,4 @@ import java.io.IOException; ...@@ -185,51 +193,4 @@ import java.io.IOException;
return C.TIME_UNSET; return C.TIME_UNSET;
} }
private static long readPcrFromPacket(
ParsableByteArray packetBuffer, int startOfPacket, int pcrPid) {
packetBuffer.setPosition(startOfPacket);
if (packetBuffer.bytesLeft() < 5) {
// Header = 4 bytes, adaptationFieldLength = 1 byte.
return C.TIME_UNSET;
}
// Note: See ISO/IEC 13818-1, section 2.4.3.2 for details of the header format.
int tsPacketHeader = packetBuffer.readInt();
if ((tsPacketHeader & 0x800000) != 0) {
// transport_error_indicator != 0 means there are uncorrectable errors in this packet.
return C.TIME_UNSET;
}
int pid = (tsPacketHeader & 0x1FFF00) >> 8;
if (pid != pcrPid) {
return C.TIME_UNSET;
}
boolean adaptationFieldExists = (tsPacketHeader & 0x20) != 0;
if (!adaptationFieldExists) {
return C.TIME_UNSET;
}
int adaptationFieldLength = packetBuffer.readUnsignedByte();
if (adaptationFieldLength >= 7 && packetBuffer.bytesLeft() >= 7) {
int flags = packetBuffer.readUnsignedByte();
boolean pcrFlagSet = (flags & 0x10) == 0x10;
if (pcrFlagSet) {
byte[] pcrBytes = new byte[6];
packetBuffer.readBytes(pcrBytes, /* offset= */ 0, pcrBytes.length);
return readPcrValueFromPcrBytes(pcrBytes);
}
}
return C.TIME_UNSET;
}
/**
* Returns the value of PCR base - first 33 bits in big endian order from the PCR bytes.
*
* <p>We ignore PCR Ext, because it's too small to have any significance.
*/
private static long readPcrValueFromPcrBytes(byte[] pcrBytes) {
return (pcrBytes[0] & 0xFFL) << 25
| (pcrBytes[1] & 0xFFL) << 17
| (pcrBytes[2] & 0xFFL) << 9
| (pcrBytes[3] & 0xFFL) << 1
| (pcrBytes[4] & 0xFFL) >> 7;
}
} }
...@@ -113,6 +113,7 @@ public final class TsExtractor implements Extractor { ...@@ -113,6 +113,7 @@ public final class TsExtractor implements Extractor {
private final TsDurationReader durationReader; private final TsDurationReader durationReader;
// Accessed only by the loading thread. // Accessed only by the loading thread.
private TsBinarySearchSeeker tsBinarySearchSeeker;
private ExtractorOutput output; private ExtractorOutput output;
private int remainingPmts; private int remainingPmts;
private boolean tracksEnded; private boolean tracksEnded;
...@@ -208,7 +209,23 @@ public final class TsExtractor implements Extractor { ...@@ -208,7 +209,23 @@ public final class TsExtractor implements Extractor {
Assertions.checkState(mode != MODE_HLS); Assertions.checkState(mode != MODE_HLS);
int timestampAdjustersCount = timestampAdjusters.size(); int timestampAdjustersCount = timestampAdjusters.size();
for (int i = 0; i < timestampAdjustersCount; i++) { for (int i = 0; i < timestampAdjustersCount; i++) {
timestampAdjusters.get(i).reset(); TimestampAdjuster timestampAdjuster = timestampAdjusters.get(i);
boolean hasNotEncounteredFirstTimestamp =
timestampAdjuster.getTimestampOffsetUs() == C.TIME_UNSET;
if (hasNotEncounteredFirstTimestamp
|| (timestampAdjuster.getTimestampOffsetUs() != 0
&& timestampAdjuster.getFirstSampleTimestampUs() != timeUs)) {
// - If a track in the TS stream has not encountered any sample, it's going to treat the
// first sample encountered as timestamp 0, which is incorrect. So we have to set the first
// sample timestamp for that track 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 (timeUs != 0 && tsBinarySearchSeeker != null) {
tsBinarySearchSeeker.setSeekTargetUs(timeUs);
} }
tsPacketBuffer.reset(); tsPacketBuffer.reset();
continuityCounters.clear(); continuityCounters.clear();
...@@ -227,11 +244,12 @@ public final class TsExtractor implements Extractor { ...@@ -227,11 +244,12 @@ public final class TsExtractor implements Extractor {
public @ReadResult int read(ExtractorInput input, PositionHolder seekPosition) public @ReadResult int read(ExtractorInput input, PositionHolder seekPosition)
throws IOException, InterruptedException { throws IOException, InterruptedException {
if (tracksEnded) { if (tracksEnded) {
boolean canReadDuration = input.getLength() != C.LENGTH_UNSET && mode != MODE_HLS; long inputLength = input.getLength();
boolean canReadDuration = inputLength != C.LENGTH_UNSET && mode != MODE_HLS;
if (canReadDuration && !durationReader.isDurationReadFinished()) { if (canReadDuration && !durationReader.isDurationReadFinished()) {
return durationReader.readDuration(input, seekPosition, pcrPid); return durationReader.readDuration(input, seekPosition, pcrPid);
} }
maybeOutputSeekMap(); maybeOutputSeekMap(inputLength);
if (pendingSeekToStart) { if (pendingSeekToStart) {
pendingSeekToStart = false; pendingSeekToStart = false;
...@@ -241,6 +259,11 @@ public final class TsExtractor implements Extractor { ...@@ -241,6 +259,11 @@ public final class TsExtractor implements Extractor {
return RESULT_SEEK; return RESULT_SEEK;
} }
} }
if (tsBinarySearchSeeker != null && tsBinarySearchSeeker.isSeeking()) {
return tsBinarySearchSeeker.handlePendingSeek(
input, seekPosition, /* outputFrameHolder= */ null);
}
} }
if (!fillBufferWithAtLeastOnePacket(input)) { if (!fillBufferWithAtLeastOnePacket(input)) {
...@@ -314,10 +337,20 @@ public final class TsExtractor implements Extractor { ...@@ -314,10 +337,20 @@ public final class TsExtractor 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) {
tsBinarySearchSeeker =
new TsBinarySearchSeeker(
durationReader.getPcrTimestampAdjuster(),
durationReader.getDurationUs(),
inputLength,
pcrPid);
output.seekMap(tsBinarySearchSeeker.getSeekMap());
} else {
output.seekMap(new SeekMap.Unseekable(durationReader.getDurationUs()));
}
} }
} }
...@@ -353,7 +386,7 @@ public final class TsExtractor implements Extractor { ...@@ -353,7 +386,7 @@ public final class TsExtractor implements Extractor {
private int findEndOfFirstTsPacketInBuffer() throws ParserException { private int findEndOfFirstTsPacketInBuffer() throws ParserException {
int searchStart = tsPacketBuffer.getPosition(); int searchStart = tsPacketBuffer.getPosition();
int limit = tsPacketBuffer.limit(); int limit = tsPacketBuffer.limit();
int syncBytePosition = findSyncBytePosition(tsPacketBuffer.data, searchStart, limit); int syncBytePosition = TsUtil.findSyncBytePosition(tsPacketBuffer.data, searchStart, limit);
// Discard all bytes before the sync byte. // Discard all bytes before the sync byte.
// If sync byte is not found, this means discard the whole buffer. // If sync byte is not found, this means discard the whole buffer.
tsPacketBuffer.setPosition(syncBytePosition); tsPacketBuffer.setPosition(syncBytePosition);
...@@ -370,18 +403,6 @@ public final class TsExtractor implements Extractor { ...@@ -370,18 +403,6 @@ public final class TsExtractor implements Extractor {
return endOfPacket; return endOfPacket;
} }
/**
* Returns the position of the first TS_SYNC_BYTE within the range [startPosition, limitPosition)
* from the provided data array, or returns limitPosition if sync byte could not be found.
*/
private static int findSyncBytePosition(byte[] data, int startPosition, int limitPosition) {
int position = startPosition;
while (position < limitPosition && data[position] != TS_SYNC_BYTE) {
position++;
}
return position;
}
private boolean shouldConsumePacketPayload(int packetPid) { private boolean shouldConsumePacketPayload(int packetPid) {
return mode == MODE_HLS return mode == MODE_HLS
|| tracksEnded || tracksEnded
......
/*
* 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.util.ParsableByteArray;
/** Utilities method for extracting MPEG-TS streams. */
public final class TsUtil {
/**
* Returns the position of the first TS_SYNC_BYTE within the range [startPosition, limitPosition)
* from the provided data array, or returns limitPosition if sync byte could not be found.
*/
public static int findSyncBytePosition(byte[] data, int startPosition, int limitPosition) {
int position = startPosition;
while (position < limitPosition && data[position] != TsExtractor.TS_SYNC_BYTE) {
position++;
}
return position;
}
/**
* Returns the PCR value read from a given TS packet.
*
* @param packetBuffer The buffer that holds the packet.
* @param startOfPacket The starting position of the packet in the buffer.
* @param pcrPid The PID for valid packets that contain PCR values.
* @return The PCR value read from the packet, if its PID is equal to {@code pcrPid} and it
* contains a valid PCR value. Returns {@link C#TIME_UNSET} otherwise.
*/
public static long readPcrFromPacket(
ParsableByteArray packetBuffer, int startOfPacket, int pcrPid) {
packetBuffer.setPosition(startOfPacket);
if (packetBuffer.bytesLeft() < 5) {
// Header = 4 bytes, adaptationFieldLength = 1 byte.
return C.TIME_UNSET;
}
// Note: See ISO/IEC 13818-1, section 2.4.3.2 for details of the header format.
int tsPacketHeader = packetBuffer.readInt();
if ((tsPacketHeader & 0x800000) != 0) {
// transport_error_indicator != 0 means there are uncorrectable errors in this packet.
return C.TIME_UNSET;
}
int pid = (tsPacketHeader & 0x1FFF00) >> 8;
if (pid != pcrPid) {
return C.TIME_UNSET;
}
boolean adaptationFieldExists = (tsPacketHeader & 0x20) != 0;
if (!adaptationFieldExists) {
return C.TIME_UNSET;
}
int adaptationFieldLength = packetBuffer.readUnsignedByte();
if (adaptationFieldLength >= 7 && packetBuffer.bytesLeft() >= 7) {
int flags = packetBuffer.readUnsignedByte();
boolean pcrFlagSet = (flags & 0x10) == 0x10;
if (pcrFlagSet) {
byte[] pcrBytes = new byte[6];
packetBuffer.readBytes(pcrBytes, /* offset= */ 0, pcrBytes.length);
return readPcrValueFromPcrBytes(pcrBytes);
}
}
return C.TIME_UNSET;
}
/**
* Returns the value of PCR base - first 33 bits in big endian order from the PCR bytes.
*
* <p>We ignore PCR Ext, because it's too small to have any significance.
*/
private static long readPcrValueFromPcrBytes(byte[] pcrBytes) {
return (pcrBytes[0] & 0xFFL) << 25
| (pcrBytes[1] & 0xFFL) << 17
| (pcrBytes[2] & 0xFFL) << 9
| (pcrBytes[3] & 0xFFL) << 1
| (pcrBytes[4] & 0xFFL) >> 7;
}
private TsUtil() {
// Prevent instantiation.
}
}
seekMap: seekMap:
isSeekable = false isSeekable = true
duration = 66733 duration = 66733
getPosition(0) = [[timeUs=0, position=0]] getPosition(0) = [[timeUs=0, position=0]]
numberOfTracks = 3 numberOfTracks = 3
......
seekMap:
isSeekable = true
duration = 66733
getPosition(0) = [[timeUs=0, position=0]]
numberOfTracks = 3
track 256:
format:
bitrate = -1
id = 1/256
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 CE183139
total output bytes = 24315
sample count = 1
sample 0:
time = 55611
flags = 0
data = length 18112, hash EC44B35B
track 257:
format:
bitrate = -1
id = 1/257
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 = und
drmInitData = -
initializationData:
total output bytes = 5015
sample count = 4
sample 0:
time = 11333
flags = 1
data = length 1253, hash 727FD1C6
sample 1:
time = 37455
flags = 1
data = length 1254, hash 73FB07B8
sample 2:
time = 63578
flags = 1
data = length 1254, hash 73FB07B8
sample 3:
time = 89700
flags = 1
data = length 1254, hash 73FB07B8
track 8448:
format:
bitrate = -1
id = 1/8448
containerMimeType = null
sampleMimeType = application/cea-608
maxInputSize = -1
width = -1
height = -1
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:
total output bytes = 0
sample count = 0
tracksEnded = true
seekMap:
isSeekable = true
duration = 66733
getPosition(0) = [[timeUs=0, position=0]]
numberOfTracks = 3
track 256:
format:
bitrate = -1
id = 1/256
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 CE183139
total output bytes = 24315
sample count = 1
sample 0:
time = 77855
flags = 0
data = length 18112, hash EC44B35B
track 257:
format:
bitrate = -1
id = 1/257
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 = und
drmInitData = -
initializationData:
total output bytes = 5015
sample count = 4
sample 0:
time = 33577
flags = 1
data = length 1253, hash 727FD1C6
sample 1:
time = 59699
flags = 1
data = length 1254, hash 73FB07B8
sample 2:
time = 85822
flags = 1
data = length 1254, hash 73FB07B8
sample 3:
time = 111944
flags = 1
data = length 1254, hash 73FB07B8
track 8448:
format:
bitrate = -1
id = 1/8448
containerMimeType = null
sampleMimeType = application/cea-608
maxInputSize = -1
width = -1
height = -1
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:
total output bytes = 0
sample count = 0
tracksEnded = true
seekMap:
isSeekable = true
duration = 66733
getPosition(0) = [[timeUs=0, position=0]]
numberOfTracks = 3
track 256:
format:
bitrate = -1
id = 1/256
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 CE183139
total output bytes = 0
sample count = 0
track 257:
format:
bitrate = -1
id = 1/257
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 = und
drmInitData = -
initializationData:
total output bytes = 2508
sample count = 2
sample 0:
time = 66733
flags = 1
data = length 1254, hash 73FB07B8
sample 1:
time = 92855
flags = 1
data = length 1254, hash 73FB07B8
track 8448:
format:
bitrate = -1
id = 1/8448
containerMimeType = null
sampleMimeType = application/cea-608
maxInputSize = -1
width = -1
height = -1
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:
total output bytes = 0
sample count = 0
tracksEnded = true
...@@ -90,7 +90,7 @@ public final class AdtsExtractorSeekTest { ...@@ -90,7 +90,7 @@ public final class AdtsExtractorSeekTest {
SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri); SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(0); FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(0);
long targetSeekTimeUs = 3330033; // 980_000; long targetSeekTimeUs = 980_000;
int extractedSampleIndex = int extractedSampleIndex =
TestUtil.seekToTimeUs( TestUtil.seekToTimeUs(
extractor, seekMap, targetSeekTimeUs, dataSource, trackOutput, fileUri); extractor, seekMap, targetSeekTimeUs, dataSource, trackOutput, fileUri);
......
...@@ -61,15 +61,21 @@ public final class TsExtractorTest { ...@@ -61,15 +61,21 @@ public final class TsExtractorTest {
} }
@Test @Test
public void testIncompleteSample() throws Exception { public void testStreamWithJunkData() throws Exception {
Random random = new Random(0); Random random = new Random(0);
byte[] fileData = TestUtil.getByteArray(RuntimeEnvironment.application, "ts/sample.ts"); byte[] fileData = TestUtil.getByteArray(RuntimeEnvironment.application, "ts/sample.ts");
ByteArrayOutputStream out = new ByteArrayOutputStream(fileData.length * 2); ByteArrayOutputStream out = new ByteArrayOutputStream(fileData.length * 2);
int bytesLeft = fileData.length;
writeJunkData(out, random.nextInt(TS_PACKET_SIZE - 1) + 1); writeJunkData(out, random.nextInt(TS_PACKET_SIZE - 1) + 1);
out.write(fileData, 0, TS_PACKET_SIZE * 5); out.write(fileData, 0, TS_PACKET_SIZE * 5);
for (int i = TS_PACKET_SIZE * 5; i < fileData.length; i += TS_PACKET_SIZE) { bytesLeft -= TS_PACKET_SIZE * 5;
for (int i = TS_PACKET_SIZE * 5; i < fileData.length; i += 5 * TS_PACKET_SIZE) {
writeJunkData(out, random.nextInt(TS_PACKET_SIZE)); writeJunkData(out, random.nextInt(TS_PACKET_SIZE));
out.write(fileData, i, TS_PACKET_SIZE); int length = Math.min(5 * TS_PACKET_SIZE, bytesLeft);
out.write(fileData, i, length);
bytesLeft -= length;
} }
out.write(TS_SYNC_BYTE); out.write(TS_SYNC_BYTE);
writeJunkData(out, random.nextInt(TS_PACKET_SIZE - 1) + 1); writeJunkData(out, random.nextInt(TS_PACKET_SIZE - 1) + 1);
......
...@@ -368,10 +368,13 @@ public class TestUtil { ...@@ -368,10 +368,13 @@ public class TestUtil {
} }
/** Returns an {@link ExtractorInput} to read from the given input at given position. */ /** Returns an {@link ExtractorInput} to read from the given input at given position. */
private static ExtractorInput getExtractorInputFromPosition( public static ExtractorInput getExtractorInputFromPosition(
DataSource dataSource, long position, Uri uri) throws IOException { DataSource dataSource, long position, Uri uri) throws IOException {
DataSpec dataSpec = new DataSpec(uri, position, C.LENGTH_UNSET, /* key= */ null); DataSpec dataSpec = new DataSpec(uri, position, C.LENGTH_UNSET, /* key= */ null);
long inputLength = dataSource.open(dataSpec); long length = dataSource.open(dataSpec);
return new DefaultExtractorInput(dataSource, position, inputLength); if (length != C.LENGTH_UNSET) {
length += position;
}
return new DefaultExtractorInput(dataSource, position, length);
} }
} }
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