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;
|
||||
|
||||
import com.google.android.exoplayer.FrameworkSampleSource;
|
||||
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
||||
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
||||
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.RendererBuilderCallback;
|
||||
import com.google.android.exoplayer.source.DefaultSampleSource;
|
||||
import com.google.android.exoplayer.source.FrameworkSampleExtractor;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.MediaCodec;
|
||||
@ -46,7 +47,8 @@ public class DefaultRendererBuilder implements RendererBuilder {
|
||||
@Override
|
||||
public void buildRenderers(DemoPlayer player, RendererBuilderCallback callback) {
|
||||
// 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,
|
||||
null, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, player.getMainHandler(),
|
||||
player, 50);
|
||||
|
@ -15,11 +15,12 @@
|
||||
*/
|
||||
package com.google.android.exoplayer.demo.simple;
|
||||
|
||||
import com.google.android.exoplayer.FrameworkSampleSource;
|
||||
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
||||
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
||||
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilder;
|
||||
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.net.Uri;
|
||||
@ -41,7 +42,8 @@ import android.net.Uri;
|
||||
@Override
|
||||
public void buildRenderers(RendererBuilderCallback callback) {
|
||||
// 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,
|
||||
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, playerActivity.getMainHandler(),
|
||||
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