Commit 52a300f1 by Oliver Woodman

Merge fMP4/H264 and WebM/VP9 DASH implementations.

parent 1ddd5c6e
...@@ -28,8 +28,7 @@ import com.google.android.exoplayer.chunk.Format; ...@@ -28,8 +28,7 @@ import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.chunk.FormatEvaluator; import com.google.android.exoplayer.chunk.FormatEvaluator;
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator; import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
import com.google.android.exoplayer.chunk.MultiTrackChunkSource; import com.google.android.exoplayer.chunk.MultiTrackChunkSource;
import com.google.android.exoplayer.dash.DashMp4ChunkSource; import com.google.android.exoplayer.dash.DashChunkSource;
import com.google.android.exoplayer.dash.DashWebmChunkSource;
import com.google.android.exoplayer.dash.mpd.AdaptationSet; import com.google.android.exoplayer.dash.mpd.AdaptationSet;
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription; import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription;
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionFetcher; import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionFetcher;
...@@ -163,14 +162,8 @@ public class DashVodRendererBuilder implements RendererBuilder, ...@@ -163,14 +162,8 @@ public class DashVodRendererBuilder implements RendererBuilder,
DataSource videoDataSource = new HttpDataSource(userAgent, null, bandwidthMeter); DataSource videoDataSource = new HttpDataSource(userAgent, null, bandwidthMeter);
ChunkSource videoChunkSource; ChunkSource videoChunkSource;
String mimeType = videoRepresentations[0].format.mimeType; String mimeType = videoRepresentations[0].format.mimeType;
if (mimeType.equals(MimeTypes.VIDEO_MP4)) { if (mimeType.equals(MimeTypes.VIDEO_MP4) || mimeType.equals(MimeTypes.VIDEO_WEBM)) {
videoChunkSource = new DashMp4ChunkSource(videoDataSource, videoChunkSource = new DashChunkSource(videoDataSource,
new AdaptiveEvaluator(bandwidthMeter), videoRepresentations);
} else if (mimeType.equals(MimeTypes.VIDEO_WEBM)) {
// TODO: Figure out how to query supported vpX resolutions. For now, restrict to standard
// definition streams.
videoRepresentations = getSdRepresentations(videoRepresentations);
videoChunkSource = new DashWebmChunkSource(videoDataSource,
new AdaptiveEvaluator(bandwidthMeter), videoRepresentations); new AdaptiveEvaluator(bandwidthMeter), videoRepresentations);
} else { } else {
throw new IllegalStateException("Unexpected mime type: " + mimeType); throw new IllegalStateException("Unexpected mime type: " + mimeType);
...@@ -200,7 +193,7 @@ public class DashVodRendererBuilder implements RendererBuilder, ...@@ -200,7 +193,7 @@ public class DashVodRendererBuilder implements RendererBuilder,
Format format = representation.format; Format format = representation.format;
audioTrackNames[i] = format.id + " (" + format.numChannels + "ch, " + audioTrackNames[i] = format.id + " (" + format.numChannels + "ch, " +
format.audioSamplingRate + "Hz)"; format.audioSamplingRate + "Hz)";
audioChunkSources[i] = new DashMp4ChunkSource(audioDataSource, audioChunkSources[i] = new DashChunkSource(audioDataSource,
audioEvaluator, representation); audioEvaluator, representation);
} }
audioChunkSource = new MultiTrackChunkSource(audioChunkSources); audioChunkSource = new MultiTrackChunkSource(audioChunkSources);
......
...@@ -26,7 +26,7 @@ import com.google.android.exoplayer.chunk.ChunkSource; ...@@ -26,7 +26,7 @@ import com.google.android.exoplayer.chunk.ChunkSource;
import com.google.android.exoplayer.chunk.Format; import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.chunk.FormatEvaluator; import com.google.android.exoplayer.chunk.FormatEvaluator;
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator; import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
import com.google.android.exoplayer.dash.DashMp4ChunkSource; import com.google.android.exoplayer.dash.DashChunkSource;
import com.google.android.exoplayer.dash.mpd.AdaptationSet; import com.google.android.exoplayer.dash.mpd.AdaptationSet;
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription; import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription;
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionFetcher; import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionFetcher;
...@@ -116,7 +116,7 @@ import java.util.ArrayList; ...@@ -116,7 +116,7 @@ import java.util.ArrayList;
// Build the video renderer. // Build the video renderer.
DataSource videoDataSource = new HttpDataSource(userAgent, null, bandwidthMeter); DataSource videoDataSource = new HttpDataSource(userAgent, null, bandwidthMeter);
ChunkSource videoChunkSource = new DashMp4ChunkSource(videoDataSource, ChunkSource videoChunkSource = new DashChunkSource(videoDataSource,
new AdaptiveEvaluator(bandwidthMeter), videoRepresentations); new AdaptiveEvaluator(bandwidthMeter), videoRepresentations);
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl, ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true); VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true);
...@@ -125,7 +125,7 @@ import java.util.ArrayList; ...@@ -125,7 +125,7 @@ import java.util.ArrayList;
// Build the audio renderer. // Build the audio renderer.
DataSource audioDataSource = new HttpDataSource(userAgent, null, bandwidthMeter); DataSource audioDataSource = new HttpDataSource(userAgent, null, bandwidthMeter);
ChunkSource audioChunkSource = new DashMp4ChunkSource(audioDataSource, ChunkSource audioChunkSource = new DashChunkSource(audioDataSource,
new FormatEvaluator.FixedEvaluator(), audioRepresentation); new FormatEvaluator.FixedEvaluator(), audioRepresentation);
SampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl, SampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true); AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true);
......
...@@ -18,7 +18,7 @@ package com.google.android.exoplayer.chunk; ...@@ -18,7 +18,7 @@ package com.google.android.exoplayer.chunk;
import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.parser.mp4.FragmentedMp4Extractor; import com.google.android.exoplayer.parser.Extractor;
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.upstream.DataSpec;
import com.google.android.exoplayer.upstream.NonBlockingInputStream; import com.google.android.exoplayer.upstream.NonBlockingInputStream;
...@@ -32,7 +32,7 @@ import java.util.UUID; ...@@ -32,7 +32,7 @@ import java.util.UUID;
*/ */
public final class Mp4MediaChunk extends MediaChunk { public final class Mp4MediaChunk extends MediaChunk {
private final FragmentedMp4Extractor extractor; private final Extractor extractor;
private final boolean maybeSelfContained; private final boolean maybeSelfContained;
private final long sampleOffsetUs; private final long sampleOffsetUs;
...@@ -57,7 +57,7 @@ public final class Mp4MediaChunk extends MediaChunk { ...@@ -57,7 +57,7 @@ public final class Mp4MediaChunk extends MediaChunk {
*/ */
public Mp4MediaChunk(DataSource dataSource, DataSpec dataSpec, Format format, public Mp4MediaChunk(DataSource dataSource, DataSpec dataSpec, Format format,
int trigger, long startTimeUs, long endTimeUs, int nextChunkIndex, int trigger, long startTimeUs, long endTimeUs, int nextChunkIndex,
FragmentedMp4Extractor extractor, boolean maybeSelfContained, long sampleOffsetUs) { Extractor extractor, boolean maybeSelfContained, long sampleOffsetUs) {
super(dataSource, dataSpec, format, trigger, startTimeUs, endTimeUs, nextChunkIndex); super(dataSource, dataSpec, format, trigger, startTimeUs, endTimeUs, nextChunkIndex);
this.extractor = extractor; this.extractor = extractor;
this.maybeSelfContained = maybeSelfContained; this.maybeSelfContained = maybeSelfContained;
...@@ -89,7 +89,7 @@ public final class Mp4MediaChunk extends MediaChunk { ...@@ -89,7 +89,7 @@ public final class Mp4MediaChunk extends MediaChunk {
NonBlockingInputStream inputStream = getNonBlockingInputStream(); NonBlockingInputStream inputStream = getNonBlockingInputStream();
Assertions.checkState(inputStream != null); Assertions.checkState(inputStream != null);
int result = extractor.read(inputStream, null); int result = extractor.read(inputStream, null);
prepared = (result & FragmentedMp4Extractor.RESULT_NEED_SAMPLE_HOLDER) != 0; prepared = (result & Extractor.RESULT_NEED_SAMPLE_HOLDER) != 0;
} else { } else {
// We know there isn't a moov atom. The extractor must have parsed one from a separate // We know there isn't a moov atom. The extractor must have parsed one from a separate
// initialization chunk. // initialization chunk.
...@@ -107,7 +107,7 @@ public final class Mp4MediaChunk extends MediaChunk { ...@@ -107,7 +107,7 @@ public final class Mp4MediaChunk extends MediaChunk {
public boolean sampleAvailable() throws ParserException { public boolean sampleAvailable() throws ParserException {
NonBlockingInputStream inputStream = getNonBlockingInputStream(); NonBlockingInputStream inputStream = getNonBlockingInputStream();
int result = extractor.read(inputStream, null); int result = extractor.read(inputStream, null);
return (result & FragmentedMp4Extractor.RESULT_NEED_SAMPLE_HOLDER) != 0; return (result & Extractor.RESULT_NEED_SAMPLE_HOLDER) != 0;
} }
@Override @Override
...@@ -115,7 +115,7 @@ public final class Mp4MediaChunk extends MediaChunk { ...@@ -115,7 +115,7 @@ public final class Mp4MediaChunk extends MediaChunk {
NonBlockingInputStream inputStream = getNonBlockingInputStream(); NonBlockingInputStream inputStream = getNonBlockingInputStream();
Assertions.checkState(inputStream != null); Assertions.checkState(inputStream != null);
int result = extractor.read(inputStream, holder); int result = extractor.read(inputStream, holder);
boolean sampleRead = (result & FragmentedMp4Extractor.RESULT_READ_SAMPLE) != 0; boolean sampleRead = (result & Extractor.RESULT_READ_SAMPLE) != 0;
if (sampleRead) { if (sampleRead) {
holder.timeUs -= sampleOffsetUs; holder.timeUs -= sampleOffsetUs;
} }
......
/*
* 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.chunk;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.parser.webm.WebmExtractor;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSpec;
import com.google.android.exoplayer.upstream.NonBlockingInputStream;
import com.google.android.exoplayer.util.Assertions;
import java.util.Map;
import java.util.UUID;
/**
* A WebM {@link MediaChunk}.
*/
public final class WebmMediaChunk extends MediaChunk {
private final WebmExtractor extractor;
/**
* @param dataSource A {@link DataSource} for loading the data.
* @param dataSpec Defines the data to be loaded.
* @param format The format of the stream to which this chunk belongs.
* @param extractor The extractor that will be used to extract the samples.
* @param trigger The reason for this chunk being selected.
* @param startTimeUs The start time of the media contained by the chunk, in microseconds.
* @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param nextChunkIndex The index of the next chunk, or -1 if this is the last chunk.
*/
public WebmMediaChunk(DataSource dataSource, DataSpec dataSpec, Format format,
int trigger, WebmExtractor extractor, long startTimeUs, long endTimeUs,
int nextChunkIndex) {
super(dataSource, dataSpec, format, trigger, startTimeUs, endTimeUs, nextChunkIndex);
this.extractor = extractor;
}
@Override
public void seekToStart() {
seekTo(0, false);
}
@Override
public boolean seekTo(long positionUs, boolean allowNoop) {
boolean isDiscontinuous = extractor.seekTo(positionUs, allowNoop);
if (isDiscontinuous) {
resetReadPosition();
}
return isDiscontinuous;
}
@Override
public boolean prepare() {
return true;
}
@Override
public boolean sampleAvailable() throws ParserException {
NonBlockingInputStream inputStream = getNonBlockingInputStream();
int result = extractor.read(inputStream, null);
return (result & WebmExtractor.RESULT_NEED_SAMPLE_HOLDER) != 0;
}
@Override
public boolean read(SampleHolder holder) {
NonBlockingInputStream inputStream = getNonBlockingInputStream();
Assertions.checkState(inputStream != null);
int result = extractor.read(inputStream, holder);
return (result & WebmExtractor.RESULT_READ_SAMPLE) != 0;
}
@Override
public MediaFormat getMediaFormat() {
return extractor.getFormat();
}
@Override
public Map<UUID, byte[]> getPsshInfo() {
// TODO: Add support for Pssh to WebmExtractor
return null;
}
}
...@@ -29,10 +29,13 @@ import com.google.android.exoplayer.chunk.MediaChunk; ...@@ -29,10 +29,13 @@ import com.google.android.exoplayer.chunk.MediaChunk;
import com.google.android.exoplayer.chunk.Mp4MediaChunk; import com.google.android.exoplayer.chunk.Mp4MediaChunk;
import com.google.android.exoplayer.dash.mpd.RangedUri; import com.google.android.exoplayer.dash.mpd.RangedUri;
import com.google.android.exoplayer.dash.mpd.Representation; import com.google.android.exoplayer.dash.mpd.Representation;
import com.google.android.exoplayer.parser.Extractor;
import com.google.android.exoplayer.parser.mp4.FragmentedMp4Extractor; import com.google.android.exoplayer.parser.mp4.FragmentedMp4Extractor;
import com.google.android.exoplayer.parser.webm.WebmExtractor;
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.upstream.DataSpec;
import com.google.android.exoplayer.upstream.NonBlockingInputStream; import com.google.android.exoplayer.upstream.NonBlockingInputStream;
import com.google.android.exoplayer.util.MimeTypes;
import android.net.Uri; import android.net.Uri;
...@@ -42,9 +45,11 @@ import java.util.HashMap; ...@@ -42,9 +45,11 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
/** /**
* An {@link ChunkSource} for Mp4 DASH streams. * An {@link ChunkSource} for DASH streams.
* <p>
* This implementation currently supports fMP4 and webm.
*/ */
public class DashMp4ChunkSource implements ChunkSource { public class DashChunkSource implements ChunkSource {
private final TrackInfo trackInfo; private final TrackInfo trackInfo;
private final DataSource dataSource; private final DataSource dataSource;
...@@ -55,7 +60,7 @@ public class DashMp4ChunkSource implements ChunkSource { ...@@ -55,7 +60,7 @@ public class DashMp4ChunkSource implements ChunkSource {
private final Format[] formats; private final Format[] formats;
private final HashMap<String, Representation> representations; private final HashMap<String, Representation> representations;
private final HashMap<String, FragmentedMp4Extractor> extractors; private final HashMap<String, Extractor> extractors;
private final HashMap<String, DashSegmentIndex> segmentIndexes; private final HashMap<String, DashSegmentIndex> segmentIndexes;
private boolean lastChunkWasInitialization; private boolean lastChunkWasInitialization;
...@@ -65,12 +70,12 @@ public class DashMp4ChunkSource implements ChunkSource { ...@@ -65,12 +70,12 @@ public class DashMp4ChunkSource implements ChunkSource {
* @param evaluator Selects from the available formats. * @param evaluator Selects from the available formats.
* @param representations The representations to be considered by the source. * @param representations The representations to be considered by the source.
*/ */
public DashMp4ChunkSource(DataSource dataSource, FormatEvaluator evaluator, public DashChunkSource(DataSource dataSource, FormatEvaluator evaluator,
Representation... representations) { Representation... representations) {
this.dataSource = dataSource; this.dataSource = dataSource;
this.evaluator = evaluator; this.evaluator = evaluator;
this.formats = new Format[representations.length]; this.formats = new Format[representations.length];
this.extractors = new HashMap<String, FragmentedMp4Extractor>(); this.extractors = new HashMap<String, Extractor>();
this.segmentIndexes = new HashMap<String, DashSegmentIndex>(); this.segmentIndexes = new HashMap<String, DashSegmentIndex>();
this.representations = new HashMap<String, Representation>(); this.representations = new HashMap<String, Representation>();
this.trackInfo = new TrackInfo(representations[0].format.mimeType, this.trackInfo = new TrackInfo(representations[0].format.mimeType,
...@@ -82,7 +87,9 @@ public class DashMp4ChunkSource implements ChunkSource { ...@@ -82,7 +87,9 @@ public class DashMp4ChunkSource implements ChunkSource {
formats[i] = representations[i].format; formats[i] = representations[i].format;
maxWidth = Math.max(formats[i].width, maxWidth); maxWidth = Math.max(formats[i].width, maxWidth);
maxHeight = Math.max(formats[i].height, maxHeight); maxHeight = Math.max(formats[i].height, maxHeight);
extractors.put(formats[i].id, new FragmentedMp4Extractor()); Extractor extractor = formats[i].mimeType.startsWith(MimeTypes.VIDEO_WEBM)
? new WebmExtractor() : new FragmentedMp4Extractor();
extractors.put(formats[i].id, extractor);
this.representations.put(formats[i].id, representations[i]); this.representations.put(formats[i].id, representations[i]);
DashSegmentIndex segmentIndex = representations[i].getIndex(); DashSegmentIndex segmentIndex = representations[i].getIndex();
if (segmentIndex != null) { if (segmentIndex != null) {
...@@ -142,7 +149,7 @@ public class DashMp4ChunkSource implements ChunkSource { ...@@ -142,7 +149,7 @@ public class DashMp4ChunkSource implements ChunkSource {
} }
Representation selectedRepresentation = representations.get(selectedFormat.id); Representation selectedRepresentation = representations.get(selectedFormat.id);
FragmentedMp4Extractor extractor = extractors.get(selectedRepresentation.format.id); Extractor extractor = extractors.get(selectedRepresentation.format.id);
RangedUri pendingInitializationUri = null; RangedUri pendingInitializationUri = null;
RangedUri pendingIndexUri = null; RangedUri pendingIndexUri = null;
...@@ -191,35 +198,39 @@ public class DashMp4ChunkSource implements ChunkSource { ...@@ -191,35 +198,39 @@ public class DashMp4ChunkSource implements ChunkSource {
} }
private Chunk newInitializationChunk(RangedUri initializationUri, RangedUri indexUri, private Chunk newInitializationChunk(RangedUri initializationUri, RangedUri indexUri,
Representation representation, FragmentedMp4Extractor extractor, DataSource dataSource, Representation representation, Extractor extractor, DataSource dataSource,
int trigger) { int trigger) {
int expectedExtractorResult = FragmentedMp4Extractor.RESULT_END_OF_STREAM; int expectedExtractorResult = Extractor.RESULT_END_OF_STREAM;
long indexAnchor = 0; long indexAnchor = 0;
RangedUri requestUri; RangedUri requestUri;
if (initializationUri != null) { if (initializationUri != null) {
// It's common for initialization and index data to be stored adjacently. Attempt to merge // It's common for initialization and index data to be stored adjacently. Attempt to merge
// the two requests together to request both at once. // the two requests together to request both at once.
expectedExtractorResult |= FragmentedMp4Extractor.RESULT_READ_INIT; expectedExtractorResult |= Extractor.RESULT_READ_INIT;
requestUri = initializationUri.attemptMerge(indexUri); requestUri = initializationUri.attemptMerge(indexUri);
if (requestUri != null) { if (requestUri != null) {
expectedExtractorResult |= FragmentedMp4Extractor.RESULT_READ_INDEX; expectedExtractorResult |= Extractor.RESULT_READ_INDEX;
indexAnchor = indexUri.start + indexUri.length; if (extractor.hasRelativeIndexOffsets()) {
indexAnchor = indexUri.start + indexUri.length;
}
} else { } else {
requestUri = initializationUri; requestUri = initializationUri;
} }
} else { } else {
requestUri = indexUri; requestUri = indexUri;
indexAnchor = indexUri.start + indexUri.length; if (extractor.hasRelativeIndexOffsets()) {
expectedExtractorResult |= FragmentedMp4Extractor.RESULT_READ_INDEX; indexAnchor = indexUri.start + indexUri.length;
}
expectedExtractorResult |= Extractor.RESULT_READ_INDEX;
} }
DataSpec dataSpec = new DataSpec(requestUri.getUri(), requestUri.start, requestUri.length, DataSpec dataSpec = new DataSpec(requestUri.getUri(), requestUri.start, requestUri.length,
representation.getCacheKey()); representation.getCacheKey());
return new InitializationMp4Loadable(dataSource, dataSpec, trigger, representation.format, return new InitializationLoadable(dataSource, dataSpec, trigger, representation.format,
extractor, expectedExtractorResult, indexAnchor); extractor, expectedExtractorResult, indexAnchor);
} }
private Chunk newMediaChunk(Representation representation, DashSegmentIndex segmentIndex, private Chunk newMediaChunk(Representation representation, DashSegmentIndex segmentIndex,
FragmentedMp4Extractor extractor, DataSource dataSource, int segmentNum, int trigger) { Extractor extractor, DataSource dataSource, int segmentNum, int trigger) {
int lastSegmentNum = segmentIndex.getLastSegmentNum(); int lastSegmentNum = segmentIndex.getLastSegmentNum();
int nextSegmentNum = segmentNum == lastSegmentNum ? -1 : segmentNum + 1; int nextSegmentNum = segmentNum == lastSegmentNum ? -1 : segmentNum + 1;
long startTimeUs = segmentIndex.getTimeUs(segmentNum); long startTimeUs = segmentIndex.getTimeUs(segmentNum);
...@@ -232,15 +243,15 @@ public class DashMp4ChunkSource implements ChunkSource { ...@@ -232,15 +243,15 @@ public class DashMp4ChunkSource implements ChunkSource {
endTimeUs, nextSegmentNum, extractor, false, 0); endTimeUs, nextSegmentNum, extractor, false, 0);
} }
private class InitializationMp4Loadable extends Chunk { private class InitializationLoadable extends Chunk {
private final FragmentedMp4Extractor extractor; private final Extractor extractor;
private final int expectedExtractorResult; private final int expectedExtractorResult;
private final long indexAnchor; private final long indexAnchor;
private final Uri uri; private final Uri uri;
public InitializationMp4Loadable(DataSource dataSource, DataSpec dataSpec, int trigger, public InitializationLoadable(DataSource dataSource, DataSpec dataSpec, int trigger,
Format format, FragmentedMp4Extractor extractor, int expectedExtractorResult, Format format, Extractor extractor, int expectedExtractorResult,
long indexAnchor) { long indexAnchor) {
super(dataSource, dataSpec, format, trigger); super(dataSource, dataSpec, format, trigger);
this.extractor = extractor; this.extractor = extractor;
...@@ -256,7 +267,7 @@ public class DashMp4ChunkSource implements ChunkSource { ...@@ -256,7 +267,7 @@ public class DashMp4ChunkSource implements ChunkSource {
throw new ParserException("Invalid extractor result. Expected " throw new ParserException("Invalid extractor result. Expected "
+ expectedExtractorResult + ", got " + result); + expectedExtractorResult + ", got " + result);
} }
if ((result & FragmentedMp4Extractor.RESULT_READ_INDEX) != 0) { if ((result & Extractor.RESULT_READ_INDEX) != 0) {
segmentIndexes.put(format.id, segmentIndexes.put(format.id,
new DashWrappingSegmentIndex(extractor.getIndex(), uri, indexAnchor)); new DashWrappingSegmentIndex(extractor.getIndex(), uri, indexAnchor));
} }
......
/*
* 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.parser;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.upstream.NonBlockingInputStream;
import java.util.Map;
import java.util.UUID;
/**
* Facilitates extraction of media samples from a container format.
*/
public interface Extractor {
/**
* An attempt to read from the input stream returned insufficient data.
*/
public static final int RESULT_NEED_MORE_DATA = 1;
/**
* The end of the input stream was reached.
*/
public static final int RESULT_END_OF_STREAM = 2;
/**
* A media sample was read.
*/
public static final int RESULT_READ_SAMPLE = 4;
/**
* Initialization data was read. The parsed data can be read using {@link #getFormat()} and
* {@link #getPsshInfo}.
*/
public static final int RESULT_READ_INIT = 8;
/**
* A sidx atom was read. The parsed data can be read using {@link #getIndex()}.
*/
public static final int RESULT_READ_INDEX = 16;
/**
* The next thing to be read is a sample, but a {@link SampleHolder} was not supplied.
*/
public static final int RESULT_NEED_SAMPLE_HOLDER = 32;
/**
* Returns the segment index parsed from the stream.
*
* @return The segment index, or null if a SIDX atom has yet to be parsed.
*/
public SegmentIndex getIndex();
/**
* Returns true if the offsets in the index returned by {@link #getIndex()} are relative to the
* first byte following the initialization data, or false if they are absolute (i.e. relative to
* the first byte of the stream).
*
* @return True if the offsets are relative to the first byte following the initialization data.
* False otherwise.
*/
public boolean hasRelativeIndexOffsets();
/**
* Returns the format of the samples contained within the media stream.
*
* @return The sample media format, or null if the format has yet to be parsed.
*/
public MediaFormat getFormat();
/**
* Returns the pssh information parsed from the stream.
*
* @return The pssh information. May be null if pssh data has yet to be parsed, or if the stream
* does not contain any pssh data.
*/
public Map<UUID, byte[]> getPsshInfo();
/**
* Consumes data from a {@link NonBlockingInputStream}.
* <p>
* The read terminates if the end of the input stream is reached, if an attempt to read from the
* input stream returned 0 bytes of data, or if a sample is read. The returned flags indicate
* both the reason for termination and data that was parsed during the read.
*
* @param inputStream The input stream from which data should be read.
* @param out A {@link SampleHolder} into which the next sample should be read. If null then
* {@link #RESULT_NEED_SAMPLE_HOLDER} will be returned once a sample has been reached.
* @return One or more of the {@code RESULT_*} flags defined in this class.
* @throws ParserException If an error occurs parsing the media data.
*/
public int read(NonBlockingInputStream inputStream, SampleHolder out) throws ParserException;
/**
* Seeks to a position before or equal to the requested time.
*
* @param seekTimeUs The desired seek time in microseconds.
* @param allowNoop Allow the seek operation to do nothing if the seek time is in the current
* fragment run, is equal to or greater than the time of the current sample, and if there
* does not exist a sync frame between these two times.
* @return True if the operation resulted in a change of state. False if it was a no-op.
*/
public boolean seekTo(long seekTimeUs, boolean allowNoop);
}
...@@ -18,6 +18,7 @@ package com.google.android.exoplayer.parser.mp4; ...@@ -18,6 +18,7 @@ package com.google.android.exoplayer.parser.mp4;
import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.parser.Extractor;
import com.google.android.exoplayer.parser.SegmentIndex; import com.google.android.exoplayer.parser.SegmentIndex;
import com.google.android.exoplayer.parser.mp4.Atom.ContainerAtom; import com.google.android.exoplayer.parser.mp4.Atom.ContainerAtom;
import com.google.android.exoplayer.parser.mp4.Atom.LeafAtom; import com.google.android.exoplayer.parser.mp4.Atom.LeafAtom;
...@@ -48,7 +49,7 @@ import java.util.UUID; ...@@ -48,7 +49,7 @@ import java.util.UUID;
* <p> * <p>
* This implementation only supports de-muxed (i.e. single track) streams. * This implementation only supports de-muxed (i.e. single track) streams.
*/ */
public final class FragmentedMp4Extractor { public final class FragmentedMp4Extractor implements Extractor {
/** /**
* Flag to work around an issue in some video streams where every frame is marked as a sync frame. * Flag to work around an issue in some video streams where every frame is marked as a sync frame.
...@@ -59,32 +60,6 @@ public final class FragmentedMp4Extractor { ...@@ -59,32 +60,6 @@ public final class FragmentedMp4Extractor {
*/ */
public static final int WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME = 1; public static final int WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME = 1;
/**
* An attempt to read from the input stream returned insufficient data.
*/
public static final int RESULT_NEED_MORE_DATA = 1;
/**
* The end of the input stream was reached.
*/
public static final int RESULT_END_OF_STREAM = 2;
/**
* A media sample was read.
*/
public static final int RESULT_READ_SAMPLE = 4;
/**
* A moov atom was read. The parsed data can be read using {@link #getFormat()} and
* {@link #getPsshInfo}.
*/
public static final int RESULT_READ_INIT = 8;
/**
* A sidx atom was read. The parsed data can be read using {@link #getIndex()}.
*/
public static final int RESULT_READ_INDEX = 16;
/**
* The next thing to be read is a sample, but a {@link SampleHolder} was not supplied.
*/
public static final int RESULT_NEED_SAMPLE_HOLDER = 32;
private static final int READ_TERMINATING_RESULTS = RESULT_NEED_MORE_DATA | RESULT_END_OF_STREAM private static final int READ_TERMINATING_RESULTS = RESULT_NEED_MORE_DATA | RESULT_END_OF_STREAM
| RESULT_READ_SAMPLE | RESULT_NEED_SAMPLE_HOLDER; | RESULT_READ_SAMPLE | RESULT_NEED_SAMPLE_HOLDER;
private static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1}; private static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1};
...@@ -197,22 +172,13 @@ public final class FragmentedMp4Extractor { ...@@ -197,22 +172,13 @@ public final class FragmentedMp4Extractor {
} }
/** /**
* Returns the segment index parsed from the stream. * Sideloads track information into the extractor.
*
* @return The segment index, or null if a SIDX atom has yet to be parsed.
*/
public SegmentIndex getIndex() {
return segmentIndex;
}
/**
* Returns the pssh information parsed from the stream.
* *
* @return The pssh information. May be null if the MOOV atom has yet to be parsed of if it did * @param track The track to sideload.
* not contain any pssh information.
*/ */
public Map<UUID, byte[]> getPsshInfo() { public void setTrack(Track track) {
return psshData.isEmpty() ? null : psshData; this.extendsDefaults = new DefaultSampleValues(0, 0, 0, 0);
this.track = track;
} }
/** /**
...@@ -229,38 +195,27 @@ public final class FragmentedMp4Extractor { ...@@ -229,38 +195,27 @@ public final class FragmentedMp4Extractor {
psshData.put(uuid, data); psshData.put(uuid, data);
} }
/** @Override
* Returns the format of the samples contained within the media stream. public Map<UUID, byte[]> getPsshInfo() {
* return psshData.isEmpty() ? null : psshData;
* @return The sample media format, or null if a MOOV atom has yet to be parsed.
*/
public MediaFormat getFormat() {
return track == null ? null : track.mediaFormat;
} }
/** @Override
* Sideloads track information into the extractor. public SegmentIndex getIndex() {
* return segmentIndex;
* @param track The track to sideload.
*/
public void setTrack(Track track) {
this.extendsDefaults = new DefaultSampleValues(0, 0, 0, 0);
this.track = track;
} }
/** @Override
* Consumes data from a {@link NonBlockingInputStream}. public boolean hasRelativeIndexOffsets() {
* <p> return true;
* The read terminates if the end of the input stream is reached, if an attempt to read from the }
* input stream returned 0 bytes of data, or if a sample is read. The returned flags indicate
* both the reason for termination and data that was parsed during the read. @Override
* public MediaFormat getFormat() {
* @param inputStream The input stream from which data should be read. return track == null ? null : track.mediaFormat;
* @param out A {@link SampleHolder} into which the next sample should be read. If null then }
* {@link #RESULT_NEED_SAMPLE_HOLDER} will be returned once a sample has been reached.
* @return One or more of the {@code RESULT_*} flags defined in this class. @Override
* @throws ParserException If an error occurs parsing the media data.
*/
public int read(NonBlockingInputStream inputStream, SampleHolder out) public int read(NonBlockingInputStream inputStream, SampleHolder out)
throws ParserException { throws ParserException {
try { try {
...@@ -287,15 +242,7 @@ public final class FragmentedMp4Extractor { ...@@ -287,15 +242,7 @@ public final class FragmentedMp4Extractor {
} }
} }
/** @Override
* Seeks to a position before or equal to the requested time.
*
* @param seekTimeUs The desired seek time in microseconds.
* @param allowNoop Allow the seek operation to do nothing if the seek time is in the current
* fragment run, is equal to or greater than the time of the current sample, and if there
* does not exist a sync frame between these two times.
* @return True if the operation resulted in a change of state. False if it was a no-op.
*/
public boolean seekTo(long seekTimeUs, boolean allowNoop) { public boolean seekTo(long seekTimeUs, boolean allowNoop) {
pendingSeekTimeMs = (int) (seekTimeUs / 1000); pendingSeekTimeMs = (int) (seekTimeUs / 1000);
if (allowNoop && fragmentRun != null if (allowNoop && fragmentRun != null
......
...@@ -26,6 +26,7 @@ import com.google.android.exoplayer.chunk.FormatEvaluator; ...@@ -26,6 +26,7 @@ import com.google.android.exoplayer.chunk.FormatEvaluator;
import com.google.android.exoplayer.chunk.FormatEvaluator.Evaluation; 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.Mp4MediaChunk; import com.google.android.exoplayer.chunk.Mp4MediaChunk;
import com.google.android.exoplayer.parser.Extractor;
import com.google.android.exoplayer.parser.mp4.FragmentedMp4Extractor; import com.google.android.exoplayer.parser.mp4.FragmentedMp4Extractor;
import com.google.android.exoplayer.parser.mp4.Track; import com.google.android.exoplayer.parser.mp4.Track;
import com.google.android.exoplayer.parser.mp4.TrackEncryptionBox; import com.google.android.exoplayer.parser.mp4.TrackEncryptionBox;
...@@ -227,7 +228,7 @@ public class SmoothStreamingChunkSource implements ChunkSource { ...@@ -227,7 +228,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
} }
private static MediaChunk newMediaChunk(Format formatInfo, Uri uri, String cacheKey, private static MediaChunk newMediaChunk(Format formatInfo, Uri uri, String cacheKey,
FragmentedMp4Extractor extractor, DataSource dataSource, int chunkIndex, Extractor extractor, DataSource dataSource, int chunkIndex,
boolean isLast, long chunkStartTimeUs, long nextChunkStartTimeUs, int trigger) { boolean isLast, long chunkStartTimeUs, long nextChunkStartTimeUs, int trigger) {
int nextChunkIndex = isLast ? -1 : chunkIndex + 1; int nextChunkIndex = isLast ? -1 : chunkIndex + 1;
long nextStartTimeUs = isLast ? -1 : nextChunkStartTimeUs; long nextStartTimeUs = isLast ? -1 : nextChunkStartTimeUs;
......
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