Commit c4eee71f by Oliver Woodman

Support AAC without platform MediaExtractor.

Issue: #231
Issue: #227
parent fb97fca0
...@@ -43,10 +43,11 @@ public class DemoUtil { ...@@ -43,10 +43,11 @@ public class DemoUtil {
public static final int TYPE_DASH = 0; public static final int TYPE_DASH = 0;
public static final int TYPE_SS = 1; public static final int TYPE_SS = 1;
public static final int TYPE_OTHER = 2; public static final int TYPE_HLS = 2;
public static final int TYPE_HLS = 3; public static final int TYPE_MP4 = 3;
public static final int TYPE_MP4 = 4; public static final int TYPE_MP3 = 4;
public static final int TYPE_MP3 = 5; public static final int TYPE_AAC = 5;
public static final int TYPE_OTHER = 6;
private static final CookieManager defaultCookieManager; private static final CookieManager defaultCookieManager;
......
...@@ -29,6 +29,7 @@ import com.google.android.exoplayer.demo.player.SmoothStreamingRendererBuilder; ...@@ -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.demo.player.UnsupportedDrmException;
import com.google.android.exoplayer.extractor.mp3.Mp3Extractor; import com.google.android.exoplayer.extractor.mp3.Mp3Extractor;
import com.google.android.exoplayer.extractor.mp4.Mp4Extractor; 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.GeobMetadata;
import com.google.android.exoplayer.metadata.PrivMetadata; import com.google.android.exoplayer.metadata.PrivMetadata;
import com.google.android.exoplayer.metadata.TxxxMetadata; import com.google.android.exoplayer.metadata.TxxxMetadata;
...@@ -234,6 +235,9 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, ...@@ -234,6 +235,9 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
case DemoUtil.TYPE_MP3: case DemoUtil.TYPE_MP3:
return new ExtractorRendererBuilder(userAgent, contentUri, debugTextView, return new ExtractorRendererBuilder(userAgent, contentUri, debugTextView,
new Mp3Extractor()); new Mp3Extractor());
case DemoUtil.TYPE_AAC:
return new ExtractorRendererBuilder(userAgent, contentUri, debugTextView,
new AdtsExtractor());
default: default:
return new DefaultRendererBuilder(this, contentUri, debugTextView); return new DefaultRendererBuilder(this, contentUri, debugTextView);
} }
......
...@@ -134,7 +134,7 @@ import java.util.Locale; ...@@ -134,7 +134,7 @@ import java.util.Locale;
DemoUtil.TYPE_OTHER), DemoUtil.TYPE_OTHER),
new Sample("Apple AAC 10s", "https://devimages.apple.com.edgekey.net/" new Sample("Apple AAC 10s", "https://devimages.apple.com.edgekey.net/"
+ "streaming/examples/bipbop_4x3/gear0/fileSequence0.aac", + "streaming/examples/bipbop_4x3/gear0/fileSequence0.aac",
DemoUtil.TYPE_OTHER), DemoUtil.TYPE_AAC),
new Sample("Big Buck Bunny (MP4 Video)", new Sample("Big Buck Bunny (MP4 Video)",
"http://redirector.c.youtube.com/videoplayback?id=604ed5ce52eda7ee&itag=22&source=youtube" "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=" + "&sparams=ip,ipbits,expire&ip=0.0.0.0&ipbits=0&expire=19000000000&signature="
......
...@@ -71,6 +71,13 @@ public final class ChunkIndex implements SeekMap { ...@@ -71,6 +71,13 @@ public final class ChunkIndex implements SeekMap {
return Util.binarySearchFloor(timesUs, timeUs, true, true); return Util.binarySearchFloor(timesUs, timeUs, true, true);
} }
// SeekMap implementation.
@Override
public boolean isSeekable() {
return true;
}
@Override @Override
public long getPosition(long timeUs) { public long getPosition(long timeUs) {
return offsets[getChunkIndex(timeUs)]; return offsets[getChunkIndex(timeUs)];
......
...@@ -21,12 +21,22 @@ package com.google.android.exoplayer.extractor; ...@@ -21,12 +21,22 @@ package com.google.android.exoplayer.extractor;
public interface SeekMap { public interface SeekMap {
/** /**
* Whether or not the seeking is supported.
* <p>
* 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 * Maps a seek position in microseconds to a corresponding position (byte offset) in the stream
* from which data can be provided to the extractor. * from which data can be provided to the extractor.
* *
* @param timeUs A seek position in microseconds. * @param timeUs A seek position in microseconds.
* @return The corresponding position (byte offset) in the stream from which data can be provided * @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); long getPosition(long timeUs);
......
...@@ -20,7 +20,7 @@ import com.google.android.exoplayer.C; ...@@ -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. * 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 MICROSECONDS_PER_SECOND = 1000000;
private static final int BITS_PER_BYTE = 8; private static final int BITS_PER_BYTE = 8;
......
...@@ -37,25 +37,6 @@ import java.util.Collections; ...@@ -37,25 +37,6 @@ import java.util.Collections;
*/ */
public final class Mp3Extractor implements Extractor { 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. */ /** The maximum number of bytes to search when synchronizing, before giving up. */
private static final int MAX_BYTES_TO_SEARCH = 128 * 1024; private static final int MAX_BYTES_TO_SEARCH = 128 * 1024;
...@@ -288,4 +269,28 @@ public final class Mp3Extractor implements Extractor { ...@@ -288,4 +269,28 @@ public final class Mp3Extractor implements Extractor {
return extractorInput.getPosition() - bufferingInput.getAvailableByteCount(); 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();
}
} }
...@@ -21,7 +21,7 @@ import com.google.android.exoplayer.util.Util; ...@@ -21,7 +21,7 @@ import com.google.android.exoplayer.util.Util;
/** /**
* MP3 seeker that uses metadata from a VBRI header. * 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"); private static final int VBRI_HEADER = Util.getIntegerCodeForString("VBRI");
......
...@@ -22,7 +22,7 @@ import com.google.android.exoplayer.util.Util; ...@@ -22,7 +22,7 @@ import com.google.android.exoplayer.util.Util;
/** /**
* MP3 seeker that uses metadata from a XING header. * 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 XING_HEADER = Util.getIntegerCodeForString("Xing");
private static final int INFO_HEADER = Util.getIntegerCodeForString("Info"); private static final int INFO_HEADER = Util.getIntegerCodeForString("Info");
......
...@@ -114,6 +114,11 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -114,6 +114,11 @@ public final class Mp4Extractor implements Extractor, SeekMap {
// SeekMap implementation. // SeekMap implementation.
@Override @Override
public boolean isSeekable() {
return true;
}
@Override
public long getPosition(long timeUs) { public long getPosition(long timeUs) {
long earliestSamplePosition = Long.MAX_VALUE; long earliestSamplePosition = Long.MAX_VALUE;
for (int trackIndex = 0; trackIndex < tracks.length; trackIndex++) { for (int trackIndex = 0; trackIndex < tracks.length; trackIndex++) {
...@@ -132,6 +137,8 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -132,6 +137,8 @@ public final class Mp4Extractor implements Extractor, SeekMap {
return earliestSamplePosition; return earliestSamplePosition;
} }
// Private methods.
private boolean readAtomHeader(ExtractorInput input) throws IOException, InterruptedException { private boolean readAtomHeader(ExtractorInput input) throws IOException, InterruptedException {
if (!input.readFully(atomHeader.data, 0, Atom.HEADER_SIZE, true)) { if (!input.readFully(atomHeader.data, 0, Atom.HEADER_SIZE, true)) {
return false; return false;
......
...@@ -19,6 +19,7 @@ import com.google.android.exoplayer.extractor.Extractor; ...@@ -19,6 +19,7 @@ import com.google.android.exoplayer.extractor.Extractor;
import com.google.android.exoplayer.extractor.ExtractorInput; import com.google.android.exoplayer.extractor.ExtractorInput;
import com.google.android.exoplayer.extractor.ExtractorOutput; import com.google.android.exoplayer.extractor.ExtractorOutput;
import com.google.android.exoplayer.extractor.PositionHolder; import com.google.android.exoplayer.extractor.PositionHolder;
import com.google.android.exoplayer.extractor.SeekMap;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
import java.io.IOException; import java.io.IOException;
...@@ -27,19 +28,23 @@ 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 * Facilitates the extraction of AAC samples from elementary audio files formatted as AAC with ADTS
* headers. * headers.
*/ */
public class AdtsExtractor implements Extractor { public class AdtsExtractor implements Extractor, SeekMap {
private static final int MAX_PACKET_SIZE = 200; private static final int MAX_PACKET_SIZE = 200;
private final long firstSampleTimestamp; private final long firstSampleTimestampUs;
private final ParsableByteArray packetBuffer; private final ParsableByteArray packetBuffer;
// Accessed only by the loading thread. // Accessed only by the loading thread.
private AdtsReader adtsReader; private AdtsReader adtsReader;
private boolean firstPacket; private boolean firstPacket;
public AdtsExtractor(long firstSampleTimestamp) { public AdtsExtractor() {
this.firstSampleTimestamp = firstSampleTimestamp; this(0);
}
public AdtsExtractor(long firstSampleTimestampUs) {
this.firstSampleTimestampUs = firstSampleTimestampUs;
packetBuffer = new ParsableByteArray(MAX_PACKET_SIZE); packetBuffer = new ParsableByteArray(MAX_PACKET_SIZE);
firstPacket = true; firstPacket = true;
} }
...@@ -48,11 +53,13 @@ public class AdtsExtractor implements Extractor { ...@@ -48,11 +53,13 @@ public class AdtsExtractor implements Extractor {
public void init(ExtractorOutput output) { public void init(ExtractorOutput output) {
adtsReader = new AdtsReader(output.track(0)); adtsReader = new AdtsReader(output.track(0));
output.endTracks(); output.endTracks();
output.seekMap(this);
} }
@Override @Override
public void seek() { public void seek() {
throw new UnsupportedOperationException(); adtsReader.seek();
firstPacket = true;
} }
@Override @Override
...@@ -69,9 +76,21 @@ public class AdtsExtractor implements Extractor { ...@@ -69,9 +76,21 @@ public class AdtsExtractor implements Extractor {
// TODO: Make it possible for adtsReader to consume the dataSource directly, so that it becomes // TODO: Make it possible for adtsReader to consume the dataSource directly, so that it becomes
// unnecessary to copy the data through packetBuffer. // unnecessary to copy the data through packetBuffer.
adtsReader.consume(packetBuffer, firstSampleTimestamp, firstPacket); adtsReader.consume(packetBuffer, firstSampleTimestampUs, firstPacket);
firstPacket = false; firstPacket = false;
return RESULT_CONTINUE; return RESULT_CONTINUE;
} }
// SeekMap implementation.
@Override
public boolean isSeekable() {
return false;
}
@Override
public long getPosition(long timeUs) {
return 0;
}
} }
...@@ -62,6 +62,18 @@ import java.util.Collections; ...@@ -62,6 +62,18 @@ import java.util.Collections;
state = STATE_FINDING_SYNC; state = STATE_FINDING_SYNC;
} }
/**
* Notifies the reader that a seek has occurred.
* <p>
* 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 @Override
public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) { public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) {
if (startOfPacket) { if (startOfPacket) {
......
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