Skip to content

Commit

Permalink
Add support for choosing an extractor based on sniffing the container.
Browse files Browse the repository at this point in the history
- ExtractorSampleSource takes an array of extractors to test for suitability.
- Extractors now implement a sniff() method that returns whether they can
  extract samples in the input stream's format.
- Switch demo app samples to use format detection.

Issue: #438
  • Loading branch information
ojw28 committed Jul 21, 2015
1 parent 87daa91 commit 85e0bca
Show file tree
Hide file tree
Showing 17 changed files with 732 additions and 103 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,6 @@
import com.google.android.exoplayer.demo.player.HlsRendererBuilder;
import com.google.android.exoplayer.demo.player.SmoothStreamingRendererBuilder;
import com.google.android.exoplayer.drm.UnsupportedDrmException;
import com.google.android.exoplayer.extractor.mp3.Mp3Extractor;
import com.google.android.exoplayer.extractor.mp4.FragmentedMp4Extractor;
import com.google.android.exoplayer.extractor.mp4.Mp4Extractor;
import com.google.android.exoplayer.extractor.ts.AdtsExtractor;
import com.google.android.exoplayer.extractor.ts.TsExtractor;
import com.google.android.exoplayer.extractor.webm.WebmExtractor;
import com.google.android.exoplayer.metadata.GeobMetadata;
import com.google.android.exoplayer.metadata.PrivMetadata;
import com.google.android.exoplayer.metadata.TxxxMetadata;
Expand Down Expand Up @@ -84,14 +78,7 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
public static final int TYPE_DASH = 0;
public static final int TYPE_SS = 1;
public static final int TYPE_HLS = 2;
public static final int TYPE_MP4 = 3;
public static final int TYPE_MP3 = 4;
public static final int TYPE_FMP4 = 5;
public static final int TYPE_WEBM = 6;
public static final int TYPE_MKV = 7;
public static final int TYPE_TS = 8;
public static final int TYPE_AAC = 9;
public static final int TYPE_M4A = 10;
public static final int TYPE_OTHER = 3;

public static final String CONTENT_TYPE_EXTRA = "content_type";
public static final String CONTENT_ID_EXTRA = "content_id";
Expand Down Expand Up @@ -257,22 +244,8 @@ private RendererBuilder getRendererBuilder() {
new WidevineTestMediaDrmCallback(contentId), audioCapabilities);
case TYPE_HLS:
return new HlsRendererBuilder(this, userAgent, contentUri.toString(), audioCapabilities);
case TYPE_M4A: // There are no file format differences between M4A and MP4.
case TYPE_MP4:
return new ExtractorRendererBuilder(this, userAgent, contentUri, new Mp4Extractor());
case TYPE_MP3:
return new ExtractorRendererBuilder(this, userAgent, contentUri, new Mp3Extractor());
case TYPE_TS:
return new ExtractorRendererBuilder(this, userAgent, contentUri,
new TsExtractor(0, audioCapabilities));
case TYPE_AAC:
return new ExtractorRendererBuilder(this, userAgent, contentUri, new AdtsExtractor());
case TYPE_FMP4:
return new ExtractorRendererBuilder(this, userAgent, contentUri,
new FragmentedMp4Extractor());
case TYPE_WEBM:
case TYPE_MKV:
return new ExtractorRendererBuilder(this, userAgent, contentUri, new WebmExtractor());
case TYPE_OTHER:
return new ExtractorRendererBuilder(this, userAgent, contentUri);
default:
throw new IllegalStateException("Unsupported type: " + contentType);
}
Expand Down
21 changes: 8 additions & 13 deletions demo/src/main/java/com/google/android/exoplayer/demo/Samples.java
Original file line number Diff line number Diff line change
Expand Up @@ -128,28 +128,23 @@ public Sample(String name, String contentId, String uri, int type) {
};

