Commit 587edf8e by Oliver Woodman

Add new style mp4/fmp4 extractors.

parent f002e6a7
Showing with 329 additions and 162 deletions
...@@ -49,6 +49,7 @@ public class DemoUtil { ...@@ -49,6 +49,7 @@ public class DemoUtil {
public static final int TYPE_OTHER = 2; public static final int TYPE_OTHER = 2;
public static final int TYPE_HLS = 3; public static final int TYPE_HLS = 3;
public static final int TYPE_MP4 = 4; public static final int TYPE_MP4 = 4;
public static final int TYPE_MP3 = 5;
private static final CookieManager defaultCookieManager; private static final CookieManager defaultCookieManager;
......
...@@ -23,10 +23,12 @@ import com.google.android.exoplayer.demo.player.DashRendererBuilder; ...@@ -23,10 +23,12 @@ import com.google.android.exoplayer.demo.player.DashRendererBuilder;
import com.google.android.exoplayer.demo.player.DefaultRendererBuilder; import com.google.android.exoplayer.demo.player.DefaultRendererBuilder;
import com.google.android.exoplayer.demo.player.DemoPlayer; import com.google.android.exoplayer.demo.player.DemoPlayer;
import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder; import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder;
import com.google.android.exoplayer.demo.player.ExtractorRendererBuilder;
import com.google.android.exoplayer.demo.player.HlsRendererBuilder; import com.google.android.exoplayer.demo.player.HlsRendererBuilder;
import com.google.android.exoplayer.demo.player.Mp4RendererBuilder;
import com.google.android.exoplayer.demo.player.SmoothStreamingRendererBuilder; 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.mp4.Mp4Extractor;
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;
...@@ -217,7 +219,11 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, ...@@ -217,7 +219,11 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
case DemoUtil.TYPE_HLS: case DemoUtil.TYPE_HLS:
return new HlsRendererBuilder(userAgent, contentUri.toString()); return new HlsRendererBuilder(userAgent, contentUri.toString());
case DemoUtil.TYPE_MP4: case DemoUtil.TYPE_MP4:
return new Mp4RendererBuilder(contentUri, debugTextView); return new ExtractorRendererBuilder(userAgent, contentUri, debugTextView,
new Mp4Extractor());
case DemoUtil.TYPE_MP3:
return new ExtractorRendererBuilder(userAgent, contentUri, debugTextView,
new Mp3Extractor());
default: default:
return new DefaultRendererBuilder(this, contentUri, debugTextView); return new DefaultRendererBuilder(this, contentUri, debugTextView);
} }
......
...@@ -135,12 +135,15 @@ import java.util.Locale; ...@@ -135,12 +135,15 @@ import java.util.Locale;
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_OTHER),
new Sample("Big Buck Bunny (MP4)", 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="
+ "2E853B992F6CAB9D28CA3BEBD84A6F26709A8A55.94344B0D8BA83A7417AAD24DACC8C71A9A878ECE" + "2E853B992F6CAB9D28CA3BEBD84A6F26709A8A55.94344B0D8BA83A7417AAD24DACC8C71A9A878ECE"
+ "&key=ik0", + "&key=ik0",
DemoUtil.TYPE_MP4), DemoUtil.TYPE_MP4),
new Sample("Google Play (MP3 Audio)",
"http://storage.googleapis.com/exoplayer-test-media-0/play.mp3",
DemoUtil.TYPE_MP3),
}; };
private Samples() {} private Samples() {}
......
...@@ -20,9 +20,9 @@ import com.google.android.exoplayer.MediaCodecVideoTrackRenderer; ...@@ -20,9 +20,9 @@ import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder; import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder;
import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallback; import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallback;
import com.google.android.exoplayer.source.DefaultSampleSource; import com.google.android.exoplayer.extractor.Extractor;
import com.google.android.exoplayer.source.Mp4SampleExtractor; import com.google.android.exoplayer.extractor.ExtractorSampleSource;
import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.UriDataSource; import com.google.android.exoplayer.upstream.UriDataSource;
import android.media.MediaCodec; import android.media.MediaCodec;
...@@ -30,23 +30,31 @@ import android.net.Uri; ...@@ -30,23 +30,31 @@ import android.net.Uri;
import android.widget.TextView; import android.widget.TextView;
/** /**
* A {@link RendererBuilder} for streams that can be read using {@link Mp4SampleExtractor}. * A {@link RendererBuilder} for streams that can be read using an {@link Extractor}.
*/ */
public class Mp4RendererBuilder implements RendererBuilder { public class ExtractorRendererBuilder implements RendererBuilder {
private static final int BUFFER_SIZE = 10 * 1024 * 1024;
private final String userAgent;
private final Uri uri; private final Uri uri;
private final TextView debugTextView; private final TextView debugTextView;
private final Extractor extractor;
public Mp4RendererBuilder(Uri uri, TextView debugTextView) { public ExtractorRendererBuilder(String userAgent, Uri uri, TextView debugTextView,
Extractor extractor) {
this.userAgent = userAgent;
this.uri = uri; this.uri = uri;
this.debugTextView = debugTextView; this.debugTextView = debugTextView;
this.extractor = extractor;
} }
@Override @Override
public void buildRenderers(DemoPlayer player, RendererBuilderCallback callback) { public void buildRenderers(DemoPlayer player, RendererBuilderCallback callback) {
// Build the video and audio renderers. // Build the video and audio renderers.
DefaultSampleSource sampleSource = new DefaultSampleSource( DataSource dataSource = new UriDataSource(userAgent, null);
new Mp4SampleExtractor(new UriDataSource("exoplayer", null), new DataSpec(uri)), 2); ExtractorSampleSource sampleSource = new ExtractorSampleSource(uri, dataSource, extractor, 2,
BUFFER_SIZE);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource, MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
null, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, player.getMainHandler(), null, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, player.getMainHandler(),
player, 50); player, 50);
......
...@@ -22,11 +22,14 @@ import com.google.android.exoplayer.SampleHolder; ...@@ -22,11 +22,14 @@ import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.chunk.parser.Extractor; import com.google.android.exoplayer.chunk.parser.Extractor;
import com.google.android.exoplayer.chunk.parser.SegmentIndex; import com.google.android.exoplayer.chunk.parser.SegmentIndex;
import com.google.android.exoplayer.drm.DrmInitData; import com.google.android.exoplayer.drm.DrmInitData;
import com.google.android.exoplayer.mp4.Atom; import com.google.android.exoplayer.extractor.mp4.Atom;
import com.google.android.exoplayer.mp4.Atom.ContainerAtom; import com.google.android.exoplayer.extractor.mp4.Atom.ContainerAtom;
import com.google.android.exoplayer.mp4.Atom.LeafAtom; import com.google.android.exoplayer.extractor.mp4.Atom.LeafAtom;
import com.google.android.exoplayer.mp4.CommonMp4AtomParsers; import com.google.android.exoplayer.extractor.mp4.AtomParsers;
import com.google.android.exoplayer.mp4.Track; import com.google.android.exoplayer.extractor.mp4.DefaultSampleValues;
import com.google.android.exoplayer.extractor.mp4.Track;
import com.google.android.exoplayer.extractor.mp4.TrackEncryptionBox;
import com.google.android.exoplayer.extractor.mp4.TrackFragment;
import com.google.android.exoplayer.upstream.NonBlockingInputStream; import com.google.android.exoplayer.upstream.NonBlockingInputStream;
import com.google.android.exoplayer.util.H264Util; import com.google.android.exoplayer.util.H264Util;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
...@@ -157,7 +160,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -157,7 +160,7 @@ public final class FragmentedMp4Extractor implements Extractor {
public FragmentedMp4Extractor(int workaroundFlags) { public FragmentedMp4Extractor(int workaroundFlags) {
this.workaroundFlags = workaroundFlags; this.workaroundFlags = workaroundFlags;
parserState = STATE_READING_ATOM_HEADER; parserState = STATE_READING_ATOM_HEADER;
atomHeader = new ParsableByteArray(Atom.ATOM_HEADER_SIZE); atomHeader = new ParsableByteArray(Atom.HEADER_SIZE);
extendedTypeScratch = new byte[16]; extendedTypeScratch = new byte[16];
containerAtoms = new Stack<ContainerAtom>(); containerAtoms = new Stack<ContainerAtom>();
fragmentRun = new TrackFragment(); fragmentRun = new TrackFragment();
...@@ -259,14 +262,14 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -259,14 +262,14 @@ public final class FragmentedMp4Extractor implements Extractor {
} }
private int readAtomHeader(NonBlockingInputStream inputStream) { private int readAtomHeader(NonBlockingInputStream inputStream) {
int remainingBytes = Atom.ATOM_HEADER_SIZE - atomBytesRead; int remainingBytes = Atom.HEADER_SIZE - atomBytesRead;
int bytesRead = inputStream.read(atomHeader.data, atomBytesRead, remainingBytes); int bytesRead = inputStream.read(atomHeader.data, atomBytesRead, remainingBytes);
if (bytesRead == -1) { if (bytesRead == -1) {
return RESULT_END_OF_STREAM; return RESULT_END_OF_STREAM;
} }
rootAtomBytesRead += bytesRead; rootAtomBytesRead += bytesRead;
atomBytesRead += bytesRead; atomBytesRead += bytesRead;
if (atomBytesRead != Atom.ATOM_HEADER_SIZE) { if (atomBytesRead != Atom.HEADER_SIZE) {
return RESULT_NEED_MORE_DATA; return RESULT_NEED_MORE_DATA;
} }
...@@ -288,10 +291,10 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -288,10 +291,10 @@ public final class FragmentedMp4Extractor implements Extractor {
if (CONTAINER_TYPES.contains(atomTypeInteger)) { if (CONTAINER_TYPES.contains(atomTypeInteger)) {
enterState(STATE_READING_ATOM_HEADER); enterState(STATE_READING_ATOM_HEADER);
containerAtoms.add(new ContainerAtom(atomType, containerAtoms.add(new ContainerAtom(atomType,
rootAtomBytesRead + atomSize - Atom.ATOM_HEADER_SIZE)); rootAtomBytesRead + atomSize - Atom.HEADER_SIZE));
} else { } else {
atomData = new ParsableByteArray(atomSize); atomData = new ParsableByteArray(atomSize);
System.arraycopy(atomHeader.data, 0, atomData.data, 0, Atom.ATOM_HEADER_SIZE); System.arraycopy(atomHeader.data, 0, atomData.data, 0, Atom.HEADER_SIZE);
enterState(STATE_READING_ATOM_PAYLOAD); enterState(STATE_READING_ATOM_PAYLOAD);
} }
} else { } else {
...@@ -360,7 +363,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -360,7 +363,7 @@ public final class FragmentedMp4Extractor implements Extractor {
LeafAtom child = moovChildren.get(i); LeafAtom child = moovChildren.get(i);
if (child.type == Atom.TYPE_pssh) { if (child.type == Atom.TYPE_pssh) {
ParsableByteArray psshAtom = child.data; ParsableByteArray psshAtom = child.data;
psshAtom.setPosition(Atom.FULL_ATOM_HEADER_SIZE); psshAtom.setPosition(Atom.FULL_HEADER_SIZE);
UUID uuid = new UUID(psshAtom.readLong(), psshAtom.readLong()); UUID uuid = new UUID(psshAtom.readLong(), psshAtom.readLong());
int dataSize = psshAtom.readInt(); int dataSize = psshAtom.readInt();
byte[] data = new byte[dataSize]; byte[] data = new byte[dataSize];
...@@ -373,7 +376,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -373,7 +376,7 @@ public final class FragmentedMp4Extractor implements Extractor {
} }
ContainerAtom mvex = moov.getContainerAtomOfType(Atom.TYPE_mvex); ContainerAtom mvex = moov.getContainerAtomOfType(Atom.TYPE_mvex);
extendsDefaults = parseTrex(mvex.getLeafAtomOfType(Atom.TYPE_trex).data); extendsDefaults = parseTrex(mvex.getLeafAtomOfType(Atom.TYPE_trex).data);
track = CommonMp4AtomParsers.parseTrak(moov.getContainerAtomOfType(Atom.TYPE_trak), track = AtomParsers.parseTrak(moov.getContainerAtomOfType(Atom.TYPE_trak),
moov.getLeafAtomOfType(Atom.TYPE_mvhd)); moov.getLeafAtomOfType(Atom.TYPE_mvhd));
} }
...@@ -399,7 +402,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -399,7 +402,7 @@ public final class FragmentedMp4Extractor implements Extractor {
* Parses a trex atom (defined in 14496-12). * Parses a trex atom (defined in 14496-12).
*/ */
private static DefaultSampleValues parseTrex(ParsableByteArray trex) { private static DefaultSampleValues parseTrex(ParsableByteArray trex) {
trex.setPosition(Atom.FULL_ATOM_HEADER_SIZE + 4); trex.setPosition(Atom.FULL_HEADER_SIZE + 4);
int defaultSampleDescriptionIndex = trex.readUnsignedIntToInt() - 1; int defaultSampleDescriptionIndex = trex.readUnsignedIntToInt() - 1;
int defaultSampleDuration = trex.readUnsignedIntToInt(); int defaultSampleDuration = trex.readUnsignedIntToInt();
int defaultSampleSize = trex.readUnsignedIntToInt(); int defaultSampleSize = trex.readUnsignedIntToInt();
...@@ -453,7 +456,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -453,7 +456,7 @@ public final class FragmentedMp4Extractor implements Extractor {
private static void parseSaiz(TrackEncryptionBox encryptionBox, ParsableByteArray saiz, private static void parseSaiz(TrackEncryptionBox encryptionBox, ParsableByteArray saiz,
TrackFragment out) { TrackFragment out) {
int vectorSize = encryptionBox.initializationVectorSize; int vectorSize = encryptionBox.initializationVectorSize;
saiz.setPosition(Atom.ATOM_HEADER_SIZE); saiz.setPosition(Atom.HEADER_SIZE);
int fullAtom = saiz.readInt(); int fullAtom = saiz.readInt();
int flags = Atom.parseFullAtomFlags(fullAtom); int flags = Atom.parseFullAtomFlags(fullAtom);
if ((flags & 0x01) == 1) { if ((flags & 0x01) == 1) {
...@@ -490,7 +493,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -490,7 +493,7 @@ public final class FragmentedMp4Extractor implements Extractor {
*/ */
private static DefaultSampleValues parseTfhd(DefaultSampleValues extendsDefaults, private static DefaultSampleValues parseTfhd(DefaultSampleValues extendsDefaults,
ParsableByteArray tfhd) { ParsableByteArray tfhd) {
tfhd.setPosition(Atom.ATOM_HEADER_SIZE); tfhd.setPosition(Atom.HEADER_SIZE);
int fullAtom = tfhd.readInt(); int fullAtom = tfhd.readInt();
int flags = Atom.parseFullAtomFlags(fullAtom); int flags = Atom.parseFullAtomFlags(fullAtom);
...@@ -519,7 +522,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -519,7 +522,7 @@ public final class FragmentedMp4Extractor implements Extractor {
* media, expressed in the media's timescale. * media, expressed in the media's timescale.
*/ */
private static long parseTfdt(ParsableByteArray tfdt) { private static long parseTfdt(ParsableByteArray tfdt) {
tfdt.setPosition(Atom.ATOM_HEADER_SIZE); tfdt.setPosition(Atom.HEADER_SIZE);
int fullAtom = tfdt.readInt(); int fullAtom = tfdt.readInt();
int version = Atom.parseFullAtomVersion(fullAtom); int version = Atom.parseFullAtomVersion(fullAtom);
return version == 1 ? tfdt.readUnsignedLongToLong() : tfdt.readUnsignedInt(); return version == 1 ? tfdt.readUnsignedLongToLong() : tfdt.readUnsignedInt();
...@@ -536,7 +539,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -536,7 +539,7 @@ public final class FragmentedMp4Extractor implements Extractor {
*/ */
private static void parseTrun(Track track, DefaultSampleValues defaultSampleValues, private static void parseTrun(Track track, DefaultSampleValues defaultSampleValues,
long decodeTime, int workaroundFlags, ParsableByteArray trun, TrackFragment out) { long decodeTime, int workaroundFlags, ParsableByteArray trun, TrackFragment out) {
trun.setPosition(Atom.ATOM_HEADER_SIZE); trun.setPosition(Atom.HEADER_SIZE);
int fullAtom = trun.readInt(); int fullAtom = trun.readInt();
int flags = Atom.parseFullAtomFlags(fullAtom); int flags = Atom.parseFullAtomFlags(fullAtom);
...@@ -596,7 +599,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -596,7 +599,7 @@ public final class FragmentedMp4Extractor implements Extractor {
private static void parseUuid(ParsableByteArray uuid, TrackFragment out, private static void parseUuid(ParsableByteArray uuid, TrackFragment out,
byte[] extendedTypeScratch) { byte[] extendedTypeScratch) {
uuid.setPosition(Atom.ATOM_HEADER_SIZE); uuid.setPosition(Atom.HEADER_SIZE);
uuid.readBytes(extendedTypeScratch, 0, 16); uuid.readBytes(extendedTypeScratch, 0, 16);
// Currently this parser only supports Microsoft's PIFF SampleEncryptionBox. // Currently this parser only supports Microsoft's PIFF SampleEncryptionBox.
...@@ -615,7 +618,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -615,7 +618,7 @@ public final class FragmentedMp4Extractor implements Extractor {
} }
private static void parseSenc(ParsableByteArray senc, int offset, TrackFragment out) { private static void parseSenc(ParsableByteArray senc, int offset, TrackFragment out) {
senc.setPosition(Atom.ATOM_HEADER_SIZE + offset); senc.setPosition(Atom.HEADER_SIZE + offset);
int fullAtom = senc.readInt(); int fullAtom = senc.readInt();
int flags = Atom.parseFullAtomFlags(fullAtom); int flags = Atom.parseFullAtomFlags(fullAtom);
...@@ -639,7 +642,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -639,7 +642,7 @@ public final class FragmentedMp4Extractor implements Extractor {
* Parses a sidx atom (defined in 14496-12). * Parses a sidx atom (defined in 14496-12).
*/ */
private static SegmentIndex parseSidx(ParsableByteArray atom) { private static SegmentIndex parseSidx(ParsableByteArray atom) {
atom.setPosition(Atom.ATOM_HEADER_SIZE); atom.setPosition(Atom.HEADER_SIZE);
int fullAtom = atom.readInt(); int fullAtom = atom.readInt();
int version = Atom.parseFullAtomVersion(fullAtom); int version = Atom.parseFullAtomVersion(fullAtom);
......
...@@ -144,6 +144,16 @@ public final class DefaultTrackOutput implements TrackOutput { ...@@ -144,6 +144,16 @@ public final class DefaultTrackOutput implements TrackOutput {
} }
/** /**
* Attempts to skip to the keyframe before the specified time, if it's present in the buffer.
*
* @param timeUs The seek time.
* @return True if the skip was successful. False otherwise.
*/
public boolean skipToKeyframeBefore(long timeUs) {
return rollingBuffer.skipToKeyframeBefore(timeUs);
}
/**
* Attempts to configure a splice from this queue to the next. * Attempts to configure a splice from this queue to the next.
* *
* @param nextQueue The queue being spliced to. * @param nextQueue The queue being spliced to.
......
...@@ -25,14 +25,20 @@ import java.io.IOException; ...@@ -25,14 +25,20 @@ import java.io.IOException;
public interface Extractor { public interface Extractor {
/** /**
* Returned by {@link #read(ExtractorInput)} if the {@link ExtractorInput} passed to the next * Returned by {@link #read(ExtractorInput, PositionHolder)} if the {@link ExtractorInput} passed
* {@link #read(ExtractorInput)} is required to provide data continuing from the position in the * to the next {@link #read(ExtractorInput, PositionHolder)} is required to provide data
* stream reached by the returning call. * continuing from the position in the stream reached by the returning call.
*/ */
public static final int RESULT_CONTINUE = 0; public static final int RESULT_CONTINUE = 0;
/** /**
* Returned by {@link #read(ExtractorInput)} if the end of the {@link ExtractorInput} was reached. * Returned by {@link #read(ExtractorInput, PositionHolder)} if the {@link ExtractorInput} passed
* Equal to {@link C#RESULT_END_OF_INPUT}. * to the next {@link #read(ExtractorInput, PositionHolder)} is required to provide data starting
* from a specified position in the stream.
*/
public static final int RESULT_SEEK = 1;
/**
* Returned by {@link #read(ExtractorInput, PositionHolder)} if the end of the
* {@link ExtractorInput} was reached. Equal to {@link C#RESULT_END_OF_INPUT}.
*/ */
public static final int RESULT_END_OF_INPUT = C.RESULT_END_OF_INPUT; public static final int RESULT_END_OF_INPUT = C.RESULT_END_OF_INPUT;
...@@ -47,21 +53,31 @@ public interface Extractor { ...@@ -47,21 +53,31 @@ public interface Extractor {
* Extracts data read from a provided {@link ExtractorInput}. * Extracts data read from a provided {@link ExtractorInput}.
* <p> * <p>
* Each read will extract at most one sample from the stream before returning. * Each read will extract at most one sample from the stream before returning.
* <p>
* In the common case, {@link #RESULT_CONTINUE} is returned to indicate that
* {@link ExtractorInput} passed to the next read is required to provide data continuing from the
* position in the stream reached by the returning call. If the extractor requires data to be
* provided from a different position, then that position is set in {@code seekPosition} and
* {@link #RESULT_SEEK} is returned. If the extractor reached the end of the data provided by the
* {@link ExtractorInput}, then {@link #RESULT_END_OF_INPUT} is returned.
* *
* @param input The {@link ExtractorInput} from which data should be read. * @param input The {@link ExtractorInput} from which data should be read.
* @param seekPosition If {@link #RESULT_SEEK} is returned, this holder is updated to hold the
* position of the required data.
* @return One of the {@code RESULT_} values defined in this interface. * @return One of the {@code RESULT_} values defined in this interface.
* @throws IOException If an error occurred reading from the input. * @throws IOException If an error occurred reading from the input.
* @throws InterruptedException If the thread was interrupted. * @throws InterruptedException If the thread was interrupted.
*/ */
int read(ExtractorInput input) throws IOException, InterruptedException; int read(ExtractorInput input, PositionHolder seekPosition)
throws IOException, InterruptedException;
/** /**
* Notifies the extractor that a seek has occurred. * Notifies the extractor that a seek has occurred.
* <p> * <p>
* Following a call to this method, the {@link ExtractorInput} passed to the next invocation of * Following a call to this method, the {@link ExtractorInput} passed to the next invocation of
* {@link #read(ExtractorInput)} is required to provide data starting from any random access * {@link #read(ExtractorInput, PositionHolder)} is required to provide data starting from any
* position in the stream. Random access positions can be obtained from a {@link SeekMap} that * random access position in the stream. Random access positions can be obtained from a
* has been extracted and passed to the {@link ExtractorOutput}. * {@link SeekMap} that has been extracted and passed to the {@link ExtractorOutput}.
*/ */
void seek(); void seek();
......
/*
* 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.extractor;
/**
* Holds a position in the stream.
*/
public final class PositionHolder {
/**
* The held position.
*/
public long position;
}
...@@ -112,6 +112,21 @@ import java.util.concurrent.ConcurrentLinkedQueue; ...@@ -112,6 +112,21 @@ import java.util.concurrent.ConcurrentLinkedQueue;
} }
/** /**
* Attempts to skip to the keyframe before the specified time, if it's present in the buffer.
*
* @param timeUs The seek time.
* @return True if the skip was successful. False otherwise.
*/
public boolean skipToKeyframeBefore(long timeUs) {
long nextOffset = infoQueue.skipToKeyframeBefore(timeUs);
if (nextOffset == -1) {
return false;
}
dropDownstreamTo(nextOffset);
return true;
}
/**
* Reads the current sample, advancing the read index to the next sample. * Reads the current sample, advancing the read index to the next sample.
* *
* @param sampleHolder The holder into which the current sample should be written. * @param sampleHolder The holder into which the current sample should be written.
...@@ -471,6 +486,50 @@ import java.util.concurrent.ConcurrentLinkedQueue; ...@@ -471,6 +486,50 @@ import java.util.concurrent.ConcurrentLinkedQueue;
: (sizes[lastReadIndex] + offsets[lastReadIndex]); : (sizes[lastReadIndex] + offsets[lastReadIndex]);
} }
/**
* Attempts to locate the keyframe before the specified time, if it's present in the buffer.
*
* @param timeUs The seek time.
* @return The offset of the keyframe's data if the keyframe was present. -1 otherwise.
*/
public synchronized long skipToKeyframeBefore(long timeUs) {
if (queueSize == 0 || timeUs < timesUs[relativeReadIndex]) {
return -1;
}
int lastWriteIndex = (relativeWriteIndex == 0 ? capacity : relativeWriteIndex) - 1;
long lastTimeUs = timesUs[lastWriteIndex];
if (timeUs > lastTimeUs) {
return -1;
}
// TODO: This can be optimized further using binary search, although the fact that the array
// is cyclic means we'd need to implement the binary search ourselves.
int sampleCount = 0;
int sampleCountToKeyframe = -1;
int searchIndex = relativeReadIndex;
while (searchIndex != relativeWriteIndex) {
if (timesUs[searchIndex] > timeUs) {
// We've gone too far.
break;
} else if ((flags[searchIndex] & C.SAMPLE_FLAG_SYNC) != 0) {
// We've found a keyframe, and we're still before the seek position.
sampleCountToKeyframe = sampleCount;
}
searchIndex = (searchIndex + 1) % capacity;
sampleCount++;
}
if (sampleCountToKeyframe == -1) {
return -1;
}
queueSize -= sampleCountToKeyframe;
relativeReadIndex = (relativeReadIndex + sampleCountToKeyframe) % capacity;
absoluteReadIndex += sampleCountToKeyframe;
return offsets[relativeReadIndex];
}
// Called by the loading thread. // Called by the loading thread.
public synchronized void commitSample(long timeUs, int sampleFlags, long offset, int size, public synchronized void commitSample(long timeUs, int sampleFlags, long offset, int size,
......
...@@ -21,6 +21,7 @@ import com.google.android.exoplayer.ParserException; ...@@ -21,6 +21,7 @@ import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.extractor.Extractor; 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.SeekMap; import com.google.android.exoplayer.extractor.SeekMap;
import com.google.android.exoplayer.extractor.TrackOutput; import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
...@@ -111,7 +112,8 @@ public final class Mp3Extractor implements Extractor { ...@@ -111,7 +112,8 @@ public final class Mp3Extractor implements Extractor {
} }
@Override @Override
public int read(ExtractorInput extractorInput) throws IOException, InterruptedException { public int read(ExtractorInput extractorInput, PositionHolder seekPosition)
throws IOException, InterruptedException {
if (synchronizedHeaderData == 0 if (synchronizedHeaderData == 0
&& synchronizeCatchingEndOfInput(extractorInput) == RESULT_END_OF_INPUT) { && synchronizeCatchingEndOfInput(extractorInput) == RESULT_END_OF_INPUT) {
return RESULT_END_OF_INPUT; return RESULT_END_OF_INPUT;
......
...@@ -13,10 +13,10 @@ ...@@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer.mp4; package com.google.android.exoplayer.extractor.mp4;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
import com.google.android.exoplayer.util.Util;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
...@@ -25,69 +25,69 @@ import java.util.List; ...@@ -25,69 +25,69 @@ import java.util.List;
public abstract class Atom { public abstract class Atom {
/** Size of an atom header, in bytes. */ /** Size of an atom header, in bytes. */
public static final int ATOM_HEADER_SIZE = 8; public static final int HEADER_SIZE = 8;
/** Size of a long atom header, in bytes. */
public static final int LONG_ATOM_HEADER_SIZE = 16;
/** Size of a full atom header, in bytes. */ /** Size of a full atom header, in bytes. */
public static final int FULL_ATOM_HEADER_SIZE = 12; public static final int FULL_HEADER_SIZE = 12;
/** Size of a long atom header, in bytes. */
public static final int LONG_HEADER_SIZE = 16;
/** Value for the first 32 bits of atomSize when the atom size is actually a long value. */ /** Value for the first 32 bits of atomSize when the atom size is actually a long value. */
public static final int LONG_SIZE_PREFIX = 1; public static final int LONG_SIZE_PREFIX = 1;
public static final int TYPE_ftyp = getAtomTypeInteger("ftyp"); public static final int TYPE_ftyp = Util.getIntegerCodeForString("ftyp");
public static final int TYPE_avc1 = getAtomTypeInteger("avc1"); public static final int TYPE_avc1 = Util.getIntegerCodeForString("avc1");
public static final int TYPE_avc3 = getAtomTypeInteger("avc3"); public static final int TYPE_avc3 = Util.getIntegerCodeForString("avc3");
public static final int TYPE_esds = getAtomTypeInteger("esds"); public static final int TYPE_esds = Util.getIntegerCodeForString("esds");
public static final int TYPE_mdat = getAtomTypeInteger("mdat"); public static final int TYPE_mdat = Util.getIntegerCodeForString("mdat");
public static final int TYPE_mp4a = getAtomTypeInteger("mp4a"); public static final int TYPE_mp4a = Util.getIntegerCodeForString("mp4a");
public static final int TYPE_ac_3 = getAtomTypeInteger("ac-3"); public static final int TYPE_ac_3 = Util.getIntegerCodeForString("ac-3");
public static final int TYPE_dac3 = getAtomTypeInteger("dac3"); public static final int TYPE_dac3 = Util.getIntegerCodeForString("dac3");
public static final int TYPE_ec_3 = getAtomTypeInteger("ec-3"); public static final int TYPE_ec_3 = Util.getIntegerCodeForString("ec-3");
public static final int TYPE_dec3 = getAtomTypeInteger("dec3"); public static final int TYPE_dec3 = Util.getIntegerCodeForString("dec3");
public static final int TYPE_tfdt = getAtomTypeInteger("tfdt"); public static final int TYPE_tfdt = Util.getIntegerCodeForString("tfdt");
public static final int TYPE_tfhd = getAtomTypeInteger("tfhd"); public static final int TYPE_tfhd = Util.getIntegerCodeForString("tfhd");
public static final int TYPE_trex = getAtomTypeInteger("trex"); public static final int TYPE_trex = Util.getIntegerCodeForString("trex");
public static final int TYPE_trun = getAtomTypeInteger("trun"); public static final int TYPE_trun = Util.getIntegerCodeForString("trun");
public static final int TYPE_sidx = getAtomTypeInteger("sidx"); public static final int TYPE_sidx = Util.getIntegerCodeForString("sidx");
public static final int TYPE_moov = getAtomTypeInteger("moov"); public static final int TYPE_moov = Util.getIntegerCodeForString("moov");
public static final int TYPE_mvhd = getAtomTypeInteger("mvhd"); public static final int TYPE_mvhd = Util.getIntegerCodeForString("mvhd");
public static final int TYPE_trak = getAtomTypeInteger("trak"); public static final int TYPE_trak = Util.getIntegerCodeForString("trak");
public static final int TYPE_mdia = getAtomTypeInteger("mdia"); public static final int TYPE_mdia = Util.getIntegerCodeForString("mdia");
public static final int TYPE_minf = getAtomTypeInteger("minf"); public static final int TYPE_minf = Util.getIntegerCodeForString("minf");
public static final int TYPE_stbl = getAtomTypeInteger("stbl"); public static final int TYPE_stbl = Util.getIntegerCodeForString("stbl");
public static final int TYPE_avcC = getAtomTypeInteger("avcC"); public static final int TYPE_avcC = Util.getIntegerCodeForString("avcC");
public static final int TYPE_moof = getAtomTypeInteger("moof"); public static final int TYPE_moof = Util.getIntegerCodeForString("moof");
public static final int TYPE_traf = getAtomTypeInteger("traf"); public static final int TYPE_traf = Util.getIntegerCodeForString("traf");
public static final int TYPE_mvex = getAtomTypeInteger("mvex"); public static final int TYPE_mvex = Util.getIntegerCodeForString("mvex");
public static final int TYPE_tkhd = getAtomTypeInteger("tkhd"); public static final int TYPE_tkhd = Util.getIntegerCodeForString("tkhd");
public static final int TYPE_mdhd = getAtomTypeInteger("mdhd"); public static final int TYPE_mdhd = Util.getIntegerCodeForString("mdhd");
public static final int TYPE_hdlr = getAtomTypeInteger("hdlr"); public static final int TYPE_hdlr = Util.getIntegerCodeForString("hdlr");
public static final int TYPE_stsd = getAtomTypeInteger("stsd"); public static final int TYPE_stsd = Util.getIntegerCodeForString("stsd");
public static final int TYPE_pssh = getAtomTypeInteger("pssh"); public static final int TYPE_pssh = Util.getIntegerCodeForString("pssh");
public static final int TYPE_sinf = getAtomTypeInteger("sinf"); public static final int TYPE_sinf = Util.getIntegerCodeForString("sinf");
public static final int TYPE_schm = getAtomTypeInteger("schm"); public static final int TYPE_schm = Util.getIntegerCodeForString("schm");
public static final int TYPE_schi = getAtomTypeInteger("schi"); public static final int TYPE_schi = Util.getIntegerCodeForString("schi");
public static final int TYPE_tenc = getAtomTypeInteger("tenc"); public static final int TYPE_tenc = Util.getIntegerCodeForString("tenc");
public static final int TYPE_encv = getAtomTypeInteger("encv"); public static final int TYPE_encv = Util.getIntegerCodeForString("encv");
public static final int TYPE_enca = getAtomTypeInteger("enca"); public static final int TYPE_enca = Util.getIntegerCodeForString("enca");
public static final int TYPE_frma = getAtomTypeInteger("frma"); public static final int TYPE_frma = Util.getIntegerCodeForString("frma");
public static final int TYPE_saiz = getAtomTypeInteger("saiz"); public static final int TYPE_saiz = Util.getIntegerCodeForString("saiz");
public static final int TYPE_uuid = getAtomTypeInteger("uuid"); public static final int TYPE_uuid = Util.getIntegerCodeForString("uuid");
public static final int TYPE_senc = getAtomTypeInteger("senc"); public static final int TYPE_senc = Util.getIntegerCodeForString("senc");
public static final int TYPE_pasp = getAtomTypeInteger("pasp"); public static final int TYPE_pasp = Util.getIntegerCodeForString("pasp");
public static final int TYPE_TTML = getAtomTypeInteger("TTML"); public static final int TYPE_TTML = Util.getIntegerCodeForString("TTML");
public static final int TYPE_vmhd = getAtomTypeInteger("vmhd"); public static final int TYPE_vmhd = Util.getIntegerCodeForString("vmhd");
public static final int TYPE_smhd = getAtomTypeInteger("smhd"); public static final int TYPE_smhd = Util.getIntegerCodeForString("smhd");
public static final int TYPE_mp4v = getAtomTypeInteger("mp4v"); public static final int TYPE_mp4v = Util.getIntegerCodeForString("mp4v");
public static final int TYPE_stts = getAtomTypeInteger("stts"); public static final int TYPE_stts = Util.getIntegerCodeForString("stts");
public static final int TYPE_stss = getAtomTypeInteger("stss"); public static final int TYPE_stss = Util.getIntegerCodeForString("stss");
public static final int TYPE_ctts = getAtomTypeInteger("ctts"); public static final int TYPE_ctts = Util.getIntegerCodeForString("ctts");
public static final int TYPE_stsc = getAtomTypeInteger("stsc"); public static final int TYPE_stsc = Util.getIntegerCodeForString("stsc");
public static final int TYPE_stsz = getAtomTypeInteger("stsz"); public static final int TYPE_stsz = Util.getIntegerCodeForString("stsz");
public static final int TYPE_stco = getAtomTypeInteger("stco"); public static final int TYPE_stco = Util.getIntegerCodeForString("stco");
public static final int TYPE_co64 = getAtomTypeInteger("co64"); public static final int TYPE_co64 = Util.getIntegerCodeForString("co64");
public final int type; public final int type;
...@@ -187,14 +187,4 @@ public abstract class Atom { ...@@ -187,14 +187,4 @@ public abstract class Atom {
+ (char) (type & 0xFF); + (char) (type & 0xFF);
} }
private static int getAtomTypeInteger(String typeName) {
Assertions.checkArgument(typeName.length() == 4);
int result = 0;
for (int i = 0; i < 4; i++) {
result <<= 8;
result |= typeName.charAt(i);
}
return result;
}
} }
...@@ -13,9 +13,10 @@ ...@@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer.chunk.parser.mp4; package com.google.android.exoplayer.extractor.mp4;
/* package */ final class DefaultSampleValues { // TODO: Make package private.
public final class DefaultSampleValues {
public final int sampleDescriptionIndex; public final int sampleDescriptionIndex;
public final int duration; public final int duration;
......
...@@ -13,11 +13,10 @@ ...@@ -13,11 +13,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer.mp4; package com.google.android.exoplayer.extractor.mp4;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.chunk.parser.mp4.TrackEncryptionBox;
/** /**
* Encapsulates information describing an MP4 track. * Encapsulates information describing an MP4 track.
......
...@@ -13,11 +13,12 @@ ...@@ -13,11 +13,12 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer.chunk.parser.mp4; package com.google.android.exoplayer.extractor.mp4;
/** /**
* Encapsulates information parsed from a track encryption (tenc) box in an MP4 stream. * Encapsulates information parsed from a track encryption (tenc) box in an MP4 stream.
*/ */
// TODO: Make package private.
public final class TrackEncryptionBox { public final class TrackEncryptionBox {
/** /**
......
...@@ -13,15 +13,19 @@ ...@@ -13,15 +13,19 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer.chunk.parser.mp4; package com.google.android.exoplayer.extractor.mp4;
import com.google.android.exoplayer.extractor.ExtractorInput;
import com.google.android.exoplayer.upstream.NonBlockingInputStream; import com.google.android.exoplayer.upstream.NonBlockingInputStream;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
import java.io.IOException;
/** /**
* A holder for information corresponding to a single fragment of an mp4 file. * A holder for information corresponding to a single fragment of an mp4 file.
*/ */
/* package */ final class TrackFragment { // TODO: Make package private.
public final class TrackFragment {
public int sampleDescriptionIndex; public int sampleDescriptionIndex;
...@@ -122,6 +126,17 @@ import com.google.android.exoplayer.util.ParsableByteArray; ...@@ -122,6 +126,17 @@ import com.google.android.exoplayer.util.ParsableByteArray;
} }
/** /**
* Fills {@link #sampleEncryptionData} from the provided input.
*
* @param input An {@link ExtractorInput} from which to read the encryption data.
*/
public void fillEncryptionData(ExtractorInput input) throws IOException, InterruptedException {
input.readFully(sampleEncryptionData.data, 0, sampleEncryptionDataLength);
sampleEncryptionData.setPosition(0);
sampleEncryptionDataNeedsFill = false;
}
/**
* Fills {@link #sampleEncryptionData} from the provided source. * Fills {@link #sampleEncryptionData} from the provided source.
* *
* @param source A source from which to read the encryption data. * @param source A source from which to read the encryption data.
......
...@@ -13,18 +13,20 @@ ...@@ -13,18 +13,20 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer.mp4; package com.google.android.exoplayer.extractor.mp4;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
/** Sample table for a track in an MP4 file. */ /** Sample table for a track in an MP4 file. */
public final class Mp4TrackSampleTable { public final class TrackSampleTable {
/** Sample index when no sample is available. */ /** Sample index when no sample is available. */
public static final int NO_SAMPLE = -1; public static final int NO_SAMPLE = -1;
/** Number of samples. */
public final int sampleCount;
/** Sample offsets in bytes. */ /** Sample offsets in bytes. */
public final long[] offsets; public final long[] offsets;
/** Sample sizes in bytes. */ /** Sample sizes in bytes. */
...@@ -34,7 +36,7 @@ public final class Mp4TrackSampleTable { ...@@ -34,7 +36,7 @@ public final class Mp4TrackSampleTable {
/** Sample flags. */ /** Sample flags. */
public final int[] flags; public final int[] flags;
Mp4TrackSampleTable( TrackSampleTable(
long[] offsets, int[] sizes, long[] timestampsUs, int[] flags) { long[] offsets, int[] sizes, long[] timestampsUs, int[] flags) {
Assertions.checkArgument(sizes.length == timestampsUs.length); Assertions.checkArgument(sizes.length == timestampsUs.length);
Assertions.checkArgument(offsets.length == timestampsUs.length); Assertions.checkArgument(offsets.length == timestampsUs.length);
...@@ -44,11 +46,7 @@ public final class Mp4TrackSampleTable { ...@@ -44,11 +46,7 @@ public final class Mp4TrackSampleTable {
this.sizes = sizes; this.sizes = sizes;
this.timestampsUs = timestampsUs; this.timestampsUs = timestampsUs;
this.flags = flags; this.flags = flags;
} sampleCount = offsets.length;
/** Returns the number of samples in the table. */
public int getSampleCount() {
return sizes.length;
} }
/** /**
...@@ -65,7 +63,6 @@ public final class Mp4TrackSampleTable { ...@@ -65,7 +63,6 @@ public final class Mp4TrackSampleTable {
return i; return i;
} }
} }
return NO_SAMPLE; return NO_SAMPLE;
} }
...@@ -83,7 +80,6 @@ public final class Mp4TrackSampleTable { ...@@ -83,7 +80,6 @@ public final class Mp4TrackSampleTable {
return i; return i;
} }
} }
return NO_SAMPLE; return NO_SAMPLE;
} }
......
...@@ -18,6 +18,7 @@ package com.google.android.exoplayer.extractor.ts; ...@@ -18,6 +18,7 @@ package com.google.android.exoplayer.extractor.ts;
import com.google.android.exoplayer.extractor.Extractor; 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.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
import java.io.IOException; import java.io.IOException;
...@@ -55,7 +56,8 @@ public class AdtsExtractor implements Extractor { ...@@ -55,7 +56,8 @@ public class AdtsExtractor implements Extractor {
} }
@Override @Override
public int read(ExtractorInput input) throws IOException, InterruptedException { public int read(ExtractorInput input, PositionHolder seekPosition)
throws IOException, InterruptedException {
int bytesRead = input.read(packetBuffer.data, 0, MAX_PACKET_SIZE); int bytesRead = input.read(packetBuffer.data, 0, MAX_PACKET_SIZE);
if (bytesRead == -1) { if (bytesRead == -1) {
return RESULT_END_OF_INPUT; return RESULT_END_OF_INPUT;
......
...@@ -19,6 +19,7 @@ import com.google.android.exoplayer.C; ...@@ -19,6 +19,7 @@ import com.google.android.exoplayer.C;
import com.google.android.exoplayer.extractor.Extractor; 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.util.ParsableBitArray; import com.google.android.exoplayer.util.ParsableBitArray;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
...@@ -77,7 +78,8 @@ public final class TsExtractor implements Extractor { ...@@ -77,7 +78,8 @@ public final class TsExtractor implements Extractor {
} }
@Override @Override
public int read(ExtractorInput input) throws IOException, InterruptedException { public int read(ExtractorInput input, PositionHolder seekPosition)
throws IOException, InterruptedException {
if (!input.readFully(tsPacketBuffer.data, 0, TS_PACKET_SIZE, true)) { if (!input.readFully(tsPacketBuffer.data, 0, TS_PACKET_SIZE, true)) {
return RESULT_END_OF_INPUT; return RESULT_END_OF_INPUT;
} }
......
...@@ -23,6 +23,7 @@ import com.google.android.exoplayer.extractor.ChunkIndex; ...@@ -23,6 +23,7 @@ import com.google.android.exoplayer.extractor.ChunkIndex;
import com.google.android.exoplayer.extractor.Extractor; 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.TrackOutput; import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.util.LongArray; import com.google.android.exoplayer.util.LongArray;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
...@@ -162,7 +163,8 @@ public final class WebmExtractor implements Extractor { ...@@ -162,7 +163,8 @@ public final class WebmExtractor implements Extractor {
} }
@Override @Override
public int read(ExtractorInput input) throws IOException, InterruptedException { public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException,
InterruptedException {
sampleRead = false; sampleRead = false;
boolean inputHasData = true; boolean inputHasData = true;
while (!sampleRead && inputHasData) { while (!sampleRead && inputHasData) {
......
...@@ -193,7 +193,8 @@ public final class HlsExtractorWrapper implements ExtractorOutput { ...@@ -193,7 +193,8 @@ public final class HlsExtractorWrapper implements ExtractorOutput {
* @throws InterruptedException If the thread was interrupted. * @throws InterruptedException If the thread was interrupted.
*/ */
public int read(ExtractorInput input) throws IOException, InterruptedException { public int read(ExtractorInput input) throws IOException, InterruptedException {
int result = extractor.read(input); int result = extractor.read(input, null);
Assertions.checkState(result != Extractor.RESULT_SEEK);
return result; return result;
} }
......
...@@ -29,9 +29,9 @@ import com.google.android.exoplayer.chunk.FormatEvaluator.Evaluation; ...@@ -29,9 +29,9 @@ import com.google.android.exoplayer.chunk.FormatEvaluator.Evaluation;
import com.google.android.exoplayer.chunk.MediaChunk; import com.google.android.exoplayer.chunk.MediaChunk;
import com.google.android.exoplayer.chunk.parser.Extractor; import com.google.android.exoplayer.chunk.parser.Extractor;
import com.google.android.exoplayer.chunk.parser.mp4.FragmentedMp4Extractor; import com.google.android.exoplayer.chunk.parser.mp4.FragmentedMp4Extractor;
import com.google.android.exoplayer.chunk.parser.mp4.TrackEncryptionBox;
import com.google.android.exoplayer.drm.DrmInitData; import com.google.android.exoplayer.drm.DrmInitData;
import com.google.android.exoplayer.mp4.Track; import com.google.android.exoplayer.extractor.mp4.Track;
import com.google.android.exoplayer.extractor.mp4.TrackEncryptionBox;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.ProtectionElement; import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.ProtectionElement;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement; import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.TrackElement; import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.TrackElement;
......
...@@ -129,6 +129,16 @@ public final class BufferPool implements Allocator { ...@@ -129,6 +129,16 @@ public final class BufferPool implements Allocator {
} }
/** /**
* Blocks execution until the allocated size is not greater than the threshold, or the thread is
* interrupted.
*/
public synchronized void blockWhileAllocatedSizeExceeds(int limit) throws InterruptedException {
while (getAllocatedSize() > limit) {
wait();
}
}
/**
* Returns the buffers belonging to an allocation to the pool. * Returns the buffers belonging to an allocation to the pool.
* *
* @param allocation The allocation to return. * @param allocation The allocation to return.
......
...@@ -29,6 +29,7 @@ public class ExtractorTest extends TestCase { ...@@ -29,6 +29,7 @@ public class ExtractorTest extends TestCase {
assertEquals(C.RESULT_END_OF_INPUT, Extractor.RESULT_END_OF_INPUT); assertEquals(C.RESULT_END_OF_INPUT, Extractor.RESULT_END_OF_INPUT);
// Sanity check that the other constant values don't overlap. // Sanity check that the other constant values don't overlap.
assertTrue(C.RESULT_END_OF_INPUT != Extractor.RESULT_CONTINUE); assertTrue(C.RESULT_END_OF_INPUT != Extractor.RESULT_CONTINUE);
assertTrue(C.RESULT_END_OF_INPUT != Extractor.RESULT_SEEK);
} }
} }
...@@ -13,23 +13,22 @@ ...@@ -13,23 +13,22 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer.source; package com.google.android.exoplayer.extractor.mp4;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.MediaFormatHolder;
import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.mp4.Atom; import com.google.android.exoplayer.extractor.ExtractorSampleSource;
import com.google.android.exoplayer.upstream.ByteArrayDataSource; import com.google.android.exoplayer.upstream.ByteArrayDataSource;
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.util.Assertions; import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.media.MediaExtractor;
import android.net.Uri; import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
...@@ -43,10 +42,10 @@ import java.util.List; ...@@ -43,10 +42,10 @@ import java.util.List;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
/** /**
* Tests for {@link Mp4SampleExtractor}. * Tests for {@link Mp4Extractor}.
*/ */
@TargetApi(16) @TargetApi(16)
public class Mp4SampleExtractorTest extends TestCase { public class Mp4ExtractorTest extends TestCase {
/** String of hexadecimal bytes containing the video stsd payload from an AVC video. */ /** String of hexadecimal bytes containing the video stsd payload from an AVC video. */
private static final byte[] VIDEO_STSD_PAYLOAD = getByteArray( private static final byte[] VIDEO_STSD_PAYLOAD = getByteArray(
...@@ -97,7 +96,7 @@ public class Mp4SampleExtractorTest extends TestCase { ...@@ -97,7 +96,7 @@ public class Mp4SampleExtractorTest extends TestCase {
/** Indices of key-frames. */ /** Indices of key-frames. */
private static final int[] SYNCHRONIZATION_SAMPLE_INDICES = {0, 4, 5}; private static final int[] SYNCHRONIZATION_SAMPLE_INDICES = {0, 4, 5};
/** Indices of video frame chunk offsets. */ /** Indices of video frame chunk offsets. */
private static final int[] CHUNK_OFFSETS = {1000, 2000, 3000, 4000}; private static final int[] CHUNK_OFFSETS = {1080, 2000, 3000, 4000};
/** Numbers of video frames in each chunk. */ /** Numbers of video frames in each chunk. */
private static final int[] SAMPLES_IN_CHUNK = {2, 2, 1, 1}; private static final int[] SAMPLES_IN_CHUNK = {2, 2, 1, 1};
/** The mdat box must be large enough to avoid reading chunk sample data out of bounds. */ /** The mdat box must be large enough to avoid reading chunk sample data out of bounds. */
...@@ -194,7 +193,7 @@ public class Mp4SampleExtractorTest extends TestCase { ...@@ -194,7 +193,7 @@ public class Mp4SampleExtractorTest extends TestCase {
while (true) { while (true) {
int result = extractor.readSample(0, sampleHolder); int result = extractor.readSample(0, sampleHolder);
if (result == SampleSource.SAMPLE_READ) { if (result == SampleSource.SAMPLE_READ) {
assertTrue((sampleHolder.flags & MediaExtractor.SAMPLE_FLAG_SYNC) != 0); assertTrue(sampleHolder.isSyncFrame());
sampleHolder.clearData(); sampleHolder.clearData();
sampleIndex++; sampleIndex++;
} else if (result == SampleSource.END_OF_STREAM) { } else if (result == SampleSource.END_OF_STREAM) {
...@@ -343,10 +342,18 @@ public class Mp4SampleExtractorTest extends TestCase { ...@@ -343,10 +342,18 @@ public class Mp4SampleExtractorTest extends TestCase {
return result; return result;
} }
private static byte[] getMdat() { private static byte[] getMdat(int mdatOffset) {
// TODO: Put NAL length tags in at each sample position so the sample lengths don't have to ByteBuffer mdat = ByteBuffer.allocate(MDAT_SIZE);
// be multiples of four. int sampleIndex = 0;
return new byte[MDAT_SIZE]; for (int chunk = 0; chunk < CHUNK_OFFSETS.length; chunk++) {
int sampleOffset = CHUNK_OFFSETS[chunk];
for (int sample = 0; sample < SAMPLES_IN_CHUNK[chunk]; sample++) {
int sampleSize = SAMPLE_SIZES[sampleIndex++];
mdat.putInt(sampleOffset - mdatOffset, sampleSize);
sampleOffset += sampleSize;
}
}
return mdat.array();
} }
private static final DataSource getFakeDataSource(boolean includeStss, boolean mp4vFormat) { private static final DataSource getFakeDataSource(boolean includeStss, boolean mp4vFormat) {
...@@ -389,7 +396,7 @@ public class Mp4SampleExtractorTest extends TestCase { ...@@ -389,7 +396,7 @@ public class Mp4SampleExtractorTest extends TestCase {
atom(Atom.TYPE_stsc, getStsc()), atom(Atom.TYPE_stsc, getStsc()),
atom(Atom.TYPE_stsz, getStsz()), atom(Atom.TYPE_stsz, getStsz()),
atom(Atom.TYPE_stco, getStco())))))), atom(Atom.TYPE_stco, getStco())))))),
atom(Atom.TYPE_mdat, getMdat())); atom(Atom.TYPE_mdat, getMdat(mp4vFormat ? 1048 : 1038)));
} }
/** Gets a valid MP4 file with audio/video tracks and without a synchronization table. */ /** Gets a valid MP4 file with audio/video tracks and without a synchronization table. */
...@@ -425,7 +432,7 @@ public class Mp4SampleExtractorTest extends TestCase { ...@@ -425,7 +432,7 @@ public class Mp4SampleExtractorTest extends TestCase {
atom(Atom.TYPE_stsc, getStsc()), atom(Atom.TYPE_stsc, getStsc()),
atom(Atom.TYPE_stsz, getStsz()), atom(Atom.TYPE_stsz, getStsz()),
atom(Atom.TYPE_stco, getStco())))))), atom(Atom.TYPE_stco, getStco())))))),
atom(Atom.TYPE_mdat, getMdat())); atom(Atom.TYPE_mdat, getMdat(mp4vFormat ? 992 : 982)));
} }
private static Mp4Atom atom(int type, Mp4Atom... containedMp4Atoms) { private static Mp4Atom atom(int type, Mp4Atom... containedMp4Atoms) {
...@@ -506,9 +513,8 @@ public class Mp4SampleExtractorTest extends TestCase { ...@@ -506,9 +513,8 @@ public class Mp4SampleExtractorTest extends TestCase {
} }
/** /**
* Creates a {@link Mp4SampleExtractor} on a separate thread with a looper, so that it can use a * Creates a {@link Mp4Extractor} on a separate thread with a looper, so that it can use a handler
* handler for loading, and provides blocking operations like {@link #seekTo} and * for loading, and provides blocking operations like {@link #seekTo} and {@link #readSample}.
* {@link #readSample}.
*/ */
private static final class Mp4ExtractorWrapper extends Thread { private static final class Mp4ExtractorWrapper extends Thread {
...@@ -526,7 +532,7 @@ public class Mp4SampleExtractorTest extends TestCase { ...@@ -526,7 +532,7 @@ public class Mp4SampleExtractorTest extends TestCase {
private volatile CountDownLatch pendingOperationLatch; private volatile CountDownLatch pendingOperationLatch;
public Mp4ExtractorWrapper(DataSource dataSource) { public Mp4ExtractorWrapper(DataSource dataSource) {
super("Mp4SampleExtractorTest"); super("Mp4ExtractorTest");
this.dataSource = Assertions.checkNotNull(dataSource); this.dataSource = Assertions.checkNotNull(dataSource);
pendingOperationLatch = new CountDownLatch(1); pendingOperationLatch = new CountDownLatch(1);
start(); start();
...@@ -563,40 +569,45 @@ public class Mp4SampleExtractorTest extends TestCase { ...@@ -563,40 +569,45 @@ public class Mp4SampleExtractorTest extends TestCase {
@SuppressLint("HandlerLeak") @SuppressLint("HandlerLeak")
@Override @Override
public void run() { public void run() {
final Mp4SampleExtractor mp4SampleExtractor = final ExtractorSampleSource source = new ExtractorSampleSource(FAKE_URI, dataSource,
new Mp4SampleExtractor(dataSource, new DataSpec(FAKE_URI)); new Mp4Extractor(), 1, 2 * 1024 * 1024);
Looper.prepare(); Looper.prepare();
handler = new Handler() { handler = new Handler() {
@Override @Override
public void handleMessage(Message message) { public void handleMessage(Message message) {
try { try {
switch (message.what) { switch (message.what) {
case MSG_PREPARE: case MSG_PREPARE:
if (!mp4SampleExtractor.prepare()) { if (!source.prepare()) {
sendEmptyMessage(MSG_PREPARE); sendEmptyMessage(MSG_PREPARE);
} else { } else {
// Select the video track and get its metadata. // Select the video track and get its metadata.
mediaFormats = new MediaFormat[mp4SampleExtractor.getTrackCount()]; mediaFormats = new MediaFormat[source.getTrackCount()];
for (int track = 0; track < mp4SampleExtractor.getTrackCount(); track++) { MediaFormatHolder mediaFormatHolder = new MediaFormatHolder();
MediaFormat mediaFormat = mp4SampleExtractor.getMediaFormat(track); for (int track = 0; track < source.getTrackCount(); track++) {
source.enable(track, 0);
source.readData(track, 0, mediaFormatHolder, null, false);
MediaFormat mediaFormat = mediaFormatHolder.format;
mediaFormats[track] = mediaFormat; mediaFormats[track] = mediaFormat;
if (MimeTypes.isVideo(mediaFormat.mimeType)) { if (MimeTypes.isVideo(mediaFormat.mimeType)) {
mp4SampleExtractor.selectTrack(track);
selectedTrackMediaFormat = mediaFormat; selectedTrackMediaFormat = mediaFormat;
} else {
source.disable(track);
} }
} }
pendingOperationLatch.countDown(); pendingOperationLatch.countDown();
} }
break; break;
case MSG_SEEK_TO: case MSG_SEEK_TO:
long timestampUs = (long) message.obj; long timestampUs = (Long) message.obj;
mp4SampleExtractor.seekTo(timestampUs); source.seekToUs(timestampUs);
break; break;
case MSG_READ_SAMPLE: case MSG_READ_SAMPLE:
int trackIndex = message.arg1; int trackIndex = message.arg1;
SampleHolder sampleHolder = (SampleHolder) message.obj; SampleHolder sampleHolder = (SampleHolder) message.obj;
sampleHolder.clearData(); sampleHolder.clearData();
readSampleResult = mp4SampleExtractor.readSample(trackIndex, sampleHolder); readSampleResult = source.readData(trackIndex, 0, null, sampleHolder, false);
if (readSampleResult == SampleSource.NOTHING_READ) { if (readSampleResult == SampleSource.NOTHING_READ) {
Message.obtain(message).sendToTarget(); Message.obtain(message).sendToTarget();
return; return;
......
...@@ -289,7 +289,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -289,7 +289,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
ExtractorInput input = createTestInput(data); ExtractorInput input = createTestInput(data);
int readResult = Extractor.RESULT_CONTINUE; int readResult = Extractor.RESULT_CONTINUE;
while (readResult == Extractor.RESULT_CONTINUE) { while (readResult == Extractor.RESULT_CONTINUE) {
readResult = extractor.read(input); readResult = extractor.read(input, null);
} }
assertEquals(Extractor.RESULT_END_OF_INPUT, readResult); assertEquals(Extractor.RESULT_END_OF_INPUT, readResult);
} }
......
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