From c4eee71fa204d33da48dba6659368eca284a4a3d Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Fri, 17 Apr 2015 20:07:04 +0100 Subject: [PATCH] Support AAC without platform MediaExtractor. Issue: #231 Issue: #227 --- .../android/exoplayer/demo/DemoUtil.java | 9 ++-- .../exoplayer/demo/PlayerActivity.java | 4 ++ .../android/exoplayer/demo/Samples.java | 2 +- .../exoplayer/extractor/ChunkIndex.java | 7 +++ .../android/exoplayer/extractor/SeekMap.java | 12 +++++- .../extractor/mp3/ConstantBitrateSeeker.java | 2 +- .../exoplayer/extractor/mp3/Mp3Extractor.java | 43 +++++++++++-------- .../exoplayer/extractor/mp3/VbriSeeker.java | 2 +- .../exoplayer/extractor/mp3/XingSeeker.java | 2 +- .../exoplayer/extractor/mp4/Mp4Extractor.java | 7 +++ .../exoplayer/extractor/ts/AdtsExtractor.java | 31 ++++++++++--- .../exoplayer/extractor/ts/AdtsReader.java | 12 ++++++ 12 files changed, 99 insertions(+), 34 deletions(-) diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/DemoUtil.java b/demo/src/main/java/com/google/android/exoplayer/demo/DemoUtil.java index 9ad0f27ce4..91f9be9e92 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/DemoUtil.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/DemoUtil.java @@ -43,10 +43,11 @@ public class DemoUtil { public static final int TYPE_DASH = 0; public static final int TYPE_SS = 1; - public static final int TYPE_OTHER = 2; - public static final int TYPE_HLS = 3; - public static final int TYPE_MP4 = 4; - public static final int TYPE_MP3 = 5; + 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_AAC = 5; + public static final int TYPE_OTHER = 6; private static final CookieManager defaultCookieManager; diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java index b2a8126347..725fc07028 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java @@ -29,6 +29,7 @@ import com.google.android.exoplayer.demo.player.SmoothStreamingRendererBuilder; import com.google.android.exoplayer.demo.player.UnsupportedDrmException; import com.google.android.exoplayer.extractor.mp3.Mp3Extractor; import com.google.android.exoplayer.extractor.mp4.Mp4Extractor; +import com.google.android.exoplayer.extractor.ts.AdtsExtractor; import com.google.android.exoplayer.metadata.GeobMetadata; import com.google.android.exoplayer.metadata.PrivMetadata; import com.google.android.exoplayer.metadata.TxxxMetadata; @@ -234,6 +235,9 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, case DemoUtil.TYPE_MP3: return new ExtractorRendererBuilder(userAgent, contentUri, debugTextView, new Mp3Extractor()); + case DemoUtil.TYPE_AAC: + return new ExtractorRendererBuilder(userAgent, contentUri, debugTextView, + new AdtsExtractor()); default: return new DefaultRendererBuilder(this, contentUri, debugTextView); } diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/Samples.java b/demo/src/main/java/com/google/android/exoplayer/demo/Samples.java index 624f5b8568..b407670056 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/Samples.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/Samples.java @@ -134,7 +134,7 @@ import java.util.Locale; DemoUtil.TYPE_OTHER), new Sample("Apple AAC 10s", "https://devimages.apple.com.edgekey.net/" + "streaming/examples/bipbop_4x3/gear0/fileSequence0.aac", - DemoUtil.TYPE_OTHER), + DemoUtil.TYPE_AAC), new Sample("Big Buck Bunny (MP4 Video)", "http://redirector.c.youtube.com/videoplayback?id=604ed5ce52eda7ee&itag=22&source=youtube" + "&sparams=ip,ipbits,expire&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=" diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ChunkIndex.java b/library/src/main/java/com/google/android/exoplayer/extractor/ChunkIndex.java index 1009c7b899..a54b71f7f4 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ChunkIndex.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ChunkIndex.java @@ -71,6 +71,13 @@ public final class ChunkIndex implements SeekMap { return Util.binarySearchFloor(timesUs, timeUs, true, true); } + // SeekMap implementation. + + @Override + public boolean isSeekable() { + return true; + } + @Override public long getPosition(long timeUs) { return offsets[getChunkIndex(timeUs)]; diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/SeekMap.java b/library/src/main/java/com/google/android/exoplayer/extractor/SeekMap.java index 812e5e7134..20b333c4a5 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/SeekMap.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/SeekMap.java @@ -20,13 +20,23 @@ package com.google.android.exoplayer.extractor; */ public interface SeekMap { + /** + * Whether or not the seeking is supported. + *