public static final Sample[] MISC = new Sample[] {
new Sample("Dizzy", "http://html5demos.com/assets/dizzy.mp4",
PlayerActivity.TYPE_MP4),
new Sample("Dizzy", "http://html5demos.com/assets/dizzy.mp4", PlayerActivity.TYPE_OTHER),
new Sample("Apple AAC 10s", "https://devimages.apple.com.edgekey.net/"
+ "streaming/examples/bipbop_4x3/gear0/fileSequence0.aac",
PlayerActivity.TYPE_AAC),
+ "streaming/examples/bipbop_4x3/gear0/fileSequence0.aac", PlayerActivity.TYPE_OTHER),
new Sample("Apple TS 10s", "https://devimages.apple.com.edgekey.net/streaming/examples/"
+ "bipbop_4x3/gear1/fileSequence0.ts",
PlayerActivity.TYPE_TS),
+ "bipbop_4x3/gear1/fileSequence0.ts", PlayerActivity.TYPE_OTHER),
new Sample("Android screens (Matroska)", "http://storage.googleapis.com/exoplayer-test-media-1/"
+ "mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv", PlayerActivity.TYPE_MKV),
+ "mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
PlayerActivity.TYPE_OTHER),
new Sample("Big Buck Bunny (MP4 Video)",
"http://redirector.c.youtube.com/videoplayback?id=604ed5ce52eda7ee&itag=22&source=youtube&"
+ "sparams=ip,ipbits,expire,source,id&ip=0.0.0.0&ipbits=0&expire=19000000000&signature="
+ "513F28C7FDCBEC60A66C86C9A393556C99DC47FB.04C88036EEE12565A1ED864A875A58F15D8B5300"
+ "&key=ik0",
PlayerActivity.TYPE_MP4),
+ "&key=ik0", PlayerActivity.TYPE_OTHER),
new Sample("Google Play (MP3 Audio)",
"http://storage.googleapis.com/exoplayer-test-media-0/play.mp3",
PlayerActivity.TYPE_MP3),
"http://storage.googleapis.com/exoplayer-test-media-0/play.mp3", PlayerActivity.TYPE_OTHER),
new Sample("Google Glass (WebM Video with Vorbis Audio)",
"http://demos.webmproject.org/exoplayer/glass_vp9_vorbis.webm",
PlayerActivity.TYPE_WEBM),
"http://demos.webmproject.org/exoplayer/glass_vp9_vorbis.webm", PlayerActivity.TYPE_OTHER),
};

