Steps toward generalizing the Extractor interface for all extractors.
This commit is contained in:
parent
3a551c73ba
commit
37d12ff14a
@ -13,32 +13,35 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.extractor.ts;
|
package com.google.android.exoplayer.extractor;
|
||||||
|
|
||||||
import com.google.android.exoplayer.extractor.Extractor.ExtractorInput;
|
import com.google.android.exoplayer.C;
|
||||||
import com.google.android.exoplayer.upstream.DataSource;
|
import com.google.android.exoplayer.upstream.DataSource;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@link ExtractorInput} that wraps a {@link DataSource}.
|
* An {@link ExtractorInput} that wraps a {@link DataSource}.
|
||||||
*/
|
*/
|
||||||
public final class DataSourceExtractorInput implements ExtractorInput {
|
public final class DefaultExtractorInput implements ExtractorInput {
|
||||||
|
|
||||||
private static final byte[] SCRATCH_SPACE = new byte[4096];
|
private static final byte[] SCRATCH_SPACE = new byte[4096];
|
||||||
|
|
||||||
private final DataSource dataSource;
|
private final DataSource dataSource;
|
||||||
|
|
||||||
private long position;
|
private long position;
|
||||||
private boolean isEnded;
|
private long length;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param dataSource The wrapped {@link DataSource}.
|
* @param dataSource The wrapped {@link DataSource}.
|
||||||
* @param position The initial position in the stream.
|
* @param position The initial position in the stream.
|
||||||
|
* @param length The length of the stream, or {@link C#LENGTH_UNBOUNDED} if it is unknown.
|
||||||
*/
|
*/
|
||||||
public DataSourceExtractorInput(DataSource dataSource, long position) {
|
public DefaultExtractorInput(DataSource dataSource, long position, long length) {
|
||||||
this.dataSource = dataSource;
|
this.dataSource = dataSource;
|
||||||
this.position = position;
|
this.position = position;
|
||||||
|
this.length = length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -47,16 +50,15 @@ public final class DataSourceExtractorInput implements ExtractorInput {
|
|||||||
throw new InterruptedException();
|
throw new InterruptedException();
|
||||||
}
|
}
|
||||||
int bytesRead = dataSource.read(target, offset, length);
|
int bytesRead = dataSource.read(target, offset, length);
|
||||||
if (bytesRead == -1) {
|
if (bytesRead == C.RESULT_END_OF_INPUT) {
|
||||||
isEnded = true;
|
return C.RESULT_END_OF_INPUT;
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
position += bytesRead;
|
position += bytesRead;
|
||||||
return bytesRead;
|
return bytesRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean readFully(byte[] target, int offset, int length)
|
public boolean readFully(byte[] target, int offset, int length, boolean allowEndOfInput)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
int remaining = length;
|
int remaining = length;
|
||||||
while (remaining > 0) {
|
while (remaining > 0) {
|
||||||
@ -64,9 +66,11 @@ public final class DataSourceExtractorInput implements ExtractorInput {
|
|||||||
throw new InterruptedException();
|
throw new InterruptedException();
|
||||||
}
|
}
|
||||||
int bytesRead = dataSource.read(target, offset, remaining);
|
int bytesRead = dataSource.read(target, offset, remaining);
|
||||||
if (bytesRead == -1) {
|
if (bytesRead == C.RESULT_END_OF_INPUT) {
|
||||||
isEnded = true;
|
if (allowEndOfInput && remaining == length) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
throw new EOFException();
|
||||||
}
|
}
|
||||||
offset += bytesRead;
|
offset += bytesRead;
|
||||||
remaining -= bytesRead;
|
remaining -= bytesRead;
|
||||||
@ -76,21 +80,25 @@ public final class DataSourceExtractorInput implements ExtractorInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean skipFully(int length) throws IOException, InterruptedException {
|
public void readFully(byte[] target, int offset, int length)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
readFully(target, offset, length, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void skipFully(int length) throws IOException, InterruptedException {
|
||||||
int remaining = length;
|
int remaining = length;
|
||||||
while (remaining > 0) {
|
while (remaining > 0) {
|
||||||
if (Thread.interrupted()) {
|
if (Thread.interrupted()) {
|
||||||
throw new InterruptedException();
|
throw new InterruptedException();
|
||||||
}
|
}
|
||||||
int bytesRead = dataSource.read(SCRATCH_SPACE, 0, Math.min(SCRATCH_SPACE.length, remaining));
|
int bytesRead = dataSource.read(SCRATCH_SPACE, 0, Math.min(SCRATCH_SPACE.length, remaining));
|
||||||
if (bytesRead == -1) {
|
if (bytesRead == C.RESULT_END_OF_INPUT) {
|
||||||
isEnded = true;
|
throw new EOFException();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
remaining -= bytesRead;
|
remaining -= bytesRead;
|
||||||
}
|
}
|
||||||
position += length;
|
position += length;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -99,8 +107,8 @@ public final class DataSourceExtractorInput implements ExtractorInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEnded() {
|
public long getLength() {
|
||||||
return isEnded;
|
return length;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -17,7 +17,6 @@ package com.google.android.exoplayer.extractor;
|
|||||||
|
|
||||||
import com.google.android.exoplayer.MediaFormat;
|
import com.google.android.exoplayer.MediaFormat;
|
||||||
import com.google.android.exoplayer.SampleHolder;
|
import com.google.android.exoplayer.SampleHolder;
|
||||||
import com.google.android.exoplayer.extractor.Extractor.TrackOutput;
|
|
||||||
import com.google.android.exoplayer.upstream.BufferPool;
|
import com.google.android.exoplayer.upstream.BufferPool;
|
||||||
import com.google.android.exoplayer.upstream.DataSource;
|
import com.google.android.exoplayer.upstream.DataSource;
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
@ -29,7 +28,7 @@ import java.io.IOException;
|
|||||||
* the first sample returned from the queue is a keyframe, allowing splicing to another queue, and
|
* the first sample returned from the queue is a keyframe, allowing splicing to another queue, and
|
||||||
* so on.
|
* so on.
|
||||||
*/
|
*/
|
||||||
public final class SampleQueue implements TrackOutput {
|
public final class DefaultTrackOutput implements TrackOutput {
|
||||||
|
|
||||||
private final RollingSampleBuffer rollingBuffer;
|
private final RollingSampleBuffer rollingBuffer;
|
||||||
private final SampleHolder sampleInfoHolder;
|
private final SampleHolder sampleInfoHolder;
|
||||||
@ -39,14 +38,11 @@ public final class SampleQueue implements TrackOutput {
|
|||||||
private long lastReadTimeUs;
|
private long lastReadTimeUs;
|
||||||
private long spliceOutTimeUs;
|
private long spliceOutTimeUs;
|
||||||
|
|
||||||
// Accessed only by the loading thread.
|
|
||||||
private boolean writingSample;
|
|
||||||
|
|
||||||
// Accessed by both the loading and consuming threads.
|
// Accessed by both the loading and consuming threads.
|
||||||
private volatile long largestParsedTimestampUs;
|
private volatile long largestParsedTimestampUs;
|
||||||
private volatile MediaFormat format;
|
private volatile MediaFormat format;
|
||||||
|
|
||||||
public SampleQueue(BufferPool bufferPool) {
|
public DefaultTrackOutput(BufferPool bufferPool) {
|
||||||
rollingBuffer = new RollingSampleBuffer(bufferPool);
|
rollingBuffer = new RollingSampleBuffer(bufferPool);
|
||||||
sampleInfoHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DISABLED);
|
sampleInfoHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DISABLED);
|
||||||
needKeyframe = true;
|
needKeyframe = true;
|
||||||
@ -61,6 +57,13 @@ public final class SampleQueue implements TrackOutput {
|
|||||||
|
|
||||||
// Called by the consuming thread.
|
// Called by the consuming thread.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the output has received a format. False otherwise.
|
||||||
|
*/
|
||||||
|
public boolean hasFormat() {
|
||||||
|
return format != null;
|
||||||
|
}
|
||||||
|
|
||||||
public MediaFormat getFormat() {
|
public MediaFormat getFormat() {
|
||||||
return format;
|
return format;
|
||||||
}
|
}
|
||||||
@ -114,7 +117,7 @@ public final class SampleQueue implements TrackOutput {
|
|||||||
* @param nextQueue The queue being spliced to.
|
* @param nextQueue The queue being spliced to.
|
||||||
* @return Whether the splice was configured successfully.
|
* @return Whether the splice was configured successfully.
|
||||||
*/
|
*/
|
||||||
public boolean configureSpliceTo(SampleQueue nextQueue) {
|
public boolean configureSpliceTo(DefaultTrackOutput nextQueue) {
|
||||||
if (spliceOutTimeUs != Long.MIN_VALUE) {
|
if (spliceOutTimeUs != Long.MIN_VALUE) {
|
||||||
// We've already configured the splice.
|
// We've already configured the splice.
|
||||||
return true;
|
return true;
|
||||||
@ -164,44 +167,29 @@ public final class SampleQueue implements TrackOutput {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called by the loading thread.
|
||||||
|
|
||||||
|
public int sampleData(DataSource dataSource, int length) throws IOException {
|
||||||
|
return rollingBuffer.appendData(dataSource, length);
|
||||||
|
}
|
||||||
|
|
||||||
// TrackOutput implementation. Called by the loading thread.
|
// TrackOutput implementation. Called by the loading thread.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasFormat() {
|
public void format(MediaFormat format) {
|
||||||
return format != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setFormat(MediaFormat format) {
|
|
||||||
this.format = format;
|
this.format = format;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int appendData(DataSource dataSource, int length) throws IOException {
|
public void sampleData(ParsableByteArray buffer, int length) {
|
||||||
return rollingBuffer.appendData(dataSource, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void appendData(ParsableByteArray buffer, int length) {
|
|
||||||
rollingBuffer.appendData(buffer, length);
|
rollingBuffer.appendData(buffer, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startSample(long sampleTimeUs, int offset) {
|
public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) {
|
||||||
writingSample = true;
|
largestParsedTimestampUs = Math.max(largestParsedTimestampUs, timeUs);
|
||||||
largestParsedTimestampUs = Math.max(largestParsedTimestampUs, sampleTimeUs);
|
rollingBuffer.commitSample(timeUs, flags, rollingBuffer.getWritePosition() - size - offset,
|
||||||
rollingBuffer.startSample(sampleTimeUs, offset);
|
size, encryptionKey);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void commitSample(int flags, int offset, byte[] encryptionKey) {
|
|
||||||
rollingBuffer.commitSample(flags, offset, encryptionKey);
|
|
||||||
writingSample = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isWritingSample() {
|
|
||||||
return writingSample;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -15,139 +15,44 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.extractor;
|
package com.google.android.exoplayer.extractor;
|
||||||
|
|
||||||
import com.google.android.exoplayer.MediaFormat;
|
import com.google.android.exoplayer.C;
|
||||||
import com.google.android.exoplayer.upstream.DataSource;
|
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Facilitates extraction of media samples for HLS playbacks.
|
* Facilitates extraction of data from a container format.
|
||||||
*/
|
*/
|
||||||
public interface Extractor {
|
public interface Extractor {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An object from which source data can be read.
|
* Returned by {@link #read(ExtractorInput)} if the {@link ExtractorInput} passed to the next
|
||||||
|
* {@link #read(ExtractorInput)} is required to provide data continuing from the position in the
|
||||||
|
* stream reached by the returning call.
|
||||||
*/
|
*/
|
||||||
public interface ExtractorInput {
|
public static final int RESULT_CONTINUE = 0;
|
||||||
|
/**
|
||||||
/**
|
* Returned by {@link #read(ExtractorInput)} if the end of the {@link ExtractorInput} was reached.
|
||||||
* Reads up to {@code length} bytes from the input.
|
* Equal to {@link C#RESULT_END_OF_INPUT}.
|
||||||
* <p>
|
*/
|
||||||
* This method blocks until at least one byte of data can be read, the end of the input is
|
public static final int RESULT_END_OF_INPUT = C.RESULT_END_OF_INPUT;
|
||||||
* detected, or an exception is thrown.
|
|
||||||
*
|
|
||||||
* @param target A target array into which data should be written.
|
|
||||||
* @param offset The offset into the target array at which to write.
|
|
||||||
* @param length The maximum number of bytes to read from the input.
|
|
||||||
* @return The number of bytes read, or -1 if the input has ended.
|
|
||||||
* @throws IOException If an error occurs reading from the input.
|
|
||||||
* @throws InterruptedException If the thread has been interrupted.
|
|
||||||
*/
|
|
||||||
int read(byte[] target, int offset, int length) throws IOException, InterruptedException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Like {@link #read(byte[], int, int)}, but guaranteed to read request {@code length} in full
|
|
||||||
* unless the end of the input is detected, or an exception is thrown.
|
|
||||||
*
|
|
||||||
* TODO: Firm up behavior of this method if (a) zero bytes are read before EOS, (b) the read
|
|
||||||
* is partially satisfied before EOS.
|
|
||||||
*
|
|
||||||
* @param target A target array into which data should be written.
|
|
||||||
* @param offset The offset into the target array at which to write.
|
|
||||||
* @param length The number of bytes to read from the input.
|
|
||||||
* @return True if the read was successful. False if the end of the input was reached.
|
|
||||||
* @throws IOException If an error occurs reading from the input.
|
|
||||||
* @throws InterruptedException If the thread has been interrupted.
|
|
||||||
*/
|
|
||||||
boolean readFully(byte[] target, int offset, int length)
|
|
||||||
throws IOException, InterruptedException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Like {@link #readFully(byte[], int, int)}, except the data is skipped instead of read.
|
|
||||||
*
|
|
||||||
* TODO: Firm up behavior of this method if (a) zero bytes are skipped before EOS, (b) the skip
|
|
||||||
* is partially satisfied before EOS.
|
|
||||||
*
|
|
||||||
* @param length The number of bytes to skip from the input.
|
|
||||||
* @return True if the read was successful. False if the end of the input was reached.
|
|
||||||
* @throws IOException If an error occurs reading from the input.
|
|
||||||
* @throws InterruptedException If the thread is interrupted.
|
|
||||||
*/
|
|
||||||
boolean skipFully(int length) throws IOException, InterruptedException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current position in the stream.
|
|
||||||
*
|
|
||||||
* @return The position in the stream.
|
|
||||||
*/
|
|
||||||
long getPosition();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not the input has ended.
|
|
||||||
*
|
|
||||||
* @return True if the input has ended. False otherwise.
|
|
||||||
*/
|
|
||||||
boolean isEnded();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An object to which extracted data should be output.
|
* Initializes the extractor with an {@link ExtractorOutput}.
|
||||||
*/
|
|
||||||
public interface TrackOutputBuilder {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked to build a {@link TrackOutput} to which data should be output for a given track.
|
|
||||||
*
|
|
||||||
* @param trackId A stable track id.
|
|
||||||
* @return The corresponding {@link TrackOutput}.
|
|
||||||
*/
|
|
||||||
TrackOutput buildOutput(int trackId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked when all {@link TrackOutput}s have been built, meaning {@link #buildOutput(int)}
|
|
||||||
* will not be invoked again.
|
|
||||||
*/
|
|
||||||
void allOutputsBuilt();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An object to which extracted data belonging to a given track should be output.
|
|
||||||
*/
|
|
||||||
public interface TrackOutput {
|
|
||||||
|
|
||||||
boolean hasFormat();
|
|
||||||
|
|
||||||
void setFormat(MediaFormat format);
|
|
||||||
|
|
||||||
boolean isWritingSample();
|
|
||||||
|
|
||||||
int appendData(DataSource dataSource, int length) throws IOException;
|
|
||||||
|
|
||||||
void appendData(ParsableByteArray data, int length);
|
|
||||||
|
|
||||||
void startSample(long timeUs, int offset);
|
|
||||||
|
|
||||||
void commitSample(int flags, int offset, byte[] encryptionKey);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the extractor.
|
|
||||||
*
|
*
|
||||||
* @param output A {@link TrackOutputBuilder} to which extracted data should be output.
|
* @param output An {@link ExtractorOutput} to receive extracted data.
|
||||||
*/
|
*/
|
||||||
void init(TrackOutputBuilder output);
|
void init(ExtractorOutput output);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads from the provided {@link ExtractorInput}.
|
* Extracts data read from a provided {@link ExtractorInput}.
|
||||||
|
* <p>
|
||||||
|
* Each read will extract at most one sample from the stream before returning.
|
||||||
*
|
*
|
||||||
* @param input The {@link ExtractorInput} from which to read.
|
* @param input The {@link ExtractorInput} from which data should be read.
|
||||||
* @throws IOException If an error occurred reading from the source.
|
* @return One of the {@code RESULT_} values defined in this interface.
|
||||||
|
* @throws IOException If an error occurred reading from the input.
|
||||||
* @throws InterruptedException If the thread was interrupted.
|
* @throws InterruptedException If the thread was interrupted.
|
||||||
*/
|
*/
|
||||||
void read(ExtractorInput input) throws IOException, InterruptedException;
|
int read(ExtractorInput input) throws IOException, InterruptedException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 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.exoplayer.extractor;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.C;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides data to be consumed by an {@link Extractor}.
|
||||||
|
*/
|
||||||
|
public interface ExtractorInput {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads up to {@code length} bytes from the input.
|
||||||
|
* <p>
|
||||||
|
* This method blocks until at least one byte of data can be read, the end of the input is
|
||||||
|
* detected, or an exception is thrown.
|
||||||
|
*
|
||||||
|
* @param target A target array into which data should be written.
|
||||||
|
* @param offset The offset into the target array at which to write.
|
||||||
|
* @param length The maximum number of bytes to read from the input.
|
||||||
|
* @return The number of bytes read, or {@link C#RESULT_END_OF_INPUT} if the input has ended.
|
||||||
|
* @throws IOException If an error occurs reading from the input.
|
||||||
|
* @throws InterruptedException If the thread has been interrupted.
|
||||||
|
*/
|
||||||
|
int read(byte[] target, int offset, int length) throws IOException, InterruptedException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #read(byte[], int, int)}, but reads the requested {@code length} in full.
|
||||||
|
* <p>
|
||||||
|
* If the end of the input is found having read no data, then behavior is dependent on
|
||||||
|
* {@code allowEndOfInput}. If {@code allowEndOfInput == true} then {@code false} is returned.
|
||||||
|
* Otherwise an {@link EOFException} is thrown.
|
||||||
|
* <p>
|
||||||
|
* Encountering the end of input having partially satisfied the read is always considered an
|
||||||
|
* error, and will result in an {@link EOFException} being thrown.
|
||||||
|
*
|
||||||
|
* @param target A target array into which data should be written.
|
||||||
|
* @param offset The offset into the target array at which to write.
|
||||||
|
* @param length The number of bytes to read from the input.
|
||||||
|
* @param allowEndOfInput True if encountering the end of the input having read no data is
|
||||||
|
* allowed, and should result in {@code false} being returned. False if it should be
|
||||||
|
* considered an error, causing an {@link EOFException} to be thrown.
|
||||||
|
* @return True if the read was successful. False if the end of the input was encountered having
|
||||||
|
* read no data.
|
||||||
|
* @throws EOFException If the end of input was encountered having partially satisfied the read
|
||||||
|
* (i.e. having read at least one byte, but fewer than {@code length}), or if no bytes were
|
||||||
|
* read and {@code allowEndOfInput} is false.
|
||||||
|
* @throws IOException If an error occurs reading from the input.
|
||||||
|
* @throws InterruptedException If the thread has been interrupted.
|
||||||
|
*/
|
||||||
|
boolean readFully(byte[] target, int offset, int length, boolean allowEndOfInput)
|
||||||
|
throws IOException, InterruptedException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equivalent to {@code readFully(target, offset, length, false)}.
|
||||||
|
*
|
||||||
|
* @param target A target array into which data should be written.
|
||||||
|
* @param offset The offset into the target array at which to write.
|
||||||
|
* @param length The number of bytes to read from the input.
|
||||||
|
* @throws EOFException If the end of input was encountered.
|
||||||
|
* @throws IOException If an error occurs reading from the input.
|
||||||
|
* @throws InterruptedException If the thread is interrupted.
|
||||||
|
*/
|
||||||
|
void readFully(byte[] target, int offset, int length) throws IOException, InterruptedException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #readFully(byte[], int, int)}, except the data is skipped instead of read.
|
||||||
|
* <p>
|
||||||
|
* Encountering the end of input is always considered an error, and will result in an
|
||||||
|
* {@link EOFException} being thrown.
|
||||||
|
*
|
||||||
|
* @param length The number of bytes to skip from the input.
|
||||||
|
* @throws EOFException If the end of input was encountered.
|
||||||
|
* @throws IOException If an error occurs reading from the input.
|
||||||
|
* @throws InterruptedException If the thread is interrupted.
|
||||||
|
*/
|
||||||
|
void skipFully(int length) throws IOException, InterruptedException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current position (byte offset) in the stream.
|
||||||
|
*
|
||||||
|
* @return The position (byte offset) in the stream.
|
||||||
|
*/
|
||||||
|
long getPosition();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the length of the source stream, or {@link C#LENGTH_UNBOUNDED} if it is unknown.
|
||||||
|
*
|
||||||
|
* @return The length of the source stream, or {@link C#LENGTH_UNBOUNDED}.
|
||||||
|
*/
|
||||||
|
long getLength();
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 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.exoplayer.extractor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives stream level data extracted by an {@link Extractor}.
|
||||||
|
*/
|
||||||
|
public interface ExtractorOutput {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when the {@link Extractor} identifies the existence of a track in the stream.
|
||||||
|
* <p>
|
||||||
|
* Returns a {@link TrackOutput} that will receive track level data belonging to the track.
|
||||||
|
*
|
||||||
|
* @param trackId A track identifier.
|
||||||
|
* @return The {@link TrackOutput} that should receive track level data belonging to the track.
|
||||||
|
*/
|
||||||
|
TrackOutput track(int trackId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when all tracks have been identified, meaning that {@link #track(int)} will not be
|
||||||
|
* invoked again.
|
||||||
|
*/
|
||||||
|
void endTracks();
|
||||||
|
|
||||||
|
}
|
@ -19,7 +19,6 @@ import com.google.android.exoplayer.C;
|
|||||||
import com.google.android.exoplayer.SampleHolder;
|
import com.google.android.exoplayer.SampleHolder;
|
||||||
import com.google.android.exoplayer.upstream.BufferPool;
|
import com.google.android.exoplayer.upstream.BufferPool;
|
||||||
import com.google.android.exoplayer.upstream.DataSource;
|
import com.google.android.exoplayer.upstream.DataSource;
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -48,8 +47,6 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
|||||||
private long totalBytesWritten;
|
private long totalBytesWritten;
|
||||||
private byte[] lastFragment;
|
private byte[] lastFragment;
|
||||||
private int lastFragmentOffset;
|
private int lastFragmentOffset;
|
||||||
private long pendingSampleTimeUs;
|
|
||||||
private long pendingSampleOffset;
|
|
||||||
|
|
||||||
public RollingSampleBuffer(BufferPool bufferPool) {
|
public RollingSampleBuffer(BufferPool bufferPool) {
|
||||||
this.fragmentPool = bufferPool;
|
this.fragmentPool = bufferPool;
|
||||||
@ -71,7 +68,8 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
|||||||
/**
|
/**
|
||||||
* Fills {@code holder} with information about the current sample, but does not write its data.
|
* Fills {@code holder} with information about the current sample, but does not write its data.
|
||||||
* <p>
|
* <p>
|
||||||
* The fields set are {SampleHolder#size}, {SampleHolder#timeUs} and {SampleHolder#flags}.
|
* The fields set are {@link SampleHolder#size}, {@link SampleHolder#timeUs} and
|
||||||
|
* {@link SampleHolder#flags}.
|
||||||
*
|
*
|
||||||
* @param holder The holder into which the current sample information should be written.
|
* @param holder The holder into which the current sample information should be written.
|
||||||
* @return True if the holder was filled. False if there is no current sample.
|
* @return True if the holder was filled. False if there is no current sample.
|
||||||
@ -92,10 +90,15 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
|||||||
* Reads the current sample, advancing the read index to the next sample.
|
* Reads the current sample, advancing the read index to the next sample.
|
||||||
*
|
*
|
||||||
* @param sampleHolder The holder into which the current sample should be written.
|
* @param sampleHolder The holder into which the current sample should be written.
|
||||||
|
* @return True if a sample was read. False if there is no current sample.
|
||||||
*/
|
*/
|
||||||
public void readSample(SampleHolder sampleHolder) {
|
public boolean readSample(SampleHolder sampleHolder) {
|
||||||
// Write the sample information into the holder and extrasHolder.
|
// Write the sample information into the holder and extrasHolder.
|
||||||
infoQueue.peekSample(sampleHolder, extrasHolder);
|
boolean haveSample = infoQueue.peekSample(sampleHolder, extrasHolder);
|
||||||
|
if (!haveSample) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Read encryption data if the sample is encrypted.
|
// Read encryption data if the sample is encrypted.
|
||||||
if (sampleHolder.isEncrypted()) {
|
if (sampleHolder.isEncrypted()) {
|
||||||
readEncryptionData(sampleHolder, extrasHolder);
|
readEncryptionData(sampleHolder, extrasHolder);
|
||||||
@ -110,6 +113,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
|||||||
// Advance the read head.
|
// Advance the read head.
|
||||||
long nextOffset = infoQueue.moveToNextSample();
|
long nextOffset = infoQueue.moveToNextSample();
|
||||||
dropFragmentsTo(nextOffset);
|
dropFragmentsTo(nextOffset);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -250,16 +254,12 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
|||||||
// Called by the loading thread.
|
// Called by the loading thread.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates the start point for the next sample.
|
* Returns the current write position in the rolling buffer.
|
||||||
*
|
*
|
||||||
* @param sampleTimeUs The sample timestamp.
|
* @return The current write position.
|
||||||
* @param offset The offset of the sample's data, relative to the total number of bytes written
|
|
||||||
* to the buffer. Must be negative or zero.
|
|
||||||
*/
|
*/
|
||||||
public void startSample(long sampleTimeUs, int offset) {
|
public long getWritePosition() {
|
||||||
Assertions.checkState(offset <= 0);
|
return totalBytesWritten;
|
||||||
pendingSampleTimeUs = sampleTimeUs;
|
|
||||||
pendingSampleOffset = totalBytesWritten + offset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -314,16 +314,15 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
|||||||
/**
|
/**
|
||||||
* Indicates the end point for the current sample, making it available for consumption.
|
* Indicates the end point for the current sample, making it available for consumption.
|
||||||
*
|
*
|
||||||
|
* @param sampleTimeUs The sample timestamp.
|
||||||
* @param flags Flags that accompany the sample. See {@link SampleHolder#flags}.
|
* @param flags Flags that accompany the sample. See {@link SampleHolder#flags}.
|
||||||
* @param offset The offset of the first byte after the end of the sample's data, relative to
|
* @param position The position of the sample data in the rolling buffer.
|
||||||
* the total number of bytes written to the buffer. Must be negative or zero.
|
* @param size The size of the sample, in bytes.
|
||||||
* @param encryptionKey The encryption key associated with the sample, or null.
|
* @param encryptionKey The encryption key associated with the sample, or null.
|
||||||
*/
|
*/
|
||||||
public void commitSample(int flags, int offset, byte[] encryptionKey) {
|
public void commitSample(long sampleTimeUs, int flags, long position, int size,
|
||||||
Assertions.checkState(offset <= 0);
|
byte[] encryptionKey) {
|
||||||
int sampleSize = (int) (totalBytesWritten + offset - pendingSampleOffset);
|
infoQueue.commitSample(sampleTimeUs, flags, position, size, encryptionKey);
|
||||||
infoQueue.commitSample(pendingSampleTimeUs, pendingSampleOffset, sampleSize, flags,
|
|
||||||
encryptionKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -398,7 +397,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
|||||||
|
|
||||||
// Called by the loading thread.
|
// Called by the loading thread.
|
||||||
|
|
||||||
public synchronized void commitSample(long timeUs, long offset, int size, int sampleFlags,
|
public synchronized void commitSample(long timeUs, int sampleFlags, long offset, int size,
|
||||||
byte[] encryptionKey) {
|
byte[] encryptionKey) {
|
||||||
timesUs[writeIndex] = timeUs;
|
timesUs[writeIndex] = timeUs;
|
||||||
offsets[writeIndex] = offset;
|
offsets[writeIndex] = offset;
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 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.exoplayer.extractor;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.MediaFormat;
|
||||||
|
import com.google.android.exoplayer.SampleHolder;
|
||||||
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives track level data extracted by an {@link Extractor}.
|
||||||
|
*/
|
||||||
|
public interface TrackOutput {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when the {@link MediaFormat} of the track has been extracted from the stream.
|
||||||
|
*
|
||||||
|
* @param format The extracted {@link MediaFormat}.
|
||||||
|
*/
|
||||||
|
void format(MediaFormat format);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked to write sample data to the output.
|
||||||
|
*
|
||||||
|
* @param data A {@link ParsableByteArray} from which to read the sample data.
|
||||||
|
* @param length The number of bytes to read.
|
||||||
|
*/
|
||||||
|
void sampleData(ParsableByteArray data, int length);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when metadata associated with a sample has been extracted from the stream.
|
||||||
|
* <p>
|
||||||
|
* The corresponding sample data will have already been passed to the output via calls to
|
||||||
|
* {@link #sampleData(ParsableByteArray, int)}.
|
||||||
|
*
|
||||||
|
* @param timeUs The media timestamp associated with the sample, in microseconds.
|
||||||
|
* @param flags Flags associated with the sample. See {@link SampleHolder#flags}.
|
||||||
|
* @param size The size of the sample data, in bytes.
|
||||||
|
* @param offset The number of bytes that have been passed to
|
||||||
|
* {@link #sampleData(ParsableByteArray, int)} since the last byte belonging to the sample
|
||||||
|
* whose metadata is being passed.
|
||||||
|
* @param encryptionKey The encryption key associated with the sample. May be null.
|
||||||
|
*/
|
||||||
|
void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey);
|
||||||
|
|
||||||
|
}
|
@ -16,6 +16,8 @@
|
|||||||
package com.google.android.exoplayer.extractor.ts;
|
package com.google.android.exoplayer.extractor.ts;
|
||||||
|
|
||||||
import com.google.android.exoplayer.extractor.Extractor;
|
import com.google.android.exoplayer.extractor.Extractor;
|
||||||
|
import com.google.android.exoplayer.extractor.ExtractorInput;
|
||||||
|
import com.google.android.exoplayer.extractor.ExtractorOutput;
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -42,16 +44,17 @@ public class AdtsExtractor implements Extractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(TrackOutputBuilder output) {
|
public void init(ExtractorOutput output) {
|
||||||
adtsReader = new AdtsReader(output.buildOutput(0));
|
adtsReader = new AdtsReader(output.track(0));
|
||||||
output.allOutputsBuilt();
|
output.endTracks();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void read(ExtractorInput input) throws IOException, InterruptedException {
|
public int read(ExtractorInput input)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
int bytesRead = input.read(packetBuffer.data, 0, MAX_PACKET_SIZE);
|
int bytesRead = input.read(packetBuffer.data, 0, MAX_PACKET_SIZE);
|
||||||
if (bytesRead == -1) {
|
if (bytesRead == -1) {
|
||||||
return;
|
return RESULT_END_OF_INPUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Feed whatever data we have to the reader, regardless of whether the read finished or not.
|
// Feed whatever data we have to the reader, regardless of whether the read finished or not.
|
||||||
@ -62,6 +65,7 @@ public class AdtsExtractor implements Extractor {
|
|||||||
// unnecessary to copy the data through packetBuffer.
|
// unnecessary to copy the data through packetBuffer.
|
||||||
adtsReader.consume(packetBuffer, firstSampleTimestamp, firstPacket);
|
adtsReader.consume(packetBuffer, firstSampleTimestamp, firstPacket);
|
||||||
firstPacket = false;
|
firstPacket = false;
|
||||||
|
return RESULT_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ package com.google.android.exoplayer.extractor.ts;
|
|||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.C;
|
||||||
import com.google.android.exoplayer.MediaFormat;
|
import com.google.android.exoplayer.MediaFormat;
|
||||||
import com.google.android.exoplayer.extractor.Extractor.TrackOutput;
|
import com.google.android.exoplayer.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
|
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
import com.google.android.exoplayer.util.ParsableBitArray;
|
import com.google.android.exoplayer.util.ParsableBitArray;
|
||||||
@ -48,7 +48,8 @@ import java.util.Collections;
|
|||||||
private boolean lastByteWasFF;
|
private boolean lastByteWasFF;
|
||||||
private boolean hasCrc;
|
private boolean hasCrc;
|
||||||
|
|
||||||
// Parsed from the header.
|
// Used when parsing the header.
|
||||||
|
private boolean hasOutputFormat;
|
||||||
private long frameDurationUs;
|
private long frameDurationUs;
|
||||||
private int sampleSize;
|
private int sampleSize;
|
||||||
|
|
||||||
@ -78,17 +79,16 @@ import java.util.Collections;
|
|||||||
int targetLength = hasCrc ? HEADER_SIZE + CRC_SIZE : HEADER_SIZE;
|
int targetLength = hasCrc ? HEADER_SIZE + CRC_SIZE : HEADER_SIZE;
|
||||||
if (continueRead(data, adtsScratch.getData(), targetLength)) {
|
if (continueRead(data, adtsScratch.getData(), targetLength)) {
|
||||||
parseHeader();
|
parseHeader();
|
||||||
output.startSample(timeUs, 0);
|
|
||||||
bytesRead = 0;
|
bytesRead = 0;
|
||||||
state = STATE_READING_SAMPLE;
|
state = STATE_READING_SAMPLE;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case STATE_READING_SAMPLE:
|
case STATE_READING_SAMPLE:
|
||||||
int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead);
|
int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead);
|
||||||
output.appendData(data, bytesToRead);
|
output.sampleData(data, bytesToRead);
|
||||||
bytesRead += bytesToRead;
|
bytesRead += bytesToRead;
|
||||||
if (bytesRead == sampleSize) {
|
if (bytesRead == sampleSize) {
|
||||||
output.commitSample(C.SAMPLE_FLAG_SYNC, 0, null);
|
output.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null);
|
||||||
timeUs += frameDurationUs;
|
timeUs += frameDurationUs;
|
||||||
bytesRead = 0;
|
bytesRead = 0;
|
||||||
state = STATE_FINDING_SYNC;
|
state = STATE_FINDING_SYNC;
|
||||||
@ -152,7 +152,7 @@ import java.util.Collections;
|
|||||||
private void parseHeader() {
|
private void parseHeader() {
|
||||||
adtsScratch.setPosition(0);
|
adtsScratch.setPosition(0);
|
||||||
|
|
||||||
if (!output.hasFormat()) {
|
if (!hasOutputFormat) {
|
||||||
int audioObjectType = adtsScratch.readBits(2) + 1;
|
int audioObjectType = adtsScratch.readBits(2) + 1;
|
||||||
int sampleRateIndex = adtsScratch.readBits(4);
|
int sampleRateIndex = adtsScratch.readBits(4);
|
||||||
adtsScratch.skipBits(1);
|
adtsScratch.skipBits(1);
|
||||||
@ -167,7 +167,8 @@ import java.util.Collections;
|
|||||||
MediaFormat.NO_VALUE, audioParams.second, audioParams.first,
|
MediaFormat.NO_VALUE, audioParams.second, audioParams.first,
|
||||||
Collections.singletonList(audioSpecificConfig));
|
Collections.singletonList(audioSpecificConfig));
|
||||||
frameDurationUs = (C.MICROS_PER_SECOND * 1024L) / mediaFormat.sampleRate;
|
frameDurationUs = (C.MICROS_PER_SECOND * 1024L) / mediaFormat.sampleRate;
|
||||||
output.setFormat(mediaFormat);
|
output.format(mediaFormat);
|
||||||
|
hasOutputFormat = true;
|
||||||
} else {
|
} else {
|
||||||
adtsScratch.skipBits(10);
|
adtsScratch.skipBits(10);
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.extractor.ts;
|
package com.google.android.exoplayer.extractor.ts;
|
||||||
|
|
||||||
import com.google.android.exoplayer.extractor.Extractor.TrackOutput;
|
import com.google.android.exoplayer.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -17,7 +17,7 @@ package com.google.android.exoplayer.extractor.ts;
|
|||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.C;
|
||||||
import com.google.android.exoplayer.MediaFormat;
|
import com.google.android.exoplayer.MediaFormat;
|
||||||
import com.google.android.exoplayer.extractor.Extractor.TrackOutput;
|
import com.google.android.exoplayer.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
import com.google.android.exoplayer.util.H264Util;
|
import com.google.android.exoplayer.util.H264Util;
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
@ -46,9 +46,15 @@ import java.util.List;
|
|||||||
private final NalUnitTargetBuffer sei;
|
private final NalUnitTargetBuffer sei;
|
||||||
private final ParsableByteArray seiWrapper;
|
private final ParsableByteArray seiWrapper;
|
||||||
|
|
||||||
|
private boolean hasOutputFormat;
|
||||||
private int scratchEscapeCount;
|
private int scratchEscapeCount;
|
||||||
private int[] scratchEscapePositions;
|
private int[] scratchEscapePositions;
|
||||||
|
|
||||||
|
private boolean writingSample;
|
||||||
private boolean isKeyframe;
|
private boolean isKeyframe;
|
||||||
|
private long samplePosition;
|
||||||
|
private long sampleTimeUs;
|
||||||
|
private long totalBytesWritten;
|
||||||
|
|
||||||
public H264Reader(TrackOutput output, SeiReader seiReader) {
|
public H264Reader(TrackOutput output, SeiReader seiReader) {
|
||||||
super(output);
|
super(output);
|
||||||
@ -69,7 +75,8 @@ import java.util.List;
|
|||||||
byte[] dataArray = data.data;
|
byte[] dataArray = data.data;
|
||||||
|
|
||||||
// Append the data to the buffer.
|
// Append the data to the buffer.
|
||||||
output.appendData(data, data.bytesLeft());
|
totalBytesWritten += data.bytesLeft();
|
||||||
|
output.sampleData(data, data.bytesLeft());
|
||||||
|
|
||||||
// Scan the appended data, processing NAL units as they are encountered
|
// Scan the appended data, processing NAL units as they are encountered
|
||||||
while (offset < limit) {
|
while (offset < limit) {
|
||||||
@ -85,15 +92,20 @@ import java.util.List;
|
|||||||
}
|
}
|
||||||
|
|
||||||
int nalUnitType = H264Util.getNalUnitType(dataArray, nextNalUnitOffset);
|
int nalUnitType = H264Util.getNalUnitType(dataArray, nextNalUnitOffset);
|
||||||
int nalUnitOffsetInData = nextNalUnitOffset - limit;
|
int bytesWrittenPastNalUnit = limit - nextNalUnitOffset;
|
||||||
if (nalUnitType == NAL_UNIT_TYPE_AUD) {
|
if (nalUnitType == NAL_UNIT_TYPE_AUD) {
|
||||||
if (output.isWritingSample()) {
|
if (writingSample) {
|
||||||
if (isKeyframe && !output.hasFormat() && sps.isCompleted() && pps.isCompleted()) {
|
if (isKeyframe && !hasOutputFormat && sps.isCompleted() && pps.isCompleted()) {
|
||||||
parseMediaFormat(sps, pps);
|
parseMediaFormat(sps, pps);
|
||||||
}
|
}
|
||||||
output.commitSample(isKeyframe ? C.SAMPLE_FLAG_SYNC : 0, nalUnitOffsetInData, null);
|
int flags = isKeyframe ? C.SAMPLE_FLAG_SYNC : 0;
|
||||||
|
int size = (int) (totalBytesWritten - samplePosition) - bytesWrittenPastNalUnit;
|
||||||
|
output.sampleMetadata(sampleTimeUs, flags, size, bytesWrittenPastNalUnit, null);
|
||||||
|
writingSample = false;
|
||||||
}
|
}
|
||||||
output.startSample(pesTimeUs, nalUnitOffsetInData);
|
writingSample = true;
|
||||||
|
samplePosition = totalBytesWritten - bytesWrittenPastNalUnit;
|
||||||
|
sampleTimeUs = pesTimeUs;
|
||||||
isKeyframe = false;
|
isKeyframe = false;
|
||||||
} else if (nalUnitType == NAL_UNIT_TYPE_IDR) {
|
} else if (nalUnitType == NAL_UNIT_TYPE_IDR) {
|
||||||
isKeyframe = true;
|
isKeyframe = true;
|
||||||
@ -120,7 +132,7 @@ import java.util.List;
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void feedNalUnitTargetBuffersStart(int nalUnitType) {
|
private void feedNalUnitTargetBuffersStart(int nalUnitType) {
|
||||||
if (!output.hasFormat()) {
|
if (!hasOutputFormat) {
|
||||||
sps.startNalUnit(nalUnitType);
|
sps.startNalUnit(nalUnitType);
|
||||||
pps.startNalUnit(nalUnitType);
|
pps.startNalUnit(nalUnitType);
|
||||||
}
|
}
|
||||||
@ -128,7 +140,7 @@ import java.util.List;
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void feedNalUnitTargetBuffersData(byte[] dataArray, int offset, int limit) {
|
private void feedNalUnitTargetBuffersData(byte[] dataArray, int offset, int limit) {
|
||||||
if (!output.hasFormat()) {
|
if (!hasOutputFormat) {
|
||||||
sps.appendToNalUnit(dataArray, offset, limit);
|
sps.appendToNalUnit(dataArray, offset, limit);
|
||||||
pps.appendToNalUnit(dataArray, offset, limit);
|
pps.appendToNalUnit(dataArray, offset, limit);
|
||||||
}
|
}
|
||||||
@ -232,9 +244,9 @@ import java.util.List;
|
|||||||
frameHeight -= (frameCropTopOffset + frameCropBottomOffset) * cropUnitY;
|
frameHeight -= (frameCropTopOffset + frameCropBottomOffset) * cropUnitY;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the format.
|
output.format(MediaFormat.createVideoFormat(MimeTypes.VIDEO_H264, MediaFormat.NO_VALUE,
|
||||||
output.setFormat(MediaFormat.createVideoFormat(MimeTypes.VIDEO_H264, MediaFormat.NO_VALUE,
|
C.UNKNOWN_TIME_US, frameWidth, frameHeight, initializationData));
|
||||||
frameWidth, frameHeight, initializationData));
|
hasOutputFormat = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void skipScalingList(ParsableBitArray bitArray, int size) {
|
private void skipScalingList(ParsableBitArray bitArray, int size) {
|
||||||
|
@ -17,7 +17,7 @@ package com.google.android.exoplayer.extractor.ts;
|
|||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.C;
|
||||||
import com.google.android.exoplayer.MediaFormat;
|
import com.google.android.exoplayer.MediaFormat;
|
||||||
import com.google.android.exoplayer.extractor.Extractor.TrackOutput;
|
import com.google.android.exoplayer.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -25,24 +25,32 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
*/
|
*/
|
||||||
/* package */ class Id3Reader extends ElementaryStreamReader {
|
/* package */ class Id3Reader extends ElementaryStreamReader {
|
||||||
|
|
||||||
|
private boolean writingSample;
|
||||||
|
private long sampleTimeUs;
|
||||||
|
private int sampleSize;
|
||||||
|
|
||||||
public Id3Reader(TrackOutput output) {
|
public Id3Reader(TrackOutput output) {
|
||||||
super(output);
|
super(output);
|
||||||
output.setFormat(MediaFormat.createId3Format());
|
output.format(MediaFormat.createId3Format());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) {
|
public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) {
|
||||||
if (startOfPacket) {
|
if (startOfPacket) {
|
||||||
output.startSample(pesTimeUs, 0);
|
writingSample = true;
|
||||||
|
sampleTimeUs = pesTimeUs;
|
||||||
|
sampleSize = 0;
|
||||||
}
|
}
|
||||||
if (output.isWritingSample()) {
|
if (writingSample) {
|
||||||
output.appendData(data, data.bytesLeft());
|
sampleSize += data.bytesLeft();
|
||||||
|
output.sampleData(data, data.bytesLeft());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void packetFinished() {
|
public void packetFinished() {
|
||||||
output.commitSample(C.SAMPLE_FLAG_SYNC, 0, null);
|
output.sampleMetadata(sampleTimeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null);
|
||||||
|
writingSample = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ package com.google.android.exoplayer.extractor.ts;
|
|||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.C;
|
||||||
import com.google.android.exoplayer.MediaFormat;
|
import com.google.android.exoplayer.MediaFormat;
|
||||||
import com.google.android.exoplayer.extractor.Extractor.TrackOutput;
|
import com.google.android.exoplayer.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer.text.eia608.Eia608Parser;
|
import com.google.android.exoplayer.text.eia608.Eia608Parser;
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
|
|
||||||
public SeiReader(TrackOutput output) {
|
public SeiReader(TrackOutput output) {
|
||||||
super(output);
|
super(output);
|
||||||
output.setFormat(MediaFormat.createEia608Format());
|
output.format(MediaFormat.createEia608Format());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -55,9 +55,8 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
} while (b == 0xFF);
|
} while (b == 0xFF);
|
||||||
// Process the payload. We only support EIA-608 payloads currently.
|
// Process the payload. We only support EIA-608 payloads currently.
|
||||||
if (Eia608Parser.isSeiMessageEia608(payloadType, payloadSize, seiBuffer)) {
|
if (Eia608Parser.isSeiMessageEia608(payloadType, payloadSize, seiBuffer)) {
|
||||||
output.startSample(pesTimeUs, 0);
|
output.sampleData(seiBuffer, payloadSize);
|
||||||
output.appendData(seiBuffer, payloadSize);
|
output.sampleMetadata(pesTimeUs, C.SAMPLE_FLAG_SYNC, payloadSize, 0, null);
|
||||||
output.commitSample(C.SAMPLE_FLAG_SYNC, 0, null);
|
|
||||||
} else {
|
} else {
|
||||||
seiBuffer.skip(payloadSize);
|
seiBuffer.skip(payloadSize);
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,8 @@ package com.google.android.exoplayer.extractor.ts;
|
|||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.C;
|
||||||
import com.google.android.exoplayer.extractor.Extractor;
|
import com.google.android.exoplayer.extractor.Extractor;
|
||||||
|
import com.google.android.exoplayer.extractor.ExtractorInput;
|
||||||
|
import com.google.android.exoplayer.extractor.ExtractorOutput;
|
||||||
import com.google.android.exoplayer.util.ParsableBitArray;
|
import com.google.android.exoplayer.util.ParsableBitArray;
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
|
||||||
@ -50,7 +52,7 @@ public final class TsExtractor implements Extractor {
|
|||||||
private final ParsableBitArray tsScratch;
|
private final ParsableBitArray tsScratch;
|
||||||
|
|
||||||
// Accessed only by the loading thread.
|
// Accessed only by the loading thread.
|
||||||
private TrackOutputBuilder output;
|
private ExtractorOutput output;
|
||||||
private long timestampOffsetUs;
|
private long timestampOffsetUs;
|
||||||
private long lastPts;
|
private long lastPts;
|
||||||
|
|
||||||
@ -65,21 +67,22 @@ public final class TsExtractor implements Extractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(TrackOutputBuilder output) {
|
public void init(ExtractorOutput output) {
|
||||||
this.output = output;
|
this.output = output;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void read(ExtractorInput input) throws IOException, InterruptedException {
|
public int read(ExtractorInput input)
|
||||||
if (!input.readFully(tsPacketBuffer.data, 0, TS_PACKET_SIZE)) {
|
throws IOException, InterruptedException {
|
||||||
return;
|
if (!input.readFully(tsPacketBuffer.data, 0, TS_PACKET_SIZE, true)) {
|
||||||
|
return RESULT_END_OF_INPUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
tsPacketBuffer.setPosition(0);
|
tsPacketBuffer.setPosition(0);
|
||||||
tsPacketBuffer.setLimit(TS_PACKET_SIZE);
|
tsPacketBuffer.setLimit(TS_PACKET_SIZE);
|
||||||
int syncByte = tsPacketBuffer.readUnsignedByte();
|
int syncByte = tsPacketBuffer.readUnsignedByte();
|
||||||
if (syncByte != TS_SYNC_BYTE) {
|
if (syncByte != TS_SYNC_BYTE) {
|
||||||
return;
|
return RESULT_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
tsPacketBuffer.readBytes(tsScratch, 3);
|
tsPacketBuffer.readBytes(tsScratch, 3);
|
||||||
@ -105,6 +108,8 @@ public final class TsExtractor implements Extractor {
|
|||||||
payloadReader.consume(tsPacketBuffer, payloadUnitStartIndicator, output);
|
payloadReader.consume(tsPacketBuffer, payloadUnitStartIndicator, output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return RESULT_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -140,7 +145,7 @@ public final class TsExtractor implements Extractor {
|
|||||||
private abstract static class TsPayloadReader {
|
private abstract static class TsPayloadReader {
|
||||||
|
|
||||||
public abstract void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
|
public abstract void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
|
||||||
TrackOutputBuilder output);
|
ExtractorOutput output);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +162,7 @@ public final class TsExtractor implements Extractor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
|
public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
|
||||||
TrackOutputBuilder output) {
|
ExtractorOutput output) {
|
||||||
// Skip pointer.
|
// Skip pointer.
|
||||||
if (payloadUnitStartIndicator) {
|
if (payloadUnitStartIndicator) {
|
||||||
int pointerField = data.readUnsignedByte();
|
int pointerField = data.readUnsignedByte();
|
||||||
@ -197,7 +202,7 @@ public final class TsExtractor implements Extractor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
|
public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
|
||||||
TrackOutputBuilder output) {
|
ExtractorOutput output) {
|
||||||
// Skip pointer.
|
// Skip pointer.
|
||||||
if (payloadUnitStartIndicator) {
|
if (payloadUnitStartIndicator) {
|
||||||
int pointerField = data.readUnsignedByte();
|
int pointerField = data.readUnsignedByte();
|
||||||
@ -241,16 +246,16 @@ public final class TsExtractor implements Extractor {
|
|||||||
ElementaryStreamReader pesPayloadReader = null;
|
ElementaryStreamReader pesPayloadReader = null;
|
||||||
switch (streamType) {
|
switch (streamType) {
|
||||||
case TS_STREAM_TYPE_AAC:
|
case TS_STREAM_TYPE_AAC:
|
||||||
pesPayloadReader = new AdtsReader(output.buildOutput(TS_STREAM_TYPE_AAC));
|
pesPayloadReader = new AdtsReader(output.track(TS_STREAM_TYPE_AAC));
|
||||||
break;
|
break;
|
||||||
case TS_STREAM_TYPE_H264:
|
case TS_STREAM_TYPE_H264:
|
||||||
SeiReader seiReader = new SeiReader(output.buildOutput(TS_STREAM_TYPE_EIA608));
|
SeiReader seiReader = new SeiReader(output.track(TS_STREAM_TYPE_EIA608));
|
||||||
streamReaders.put(TS_STREAM_TYPE_EIA608, seiReader);
|
streamReaders.put(TS_STREAM_TYPE_EIA608, seiReader);
|
||||||
pesPayloadReader = new H264Reader(output.buildOutput(TS_STREAM_TYPE_H264),
|
pesPayloadReader = new H264Reader(output.track(TS_STREAM_TYPE_H264),
|
||||||
seiReader);
|
seiReader);
|
||||||
break;
|
break;
|
||||||
case TS_STREAM_TYPE_ID3:
|
case TS_STREAM_TYPE_ID3:
|
||||||
pesPayloadReader = new Id3Reader(output.buildOutput(TS_STREAM_TYPE_ID3));
|
pesPayloadReader = new Id3Reader(output.track(TS_STREAM_TYPE_ID3));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,7 +265,7 @@ public final class TsExtractor implements Extractor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
output.allOutputsBuilt();
|
output.endTracks();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -300,7 +305,7 @@ public final class TsExtractor implements Extractor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
|
public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
|
||||||
TrackOutputBuilder output) {
|
ExtractorOutput output) {
|
||||||
if (payloadUnitStartIndicator) {
|
if (payloadUnitStartIndicator) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case STATE_FINDING_HEADER:
|
case STATE_FINDING_HEADER:
|
||||||
|
@ -17,10 +17,11 @@ package com.google.android.exoplayer.hls;
|
|||||||
|
|
||||||
import com.google.android.exoplayer.MediaFormat;
|
import com.google.android.exoplayer.MediaFormat;
|
||||||
import com.google.android.exoplayer.SampleHolder;
|
import com.google.android.exoplayer.SampleHolder;
|
||||||
|
import com.google.android.exoplayer.extractor.DefaultTrackOutput;
|
||||||
import com.google.android.exoplayer.extractor.Extractor;
|
import com.google.android.exoplayer.extractor.Extractor;
|
||||||
import com.google.android.exoplayer.extractor.SampleQueue;
|
import com.google.android.exoplayer.extractor.ExtractorInput;
|
||||||
import com.google.android.exoplayer.extractor.Extractor.ExtractorInput;
|
import com.google.android.exoplayer.extractor.ExtractorOutput;
|
||||||
import com.google.android.exoplayer.extractor.Extractor.TrackOutput;
|
import com.google.android.exoplayer.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer.upstream.BufferPool;
|
import com.google.android.exoplayer.upstream.BufferPool;
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
|
|
||||||
@ -31,25 +32,24 @@ import java.io.IOException;
|
|||||||
/**
|
/**
|
||||||
* Wraps a {@link Extractor}, adding functionality to enable reading of the extracted samples.
|
* Wraps a {@link Extractor}, adding functionality to enable reading of the extracted samples.
|
||||||
*/
|
*/
|
||||||
public final class HlsExtractorWrapper implements Extractor.TrackOutputBuilder {
|
public final class HlsExtractorWrapper implements ExtractorOutput {
|
||||||
|
|
||||||
private final BufferPool bufferPool;
|
private final BufferPool bufferPool;
|
||||||
private final Extractor extractor;
|
private final Extractor extractor;
|
||||||
private final SparseArray<SampleQueue> sampleQueues;
|
private final SparseArray<DefaultTrackOutput> sampleQueues;
|
||||||
private final boolean shouldSpliceIn;
|
private final boolean shouldSpliceIn;
|
||||||
|
|
||||||
private volatile boolean outputsBuilt;
|
private volatile boolean tracksBuilt;
|
||||||
|
|
||||||
// Accessed only by the consuming thread.
|
// Accessed only by the consuming thread.
|
||||||
private boolean prepared;
|
private boolean prepared;
|
||||||
private boolean spliceConfigured;
|
private boolean spliceConfigured;
|
||||||
|
|
||||||
public HlsExtractorWrapper(BufferPool bufferPool, Extractor extractor,
|
public HlsExtractorWrapper(BufferPool bufferPool, Extractor extractor, boolean shouldSpliceIn) {
|
||||||
boolean shouldSpliceIn) {
|
|
||||||
this.bufferPool = bufferPool;
|
this.bufferPool = bufferPool;
|
||||||
this.extractor = extractor;
|
this.extractor = extractor;
|
||||||
this.shouldSpliceIn = shouldSpliceIn;
|
this.shouldSpliceIn = shouldSpliceIn;
|
||||||
sampleQueues = new SparseArray<SampleQueue>();
|
sampleQueues = new SparseArray<DefaultTrackOutput>();
|
||||||
extractor.init(this);
|
extractor.init(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,8 +76,8 @@ public final class HlsExtractorWrapper implements Extractor.TrackOutputBuilder {
|
|||||||
boolean spliceConfigured = true;
|
boolean spliceConfigured = true;
|
||||||
int trackCount = getTrackCount();
|
int trackCount = getTrackCount();
|
||||||
for (int i = 0; i < trackCount; i++) {
|
for (int i = 0; i < trackCount; i++) {
|
||||||
SampleQueue currentSampleQueue = sampleQueues.valueAt(i);
|
DefaultTrackOutput currentSampleQueue = sampleQueues.valueAt(i);
|
||||||
SampleQueue nextSampleQueue = nextExtractor.sampleQueues.valueAt(i);
|
DefaultTrackOutput nextSampleQueue = nextExtractor.sampleQueues.valueAt(i);
|
||||||
spliceConfigured &= currentSampleQueue.configureSpliceTo(nextSampleQueue);
|
spliceConfigured &= currentSampleQueue.configureSpliceTo(nextSampleQueue);
|
||||||
}
|
}
|
||||||
this.spliceConfigured = spliceConfigured;
|
this.spliceConfigured = spliceConfigured;
|
||||||
@ -113,7 +113,7 @@ public final class HlsExtractorWrapper implements Extractor.TrackOutputBuilder {
|
|||||||
* @return True if the extractor is prepared. False otherwise.
|
* @return True if the extractor is prepared. False otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean isPrepared() {
|
public boolean isPrepared() {
|
||||||
if (!prepared && outputsBuilt) {
|
if (!prepared && tracksBuilt) {
|
||||||
for (int i = 0; i < sampleQueues.size(); i++) {
|
for (int i = 0; i < sampleQueues.size(); i++) {
|
||||||
if (!sampleQueues.valueAt(i).hasFormat()) {
|
if (!sampleQueues.valueAt(i).hasFormat()) {
|
||||||
return false;
|
return false;
|
||||||
@ -188,25 +188,27 @@ public final class HlsExtractorWrapper implements Extractor.TrackOutputBuilder {
|
|||||||
* Reads from the provided {@link ExtractorInput}.
|
* Reads from the provided {@link ExtractorInput}.
|
||||||
*
|
*
|
||||||
* @param input The {@link ExtractorInput} from which to read.
|
* @param input The {@link ExtractorInput} from which to read.
|
||||||
|
* @return One of {@link Extractor#RESULT_CONTINUE} and {@link Extractor#RESULT_END_OF_INPUT}.
|
||||||
* @throws IOException If an error occurred reading from the source.
|
* @throws IOException If an error occurred reading from the source.
|
||||||
* @throws InterruptedException If the thread was interrupted.
|
* @throws InterruptedException If the thread was interrupted.
|
||||||
*/
|
*/
|
||||||
public void read(ExtractorInput input) throws IOException, InterruptedException {
|
public int read(ExtractorInput input) throws IOException, InterruptedException {
|
||||||
extractor.read(input);
|
int result = extractor.read(input);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtractorOutput implementation.
|
// ExtractorOutput implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TrackOutput buildOutput(int id) {
|
public TrackOutput track(int id) {
|
||||||
SampleQueue sampleQueue = new SampleQueue(bufferPool);
|
DefaultTrackOutput sampleQueue = new DefaultTrackOutput(bufferPool);
|
||||||
sampleQueues.put(id, sampleQueue);
|
sampleQueues.put(id, sampleQueue);
|
||||||
return sampleQueue;
|
return sampleQueue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void allOutputsBuilt() {
|
public void endTracks() {
|
||||||
this.outputsBuilt = true;
|
this.tracksBuilt = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.hls;
|
package com.google.android.exoplayer.hls;
|
||||||
|
|
||||||
import com.google.android.exoplayer.extractor.Extractor.ExtractorInput;
|
import com.google.android.exoplayer.extractor.DefaultExtractorInput;
|
||||||
import com.google.android.exoplayer.extractor.ts.DataSourceExtractorInput;
|
import com.google.android.exoplayer.extractor.Extractor;
|
||||||
|
import com.google.android.exoplayer.extractor.ExtractorInput;
|
||||||
import com.google.android.exoplayer.upstream.DataSource;
|
import com.google.android.exoplayer.upstream.DataSource;
|
||||||
import com.google.android.exoplayer.upstream.DataSpec;
|
import com.google.android.exoplayer.upstream.DataSpec;
|
||||||
|
|
||||||
@ -101,9 +102,9 @@ public final class TsChunk extends HlsChunk {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void load() throws IOException, InterruptedException {
|
public void load() throws IOException, InterruptedException {
|
||||||
ExtractorInput input = new DataSourceExtractorInput(dataSource, 0);
|
ExtractorInput input;
|
||||||
try {
|
try {
|
||||||
dataSource.open(dataSpec);
|
input = new DefaultExtractorInput(dataSource, 0, dataSource.open(dataSpec));
|
||||||
// If we previously fed part of this chunk to the extractor, skip it this time.
|
// If we previously fed part of this chunk to the extractor, skip it this time.
|
||||||
// TODO: Ideally we'd construct a dataSpec that only loads the remainder of the data here,
|
// TODO: Ideally we'd construct a dataSpec that only loads the remainder of the data here,
|
||||||
// rather than loading the whole chunk again and then skipping data we previously loaded. To
|
// rather than loading the whole chunk again and then skipping data we previously loaded. To
|
||||||
@ -111,12 +112,12 @@ public final class TsChunk extends HlsChunk {
|
|||||||
// encrypted with AES, for which we'll need to modify the way that decryption is performed.
|
// encrypted with AES, for which we'll need to modify the way that decryption is performed.
|
||||||
input.skipFully(loadPosition);
|
input.skipFully(loadPosition);
|
||||||
try {
|
try {
|
||||||
while (!input.isEnded() && !loadCanceled) {
|
int result = Extractor.RESULT_CONTINUE;
|
||||||
extractor.read(input);
|
while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
|
||||||
|
result = extractor.read(input);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
loadPosition = (int) input.getPosition();
|
loadPosition = (int) input.getPosition();
|
||||||
loadFinished = !loadCanceled;
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
dataSource.close();
|
dataSource.close();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user