Shared super class for LibflacAudioTrackRenderer and LibopusAudioTrackRenderer.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=120225655
This commit is contained in:
eguven 2016-04-19 07:10:22 -07:00 committed by Oliver Woodman
parent a760c9bfd9
commit a7d7859478
11 changed files with 458 additions and 759 deletions

View file

@ -17,6 +17,7 @@ package com.google.android.exoplayer.ext.flac;
import com.google.android.exoplayer.DecoderInputBuffer;
import com.google.android.exoplayer.util.extensions.SimpleDecoder;
import com.google.android.exoplayer.util.extensions.SimpleOutputBuffer;
import java.nio.ByteBuffer;
import java.util.List;
@ -25,21 +26,23 @@ import java.util.List;
* Flac decoder.
*/
/* package */ final class FlacDecoder extends
SimpleDecoder<DecoderInputBuffer, FlacOutputBuffer, FlacDecoderException> {
SimpleDecoder<DecoderInputBuffer, SimpleOutputBuffer, FlacDecoderException> {
private final int maxOutputBufferSize;
private final FlacJni decoder;
/**
* Creates a Flac decoder.
*
* @param numInputBuffers The number of input buffers.
* @param numOutputBuffers The number of output buffers.
* @param initializationData Codec-specific initialization data.
* @param initializationData Codec-specific initialization data. It should contain only one entry
* which is the flac file header.
* @throws FlacDecoderException Thrown if an exception occurs when initializing the decoder.
*/
public FlacDecoder(int numInputBuffers, int numOutputBuffers, List<byte[]> initializationData)
throws FlacDecoderException {
super(new DecoderInputBuffer[numInputBuffers], new FlacOutputBuffer[numOutputBuffers]);
super(new DecoderInputBuffer[numInputBuffers], new SimpleOutputBuffer[numOutputBuffers]);
if (initializationData.size() != 1) {
throw new FlacDecoderException("Wrong number of initialization data");
}
@ -63,18 +66,13 @@ import java.util.List;
}
@Override
public FlacOutputBuffer createOutputBuffer() {
return new FlacOutputBuffer(this);
}
@Override
protected void releaseOutputBuffer(FlacOutputBuffer buffer) {
super.releaseOutputBuffer(buffer);
public SimpleOutputBuffer createOutputBuffer() {
return new SimpleOutputBuffer(this);
}
@Override
public FlacDecoderException decode(DecoderInputBuffer inputBuffer,
FlacOutputBuffer outputBuffer, boolean reset) {
SimpleOutputBuffer outputBuffer, boolean reset) {
if (reset) {
decoder.flush();
}

View file

@ -15,10 +15,12 @@
*/
package com.google.android.exoplayer.ext.flac;
import com.google.android.exoplayer.util.extensions.AudioDecoderException;
/**
* Thrown when an Flac decoder error occurs.
*/
public final class FlacDecoderException extends Exception {
public final class FlacDecoderException extends AudioDecoderException {
/* package */ FlacDecoderException(String message) {
super(message);

View file

@ -15,17 +15,9 @@
*/
package com.google.android.exoplayer.ext.flac;
import com.google.android.exoplayer.CodecCounters;
import com.google.android.exoplayer.DecoderInputBuffer;
import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.FormatHolder;
import com.google.android.exoplayer.MediaClock;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.TrackStream;
import com.google.android.exoplayer.audio.AudioTrack;
import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.extensions.AudioDecoderTrackRenderer;
import android.os.Handler;
@ -34,63 +26,16 @@ import java.util.List;
/**
* Decodes and renders audio using the native Flac decoder.
*/
public final class LibflacAudioTrackRenderer extends TrackRenderer implements MediaClock {
/**
* Interface definition for a callback to be notified of {@link LibflacAudioTrackRenderer} events.
*/
public interface EventListener {
/**
* Invoked when the {@link AudioTrack} fails to initialize.
*
* @param e The corresponding exception.
*/
void onAudioTrackInitializationError(AudioTrack.InitializationException e);
/**
* Invoked when an {@link AudioTrack} write fails.
*
* @param e The corresponding exception.
*/
void onAudioTrackWriteError(AudioTrack.WriteException e);
/**
* Invoked when decoding fails.
*
* @param e The corresponding exception.
*/
void onDecoderError(FlacDecoderException 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;
public class LibflacAudioTrackRenderer extends AudioDecoderTrackRenderer {
private static final int NUM_BUFFERS = 16;
public final CodecCounters codecCounters = new CodecCounters();
private final Handler eventHandler;
private final EventListener eventListener;
private final FormatHolder formatHolder;
private Format format;
private FlacDecoder decoder;
private DecoderInputBuffer inputBuffer;
private FlacOutputBuffer outputBuffer;
private long currentPositionUs;
private boolean allowPositionDiscontinuity;
private boolean inputStreamEnded;
private boolean outputStreamEnded;
private final AudioTrack audioTrack;
private int audioSessionId;
/**
* Returns whether the underlying libflac library is available.
*/
public static boolean isLibflacAvailable() {
return FlacJni.IS_AVAILABLE;
}
public LibflacAudioTrackRenderer() {
this(null, null);
@ -101,26 +46,10 @@ public final class LibflacAudioTrackRenderer extends TrackRenderer implements Me
* 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 LibflacAudioTrackRenderer(Handler eventHandler, EventListener eventListener) {
this.eventHandler = eventHandler;
this.eventListener = eventListener;
this.audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
this.audioTrack = new AudioTrack();
formatHolder = new FormatHolder();
public LibflacAudioTrackRenderer(Handler eventHandler,
AudioDecoderTrackRenderer.EventListener eventListener) {
super(eventHandler, eventListener);
}
/**
* Returns whether the underlying libflac library is available.
*/
public static boolean isLibflacAvailable() {
return FlacJni.IS_AVAILABLE;
}
@Override
protected MediaClock getMediaClock() {
return this;
}
@Override
protected int supportsFormat(Format format) {
return MimeTypes.AUDIO_FLAC.equalsIgnoreCase(format.sampleMimeType)
@ -128,252 +57,8 @@ public final class LibflacAudioTrackRenderer extends TrackRenderer implements Me
}
@Override
protected void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
if (outputStreamEnded) {
return;
}
// Try and read a format if we don't have one already.
if (format == null && !readFormat()) {
// We can't make progress without one.
return;
}
// If we don't have a decoder yet, we need to instantiate one.
if (decoder == null) {
// For flac, the format can contain only one entry in initializationData which is the flac
// file header.
List<byte[]> initializationData = format.initializationData;
if (initializationData.size() < 1) {
throw ExoPlaybackException.createForRenderer(
new IllegalStateException("Missing initialization data"), getIndex());
}
try {
decoder = new FlacDecoder(NUM_BUFFERS, NUM_BUFFERS, initializationData);
} catch (FlacDecoderException e) {
notifyDecoderError(e);
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
decoder.start();
codecCounters.codecInitCount++;
}
// Rendering loop.
try {
renderBuffer();
while (feedInputBuffer()) {}
} catch (AudioTrack.InitializationException e) {
notifyAudioTrackInitializationError(e);
throw ExoPlaybackException.createForRenderer(e, getIndex());
} catch (AudioTrack.WriteException e) {
notifyAudioTrackWriteError(e);
throw ExoPlaybackException.createForRenderer(e, getIndex());
} catch (FlacDecoderException e) {
notifyDecoderError(e);
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
codecCounters.ensureUpdated();
}
private void renderBuffer() throws FlacDecoderException, AudioTrack.InitializationException,
AudioTrack.WriteException {
if (outputStreamEnded) {
return;
}
if (outputBuffer == null) {
outputBuffer = decoder.dequeueOutputBuffer();
if (outputBuffer == null) {
return;
}
}
if (outputBuffer.isEndOfStream()) {
outputStreamEnded = true;
audioTrack.handleEndOfStream();
outputBuffer.release();
outputBuffer = null;
return;
}
if (!audioTrack.isInitialized()) {
if (audioSessionId != AudioTrack.SESSION_ID_NOT_SET) {
audioTrack.initialize(audioSessionId);
} else {
audioSessionId = audioTrack.initialize();
}
if (getState() == TrackRenderer.STATE_STARTED) {
audioTrack.play();
}
}
int handleBufferResult;
handleBufferResult = audioTrack.handleBuffer(outputBuffer.data, outputBuffer.data.position(),
outputBuffer.data.remaining(), outputBuffer.timestampUs);
// If we are out of sync, allow currentPositionUs to jump backwards.
if ((handleBufferResult & AudioTrack.RESULT_POSITION_DISCONTINUITY) != 0) {
allowPositionDiscontinuity = true;
}
// Release the buffer if it was consumed.
if ((handleBufferResult & AudioTrack.RESULT_BUFFER_CONSUMED) != 0) {
codecCounters.renderedOutputBufferCount++;
outputBuffer.release();
outputBuffer = null;
}
}
private boolean feedInputBuffer() throws FlacDecoderException {
if (inputStreamEnded) {
return false;
}
if (inputBuffer == null) {
inputBuffer = decoder.dequeueInputBuffer();
if (inputBuffer == null) {
return false;
}
}
int result = readSource(formatHolder, inputBuffer);
if (result == TrackStream.NOTHING_READ) {
return false;
}
if (result == TrackStream.FORMAT_READ) {
format = formatHolder.format;
return true;
}
if (inputBuffer.isEndOfStream()) {
inputStreamEnded = true;
}
decoder.queueInputBuffer(inputBuffer);
inputBuffer = null;
return true;
}
private void flushDecoder() {
inputBuffer = null;
if (outputBuffer != null) {
outputBuffer.release();
outputBuffer = null;
}
decoder.flush();
}
@Override
protected boolean isEnded() {
return outputStreamEnded && !audioTrack.hasPendingData();
}
@Override
protected boolean isReady() {
return audioTrack.hasPendingData()
|| (format != null && (isSourceReady() || outputBuffer != null));
}
@Override
public long getPositionUs() {
long newCurrentPositionUs = audioTrack.getCurrentPositionUs(isEnded());
if (newCurrentPositionUs != AudioTrack.CURRENT_POSITION_NOT_SET) {
currentPositionUs = allowPositionDiscontinuity ? newCurrentPositionUs
: Math.max(currentPositionUs, newCurrentPositionUs);
allowPositionDiscontinuity = false;
}
return currentPositionUs;
}
@Override
protected void reset(long positionUs) {
audioTrack.reset();
currentPositionUs = positionUs;
allowPositionDiscontinuity = true;
inputStreamEnded = false;
outputStreamEnded = false;
if (decoder != null) {
flushDecoder();
}
}
@Override
protected void onStarted() {
audioTrack.play();
}
@Override
protected void onStopped() {
audioTrack.pause();
}
@Override
protected void onDisabled() {
inputBuffer = null;
outputBuffer = null;
format = null;
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
try {
if (decoder != null) {
decoder.release();
decoder = null;
codecCounters.codecReleaseCount++;
}
audioTrack.release();
} finally {
super.onDisabled();
}
}
private boolean readFormat() {
int result = readSource(formatHolder, null);
if (result == TrackStream.FORMAT_READ) {
format = formatHolder.format;
audioTrack.configure(format.getFrameworkMediaFormatV16(), false);
return true;
}
return false;
}
@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);
}
});
}
}
private void notifyAudioTrackWriteError(final AudioTrack.WriteException e) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onAudioTrackWriteError(e);
}
});
}
}
private void notifyDecoderError(final FlacDecoderException e) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onDecoderError(e);
}
});
}
protected FlacDecoder createDecoder(List<byte[]> initializationData) throws FlacDecoderException {
return new FlacDecoder(NUM_BUFFERS, NUM_BUFFERS, initializationData);
}
}

View file

@ -15,17 +15,9 @@
*/
package com.google.android.exoplayer.ext.opus;
import com.google.android.exoplayer.CodecCounters;
import com.google.android.exoplayer.DecoderInputBuffer;
import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.FormatHolder;
import com.google.android.exoplayer.MediaClock;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.TrackStream;
import com.google.android.exoplayer.audio.AudioTrack;
import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.extensions.AudioDecoderTrackRenderer;
import android.os.Handler;
@ -34,82 +26,11 @@ import java.util.List;
/**
* Decodes and renders audio using the native Opus decoder.
*/
public final class LibopusAudioTrackRenderer extends TrackRenderer implements MediaClock {
/**
* Interface definition for a callback to be notified of {@link LibopusAudioTrackRenderer} events.
*/
public interface EventListener {
/**
* Invoked when the {@link AudioTrack} fails to initialize.
*
* @param e The corresponding exception.
*/
void onAudioTrackInitializationError(AudioTrack.InitializationException e);
/**
* Invoked when an {@link AudioTrack} write fails.
*
* @param e The corresponding exception.
*/
void onAudioTrackWriteError(AudioTrack.WriteException e);
/**
* Invoked when decoding fails.
*
* @param e The corresponding exception.
*/
void onDecoderError(OpusDecoderException 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;
public final class LibopusAudioTrackRenderer extends AudioDecoderTrackRenderer {
private static final int NUM_BUFFERS = 16;
private static final int INITIAL_INPUT_BUFFER_SIZE = 960 * 6;
public final CodecCounters codecCounters = new CodecCounters();
private final Handler eventHandler;
private final EventListener eventListener;
private final AudioTrack audioTrack;
private final FormatHolder formatHolder;
private Format format;
private OpusDecoder decoder;
private DecoderInputBuffer inputBuffer;
private OpusOutputBuffer outputBuffer;
private long currentPositionUs;
private boolean allowPositionDiscontinuity;
private boolean inputStreamEnded;
private boolean outputStreamEnded;
private int audioSessionId;
public LibopusAudioTrackRenderer() {
this(null, null);
}
/**
* @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 LibopusAudioTrackRenderer(Handler eventHandler, EventListener eventListener) {
this.eventHandler = eventHandler;
this.eventListener = eventListener;
this.audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
audioTrack = new AudioTrack();
formatHolder = new FormatHolder();
}
/**
* Returns whether the underlying libopus library is available.
*/
@ -124,9 +45,17 @@ public final class LibopusAudioTrackRenderer extends TrackRenderer implements Me
return isLibopusAvailable() ? OpusDecoder.getLibopusVersion() : null;
}
@Override
protected MediaClock getMediaClock() {
return this;
public LibopusAudioTrackRenderer() {
this(null, null);
}
/**
* @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 LibopusAudioTrackRenderer(Handler eventHandler, EventListener eventListener) {
super(eventHandler, eventListener);
}
@Override
@ -136,256 +65,9 @@ public final class LibopusAudioTrackRenderer extends TrackRenderer implements Me
}
@Override
protected void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
if (outputStreamEnded) {
return;
}
// Try and read a format if we don't have one already.
if (format == null && !readFormat()) {
// We can't make progress without one.
return;
}
// If we don't have a decoder yet, we need to instantiate one.
if (decoder == null) {
// For opus, the format can contain upto 3 entries in initializationData in the following
// exact order:
// 1) Opus Header Information (required)
// 2) Codec Delay in nanoseconds (required if Seek Preroll is present)
// 3) Seek Preroll in nanoseconds (required if Codec Delay is present)
List<byte[]> initializationData = format.initializationData;
if (initializationData.size() < 1) {
throw ExoPlaybackException.createForRenderer(
new IllegalStateException("Missing initialization data"), getIndex());
}
try {
decoder = new OpusDecoder(NUM_BUFFERS, NUM_BUFFERS, INITIAL_INPUT_BUFFER_SIZE,
initializationData);
} catch (OpusDecoderException e) {
notifyDecoderError(e);
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
decoder.start();
codecCounters.codecInitCount++;
}
// Rendering loop.
try {
renderBuffer();
while (feedInputBuffer()) {}
} catch (AudioTrack.InitializationException e) {
notifyAudioTrackInitializationError(e);
throw ExoPlaybackException.createForRenderer(e, getIndex());
} catch (AudioTrack.WriteException e) {
notifyAudioTrackWriteError(e);
throw ExoPlaybackException.createForRenderer(e, getIndex());
} catch (OpusDecoderException e) {
notifyDecoderError(e);
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
codecCounters.ensureUpdated();
}
private void renderBuffer() throws OpusDecoderException, AudioTrack.InitializationException,
AudioTrack.WriteException {
if (outputStreamEnded) {
return;
}
if (outputBuffer == null) {
outputBuffer = decoder.dequeueOutputBuffer();
if (outputBuffer == null) {
return;
}
}
if (outputBuffer.isEndOfStream()) {
outputStreamEnded = true;
audioTrack.handleEndOfStream();
outputBuffer.release();
outputBuffer = null;
return;
}
if (!audioTrack.isInitialized()) {
if (audioSessionId != AudioTrack.SESSION_ID_NOT_SET) {
audioTrack.initialize(audioSessionId);
} else {
audioSessionId = audioTrack.initialize();
}
if (getState() == TrackRenderer.STATE_STARTED) {
audioTrack.play();
}
}
int handleBufferResult;
handleBufferResult = audioTrack.handleBuffer(outputBuffer.data, outputBuffer.data.position(),
outputBuffer.data.remaining(), outputBuffer.timestampUs);
// If we are out of sync, allow currentPositionUs to jump backwards.
if ((handleBufferResult & AudioTrack.RESULT_POSITION_DISCONTINUITY) != 0) {
allowPositionDiscontinuity = true;
}
// Release the buffer if it was consumed.
if ((handleBufferResult & AudioTrack.RESULT_BUFFER_CONSUMED) != 0) {
codecCounters.renderedOutputBufferCount++;
outputBuffer.release();
outputBuffer = null;
}
}
private boolean feedInputBuffer() throws OpusDecoderException {
if (inputStreamEnded) {
return false;
}
if (inputBuffer == null) {
inputBuffer = decoder.dequeueInputBuffer();
if (inputBuffer == null) {
return false;
}
}
int result = readSource(formatHolder, inputBuffer);
if (result == TrackStream.NOTHING_READ) {
return false;
}
if (result == TrackStream.FORMAT_READ) {
format = formatHolder.format;
return true;
}
if (inputBuffer.isEndOfStream()) {
inputStreamEnded = true;
}
decoder.queueInputBuffer(inputBuffer);
inputBuffer = null;
return true;
}
private void flushDecoder() {
inputBuffer = null;
if (outputBuffer != null) {
outputBuffer.release();
outputBuffer = null;
}
decoder.flush();
}
@Override
protected boolean isEnded() {
return outputStreamEnded && !audioTrack.hasPendingData();
}
@Override
protected boolean isReady() {
return audioTrack.hasPendingData()
|| (format != null && (isSourceReady() || outputBuffer != null));
}
@Override
public long getPositionUs() {
long newCurrentPositionUs = audioTrack.getCurrentPositionUs(isEnded());
if (newCurrentPositionUs != AudioTrack.CURRENT_POSITION_NOT_SET) {
currentPositionUs = allowPositionDiscontinuity ? newCurrentPositionUs
: Math.max(currentPositionUs, newCurrentPositionUs);
allowPositionDiscontinuity = false;
}
return currentPositionUs;
}
@Override
protected void reset(long positionUs) {
audioTrack.reset();
currentPositionUs = positionUs;
allowPositionDiscontinuity = true;
inputStreamEnded = false;
outputStreamEnded = false;
if (decoder != null) {
flushDecoder();
}
}
@Override
protected void onStarted() {
audioTrack.play();
}
@Override
protected void onStopped() {
audioTrack.pause();
}
@Override
protected void onDisabled() {
inputBuffer = null;
outputBuffer = null;
format = null;
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
try {
if (decoder != null) {
decoder.release();
decoder = null;
codecCounters.codecReleaseCount++;
}
audioTrack.release();
} finally {
super.onDisabled();
}
}
private boolean readFormat() {
int result = readSource(formatHolder, null);
if (result == TrackStream.FORMAT_READ) {
format = formatHolder.format;
audioTrack.configure(format.getFrameworkMediaFormatV16(), false);
return true;
}
return false;
}
@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);
}
});
}
}
private void notifyAudioTrackWriteError(final AudioTrack.WriteException e) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onAudioTrackWriteError(e);
}
});
}
}
private void notifyDecoderError(final OpusDecoderException e) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onDecoderError(e);
}
});
}
protected OpusDecoder createDecoder(List<byte[]> initializationData) throws OpusDecoderException {
return new OpusDecoder(NUM_BUFFERS, NUM_BUFFERS, INITIAL_INPUT_BUFFER_SIZE,
initializationData);
}
}

View file

@ -18,6 +18,7 @@ package com.google.android.exoplayer.ext.opus;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.DecoderInputBuffer;
import com.google.android.exoplayer.util.extensions.SimpleDecoder;
import com.google.android.exoplayer.util.extensions.SimpleOutputBuffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@ -27,7 +28,7 @@ import java.util.List;
* JNI wrapper for the libopus Opus decoder.
*/
/* package */ final class OpusDecoder extends
SimpleDecoder<DecoderInputBuffer, OpusOutputBuffer, OpusDecoderException> {
SimpleDecoder<DecoderInputBuffer, SimpleOutputBuffer, OpusDecoderException> {
/**
* Whether the underlying libopus library is available.
@ -77,7 +78,7 @@ import java.util.List;
*/
public OpusDecoder(int numInputBuffers, int numOutputBuffers, int initialInputBufferSize,
List<byte[]> initializationData) throws OpusDecoderException {
super(new DecoderInputBuffer[numInputBuffers], new OpusOutputBuffer[numOutputBuffers]);
super(new DecoderInputBuffer[numInputBuffers], new SimpleOutputBuffer[numOutputBuffers]);
byte[] headerBytes = initializationData.get(0);
if (headerBytes.length < 19) {
throw new OpusDecoderException("Header size is too small.");
@ -139,18 +140,13 @@ import java.util.List;
}
@Override
public OpusOutputBuffer createOutputBuffer() {
return new OpusOutputBuffer(this);
}
@Override
protected void releaseOutputBuffer(OpusOutputBuffer buffer) {
super.releaseOutputBuffer(buffer);
public SimpleOutputBuffer createOutputBuffer() {
return new SimpleOutputBuffer(this);
}
@Override
public OpusDecoderException decode(DecoderInputBuffer inputBuffer,
OpusOutputBuffer outputBuffer, boolean reset) {
SimpleOutputBuffer outputBuffer, boolean reset) {
if (reset) {
opusReset(nativeDecoderContext);
// When seeking to 0, skip number of samples as specified in opus header. When seeking to

View file

@ -15,10 +15,12 @@
*/
package com.google.android.exoplayer.ext.opus;
import com.google.android.exoplayer.util.extensions.AudioDecoderException;
/**
* Thrown when an Opus decoder error occurs.
*/
public final class OpusDecoderException extends Exception {
public final class OpusDecoderException extends AudioDecoderException {
/* package */ OpusDecoderException(String message) {
super(message);

View file

@ -1,56 +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.ext.opus;
import com.google.android.exoplayer.util.extensions.OutputBuffer;
import java.nio.ByteBuffer;
/**
* Buffer for {@link OpusDecoder} output.
*/
public final class OpusOutputBuffer extends OutputBuffer {
private final OpusDecoder owner;
public ByteBuffer data;
/* package */ OpusOutputBuffer(OpusDecoder owner) {
this.owner = owner;
}
/* package */ void init(int size) {
if (data == null || data.capacity() < size) {
data = ByteBuffer.allocateDirect(size);
}
data.position(0);
data.limit(size);
}
@Override
public void clear() {
super.clear();
if (data != null) {
data.clear();
}
}
@Override
public void release() {
owner.releaseOutputBuffer(this);
}
}

View file

@ -59,13 +59,10 @@ public final class VpxOutputBuffer extends OutputBuffer {
/* package */ void initForRgbFrame(int width, int height) {
this.width = width;
this.height = height;
this.yuvPlanes = null;
int minimumRgbSize = width * height * 2;
if (data == null || data.capacity() < minimumRgbSize) {
data = ByteBuffer.allocateDirect(minimumRgbSize);
yuvPlanes = null;
}
data.position(0);
data.limit(minimumRgbSize);
initData(minimumRgbSize);
}
/**
@ -76,13 +73,12 @@ public final class VpxOutputBuffer extends OutputBuffer {
this.width = width;
this.height = height;
this.colorspace = colorspace;
int yLength = yStride * height;
int uvLength = uvStride * ((height + 1) / 2);
int minimumYuvSize = yLength + (uvLength * 2);
if (data == null || data.capacity() < minimumYuvSize) {
data = ByteBuffer.allocateDirect(minimumYuvSize);
}
data.limit(minimumYuvSize);
initData(minimumYuvSize);
if (yuvPlanes == null) {
yuvPlanes = new ByteBuffer[3];
}
@ -104,4 +100,12 @@ public final class VpxOutputBuffer extends OutputBuffer {
yuvStrides[2] = uvStride;
}
private void initData(int size) {
if (data == null || data.capacity() < size) {
data = ByteBuffer.allocateDirect(size);
}
data.position(0);
data.limit(size);
}
}

View file

@ -0,0 +1,27 @@
/*
* Copyright (C) 2016 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.util.extensions;
/**
* Thrown when a decoder error occurs.
*/
public class AudioDecoderException extends Exception {
public AudioDecoderException(String detailMessage) {
super(detailMessage);
}
}

View file

@ -0,0 +1,361 @@
/*
* Copyright (C) 2016 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.util.extensions;
import com.google.android.exoplayer.CodecCounters;
import com.google.android.exoplayer.DecoderInputBuffer;
import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.FormatHolder;
import com.google.android.exoplayer.MediaClock;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.TrackStream;
import com.google.android.exoplayer.audio.AudioTrack;
import android.os.Handler;
import java.util.List;
/**
* Decodes and renders audio using a {@link SimpleDecoder}.
*/
public abstract class AudioDecoderTrackRenderer extends TrackRenderer implements MediaClock {
/**
* Interface definition for a callback to be notified of {@link AudioDecoderTrackRenderer} events.
*/
public interface EventListener {
/**
* Invoked when the {@link AudioTrack} fails to initialize.
*
* @param e The corresponding exception.
*/
void onAudioTrackInitializationError(AudioTrack.InitializationException e);
/**
* Invoked when an {@link AudioTrack} write fails.
*
* @param e The corresponding exception.
*/
void onAudioTrackWriteError(AudioTrack.WriteException e);
/**
* Invoked when decoding fails.
*
* @param e The corresponding exception.
*/
void onDecoderError(AudioDecoderException 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;
public final CodecCounters codecCounters = new CodecCounters();
private final Handler eventHandler;
private final EventListener eventListener;
private final FormatHolder formatHolder;
private Format format;
private SimpleDecoder<DecoderInputBuffer, ? extends SimpleOutputBuffer,
? extends AudioDecoderException> decoder;
private DecoderInputBuffer inputBuffer;
private SimpleOutputBuffer outputBuffer;
private long currentPositionUs;
private boolean allowPositionDiscontinuity;
private boolean inputStreamEnded;
private boolean outputStreamEnded;
private final AudioTrack audioTrack;
private int audioSessionId;
public AudioDecoderTrackRenderer() {
this(null, null);
}
/**
* @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 AudioDecoderTrackRenderer(Handler eventHandler, EventListener eventListener) {
this.eventHandler = eventHandler;
this.eventListener = eventListener;
this.audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
this.audioTrack = new AudioTrack();
formatHolder = new FormatHolder();
}
@Override
protected MediaClock getMediaClock() {
return this;
}
@Override
protected void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
if (outputStreamEnded) {
return;
}
// Try and read a format if we don't have one already.
if (format == null && !readFormat()) {
// We can't make progress without one.
return;
}
// If we don't have a decoder yet, we need to instantiate one.
if (decoder == null) {
try {
decoder = createDecoder(format.initializationData);
} catch (AudioDecoderException e) {
notifyDecoderError(e);
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
decoder.start();
codecCounters.codecInitCount++;
}
// Rendering loop.
try {
renderBuffer();
while (feedInputBuffer()) {}
} catch (AudioTrack.InitializationException e) {
notifyAudioTrackInitializationError(e);
throw ExoPlaybackException.createForRenderer(e, getIndex());
} catch (AudioTrack.WriteException e) {
notifyAudioTrackWriteError(e);
throw ExoPlaybackException.createForRenderer(e, getIndex());
} catch (AudioDecoderException e) {
notifyDecoderError(e);
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
codecCounters.ensureUpdated();
}
protected abstract SimpleDecoder<DecoderInputBuffer, ? extends SimpleOutputBuffer,
? extends AudioDecoderException> createDecoder(List<byte[]> initializationData)
throws AudioDecoderException;
private void renderBuffer() throws AudioDecoderException, AudioTrack.InitializationException,
AudioTrack.WriteException {
if (outputStreamEnded) {
return;
}
if (outputBuffer == null) {
outputBuffer = decoder.dequeueOutputBuffer();
if (outputBuffer == null) {
return;
}
}
if (outputBuffer.isEndOfStream()) {
outputStreamEnded = true;
audioTrack.handleEndOfStream();
outputBuffer.release();
outputBuffer = null;
return;
}
if (!audioTrack.isInitialized()) {
if (audioSessionId != AudioTrack.SESSION_ID_NOT_SET) {
audioTrack.initialize(audioSessionId);
} else {
audioSessionId = audioTrack.initialize();
}
if (getState() == TrackRenderer.STATE_STARTED) {
audioTrack.play();
}
}
int handleBufferResult;
handleBufferResult = audioTrack.handleBuffer(outputBuffer.data, outputBuffer.data.position(),
outputBuffer.data.remaining(), outputBuffer.timestampUs);
// If we are out of sync, allow currentPositionUs to jump backwards.
if ((handleBufferResult & AudioTrack.RESULT_POSITION_DISCONTINUITY) != 0) {
allowPositionDiscontinuity = true;
}
// Release the buffer if it was consumed.
if ((handleBufferResult & AudioTrack.RESULT_BUFFER_CONSUMED) != 0) {
codecCounters.renderedOutputBufferCount++;
outputBuffer.release();
outputBuffer = null;
}
}
private boolean feedInputBuffer() throws AudioDecoderException {
if (inputStreamEnded) {
return false;
}
if (inputBuffer == null) {
inputBuffer = decoder.dequeueInputBuffer();
if (inputBuffer == null) {
return false;
}
}
int result = readSource(formatHolder, inputBuffer);
if (result == TrackStream.NOTHING_READ) {
return false;
}
if (result == TrackStream.FORMAT_READ) {
format = formatHolder.format;
return true;
}
if (inputBuffer.isEndOfStream()) {
inputStreamEnded = true;
}
decoder.queueInputBuffer(inputBuffer);
inputBuffer = null;
return true;
}
private void flushDecoder() {
inputBuffer = null;
if (outputBuffer != null) {
outputBuffer.release();
outputBuffer = null;
}
decoder.flush();
}
@Override
protected boolean isEnded() {
return outputStreamEnded && !audioTrack.hasPendingData();
}
@Override
protected boolean isReady() {
return audioTrack.hasPendingData()
|| (format != null && (isSourceReady() || outputBuffer != null));
}
@Override
public long getPositionUs() {
long newCurrentPositionUs = audioTrack.getCurrentPositionUs(isEnded());
if (newCurrentPositionUs != AudioTrack.CURRENT_POSITION_NOT_SET) {
currentPositionUs = allowPositionDiscontinuity ? newCurrentPositionUs
: Math.max(currentPositionUs, newCurrentPositionUs);
allowPositionDiscontinuity = false;
}
return currentPositionUs;
}
@Override
protected void reset(long positionUs) {
audioTrack.reset();
currentPositionUs = positionUs;
allowPositionDiscontinuity = true;
inputStreamEnded = false;
outputStreamEnded = false;
if (decoder != null) {
flushDecoder();
}
}
@Override
protected void onStarted() {
audioTrack.play();
}
@Override
protected void onStopped() {
audioTrack.pause();
}
@Override
protected void onDisabled() {
inputBuffer = null;
outputBuffer = null;
format = null;
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
try {
if (decoder != null) {
decoder.release();
decoder = null;
codecCounters.codecReleaseCount++;
}
audioTrack.release();
} finally {
super.onDisabled();
}
}
private boolean readFormat() {
int result = readSource(formatHolder, null);
if (result == TrackStream.FORMAT_READ) {
format = formatHolder.format;
audioTrack.configure(format.getFrameworkMediaFormatV16(), false);
return true;
}
return false;
}
@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);
}
});
}
}
private void notifyAudioTrackWriteError(final AudioTrack.WriteException e) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onAudioTrackWriteError(e);
}
});
}
}
private void notifyDecoderError(final AudioDecoderException e) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onDecoderError(e);
}
});
}
}
}

View file

@ -13,26 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.ext.flac;
import com.google.android.exoplayer.util.extensions.OutputBuffer;
package com.google.android.exoplayer.util.extensions;
import java.nio.ByteBuffer;
/**
* Buffer for {@link FlacDecoder} output.
* Buffer for {@link SimpleDecoder} output.
*/
public final class FlacOutputBuffer extends OutputBuffer {
public class SimpleOutputBuffer extends OutputBuffer {
private final FlacDecoder owner;
private final SimpleDecoder<?, SimpleOutputBuffer, ?> owner;
public ByteBuffer data;
/* package */ FlacOutputBuffer(FlacDecoder owner) {
public SimpleOutputBuffer(SimpleDecoder<?, SimpleOutputBuffer, ?> owner) {
this.owner = owner;
}
/* package */ void init(int size) {
public void init(int size) {
if (data == null || data.capacity() < size) {
data = ByteBuffer.allocateDirect(size);
}