private Samples() {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,11 @@ public class ExtractorRendererBuilder implements RendererBuilder {
private final Context context;
private final String userAgent;
private final Uri uri;
private final Extractor extractor;

public ExtractorRendererBuilder(Context context, String userAgent, Uri uri, Extractor extractor) {
public ExtractorRendererBuilder(Context context, String userAgent, Uri uri) {
this.context = context;
this.userAgent = userAgent;
this.uri = uri;
this.extractor = extractor;
}

@Override
Expand All @@ -61,8 +59,8 @@ public void buildRenderers(DemoPlayer player, RendererBuilderCallback callback)
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(player.getMainHandler(),
null);
DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
ExtractorSampleSource sampleSource = new ExtractorSampleSource(uri, dataSource, extractor,
allocator, BUFFER_SEGMENT_COUNT * BUFFER_SEGMENT_SIZE);
ExtractorSampleSource sampleSource = new ExtractorSampleSource(uri, dataSource, allocator,
BUFFER_SEGMENT_COUNT * BUFFER_SEGMENT_SIZE);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
null, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, player.getMainHandler(),
player, 50);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@ private void startBasicPlayback() {
ExtractorSampleSource sampleSource = new ExtractorSampleSource(
Uri.fromFile(new File(filename)),
new DefaultUriDataSource(this, Util.getUserAgent(this, "ExoPlayerExtWebMDemo")),
new WebmExtractor(), new DefaultAllocator(BUFFER_SEGMENT_SIZE),
BUFFER_SEGMENT_SIZE * BUFFER_SEGMENT_COUNT);
new DefaultAllocator(BUFFER_SEGMENT_SIZE), BUFFER_SEGMENT_SIZE * BUFFER_SEGMENT_COUNT,
new WebmExtractor());
TrackRenderer videoRenderer =
new LibvpxVideoTrackRenderer(sampleSource, true, handler, this, 50);
if (useOpenGL) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import com.google.android.exoplayer.SampleSource.SampleSourceReader;
import com.google.android.exoplayer.drm.DrmInitData;
import com.google.android.exoplayer.extractor.ExtractorSampleSource;
import com.google.android.exoplayer.extractor.mp4.Mp4Extractor;
import com.google.android.exoplayer.extractor.mp4.PsshAtomUtil;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.MimeTypes;
Expand All @@ -39,19 +38,12 @@
* <p>
* Warning - This class is marked as deprecated because there are known device specific issues
* associated with its use, including playbacks not starting, playbacks stuttering and other
* miscellaneous failures. For mp4, m4a, mp3, webm, mpeg-ts and aac playbacks it is strongly
* recommended to use {@link ExtractorSampleSource} instead, along with the corresponding extractor
* (e.g. {@link Mp4Extractor} for mp4 playbacks). Where this is not possible this class can still be
* used, but please be aware of the associated risks. Valid use cases of this class that are not
* yet supported by {@link ExtractorSampleSource} include:
* <ul>
* <li>Playing a container format for which an ExoPlayer extractor does not yet exist (e.g. ogg).
* </li>
* <li>Playing media whose container format is unknown and so needs to be inferred automatically.
* </li>
* </ul>
* miscellaneous failures. For mp4, m4a, mp3, webm, mkv, mpeg-ts and aac playbacks it is strongly
* recommended to use {@link ExtractorSampleSource} instead. Where this is not possible this class
* can still be used, but please be aware of the associated risks. Playing container formats for
* which an ExoPlayer extractor does not yet exist (e.g. ogg) is a valid use case of this class.
* <p>
* Over time we hope to enhance {@link ExtractorSampleSource} to support these use cases, and hence
* Over time we hope to enhance {@link ExtractorSampleSource} to support more formats, and hence
* make use of this class unnecessary.
*/
// TODO: This implementation needs to be fixed so that its methods are non-blocking (either
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import java.io.EOFException;
import java.io.IOException;
import java.util.Arrays;

/**
* An {@link ExtractorInput} that wraps a {@link DataSource}.
Expand All @@ -29,9 +30,12 @@ public final class DefaultExtractorInput implements ExtractorInput {
private static final byte[] SCRATCH_SPACE = new byte[4096];

private final DataSource dataSource;
private final long streamLength;

private long position;
private long length;
private byte[] peekBuffer;
private int peekBufferPosition;
private int peekBufferLength;

/**
* @param dataSource The wrapped {@link DataSource}.
Expand All @@ -41,26 +45,36 @@ public final class DefaultExtractorInput implements ExtractorInput {
public DefaultExtractorInput(DataSource dataSource, long position, long length) {
this.dataSource = dataSource;
this.position = position;
this.length = length;
this.streamLength = length;
peekBuffer = new byte[8 * 1024];
}

@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);
int peekBytes = Math.min(peekBufferLength, length);
System.arraycopy(peekBuffer, 0, target, offset, peekBytes);
offset += peekBytes;
length -= peekBytes;
int bytesRead = length != 0 ? dataSource.read(target, offset, length) : 0;
if (bytesRead == C.RESULT_END_OF_INPUT) {
return C.RESULT_END_OF_INPUT;
}
updatePeekBuffer(peekBytes);
bytesRead += peekBytes;
position += bytesRead;
return bytesRead;
}

@Override
public boolean readFully(byte[] target, int offset, int length, boolean allowEndOfInput)
throws IOException, InterruptedException {
int remaining = length;
int peekBytes = Math.min(peekBufferLength, length);
System.arraycopy(peekBuffer, 0, target, offset, peekBytes);
offset += peekBytes;
int remaining = length - peekBytes;
while (remaining > 0) {
if (Thread.interrupted()) {
throw new InterruptedException();
Expand All @@ -75,6 +89,7 @@ public boolean readFully(byte[] target, int offset, int length, boolean allowEnd
offset += bytesRead;
remaining -= bytesRead;
}
updatePeekBuffer(peekBytes);
position += length;
return true;
}
Expand All @@ -87,7 +102,8 @@ public void readFully(byte[] target, int offset, int length)

@Override
public void skipFully(int length) throws IOException, InterruptedException {
int remaining = length;
int peekBytes = Math.min(peekBufferLength, length);
int remaining = length - peekBytes;
while (remaining > 0) {
if (Thread.interrupted()) {
throw new InterruptedException();
Expand All @@ -98,17 +114,94 @@ public void skipFully(int length) throws IOException, InterruptedException {
}
remaining -= bytesRead;
}
updatePeekBuffer(peekBytes);
position += length;
}

@Override
public void peekFully(byte[] target, int offset, int length)
throws IOException, InterruptedException {
ensureSpaceForPeek(length);
int peekBytes = Math.min(peekBufferLength - peekBufferPosition, length);
System.arraycopy(peekBuffer, peekBufferPosition, target, offset, peekBytes);
offset += peekBytes;
int fillBytes = length - peekBytes;
int remaining = fillBytes;
int writePosition = peekBufferLength;
while (remaining > 0) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
int bytesRead = dataSource.read(peekBuffer, writePosition, remaining);
if (bytesRead == C.RESULT_END_OF_INPUT) {
throw new EOFException();
}
System.arraycopy(peekBuffer, writePosition, target, offset, bytesRead);
remaining -= bytesRead;
writePosition += bytesRead;
offset += bytesRead;
}
peekBufferPosition += length;
peekBufferLength += fillBytes;
}

@Override
public void advancePeekPosition(int length) throws IOException, InterruptedException {
ensureSpaceForPeek(length);
int peekBytes = Math.min(peekBufferLength - peekBufferPosition, length);
int fillBytes = length - peekBytes;
int remaining = fillBytes;
int writePosition = peekBufferLength;
while (remaining > 0) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
int bytesRead = dataSource.read(peekBuffer, writePosition, remaining);
if (bytesRead == C.RESULT_END_OF_INPUT) {
throw new EOFException();
}
remaining -= bytesRead;
writePosition += bytesRead;
}
peekBufferPosition += length;
peekBufferLength += fillBytes;
}

@Override
public void resetPeekPosition() {
peekBufferPosition = 0;
}

@Override
public long getPosition() {
return position;
}

@Override
public long getLength() {
return length;
return streamLength;
}

/**
* Ensures {@code peekBuffer} is large enough to store at least {@code length} bytes from the
* current peek position.
*/
private void ensureSpaceForPeek(int length) {
int requiredLength = peekBufferPosition + length;
if (requiredLength > peekBuffer.length) {
peekBuffer = Arrays.copyOf(peekBuffer, Math.max(peekBuffer.length * 2, requiredLength));
}
}

/**
* Updates the peek buffer's length, position and contents after consuming data.
*
* @param bytesConsumed The number of bytes consumed from the peek buffer.
*/
private void updatePeekBuffer(int bytesConsumed) {
peekBufferLength -= bytesConsumed;
peekBufferPosition = 0;
System.arraycopy(peekBuffer, bytesConsumed, peekBuffer, 0, peekBufferLength);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ public interface Extractor {
*/
void init(ExtractorOutput output);

/**
* Returns whether this extractor can extract samples from the {@link ExtractorInput}, which must
* provide data from the start of the stream.
*
* @throws IOException If an error occurred reading from the input.
* @throws InterruptedException If the thread was interrupted.
*/
boolean sniff(ExtractorInput input) throws IOException, InterruptedException;

/**
* Extracts data read from a provided {@link ExtractorInput}.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,40 @@ boolean readFully(byte[] target, int offset, int length, boolean allowEndOfInput
void skipFully(int length) throws IOException, InterruptedException;

/**
* The current position (byte offset) in the stream.
* Peeks {@code length} bytes from the peek position, writing them into {@code target} at index
* {@code offset}. The current read position is left unchanged.
* <p>
* Calling {@link #resetPeekPosition()} resets the peek position to equal the current read
* position, so the caller can peek the same data again. Reading also resets the peek position.
*
* @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 peek from the input.
* @throws EOFException If the end of input was encountered.
* @throws IOException If an error occurs peeking from the input.
* @throws InterruptedException If the thread is interrupted.
*/
void peekFully(byte[] target, int offset, int length) throws IOException, InterruptedException;

/**
* Advances the peek position by {@code length} bytes.
*
* @param length The number of bytes to peek from the input.
* @throws EOFException If the end of input was encountered.
* @throws IOException If an error occurs peeking from the input.
* @throws InterruptedException If the thread is interrupted.
*/
void advancePeekPosition(int length) throws IOException, InterruptedException;

/**
* Resets the peek position to equal the current read position.
*/
void resetPeekPosition();

/**
* The current read position (byte offset) in the stream.
*
* @return The position (byte offset) in the stream.
* @return The read position (byte offset) in the stream.
*/
long getPosition();

Expand Down
Loading

0 comments on commit 85e0bca

Please sign in to comment.