Support AAC without platform MediaExtractor.

Issue: #231
Issue: #227
This commit is contained in:
Oliver Woodman 2015-04-17 20:07:04 +01:00
parent fb97fca04e
commit c4eee71fa2
12 changed files with 99 additions and 34 deletions

View File

@ -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;

View File

@ -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);
}

View File

@ -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="

View File

@ -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)];

View File

@ -20,13 +20,23 @@ package com.google.android.exoplayer.extractor;
*/
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
* 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);

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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");

View File

@ -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");

View File

@ -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;

View File

@ -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;
}
}

View File

@ -62,6 +62,18 @@ import java.util.Collections;
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
public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) {
if (startOfPacket) {