Add DefaultSampleSource and SampleExtractor interface.
SampleExtractor will initially only be implemented by FrameworkSampleExtractor which delegates to a MediaExtractor, but eventually it will also be implemented by additional extractors. The sample extractor can be used as a source of samples via DefaultSampleSource.
This commit is contained in:
parent
6f3ccc3615
commit
d1fe33cdf8
@ -15,12 +15,13 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.demo.full.player;
|
package com.google.android.exoplayer.demo.full.player;
|
||||||
|
|
||||||
import com.google.android.exoplayer.FrameworkSampleSource;
|
|
||||||
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
||||||
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
||||||
import com.google.android.exoplayer.TrackRenderer;
|
import com.google.android.exoplayer.TrackRenderer;
|
||||||
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder;
|
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder;
|
||||||
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilderCallback;
|
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilderCallback;
|
||||||
|
import com.google.android.exoplayer.source.DefaultSampleSource;
|
||||||
|
import com.google.android.exoplayer.source.FrameworkSampleExtractor;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
@ -46,7 +47,8 @@ public class DefaultRendererBuilder implements RendererBuilder {
|
|||||||
@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.
|
||||||
FrameworkSampleSource sampleSource = new FrameworkSampleSource(context, uri, null, 2);
|
DefaultSampleSource sampleSource =
|
||||||
|
new DefaultSampleSource(new FrameworkSampleExtractor(context, uri, null), 2);
|
||||||
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);
|
||||||
|
@ -15,11 +15,12 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.demo.simple;
|
package com.google.android.exoplayer.demo.simple;
|
||||||
|
|
||||||
import com.google.android.exoplayer.FrameworkSampleSource;
|
|
||||||
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
||||||
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
||||||
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilder;
|
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilder;
|
||||||
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilderCallback;
|
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilderCallback;
|
||||||
|
import com.google.android.exoplayer.source.DefaultSampleSource;
|
||||||
|
import com.google.android.exoplayer.source.FrameworkSampleExtractor;
|
||||||
|
|
||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@ -41,7 +42,8 @@ import android.net.Uri;
|
|||||||
@Override
|
@Override
|
||||||
public void buildRenderers(RendererBuilderCallback callback) {
|
public void buildRenderers(RendererBuilderCallback callback) {
|
||||||
// Build the video and audio renderers.
|
// Build the video and audio renderers.
|
||||||
FrameworkSampleSource sampleSource = new FrameworkSampleSource(playerActivity, uri, null, 2);
|
DefaultSampleSource sampleSource =
|
||||||
|
new DefaultSampleSource(new FrameworkSampleExtractor(playerActivity, uri, null), 2);
|
||||||
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
|
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
|
||||||
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, playerActivity.getMainHandler(),
|
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, playerActivity.getMainHandler(),
|
||||||
playerActivity, 50);
|
playerActivity, 50);
|
||||||
|
@ -1,238 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
|
||||||
import com.google.android.exoplayer.util.Util;
|
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.media.MediaExtractor;
|
|
||||||
import android.net.Uri;
|
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts samples from a stream using Android's {@link MediaExtractor}.
|
|
||||||
*/
|
|
||||||
// TODO: This implementation needs to be fixed so that its methods are non-blocking (either
|
|
||||||
// through use of a background thread, or through changes to the framework's MediaExtractor API).
|
|
||||||
@TargetApi(16)
|
|
||||||
public final class FrameworkSampleSource implements SampleSource {
|
|
||||||
|
|
||||||
private static final int TRACK_STATE_DISABLED = 0;
|
|
||||||
private static final int TRACK_STATE_ENABLED = 1;
|
|
||||||
private static final int TRACK_STATE_FORMAT_SENT = 2;
|
|
||||||
|
|
||||||
// Parameters for a Uri data source.
|
|
||||||
private final Context context;
|
|
||||||
private final Uri uri;
|
|
||||||
private final Map<String, String> headers;
|
|
||||||
|
|
||||||
// Parameters for a FileDescriptor data source.
|
|
||||||
private final FileDescriptor fileDescriptor;
|
|
||||||
private final long fileDescriptorOffset;
|
|
||||||
private final long fileDescriptorLength;
|
|
||||||
|
|
||||||
private MediaExtractor extractor;
|
|
||||||
private TrackInfo[] trackInfos;
|
|
||||||
private boolean prepared;
|
|
||||||
private int remainingReleaseCount;
|
|
||||||
private int[] trackStates;
|
|
||||||
private boolean[] pendingDiscontinuities;
|
|
||||||
|
|
||||||
private long seekPositionUs;
|
|
||||||
|
|
||||||
public FrameworkSampleSource(Context context, Uri uri, Map<String, String> headers,
|
|
||||||
int downstreamRendererCount) {
|
|
||||||
Assertions.checkState(Util.SDK_INT >= 16);
|
|
||||||
this.remainingReleaseCount = downstreamRendererCount;
|
|
||||||
|
|
||||||
this.context = context;
|
|
||||||
this.uri = uri;
|
|
||||||
this.headers = headers;
|
|
||||||
|
|
||||||
this.fileDescriptor = null;
|
|
||||||
this.fileDescriptorOffset = 0;
|
|
||||||
this.fileDescriptorLength = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FrameworkSampleSource(FileDescriptor fileDescriptor, long offset, long length,
|
|
||||||
int downstreamRendererCount) {
|
|
||||||
Assertions.checkState(Util.SDK_INT >= 16);
|
|
||||||
this.remainingReleaseCount = downstreamRendererCount;
|
|
||||||
|
|
||||||
this.fileDescriptor = fileDescriptor;
|
|
||||||
this.fileDescriptorOffset = offset;
|
|
||||||
this.fileDescriptorLength = length;
|
|
||||||
|
|
||||||
this.context = null;
|
|
||||||
this.uri = null;
|
|
||||||
this.headers = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean prepare() throws IOException {
|
|
||||||
if (!prepared) {
|
|
||||||
extractor = new MediaExtractor();
|
|
||||||
if (context != null) {
|
|
||||||
extractor.setDataSource(context, uri, headers);
|
|
||||||
} else {
|
|
||||||
extractor.setDataSource(fileDescriptor, fileDescriptorOffset, fileDescriptorLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
trackStates = new int[extractor.getTrackCount()];
|
|
||||||
pendingDiscontinuities = new boolean[trackStates.length];
|
|
||||||
trackInfos = new TrackInfo[trackStates.length];
|
|
||||||
for (int i = 0; i < trackStates.length; i++) {
|
|
||||||
android.media.MediaFormat format = extractor.getTrackFormat(i);
|
|
||||||
long durationUs = format.containsKey(android.media.MediaFormat.KEY_DURATION) ?
|
|
||||||
format.getLong(android.media.MediaFormat.KEY_DURATION) : C.UNKNOWN_TIME_US;
|
|
||||||
String mime = format.getString(android.media.MediaFormat.KEY_MIME);
|
|
||||||
trackInfos[i] = new TrackInfo(mime, durationUs);
|
|
||||||
}
|
|
||||||
prepared = true;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getTrackCount() {
|
|
||||||
Assertions.checkState(prepared);
|
|
||||||
return trackStates.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TrackInfo getTrackInfo(int track) {
|
|
||||||
Assertions.checkState(prepared);
|
|
||||||
return trackInfos[track];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void enable(int track, long positionUs) {
|
|
||||||
Assertions.checkState(prepared);
|
|
||||||
Assertions.checkState(trackStates[track] == TRACK_STATE_DISABLED);
|
|
||||||
trackStates[track] = TRACK_STATE_ENABLED;
|
|
||||||
extractor.selectTrack(track);
|
|
||||||
seekToUs(positionUs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean continueBuffering(long positionUs) {
|
|
||||||
// MediaExtractor takes care of buffering and blocks until it has samples, so we can always
|
|
||||||
// return true here. Although note that the blocking behavior is itself as bug, as per the
|
|
||||||
// TODO further up this file. This method will need to return something else as part of fixing
|
|
||||||
// the TODO.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int readData(int track, long positionUs, MediaFormatHolder formatHolder,
|
|
||||||
SampleHolder sampleHolder, boolean onlyReadDiscontinuity) {
|
|
||||||
Assertions.checkState(prepared);
|
|
||||||
Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED);
|
|
||||||
if (pendingDiscontinuities[track]) {
|
|
||||||
pendingDiscontinuities[track] = false;
|
|
||||||
return DISCONTINUITY_READ;
|
|
||||||
}
|
|
||||||
if (onlyReadDiscontinuity) {
|
|
||||||
return NOTHING_READ;
|
|
||||||
}
|
|
||||||
if (trackStates[track] != TRACK_STATE_FORMAT_SENT) {
|
|
||||||
formatHolder.format = MediaFormat.createFromFrameworkMediaFormatV16(
|
|
||||||
extractor.getTrackFormat(track));
|
|
||||||
formatHolder.drmInitData = Util.SDK_INT >= 18 ? getPsshInfoV18() : null;
|
|
||||||
trackStates[track] = TRACK_STATE_FORMAT_SENT;
|
|
||||||
return FORMAT_READ;
|
|
||||||
}
|
|
||||||
int extractorTrackIndex = extractor.getSampleTrackIndex();
|
|
||||||
if (extractorTrackIndex == track) {
|
|
||||||
if (sampleHolder.data != null) {
|
|
||||||
int offset = sampleHolder.data.position();
|
|
||||||
sampleHolder.size = extractor.readSampleData(sampleHolder.data, offset);
|
|
||||||
sampleHolder.data.position(offset + sampleHolder.size);
|
|
||||||
} else {
|
|
||||||
sampleHolder.size = 0;
|
|
||||||
}
|
|
||||||
sampleHolder.timeUs = extractor.getSampleTime();
|
|
||||||
sampleHolder.flags = extractor.getSampleFlags();
|
|
||||||
if ((sampleHolder.flags & MediaExtractor.SAMPLE_FLAG_ENCRYPTED) != 0) {
|
|
||||||
sampleHolder.cryptoInfo.setFromExtractorV16(extractor);
|
|
||||||
}
|
|
||||||
seekPositionUs = -1;
|
|
||||||
extractor.advance();
|
|
||||||
return SAMPLE_READ;
|
|
||||||
} else {
|
|
||||||
return extractorTrackIndex < 0 ? END_OF_STREAM : NOTHING_READ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(18)
|
|
||||||
private Map<UUID, byte[]> getPsshInfoV18() {
|
|
||||||
Map<UUID, byte[]> psshInfo = extractor.getPsshInfo();
|
|
||||||
return (psshInfo == null || psshInfo.isEmpty()) ? null : psshInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void disable(int track) {
|
|
||||||
Assertions.checkState(prepared);
|
|
||||||
Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED);
|
|
||||||
extractor.unselectTrack(track);
|
|
||||||
pendingDiscontinuities[track] = false;
|
|
||||||
trackStates[track] = TRACK_STATE_DISABLED;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void seekToUs(long positionUs) {
|
|
||||||
Assertions.checkState(prepared);
|
|
||||||
if (seekPositionUs != positionUs) {
|
|
||||||
// Avoid duplicate calls to the underlying extractor's seek method in the case that there
|
|
||||||
// have been no interleaving calls to advance.
|
|
||||||
seekPositionUs = positionUs;
|
|
||||||
extractor.seekTo(positionUs, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
|
|
||||||
for (int i = 0; i < trackStates.length; ++i) {
|
|
||||||
if (trackStates[i] != TRACK_STATE_DISABLED) {
|
|
||||||
pendingDiscontinuities[i] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getBufferedPositionUs() {
|
|
||||||
Assertions.checkState(prepared);
|
|
||||||
long bufferedDurationUs = extractor.getCachedDuration();
|
|
||||||
if (bufferedDurationUs == -1) {
|
|
||||||
return TrackRenderer.UNKNOWN_TIME_US;
|
|
||||||
} else {
|
|
||||||
long sampleTime = extractor.getSampleTime();
|
|
||||||
return sampleTime == -1 ? TrackRenderer.END_OF_TRACK_US : sampleTime + bufferedDurationUs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void release() {
|
|
||||||
Assertions.checkState(remainingReleaseCount > 0);
|
|
||||||
if (--remainingReleaseCount == 0) {
|
|
||||||
extractor.release();
|
|
||||||
extractor = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,161 @@
|
|||||||
|
/*
|
||||||
|
* 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.source;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.C;
|
||||||
|
import com.google.android.exoplayer.MediaFormatHolder;
|
||||||
|
import com.google.android.exoplayer.SampleHolder;
|
||||||
|
import com.google.android.exoplayer.SampleSource;
|
||||||
|
import com.google.android.exoplayer.TrackInfo;
|
||||||
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/** {@link SampleSource} that extracts sample data using a {@link SampleExtractor} */
|
||||||
|
public final class DefaultSampleSource implements SampleSource {
|
||||||
|
|
||||||
|
private static final int TRACK_STATE_DISABLED = 0;
|
||||||
|
private static final int TRACK_STATE_ENABLED = 1;
|
||||||
|
private static final int TRACK_STATE_FORMAT_SENT = 2;
|
||||||
|
|
||||||
|
private final SampleExtractor sampleExtractor;
|
||||||
|
|
||||||
|
private TrackInfo[] trackInfos;
|
||||||
|
private boolean prepared;
|
||||||
|
private int remainingReleaseCount;
|
||||||
|
private int[] trackStates;
|
||||||
|
private boolean[] pendingDiscontinuities;
|
||||||
|
|
||||||
|
private long seekPositionUs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new sample source that extracts samples using {@code sampleExtractor}. Specify the
|
||||||
|
* {@code downstreamRendererCount} to ensure that the sample source is released only when all
|
||||||
|
* downstream renderers have been released.
|
||||||
|
*
|
||||||
|
* @param sampleExtractor Sample extractor for accessing media samples.
|
||||||
|
* @param downstreamRendererCount Number of track renderers dependent on this sample source.
|
||||||
|
*/
|
||||||
|
public DefaultSampleSource(SampleExtractor sampleExtractor, int downstreamRendererCount) {
|
||||||
|
this.sampleExtractor = Assertions.checkNotNull(sampleExtractor);
|
||||||
|
this.remainingReleaseCount = downstreamRendererCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean prepare() throws IOException {
|
||||||
|
if (prepared) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sampleExtractor.prepare()) {
|
||||||
|
prepared = true;
|
||||||
|
trackInfos = sampleExtractor.getTrackInfos();
|
||||||
|
trackStates = new int[trackInfos.length];
|
||||||
|
pendingDiscontinuities = new boolean[trackInfos.length];
|
||||||
|
}
|
||||||
|
|
||||||
|
return prepared;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTrackCount() {
|
||||||
|
Assertions.checkState(prepared);
|
||||||
|
return trackInfos.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TrackInfo getTrackInfo(int track) {
|
||||||
|
Assertions.checkState(prepared);
|
||||||
|
return trackInfos[track];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enable(int track, long positionUs) {
|
||||||
|
Assertions.checkState(prepared);
|
||||||
|
Assertions.checkState(trackStates[track] == TRACK_STATE_DISABLED);
|
||||||
|
trackStates[track] = TRACK_STATE_ENABLED;
|
||||||
|
sampleExtractor.selectTrack(track);
|
||||||
|
seekToUs(positionUs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disable(int track) {
|
||||||
|
Assertions.checkState(prepared);
|
||||||
|
Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED);
|
||||||
|
sampleExtractor.deselectTrack(track);
|
||||||
|
pendingDiscontinuities[track] = false;
|
||||||
|
trackStates[track] = TRACK_STATE_DISABLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean continueBuffering(long positionUs) throws IOException {
|
||||||
|
// Do nothing.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int readData(int track, long positionUs, MediaFormatHolder formatHolder,
|
||||||
|
SampleHolder sampleHolder, boolean onlyReadDiscontinuity) throws IOException {
|
||||||
|
Assertions.checkState(prepared);
|
||||||
|
Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED);
|
||||||
|
if (pendingDiscontinuities[track]) {
|
||||||
|
pendingDiscontinuities[track] = false;
|
||||||
|
return DISCONTINUITY_READ;
|
||||||
|
}
|
||||||
|
if (onlyReadDiscontinuity) {
|
||||||
|
return NOTHING_READ;
|
||||||
|
}
|
||||||
|
if (trackStates[track] != TRACK_STATE_FORMAT_SENT) {
|
||||||
|
sampleExtractor.getTrackMediaFormat(track, formatHolder);
|
||||||
|
trackStates[track] = TRACK_STATE_FORMAT_SENT;
|
||||||
|
return FORMAT_READ;
|
||||||
|
}
|
||||||
|
|
||||||
|
seekPositionUs = C.UNKNOWN_TIME_US;
|
||||||
|
return sampleExtractor.readSample(track, sampleHolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seekToUs(long positionUs) {
|
||||||
|
Assertions.checkState(prepared);
|
||||||
|
if (seekPositionUs != positionUs) {
|
||||||
|
// Avoid duplicate calls to the underlying extractor's seek method in the case that there
|
||||||
|
// have been no interleaving calls to readSample.
|
||||||
|
seekPositionUs = positionUs;
|
||||||
|
sampleExtractor.seekTo(positionUs);
|
||||||
|
for (int i = 0; i < trackStates.length; ++i) {
|
||||||
|
if (trackStates[i] != TRACK_STATE_DISABLED) {
|
||||||
|
pendingDiscontinuities[i] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getBufferedPositionUs() {
|
||||||
|
Assertions.checkState(prepared);
|
||||||
|
return sampleExtractor.getBufferedPositionUs();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void release() {
|
||||||
|
Assertions.checkState(remainingReleaseCount > 0);
|
||||||
|
if (--remainingReleaseCount == 0) {
|
||||||
|
sampleExtractor.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,196 @@
|
|||||||
|
/*
|
||||||
|
* 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.source;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.C;
|
||||||
|
import com.google.android.exoplayer.MediaFormat;
|
||||||
|
import com.google.android.exoplayer.MediaFormatHolder;
|
||||||
|
import com.google.android.exoplayer.SampleHolder;
|
||||||
|
import com.google.android.exoplayer.SampleSource;
|
||||||
|
import com.google.android.exoplayer.TrackInfo;
|
||||||
|
import com.google.android.exoplayer.TrackRenderer;
|
||||||
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
|
import com.google.android.exoplayer.util.Util;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.media.MediaExtractor;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import java.io.FileDescriptor;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/** {@link SampleExtractor} that extracts samples from a stream using {@link MediaExtractor}. */
|
||||||
|
// TODO: This implementation needs to be fixed so that its methods are non-blocking (either
|
||||||
|
// through use of a background thread, or through changes to the framework's MediaExtractor API).
|
||||||
|
@TargetApi(16)
|
||||||
|
public final class FrameworkSampleExtractor implements SampleExtractor {
|
||||||
|
|
||||||
|
// Parameters for a Uri data source.
|
||||||
|
private final Context context;
|
||||||
|
private final Uri uri;
|
||||||
|
private final Map<String, String> headers;
|
||||||
|
|
||||||
|
// Parameters for a FileDescriptor data source.
|
||||||
|
private final FileDescriptor fileDescriptor;
|
||||||
|
private final long fileDescriptorOffset;
|
||||||
|
private final long fileDescriptorLength;
|
||||||
|
|
||||||
|
private final MediaExtractor mediaExtractor;
|
||||||
|
|
||||||
|
private TrackInfo[] trackInfos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new sample extractor reading from the specified {@code uri}.
|
||||||
|
*
|
||||||
|
* @param context Context for resolving {@code uri}.
|
||||||
|
* @param uri The content URI from which to extract data.
|
||||||
|
* @param headers Headers to send with requests for data.
|
||||||
|
*/
|
||||||
|
public FrameworkSampleExtractor(Context context, Uri uri, Map<String, String> headers) {
|
||||||
|
Assertions.checkState(Util.SDK_INT >= 16);
|
||||||
|
|
||||||
|
this.context = Assertions.checkNotNull(context);
|
||||||
|
this.uri = Assertions.checkNotNull(uri);
|
||||||
|
this.headers = headers;
|
||||||
|
|
||||||
|
fileDescriptor = null;
|
||||||
|
fileDescriptorOffset = 0;
|
||||||
|
fileDescriptorLength = 0;
|
||||||
|
|
||||||
|
mediaExtractor = new MediaExtractor();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new sample extractor reading from the specified seekable {@code fileDescriptor}.
|
||||||
|
* The caller is responsible for releasing the file descriptor.
|
||||||
|
*
|
||||||
|
* @param fileDescriptor File descriptor from which to read.
|
||||||
|
* @param offset The offset in bytes into the file where the data to be extracted starts.
|
||||||
|
* @param length The length in bytes of the data to be extracted.
|
||||||
|
*/
|
||||||
|
public FrameworkSampleExtractor(FileDescriptor fileDescriptor, long offset, long length) {
|
||||||
|
Assertions.checkState(Util.SDK_INT >= 16);
|
||||||
|
|
||||||
|
context = null;
|
||||||
|
uri = null;
|
||||||
|
headers = null;
|
||||||
|
|
||||||
|
this.fileDescriptor = Assertions.checkNotNull(fileDescriptor);
|
||||||
|
fileDescriptorOffset = offset;
|
||||||
|
fileDescriptorLength = length;
|
||||||
|
|
||||||
|
mediaExtractor = new MediaExtractor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean prepare() throws IOException {
|
||||||
|
if (context != null) {
|
||||||
|
mediaExtractor.setDataSource(context, uri, headers);
|
||||||
|
} else {
|
||||||
|
mediaExtractor.setDataSource(fileDescriptor, fileDescriptorOffset, fileDescriptorLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
int trackCount = mediaExtractor.getTrackCount();
|
||||||
|
trackInfos = new TrackInfo[trackCount];
|
||||||
|
for (int i = 0; i < trackCount; i++) {
|
||||||
|
android.media.MediaFormat format = mediaExtractor.getTrackFormat(i);
|
||||||
|
long durationUs = format.containsKey(android.media.MediaFormat.KEY_DURATION)
|
||||||
|
? format.getLong(android.media.MediaFormat.KEY_DURATION) : C.UNKNOWN_TIME_US;
|
||||||
|
String mime = format.getString(android.media.MediaFormat.KEY_MIME);
|
||||||
|
trackInfos[i] = new TrackInfo(mime, durationUs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TrackInfo[] getTrackInfos() {
|
||||||
|
return trackInfos;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void selectTrack(int index) {
|
||||||
|
mediaExtractor.selectTrack(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deselectTrack(int index) {
|
||||||
|
mediaExtractor.unselectTrack(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getBufferedPositionUs() {
|
||||||
|
long bufferedDurationUs = mediaExtractor.getCachedDuration();
|
||||||
|
if (bufferedDurationUs == -1) {
|
||||||
|
return TrackRenderer.UNKNOWN_TIME_US;
|
||||||
|
} else {
|
||||||
|
long sampleTime = mediaExtractor.getSampleTime();
|
||||||
|
return sampleTime == -1 ? TrackRenderer.END_OF_TRACK_US : sampleTime + bufferedDurationUs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seekTo(long positionUs) {
|
||||||
|
mediaExtractor.seekTo(positionUs, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getTrackMediaFormat(int track, MediaFormatHolder mediaFormatHolder) {
|
||||||
|
mediaFormatHolder.format =
|
||||||
|
MediaFormat.createFromFrameworkMediaFormatV16(mediaExtractor.getTrackFormat(track));
|
||||||
|
mediaFormatHolder.drmInitData = Util.SDK_INT >= 18 ? getPsshInfoV18() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int readSample(int track, SampleHolder sampleHolder) {
|
||||||
|
int sampleTrack = mediaExtractor.getSampleTrackIndex();
|
||||||
|
if (sampleTrack != track) {
|
||||||
|
return sampleTrack < 0 ? SampleSource.END_OF_STREAM : SampleSource.NOTHING_READ;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sampleHolder.data != null) {
|
||||||
|
int offset = sampleHolder.data.position();
|
||||||
|
sampleHolder.size = mediaExtractor.readSampleData(sampleHolder.data, offset);
|
||||||
|
sampleHolder.data.position(offset + sampleHolder.size);
|
||||||
|
} else {
|
||||||
|
sampleHolder.size = 0;
|
||||||
|
}
|
||||||
|
sampleHolder.timeUs = mediaExtractor.getSampleTime();
|
||||||
|
sampleHolder.flags = mediaExtractor.getSampleFlags();
|
||||||
|
if ((sampleHolder.flags & MediaExtractor.SAMPLE_FLAG_ENCRYPTED) != 0) {
|
||||||
|
sampleHolder.cryptoInfo.setFromExtractorV16(mediaExtractor);
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaExtractor.advance();
|
||||||
|
|
||||||
|
return SampleSource.SAMPLE_READ;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void release() {
|
||||||
|
mediaExtractor.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(18)
|
||||||
|
private Map<UUID, byte[]> getPsshInfoV18() {
|
||||||
|
Map<UUID, byte[]> psshInfo = mediaExtractor.getPsshInfo();
|
||||||
|
return (psshInfo == null || psshInfo.isEmpty()) ? null : psshInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
* 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.source;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.MediaFormat;
|
||||||
|
import com.google.android.exoplayer.MediaFormatHolder;
|
||||||
|
import com.google.android.exoplayer.SampleHolder;
|
||||||
|
import com.google.android.exoplayer.SampleSource;
|
||||||
|
import com.google.android.exoplayer.TrackInfo;
|
||||||
|
import com.google.android.exoplayer.TrackRenderer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extractor for reading track metadata and samples stored in tracks.
|
||||||
|
*
|
||||||
|
* <p>Call {@link #prepare} until it returns {@code true}, then access track metadata via
|
||||||
|
* {@link #getTrackInfos} and {@link #getTrackMediaFormat}.
|
||||||
|
*
|
||||||
|
* <p>Pass indices of tracks to read from to {@link #selectTrack}. A track can later be deselected
|
||||||
|
* by calling {@link #deselectTrack}. It is safe to select/deselect tracks after reading sample
|
||||||
|
* data or seeking. Initially, all tracks are deselected.
|
||||||
|
*
|
||||||
|
* <p>Call {@link #release()} when the extractor is no longer needed to free resources.
|
||||||
|
*/
|
||||||
|
public interface SampleExtractor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares the extractor for reading track metadata and samples.
|
||||||
|
*
|
||||||
|
* @return Whether the source is ready; if {@code false}, {@link #prepare()} must be called again.
|
||||||
|
* @throws IOException Thrown if the source can't be read.
|
||||||
|
*/
|
||||||
|
boolean prepare() throws IOException;
|
||||||
|
|
||||||
|
/** Returns track information about all tracks that can be selected. */
|
||||||
|
TrackInfo[] getTrackInfos();
|
||||||
|
|
||||||
|
/** Selects the track at {@code index} for reading sample data. */
|
||||||
|
void selectTrack(int index);
|
||||||
|
|
||||||
|
/** Deselects the track at {@code index}, so no more samples will be read from that track. */
|
||||||
|
void deselectTrack(int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an estimate of the position up to which data is buffered.
|
||||||
|
*
|
||||||
|
* <p>This method should not be called until after the extractor has been successfully prepared.
|
||||||
|
*
|
||||||
|
* @return An estimate of the absolute position in microseconds up to which data is buffered,
|
||||||
|
* or {@link TrackRenderer#END_OF_TRACK_US} if data is buffered to the end of the stream, or
|
||||||
|
* {@link TrackRenderer#UNKNOWN_TIME_US} if no estimate is available.
|
||||||
|
*/
|
||||||
|
long getBufferedPositionUs();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seeks to the specified time in microseconds.
|
||||||
|
*
|
||||||
|
* <p>This method should not be called until after the extractor has been successfully prepared.
|
||||||
|
*
|
||||||
|
* @param positionUs The seek position in microseconds.
|
||||||
|
*/
|
||||||
|
void seekTo(long positionUs);
|
||||||
|
|
||||||
|
/** Stores the {@link MediaFormat} of {@code track}. */
|
||||||
|
void getTrackMediaFormat(int track, MediaFormatHolder mediaFormatHolder);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the next sample in the track at index {@code track} into {@code sampleHolder}, returning
|
||||||
|
* {@link SampleSource#SAMPLE_READ} if it is available.
|
||||||
|
*
|
||||||
|
* <p>Advances to the next sample if a sample was read.
|
||||||
|
*
|
||||||
|
* @param track The index of the track from which to read a sample.
|
||||||
|
* @param sampleHolder The holder for read sample data, if {@link SampleSource#SAMPLE_READ} is
|
||||||
|
* returned.
|
||||||
|
* @return {@link SampleSource#SAMPLE_READ} if a sample was read into {@code sampleHolder}, or
|
||||||
|
* {@link SampleSource#END_OF_STREAM} if the last samples in all tracks have been read, or
|
||||||
|
* {@link SampleSource#NOTHING_READ} if the sample cannot be read immediately as it is not
|
||||||
|
* loaded.
|
||||||
|
*/
|
||||||
|
int readSample(int track, SampleHolder sampleHolder);
|
||||||
|
|
||||||
|
/** Releases resources associated with this extractor. */
|
||||||
|
void release();
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user