+ * If seeking is not supported then the only valid seek position is the start of the file, and so + * {@link #getPosition(long)} will return 0 for all input values. + * + * @return True if seeking is supported. False otherwise. + */ + boolean isSeekable(); + /** * Maps a seek position in microseconds to a corresponding position (byte offset) in the stream * from which data can be provided to the extractor. * * @param timeUs A seek position in microseconds. * @return The corresponding position (byte offset) in the stream from which data can be provided - * to the extractor. + * to the extractor, or 0 if {@code #isSeekable()} returns false. */ long getPosition(long timeUs); diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/ConstantBitrateSeeker.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/ConstantBitrateSeeker.java index cc705819e2..a196c9bba9 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/ConstantBitrateSeeker.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/ConstantBitrateSeeker.java @@ -20,7 +20,7 @@ import com.google.android.exoplayer.C; /** * MP3 seeker that doesn't rely on metadata and seeks assuming the source has a constant bitrate. */ -/* package */ final class ConstantBitrateSeeker implements Mp3Extractor.Seeker { +/* package */ final class ConstantBitrateSeeker extends Mp3Extractor.Seeker { private static final int MICROSECONDS_PER_SECOND = 1000000; private static final int BITS_PER_BYTE = 8; diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java index aff0923eb4..186f9d520f 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java @@ -37,25 +37,6 @@ import java.util.Collections; */ public final class Mp3Extractor implements Extractor { - /** - * {@link SeekMap} that also allows mapping from position (byte offset) back to time, which can be - * used to work out the new sample basis timestamp after seeking and resynchronization. - */ - /* package */ interface Seeker extends SeekMap { - - /** - * Maps a position (byte offset) to a corresponding sample timestamp. - * - * @param position A seek position (byte offset) relative to the start of the stream. - * @return The corresponding timestamp of the next sample to be read, in microseconds. - */ - long getTimeUs(long position); - - /** Returns the duration of the source, in microseconds. */ - long getDurationUs(); - - } - /** The maximum number of bytes to search when synchronizing, before giving up. */ private static final int MAX_BYTES_TO_SEARCH = 128 * 1024; @@ -288,4 +269,28 @@ public final class Mp3Extractor implements Extractor { return extractorInput.getPosition() - bufferingInput.getAvailableByteCount(); } + /** + * {@link SeekMap} that also allows mapping from position (byte offset) back to time, which can be + * used to work out the new sample basis timestamp after seeking and resynchronization. + */ + /* package */ abstract static class Seeker implements SeekMap { + + @Override + public final boolean isSeekable() { + return true; + } + + /** + * Maps a position (byte offset) to a corresponding sample timestamp. + * + * @param position A seek position (byte offset) relative to the start of the stream. + * @return The corresponding timestamp of the next sample to be read, in microseconds. + */ + abstract long getTimeUs(long position); + + /** Returns the duration of the source, in microseconds. */ + abstract long getDurationUs(); + + } + } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/VbriSeeker.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/VbriSeeker.java index 71a587e5da..ee516c3a1e 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/VbriSeeker.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/VbriSeeker.java @@ -21,7 +21,7 @@ import com.google.android.exoplayer.util.Util; /** * MP3 seeker that uses metadata from a VBRI header. */ -/* package */ final class VbriSeeker implements Mp3Extractor.Seeker { +/* package */ final class VbriSeeker extends Mp3Extractor.Seeker { private static final int VBRI_HEADER = Util.getIntegerCodeForString("VBRI"); diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/XingSeeker.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/XingSeeker.java index a51d700349..73c1fb0c4c 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/XingSeeker.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/XingSeeker.java @@ -22,7 +22,7 @@ import com.google.android.exoplayer.util.Util; /** * MP3 seeker that uses metadata from a XING header. */ -/* package */ final class XingSeeker implements Mp3Extractor.Seeker { +/* package */ final class XingSeeker extends Mp3Extractor.Seeker { private static final int XING_HEADER = Util.getIntegerCodeForString("Xing"); private static final int INFO_HEADER = Util.getIntegerCodeForString("Info"); diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java index 31141a0b32..312f220827 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java @@ -113,6 +113,11 @@ public final class Mp4Extractor implements Extractor, SeekMap { // SeekMap implementation. + @Override + public boolean isSeekable() { + return true; + } + @Override public long getPosition(long timeUs) { long earliestSamplePosition = Long.MAX_VALUE; @@ -132,6 +137,8 @@ public final class Mp4Extractor implements Extractor, SeekMap { return earliestSamplePosition; } + // Private methods. + private boolean readAtomHeader(ExtractorInput input) throws IOException, InterruptedException { if (!input.readFully(atomHeader.data, 0, Atom.HEADER_SIZE, true)) { return false; diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsExtractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsExtractor.java index be22bad6cb..8bfe7e82b6 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsExtractor.java @@ -19,6 +19,7 @@ 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.extractor.PositionHolder; +import com.google.android.exoplayer.extractor.SeekMap; import com.google.android.exoplayer.util.ParsableByteArray; import java.io.IOException; @@ -27,19 +28,23 @@ import java.io.IOException; * Facilitates the extraction of AAC samples from elementary audio files formatted as AAC with ADTS * headers. */ -public class AdtsExtractor implements Extractor { +public class AdtsExtractor implements Extractor, SeekMap { private static final int MAX_PACKET_SIZE = 200; - private final long firstSampleTimestamp; + private final long firstSampleTimestampUs; private final ParsableByteArray packetBuffer; // Accessed only by the loading thread. private AdtsReader adtsReader; private boolean firstPacket; - public AdtsExtractor(long firstSampleTimestamp) { - this.firstSampleTimestamp = firstSampleTimestamp; + public AdtsExtractor() { + this(0); + } + + public AdtsExtractor(long firstSampleTimestampUs) { + this.firstSampleTimestampUs = firstSampleTimestampUs; packetBuffer = new ParsableByteArray(MAX_PACKET_SIZE); firstPacket = true; } @@ -48,11 +53,13 @@ public class AdtsExtractor implements Extractor { public void init(ExtractorOutput output) { adtsReader = new AdtsReader(output.track(0)); output.endTracks(); + output.seekMap(this); } @Override public void seek() { - throw new UnsupportedOperationException(); + adtsReader.seek(); + firstPacket = true; } @Override @@ -69,9 +76,21 @@ public class AdtsExtractor implements Extractor { // TODO: Make it possible for adtsReader to consume the dataSource directly, so that it becomes // unnecessary to copy the data through packetBuffer. - adtsReader.consume(packetBuffer, firstSampleTimestamp, firstPacket); + adtsReader.consume(packetBuffer, firstSampleTimestampUs, firstPacket); firstPacket = false; return RESULT_CONTINUE; } + // SeekMap implementation. + + @Override + public boolean isSeekable() { + return false; + } + + @Override + public long getPosition(long timeUs) { + return 0; + } + } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsReader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsReader.java index 5165f089f8..2c732dcb54 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsReader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsReader.java @@ -62,6 +62,18 @@ import java.util.Collections; state = STATE_FINDING_SYNC; } + /** + * Notifies the reader that a seek has occurred. + *

+ * The data passed to the next invocation of {@link #consume(ParsableByteArray, long, boolean)} + * should not be treated as a continuation of the data passed to previous calls. + */ + public void seek() { + state = STATE_FINDING_SYNC; + bytesRead = 0; + lastByteWasFF = false; + } + @Override public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) { if (startOfPacket) {