mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Add initial AC3 passthrough support.
This commit is contained in:
parent
81bf68b1cb
commit
5a3340d638
4 changed files with 373 additions and 8 deletions
|
|
@ -17,6 +17,8 @@ package com.google.android.exoplayer.demo.full;
|
||||||
|
|
||||||
import com.google.android.exoplayer.ExoPlayer;
|
import com.google.android.exoplayer.ExoPlayer;
|
||||||
import com.google.android.exoplayer.VideoSurfaceView;
|
import com.google.android.exoplayer.VideoSurfaceView;
|
||||||
|
import com.google.android.exoplayer.audio.AudioCapabilities;
|
||||||
|
import com.google.android.exoplayer.audio.AudioCapabilitiesReceiver;
|
||||||
import com.google.android.exoplayer.demo.DemoUtil;
|
import com.google.android.exoplayer.demo.DemoUtil;
|
||||||
import com.google.android.exoplayer.demo.R;
|
import com.google.android.exoplayer.demo.R;
|
||||||
import com.google.android.exoplayer.demo.full.player.DashRendererBuilder;
|
import com.google.android.exoplayer.demo.full.player.DashRendererBuilder;
|
||||||
|
|
@ -59,7 +61,7 @@ import android.widget.Toast;
|
||||||
* An activity that plays media using {@link DemoPlayer}.
|
* An activity that plays media using {@link DemoPlayer}.
|
||||||
*/
|
*/
|
||||||
public class FullPlayerActivity extends Activity implements SurfaceHolder.Callback, OnClickListener,
|
public class FullPlayerActivity extends Activity implements SurfaceHolder.Callback, OnClickListener,
|
||||||
DemoPlayer.Listener, DemoPlayer.TextListener {
|
DemoPlayer.Listener, DemoPlayer.TextListener, AudioCapabilitiesReceiver.Listener {
|
||||||
|
|
||||||
private static final float CAPTION_LINE_HEIGHT_RATIO = 0.0533f;
|
private static final float CAPTION_LINE_HEIGHT_RATIO = 0.0533f;
|
||||||
private static final int MENU_GROUP_TRACKS = 1;
|
private static final int MENU_GROUP_TRACKS = 1;
|
||||||
|
|
@ -89,6 +91,9 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
|
||||||
private int contentType;
|
private int contentType;
|
||||||
private String contentId;
|
private String contentId;
|
||||||
|
|
||||||
|
private AudioCapabilitiesReceiver audioCapabilitiesReceiver;
|
||||||
|
private AudioCapabilities audioCapabilities;
|
||||||
|
|
||||||
// Activity lifecycle
|
// Activity lifecycle
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -112,6 +117,8 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
audioCapabilitiesReceiver = new AudioCapabilitiesReceiver(getApplicationContext(), this);
|
||||||
|
|
||||||
shutterView = findViewById(R.id.shutter);
|
shutterView = findViewById(R.id.shutter);
|
||||||
debugRootView = findViewById(R.id.controls_root);
|
debugRootView = findViewById(R.id.controls_root);
|
||||||
|
|
||||||
|
|
@ -137,7 +144,9 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
configureSubtitleView();
|
configureSubtitleView();
|
||||||
preparePlayer();
|
|
||||||
|
// The player will be prepared on receiving audio capabilities.
|
||||||
|
audioCapabilitiesReceiver.register();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -148,6 +157,8 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
|
||||||
} else {
|
} else {
|
||||||
player.blockingClearSurface();
|
player.blockingClearSurface();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
audioCapabilitiesReceiver.unregister();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -166,6 +177,17 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AudioCapabilitiesReceiver.Listener methods
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAudioCapabilitiesChanged(AudioCapabilities audioCapabilities) {
|
||||||
|
this.audioCapabilities = audioCapabilities;
|
||||||
|
releasePlayer();
|
||||||
|
|
||||||
|
autoPlay = true;
|
||||||
|
preparePlayer();
|
||||||
|
}
|
||||||
|
|
||||||
// Internal methods
|
// Internal methods
|
||||||
|
|
||||||
private RendererBuilder getRendererBuilder() {
|
private RendererBuilder getRendererBuilder() {
|
||||||
|
|
@ -176,7 +198,7 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
|
||||||
new SmoothStreamingTestMediaDrmCallback(), debugTextView);
|
new SmoothStreamingTestMediaDrmCallback(), debugTextView);
|
||||||
case DemoUtil.TYPE_DASH:
|
case DemoUtil.TYPE_DASH:
|
||||||
return new DashRendererBuilder(userAgent, contentUri.toString(), contentId,
|
return new DashRendererBuilder(userAgent, contentUri.toString(), contentId,
|
||||||
new WidevineTestMediaDrmCallback(contentId), debugTextView);
|
new WidevineTestMediaDrmCallback(contentId), debugTextView, audioCapabilities);
|
||||||
default:
|
default:
|
||||||
return new DefaultRendererBuilder(this, contentUri, debugTextView);
|
return new DefaultRendererBuilder(this, contentUri, debugTextView);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.demo.full.player;
|
package com.google.android.exoplayer.demo.full.player;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.Ac3PassthroughAudioTrackRenderer;
|
||||||
import com.google.android.exoplayer.DefaultLoadControl;
|
import com.google.android.exoplayer.DefaultLoadControl;
|
||||||
import com.google.android.exoplayer.LoadControl;
|
import com.google.android.exoplayer.LoadControl;
|
||||||
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
||||||
|
|
@ -22,6 +23,7 @@ import com.google.android.exoplayer.MediaCodecUtil;
|
||||||
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
||||||
import com.google.android.exoplayer.SampleSource;
|
import com.google.android.exoplayer.SampleSource;
|
||||||
import com.google.android.exoplayer.TrackRenderer;
|
import com.google.android.exoplayer.TrackRenderer;
|
||||||
|
import com.google.android.exoplayer.audio.AudioCapabilities;
|
||||||
import com.google.android.exoplayer.chunk.ChunkSampleSource;
|
import com.google.android.exoplayer.chunk.ChunkSampleSource;
|
||||||
import com.google.android.exoplayer.chunk.ChunkSource;
|
import com.google.android.exoplayer.chunk.ChunkSource;
|
||||||
import com.google.android.exoplayer.chunk.Format;
|
import com.google.android.exoplayer.chunk.Format;
|
||||||
|
|
@ -84,18 +86,20 @@ public class DashRendererBuilder implements RendererBuilder,
|
||||||
private final String contentId;
|
private final String contentId;
|
||||||
private final MediaDrmCallback drmCallback;
|
private final MediaDrmCallback drmCallback;
|
||||||
private final TextView debugTextView;
|
private final TextView debugTextView;
|
||||||
|
private final AudioCapabilities audioCapabilities;
|
||||||
|
|
||||||
private DemoPlayer player;
|
private DemoPlayer player;
|
||||||
private RendererBuilderCallback callback;
|
private RendererBuilderCallback callback;
|
||||||
private ManifestFetcher<MediaPresentationDescription> manifestFetcher;
|
private ManifestFetcher<MediaPresentationDescription> manifestFetcher;
|
||||||
|
|
||||||
public DashRendererBuilder(String userAgent, String url, String contentId,
|
public DashRendererBuilder(String userAgent, String url, String contentId,
|
||||||
MediaDrmCallback drmCallback, TextView debugTextView) {
|
MediaDrmCallback drmCallback, TextView debugTextView, AudioCapabilities audioCapabilities) {
|
||||||
this.userAgent = userAgent;
|
this.userAgent = userAgent;
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.contentId = contentId;
|
this.contentId = contentId;
|
||||||
this.drmCallback = drmCallback;
|
this.drmCallback = drmCallback;
|
||||||
this.debugTextView = debugTextView;
|
this.debugTextView = debugTextView;
|
||||||
|
this.audioCapabilities = audioCapabilities;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -208,6 +212,7 @@ public class DashRendererBuilder implements RendererBuilder,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the audio chunk sources.
|
// Build the audio chunk sources.
|
||||||
|
boolean haveAc3Tracks = false;
|
||||||
List<ChunkSource> audioChunkSourceList = new ArrayList<ChunkSource>();
|
List<ChunkSource> audioChunkSourceList = new ArrayList<ChunkSource>();
|
||||||
List<String> audioTrackNameList = new ArrayList<String>();
|
List<String> audioTrackNameList = new ArrayList<String>();
|
||||||
if (audioAdaptationSet != null) {
|
if (audioAdaptationSet != null) {
|
||||||
|
|
@ -220,6 +225,19 @@ public class DashRendererBuilder implements RendererBuilder,
|
||||||
format.audioSamplingRate + "Hz)");
|
format.audioSamplingRate + "Hz)");
|
||||||
audioChunkSourceList.add(new DashChunkSource(manifestFetcher, audioAdaptationSetIndex,
|
audioChunkSourceList.add(new DashChunkSource(manifestFetcher, audioAdaptationSetIndex,
|
||||||
new int[] {i}, audioDataSource, audioEvaluator, LIVE_EDGE_LATENCY_MS));
|
new int[] {i}, audioDataSource, audioEvaluator, LIVE_EDGE_LATENCY_MS));
|
||||||
|
haveAc3Tracks |= format.mimeType.equals(MimeTypes.AUDIO_AC3)
|
||||||
|
|| format.mimeType.equals(MimeTypes.AUDIO_EC3);
|
||||||
|
}
|
||||||
|
// Filter out non-AC-3 tracks if there is an AC-3 track, to avoid having to switch renderers.
|
||||||
|
if (haveAc3Tracks) {
|
||||||
|
for (int i = audioRepresentations.size() - 1; i >= 0; i--) {
|
||||||
|
Format format = audioRepresentations.get(i).format;
|
||||||
|
if (!format.mimeType.equals(MimeTypes.AUDIO_AC3)
|
||||||
|
&& !format.mimeType.equals(MimeTypes.AUDIO_EC3)) {
|
||||||
|
audioTrackNameList.remove(i);
|
||||||
|
audioChunkSourceList.remove(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -238,8 +256,16 @@ public class DashRendererBuilder implements RendererBuilder,
|
||||||
SampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
|
SampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
|
||||||
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player,
|
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player,
|
||||||
DemoPlayer.TYPE_AUDIO);
|
DemoPlayer.TYPE_AUDIO);
|
||||||
audioRenderer = new MediaCodecAudioTrackRenderer(audioSampleSource, drmSessionManager, true,
|
// TODO: There needs to be some logic to filter out non-AC3 tracks when selecting to use AC3.
|
||||||
mainHandler, player);
|
boolean useAc3Passthrough = haveAc3Tracks && audioCapabilities != null
|
||||||
|
&& (audioCapabilities.supportsAc3() || audioCapabilities.supportsEAc3());
|
||||||
|
if (useAc3Passthrough) {
|
||||||
|
audioRenderer =
|
||||||
|
new Ac3PassthroughAudioTrackRenderer(audioSampleSource, mainHandler, player);
|
||||||
|
} else {
|
||||||
|
audioRenderer = new MediaCodecAudioTrackRenderer(audioSampleSource, drmSessionManager, true,
|
||||||
|
mainHandler, player);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the text chunk sources.
|
// Build the text chunk sources.
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.demo.full.player;
|
package com.google.android.exoplayer.demo.full.player;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.Ac3PassthroughAudioTrackRenderer;
|
||||||
import com.google.android.exoplayer.DummyTrackRenderer;
|
import com.google.android.exoplayer.DummyTrackRenderer;
|
||||||
import com.google.android.exoplayer.ExoPlaybackException;
|
import com.google.android.exoplayer.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer.ExoPlayer;
|
import com.google.android.exoplayer.ExoPlayer;
|
||||||
|
|
@ -45,8 +46,8 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
*/
|
*/
|
||||||
public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventListener,
|
public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventListener,
|
||||||
DefaultBandwidthMeter.EventListener, MediaCodecVideoTrackRenderer.EventListener,
|
DefaultBandwidthMeter.EventListener, MediaCodecVideoTrackRenderer.EventListener,
|
||||||
MediaCodecAudioTrackRenderer.EventListener, TextTrackRenderer.TextRenderer,
|
MediaCodecAudioTrackRenderer.EventListener, Ac3PassthroughAudioTrackRenderer.EventListener,
|
||||||
StreamingDrmSessionManager.EventListener {
|
TextTrackRenderer.TextRenderer, StreamingDrmSessionManager.EventListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds renderers for the player.
|
* Builds renderers for the player.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,316 @@
|
||||||
|
/*
|
||||||
|
* 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.audio.AudioCapabilitiesReceiver;
|
||||||
|
import com.google.android.exoplayer.audio.AudioTrack;
|
||||||
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.media.AudioFormat;
|
||||||
|
import android.os.Handler;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders encoded AC-3/enhanced AC-3 data to an {@link AudioTrack} for decoding on the playback
|
||||||
|
* device.
|
||||||
|
*
|
||||||
|
* <p>To determine whether the playback device supports passthrough, receive an audio configuration
|
||||||
|
* using {@link AudioCapabilitiesReceiver} and check whether the audio capabilities include
|
||||||
|
* AC-3/enhanced AC-3 passthrough.
|
||||||
|
*/
|
||||||
|
@TargetApi(21)
|
||||||
|
public final class Ac3PassthroughAudioTrackRenderer extends TrackRenderer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface definition for a callback to be notified of {@link Ac3PassthroughAudioTrackRenderer}
|
||||||
|
* events.
|
||||||
|
*/
|
||||||
|
public interface EventListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when an {@link AudioTrack} fails to initialize.
|
||||||
|
*
|
||||||
|
* @param e The corresponding exception.
|
||||||
|
*/
|
||||||
|
void onAudioTrackInitializationError(AudioTrack.InitializationException e);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of a message that can be passed to an instance of this class via
|
||||||
|
* {@link ExoPlayer#sendMessage} or {@link ExoPlayer#blockingSendMessage}. The message object
|
||||||
|
* should be a {@link Float} with 0 being silence and 1 being unity gain.
|
||||||
|
*/
|
||||||
|
public static final int MSG_SET_VOLUME = 1;
|
||||||
|
|
||||||
|
private static final int SOURCE_STATE_NOT_READY = 0;
|
||||||
|
private static final int SOURCE_STATE_READY = 1;
|
||||||
|
|
||||||
|
/** Default buffer size for AC-3 packets from the sample source */
|
||||||
|
private static final int DEFAULT_BUFFER_SIZE = 16384 * 2;
|
||||||
|
|
||||||
|
/** Multiplication factor for the audio track's buffer size. */
|
||||||
|
private static final int MIN_BUFFER_MULTIPLICATION_FACTOR = 3;
|
||||||
|
|
||||||
|
private final Handler eventHandler;
|
||||||
|
private final EventListener eventListener;
|
||||||
|
|
||||||
|
private final SampleSource source;
|
||||||
|
private final SampleHolder sampleHolder;
|
||||||
|
private final MediaFormatHolder formatHolder;
|
||||||
|
|
||||||
|
private int trackIndex;
|
||||||
|
private MediaFormat format;
|
||||||
|
|
||||||
|
private int sourceState;
|
||||||
|
private boolean inputStreamEnded;
|
||||||
|
private boolean shouldReadInputBuffer;
|
||||||
|
|
||||||
|
private long currentPositionUs;
|
||||||
|
|
||||||
|
private AudioTrack audioTrack;
|
||||||
|
private int audioSessionId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new track renderer that passes AC-3 samples directly to an audio track.
|
||||||
|
*
|
||||||
|
* @param source The upstream source from which the renderer obtains samples.
|
||||||
|
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
|
||||||
|
* null if delivery of events is not required.
|
||||||
|
* @param eventListener A listener of events. May be null if delivery of events is not required.
|
||||||
|
*/
|
||||||
|
public Ac3PassthroughAudioTrackRenderer(
|
||||||
|
SampleSource source, Handler eventHandler, EventListener eventListener) {
|
||||||
|
this.source = Assertions.checkNotNull(source);
|
||||||
|
this.eventHandler = eventHandler;
|
||||||
|
this.eventListener = eventListener;
|
||||||
|
sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL);
|
||||||
|
sampleHolder.data = ByteBuffer.allocateDirect(DEFAULT_BUFFER_SIZE);
|
||||||
|
formatHolder = new MediaFormatHolder();
|
||||||
|
audioTrack = new AudioTrack(MIN_BUFFER_MULTIPLICATION_FACTOR);
|
||||||
|
shouldReadInputBuffer = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isTimeSource() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int doPrepare() throws ExoPlaybackException {
|
||||||
|
try {
|
||||||
|
boolean sourcePrepared = source.prepare();
|
||||||
|
if (!sourcePrepared) {
|
||||||
|
return TrackRenderer.STATE_UNPREPARED;
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ExoPlaybackException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < source.getTrackCount(); i++) {
|
||||||
|
// TODO(andrewlewis): Choose best format here after checking playout formats from HDMI config.
|
||||||
|
if (handlesMimeType(source.getTrackInfo(i).mimeType)) {
|
||||||
|
trackIndex = i;
|
||||||
|
return TrackRenderer.STATE_PREPARED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return TrackRenderer.STATE_IGNORE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean handlesMimeType(String mimeType) {
|
||||||
|
return MimeTypes.AUDIO_AC3.equals(mimeType) || MimeTypes.AUDIO_EC3.equals(mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onEnabled(long positionUs, boolean joining) {
|
||||||
|
source.enable(trackIndex, positionUs);
|
||||||
|
sourceState = SOURCE_STATE_NOT_READY;
|
||||||
|
inputStreamEnded = false;
|
||||||
|
currentPositionUs = positionUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
|
||||||
|
try {
|
||||||
|
sourceState = source.continueBuffering(positionUs)
|
||||||
|
? (sourceState == SOURCE_STATE_NOT_READY ? SOURCE_STATE_READY : sourceState)
|
||||||
|
: SOURCE_STATE_NOT_READY;
|
||||||
|
|
||||||
|
if (format == null) {
|
||||||
|
readFormat();
|
||||||
|
} else {
|
||||||
|
// Initialize and start the audio track now.
|
||||||
|
if (!audioTrack.isInitialized()) {
|
||||||
|
int oldAudioSessionId = audioSessionId;
|
||||||
|
try {
|
||||||
|
audioSessionId = audioTrack.initialize(oldAudioSessionId);
|
||||||
|
} catch (AudioTrack.InitializationException e) {
|
||||||
|
notifyAudioTrackInitializationError(e);
|
||||||
|
throw new ExoPlaybackException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getState() == TrackRenderer.STATE_STARTED) {
|
||||||
|
audioTrack.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
feedInputBuffer();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ExoPlaybackException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readFormat() throws IOException {
|
||||||
|
int result = source.readData(trackIndex, currentPositionUs, formatHolder, sampleHolder, false);
|
||||||
|
if (result == SampleSource.FORMAT_READ) {
|
||||||
|
format = formatHolder.format;
|
||||||
|
// TODO: For E-AC-3 input, reconfigure with AudioFormat.ENCODING_E_AC3.
|
||||||
|
audioTrack.reconfigure(format.getFrameworkMediaFormatV16(), AudioFormat.ENCODING_AC3, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void feedInputBuffer() throws IOException {
|
||||||
|
if (!audioTrack.isInitialized() || inputStreamEnded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get more data if we have run out.
|
||||||
|
if (shouldReadInputBuffer) {
|
||||||
|
sampleHolder.data.clear();
|
||||||
|
|
||||||
|
int result =
|
||||||
|
source.readData(trackIndex, currentPositionUs, formatHolder, sampleHolder, false);
|
||||||
|
sampleHolder.data.flip();
|
||||||
|
shouldReadInputBuffer = false;
|
||||||
|
|
||||||
|
if (result == SampleSource.FORMAT_READ) {
|
||||||
|
format = formatHolder.format;
|
||||||
|
}
|
||||||
|
if (result == SampleSource.END_OF_STREAM) {
|
||||||
|
inputStreamEnded = true;
|
||||||
|
}
|
||||||
|
if (result != SampleSource.SAMPLE_READ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int handleBufferResult =
|
||||||
|
audioTrack.handleBuffer(sampleHolder.data, 0, sampleHolder.size, sampleHolder.timeUs);
|
||||||
|
|
||||||
|
// If we are out of sync, allow currentPositionUs to jump backwards.
|
||||||
|
if ((handleBufferResult & AudioTrack.RESULT_POSITION_DISCONTINUITY) != 0) {
|
||||||
|
currentPositionUs = Long.MIN_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get another input buffer if this one was consumed.
|
||||||
|
shouldReadInputBuffer = (handleBufferResult & AudioTrack.RESULT_BUFFER_CONSUMED) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStarted() {
|
||||||
|
if (audioTrack.isInitialized()) {
|
||||||
|
audioTrack.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStopped() {
|
||||||
|
if (audioTrack.isInitialized()) {
|
||||||
|
audioTrack.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isEnded() {
|
||||||
|
// We've exhausted the input stream, and the AudioTrack has either played all of the data
|
||||||
|
// submitted, or has been fed insufficient data to begin playback.
|
||||||
|
return inputStreamEnded && (!audioTrack.hasPendingData()
|
||||||
|
|| !audioTrack.hasEnoughDataToBeginPlayback());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isReady() {
|
||||||
|
return audioTrack.hasPendingData() || (format != null && sourceState != SOURCE_STATE_NOT_READY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected long getCurrentPositionUs() {
|
||||||
|
long audioTrackCurrentPositionUs = audioTrack.getCurrentPositionUs(isEnded());
|
||||||
|
if (audioTrackCurrentPositionUs != AudioTrack.CURRENT_POSITION_NOT_SET) {
|
||||||
|
// Make sure we don't ever report time moving backwards.
|
||||||
|
currentPositionUs = Math.max(currentPositionUs, audioTrackCurrentPositionUs);
|
||||||
|
}
|
||||||
|
return currentPositionUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected long getDurationUs() {
|
||||||
|
return source.getTrackInfo(trackIndex).durationUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected long getBufferedPositionUs() {
|
||||||
|
long sourceBufferedPosition = source.getBufferedPositionUs();
|
||||||
|
return sourceBufferedPosition == UNKNOWN_TIME_US || sourceBufferedPosition == END_OF_TRACK_US
|
||||||
|
? sourceBufferedPosition : Math.max(sourceBufferedPosition, getCurrentPositionUs());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDisabled() {
|
||||||
|
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
|
||||||
|
shouldReadInputBuffer = true;
|
||||||
|
audioTrack.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void seekTo(long positionUs) throws ExoPlaybackException {
|
||||||
|
source.seekToUs(positionUs);
|
||||||
|
sourceState = SOURCE_STATE_NOT_READY;
|
||||||
|
inputStreamEnded = false;
|
||||||
|
shouldReadInputBuffer = true;
|
||||||
|
|
||||||
|
// TODO: Try and re-use the same AudioTrack instance once [Internal: b/7941810] is fixed.
|
||||||
|
audioTrack.reset();
|
||||||
|
currentPositionUs = Long.MIN_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleMessage(int messageType, Object message) throws ExoPlaybackException {
|
||||||
|
if (messageType == MSG_SET_VOLUME) {
|
||||||
|
audioTrack.setVolume((Float) message);
|
||||||
|
} else {
|
||||||
|
super.handleMessage(messageType, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyAudioTrackInitializationError(final AudioTrack.InitializationException e) {
|
||||||
|
if (eventHandler != null && eventListener != null) {
|
||||||
|
eventHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
eventListener.onAudioTrackInitializationError(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue