Commit a22ccf92 by Oliver Woodman

Another baby step to unified extractors.

- Have extractors read from an ExtractorInput. Benefits of this are
  (a) The ability to do a "full" read or skip of a specified number
  of bytes, (b) The ability to do multiple reads in your extractor's
  read method. The ExtractorInput will throw an InterruptedException
  if the read has been canceled.

- Provides the extractor with the ability to query the absolute
  position of the data being read in the stream. This is needed for
  things like parsing a segment index in fragmented mp4, where the
  position of the end of the box in the stream is required because
  the index offsets are all specified relative to that position.
parent 1111dd73
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
*/ */
package com.google.android.exoplayer.hls; package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.hls.parser.DataSourceExtractorInput;
import com.google.android.exoplayer.hls.parser.HlsExtractor.ExtractorInput;
import com.google.android.exoplayer.hls.parser.HlsExtractorWrapper; import com.google.android.exoplayer.hls.parser.HlsExtractorWrapper;
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;
...@@ -26,8 +28,6 @@ import java.io.IOException; ...@@ -26,8 +28,6 @@ import java.io.IOException;
*/ */
public final class TsChunk extends HlsChunk { public final class TsChunk extends HlsChunk {
private static final byte[] SCRATCH_SPACE = new byte[4096];
/** /**
* The index of the variant in the master playlist. * The index of the variant in the master playlist.
*/ */
...@@ -102,30 +102,23 @@ public final class TsChunk extends HlsChunk { ...@@ -102,30 +102,23 @@ 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);
try { try {
dataSource.open(dataSpec); dataSource.open(dataSpec);
int bytesRead = 0;
int bytesSkipped = 0;
// 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
// do this is straightforward for non-encrypted content, but more complicated for content // do this is straightforward for non-encrypted content, but more complicated for content
// 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.
while (bytesRead != -1 && !loadCanceled && bytesSkipped < loadPosition) { input.skipFully(loadPosition);
int skipLength = Math.min(loadPosition - bytesSkipped, SCRATCH_SPACE.length); try {
bytesRead = dataSource.read(SCRATCH_SPACE, 0, skipLength); while (!input.isEnded() && !loadCanceled) {
if (bytesRead != -1) { extractor.read(input);
bytesSkipped += bytesRead;
}
}
// Feed the remaining data into the extractor.
while (bytesRead != -1 && !loadCanceled) {
bytesRead = extractor.read(dataSource);
if (bytesRead != -1) {
loadPosition += bytesRead;
} }
} finally {
loadPosition = (int) input.getPosition();
loadFinished = !loadCanceled;
} }
loadFinished = !loadCanceled;
} finally { } finally {
dataSource.close(); dataSource.close();
} }
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
*/ */
package com.google.android.exoplayer.hls.parser; package com.google.android.exoplayer.hls.parser;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
import java.io.IOException; import java.io.IOException;
...@@ -48,12 +47,13 @@ public class AdtsExtractor implements HlsExtractor { ...@@ -48,12 +47,13 @@ public class AdtsExtractor implements HlsExtractor {
} }
@Override @Override
public int read(DataSource dataSource) throws IOException { public void read(ExtractorInput input) throws IOException, InterruptedException {
int bytesRead = dataSource.read(packetBuffer.data, 0, MAX_PACKET_SIZE); int bytesRead = input.read(packetBuffer.data, 0, MAX_PACKET_SIZE);
if (bytesRead == -1) { if (bytesRead == -1) {
return -1; return;
} }
// Feed whatever data we have to the reader, regardless of whether the read finished or not.
packetBuffer.setPosition(0); packetBuffer.setPosition(0);
packetBuffer.setLimit(bytesRead); packetBuffer.setLimit(bytesRead);
...@@ -61,7 +61,6 @@ public class AdtsExtractor implements HlsExtractor { ...@@ -61,7 +61,6 @@ public class AdtsExtractor implements HlsExtractor {
// 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 bytesRead;
} }
} }
/*
* 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.hls.parser;
import com.google.android.exoplayer.hls.parser.HlsExtractor.ExtractorInput;
import com.google.android.exoplayer.upstream.DataSource;
import java.io.IOException;
/**
* An {@link ExtractorInput} that wraps a {@link DataSource}.
*/
public final class DataSourceExtractorInput implements ExtractorInput {
private static final byte[] SCRATCH_SPACE = new byte[4096];
private final DataSource dataSource;
private long position;
private boolean isEnded;
/**
* @param dataSource The wrapped {@link DataSource}.
* @param position The initial position in the stream.
*/
public DataSourceExtractorInput(DataSource dataSource, long position) {
this.dataSource = dataSource;
this.position = position;
}
@Override
public int read(byte[] target, int offset, int length) throws IOException, InterruptedException {
if (Thread.interrupted()) {
throw new InterruptedException();
}
int bytesRead = dataSource.read(target, offset, length);
if (bytesRead == -1) {
isEnded = true;
return -1;
}
position += bytesRead;
return bytesRead;
}
@Override
public boolean readFully(byte[] target, int offset, int length)
throws IOException, InterruptedException {
int remaining = length;
while (remaining > 0) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
int bytesRead = dataSource.read(target, offset, remaining);
if (bytesRead == -1) {
isEnded = true;
return false;
}
offset += bytesRead;
remaining -= bytesRead;
}
position += length;
return true;
}
@Override
public boolean skipFully(int length) throws IOException, InterruptedException {
int remaining = length;
while (remaining > 0) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
int bytesRead = dataSource.read(SCRATCH_SPACE, 0, remaining);
if (bytesRead == -1) {
isEnded = true;
return true;
}
remaining -= bytesRead;
}
position += length;
return false;
}
@Override
public long getPosition() {
return position;
}
@Override
public boolean isEnded() {
return isEnded;
}
}
...@@ -27,6 +27,72 @@ import java.io.IOException; ...@@ -27,6 +27,72 @@ import java.io.IOException;
public interface HlsExtractor { public interface HlsExtractor {
/** /**
* An object from which source data can be read.
*/
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 -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. * An object to which extracted data should be output.
*/ */
public interface TrackOutputBuilder { public interface TrackOutputBuilder {
...@@ -76,12 +142,12 @@ public interface HlsExtractor { ...@@ -76,12 +142,12 @@ public interface HlsExtractor {
void init(TrackOutputBuilder output); void init(TrackOutputBuilder output);
/** /**
* Reads up to a single TS packet. * Reads from the provided {@link ExtractorInput}.
* *
* @param dataSource The {@link DataSource} from which to read. * @param input The {@link ExtractorInput} from which to read.
* @throws IOException If an error occurred reading from the source. * @throws IOException If an error occurred reading from the source.
* @return The number of bytes read from the source. * @throws InterruptedException If the thread was interrupted.
*/ */
int read(DataSource dataSource) throws IOException; void read(ExtractorInput input) throws IOException, InterruptedException;
} }
...@@ -17,9 +17,9 @@ package com.google.android.exoplayer.hls.parser; ...@@ -17,9 +17,9 @@ package com.google.android.exoplayer.hls.parser;
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.hls.parser.HlsExtractor.ExtractorInput;
import com.google.android.exoplayer.hls.parser.HlsExtractor.TrackOutput; import com.google.android.exoplayer.hls.parser.HlsExtractor.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.util.Assertions; import com.google.android.exoplayer.util.Assertions;
import android.util.SparseArray; import android.util.SparseArray;
...@@ -125,7 +125,7 @@ public final class HlsExtractorWrapper implements HlsExtractor.TrackOutputBuilde ...@@ -125,7 +125,7 @@ public final class HlsExtractorWrapper implements HlsExtractor.TrackOutputBuilde
/** /**
* Releases the extractor, recycling any pending or incomplete samples to the sample pool. * Releases the extractor, recycling any pending or incomplete samples to the sample pool.
* <p> * <p>
* This method should not be called whilst {@link #read(DataSource)} is also being invoked. * This method should not be called whilst {@link #read(ExtractorInput)} is also being invoked.
*/ */
public void release() { public void release() {
for (int i = 0; i < sampleQueues.size(); i++) { for (int i = 0; i < sampleQueues.size(); i++) {
...@@ -183,14 +183,14 @@ public final class HlsExtractorWrapper implements HlsExtractor.TrackOutputBuilde ...@@ -183,14 +183,14 @@ public final class HlsExtractorWrapper implements HlsExtractor.TrackOutputBuilde
} }
/** /**
* Reads up to a single TS packet. * Reads from the provided {@link ExtractorInput}.
* *
* @param dataSource The {@link DataSource} from which to read. * @param input The {@link ExtractorInput} from which to read.
* @throws IOException If an error occurred reading from the source. * @throws IOException If an error occurred reading from the source.
* @return The number of bytes read from the source. * @throws InterruptedException If the thread was interrupted.
*/ */
public int read(DataSource dataSource) throws IOException { public void read(ExtractorInput input) throws IOException, InterruptedException {
return extractor.read(dataSource); extractor.read(input);
} }
// ExtractorOutput implementation. // ExtractorOutput implementation.
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
package com.google.android.exoplayer.hls.parser; package com.google.android.exoplayer.hls.parser;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.upstream.DataSource;
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;
...@@ -51,7 +50,6 @@ public final class TsExtractor implements HlsExtractor { ...@@ -51,7 +50,6 @@ public final class TsExtractor implements HlsExtractor {
// Accessed only by the loading thread. // Accessed only by the loading thread.
private TrackOutputBuilder output; private TrackOutputBuilder output;
private int tsPacketBytesRead;
private long timestampOffsetUs; private long timestampOffsetUs;
private long lastPts; private long lastPts;
...@@ -71,27 +69,15 @@ public final class TsExtractor implements HlsExtractor { ...@@ -71,27 +69,15 @@ public final class TsExtractor implements HlsExtractor {
} }
@Override @Override
public int read(DataSource dataSource) throws IOException { public void read(ExtractorInput input) throws IOException, InterruptedException {
int bytesRead = dataSource.read(tsPacketBuffer.data, tsPacketBytesRead, if (!input.readFully(tsPacketBuffer.data, 0, TS_PACKET_SIZE)) {
TS_PACKET_SIZE - tsPacketBytesRead); return;
if (bytesRead == -1) {
return -1;
} }
tsPacketBytesRead += bytesRead;
if (tsPacketBytesRead < TS_PACKET_SIZE) {
// We haven't read the whole packet yet.
return bytesRead;
}
// Reset before reading the packet.
tsPacketBytesRead = 0;
tsPacketBuffer.setPosition(0); tsPacketBuffer.setPosition(0);
tsPacketBuffer.setLimit(TS_PACKET_SIZE);
int syncByte = tsPacketBuffer.readUnsignedByte(); int syncByte = tsPacketBuffer.readUnsignedByte();
if (syncByte != TS_SYNC_BYTE) { if (syncByte != TS_SYNC_BYTE) {
return bytesRead; return;
} }
tsPacketBuffer.readBytes(tsScratch, 3); tsPacketBuffer.readBytes(tsScratch, 3);
...@@ -117,8 +103,6 @@ public final class TsExtractor implements HlsExtractor { ...@@ -117,8 +103,6 @@ public final class TsExtractor implements HlsExtractor {
payloadReader.consume(tsPacketBuffer, payloadUnitStartIndicator, output); payloadReader.consume(tsPacketBuffer, payloadUnitStartIndicator, output);
} }
} }
return bytesRead;
} }
/** /**
......
...@@ -55,13 +55,15 @@ public interface DataSource { ...@@ -55,13 +55,15 @@ public interface DataSource {
/** /**
* Reads up to {@code length} bytes of data and stores them into {@code buffer}, starting at * Reads up to {@code length} bytes of data and stores them into {@code buffer}, starting at
* index {@code offset}. This method blocks until at least one byte of data can be read, the end * index {@code offset}.
* of the opened range is detected, or an exception is thrown. * <p>
* This method blocks until at least one byte of data can be read, the end of the opened range is
* detected, or an exception is thrown.
* *
* @param buffer The buffer into which the read data should be stored. * @param buffer The buffer into which the read data should be stored.
* @param offset The start offset into {@code buffer} at which data should be written. * @param offset The start offset into {@code buffer} at which data should be written.
* @param readLength The maximum number of bytes to read. * @param readLength The maximum number of bytes to read.
* @return The actual number of bytes read, or -1 if the end of the opened range is reached. * @return The number of bytes read, or -1 if the end of the opened range is reached.
* @throws IOException If an error occurs reading from the source. * @throws IOException If an error occurs reading from the source.
*/ */
public int read(byte[] buffer, int offset, int readLength) throws IOException; public int read(byte[] buffer, int offset, int readLength) throws IOException;
......
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