Commit a7d78594 by eguven Committed by Oliver Woodman

Shared super class for LibflacAudioTrackRenderer and LibopusAudioTrackRenderer.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=120225655
parent a760c9bf
...@@ -17,6 +17,7 @@ package com.google.android.exoplayer.ext.flac; ...@@ -17,6 +17,7 @@ package com.google.android.exoplayer.ext.flac;
import com.google.android.exoplayer.DecoderInputBuffer; import com.google.android.exoplayer.DecoderInputBuffer;
import com.google.android.exoplayer.util.extensions.SimpleDecoder; import com.google.android.exoplayer.util.extensions.SimpleDecoder;
import com.google.android.exoplayer.util.extensions.SimpleOutputBuffer;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.List; import java.util.List;
...@@ -25,21 +26,23 @@ import java.util.List; ...@@ -25,21 +26,23 @@ import java.util.List;
* Flac decoder. * Flac decoder.
*/ */
/* package */ final class FlacDecoder extends /* package */ final class FlacDecoder extends
SimpleDecoder<DecoderInputBuffer, FlacOutputBuffer, FlacDecoderException> { SimpleDecoder<DecoderInputBuffer, SimpleOutputBuffer, FlacDecoderException> {
private final int maxOutputBufferSize; private final int maxOutputBufferSize;
private final FlacJni decoder; private final FlacJni decoder;
/** /**
* Creates a Flac decoder. * Creates a Flac decoder.
* *
* @param numInputBuffers The number of input buffers. * @param numInputBuffers The number of input buffers.
* @param numOutputBuffers The number of output 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. * @throws FlacDecoderException Thrown if an exception occurs when initializing the decoder.
*/ */
public FlacDecoder(int numInputBuffers, int numOutputBuffers, List<byte[]> initializationData) public FlacDecoder(int numInputBuffers, int numOutputBuffers, List<byte[]> initializationData)
throws FlacDecoderException { throws FlacDecoderException {
super(new DecoderInputBuffer[numInputBuffers], new FlacOutputBuffer[numOutputBuffers]); super(new DecoderInputBuffer[numInputBuffers], new SimpleOutputBuffer[numOutputBuffers]);
if (initializationData.size() != 1) { if (initializationData.size() != 1) {
throw new FlacDecoderException("Wrong number of initialization data"); throw new FlacDecoderException("Wrong number of initialization data");
} }
...@@ -63,18 +66,13 @@ import java.util.List; ...@@ -63,18 +66,13 @@ import java.util.List;
} }
@Override @Override
public FlacOutputBuffer createOutputBuffer() { public SimpleOutputBuffer createOutputBuffer() {
return new FlacOutputBuffer(this); return new SimpleOutputBuffer(this);
}
@Override
protected void releaseOutputBuffer(FlacOutputBuffer buffer) {
super.releaseOutputBuffer(buffer);
} }
@Override @Override
public FlacDecoderException decode(DecoderInputBuffer inputBuffer, public FlacDecoderException decode(DecoderInputBuffer inputBuffer,
FlacOutputBuffer outputBuffer, boolean reset) { SimpleOutputBuffer outputBuffer, boolean reset) {
if (reset) { if (reset) {
decoder.flush(); decoder.flush();
} }
......
...@@ -15,10 +15,12 @@ ...@@ -15,10 +15,12 @@
*/ */
package com.google.android.exoplayer.ext.flac; package com.google.android.exoplayer.ext.flac;
import com.google.android.exoplayer.util.extensions.AudioDecoderException;
/** /**
* Thrown when an Flac decoder error occurs. * Thrown when an Flac decoder error occurs.
*/ */
public final class FlacDecoderException extends Exception { public final class FlacDecoderException extends AudioDecoderException {
/* package */ FlacDecoderException(String message) { /* package */ FlacDecoderException(String message) {
super(message); super(message);
......
...@@ -15,17 +15,9 @@ ...@@ -15,17 +15,9 @@
*/ */
package com.google.android.exoplayer.ext.flac; 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.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.MimeTypes;
import com.google.android.exoplayer.util.extensions.AudioDecoderTrackRenderer;
import android.os.Handler; import android.os.Handler;
...@@ -34,64 +26,17 @@ import java.util.List; ...@@ -34,64 +26,17 @@ import java.util.List;
/** /**
* Decodes and renders audio using the native Flac decoder. * Decodes and renders audio using the native Flac decoder.
*/ */
public final class LibflacAudioTrackRenderer extends TrackRenderer implements MediaClock { public class LibflacAudioTrackRenderer extends AudioDecoderTrackRenderer {
/** private static final int NUM_BUFFERS = 16;
* 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. * Returns whether the underlying libflac library is available.
*
* @param e The corresponding exception.
*/ */
void onDecoderError(FlacDecoderException e); public static boolean isLibflacAvailable() {
return FlacJni.IS_AVAILABLE;
} }
/**
* 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 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;
public LibflacAudioTrackRenderer() { public LibflacAudioTrackRenderer() {
this(null, null); this(null, null);
} }
...@@ -101,26 +46,10 @@ public final class LibflacAudioTrackRenderer extends TrackRenderer implements Me ...@@ -101,26 +46,10 @@ public final class LibflacAudioTrackRenderer extends TrackRenderer implements Me
* null if delivery of events is not required. * null if delivery of events is not required.
* @param eventListener A listener of events. 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 LibflacAudioTrackRenderer(Handler eventHandler, EventListener eventListener) { public LibflacAudioTrackRenderer(Handler eventHandler,
this.eventHandler = eventHandler; AudioDecoderTrackRenderer.EventListener eventListener) {
this.eventListener = eventListener; super(eventHandler, eventListener);
this.audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
this.audioTrack = new AudioTrack();
formatHolder = new FormatHolder();
}
/**
* Returns whether the underlying libflac library is available.
*/
public static boolean isLibflacAvailable() {
return FlacJni.IS_AVAILABLE;
}
@Override
protected MediaClock getMediaClock() {
return this;
} }
@Override @Override
protected int supportsFormat(Format format) { protected int supportsFormat(Format format) {
return MimeTypes.AUDIO_FLAC.equalsIgnoreCase(format.sampleMimeType) return MimeTypes.AUDIO_FLAC.equalsIgnoreCase(format.sampleMimeType)
...@@ -128,252 +57,8 @@ public final class LibflacAudioTrackRenderer extends TrackRenderer implements Me ...@@ -128,252 +57,8 @@ public final class LibflacAudioTrackRenderer extends TrackRenderer implements Me
} }
@Override @Override
protected void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { protected FlacDecoder createDecoder(List<byte[]> initializationData) throws FlacDecoderException {
if (outputStreamEnded) { return new FlacDecoder(NUM_BUFFERS, NUM_BUFFERS, initializationData);
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);
}
});
}
} }
} }
...@@ -15,17 +15,9 @@ ...@@ -15,17 +15,9 @@
*/ */
package com.google.android.exoplayer.ext.opus; 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.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.MimeTypes;
import com.google.android.exoplayer.util.extensions.AudioDecoderTrackRenderer;
import android.os.Handler; import android.os.Handler;
...@@ -34,64 +26,24 @@ import java.util.List; ...@@ -34,64 +26,24 @@ import java.util.List;
/** /**
* Decodes and renders audio using the native Opus decoder. * Decodes and renders audio using the native Opus decoder.
*/ */
public final class LibopusAudioTrackRenderer extends TrackRenderer implements MediaClock { public final class LibopusAudioTrackRenderer extends AudioDecoderTrackRenderer {
/** private static final int NUM_BUFFERS = 16;
* Interface definition for a callback to be notified of {@link LibopusAudioTrackRenderer} events. private static final int INITIAL_INPUT_BUFFER_SIZE = 960 * 6;
*/
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. * Returns whether the underlying libopus library is available.
*
* @param e The corresponding exception.
*/ */
void onDecoderError(OpusDecoderException e); public static boolean isLibopusAvailable() {
return OpusDecoder.IS_AVAILABLE;
} }
/** /**
* The type of a message that can be passed to an instance of this class via * Returns the version of the underlying libopus library if available, otherwise {@code null}.
* {@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 static String getLibopusVersion() {
return isLibopusAvailable() ? OpusDecoder.getLibopusVersion() : null;
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() { public LibopusAudioTrackRenderer() {
this(null, null); this(null, null);
...@@ -103,30 +55,7 @@ public final class LibopusAudioTrackRenderer extends TrackRenderer implements Me ...@@ -103,30 +55,7 @@ public final class LibopusAudioTrackRenderer extends TrackRenderer implements Me
* @param eventListener A listener of events. 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) { public LibopusAudioTrackRenderer(Handler eventHandler, EventListener eventListener) {
this.eventHandler = eventHandler; super(eventHandler, eventListener);
this.eventListener = eventListener;
this.audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
audioTrack = new AudioTrack();
formatHolder = new FormatHolder();
}
/**
* Returns whether the underlying libopus library is available.
*/
public static boolean isLibopusAvailable() {
return OpusDecoder.IS_AVAILABLE;
}
/**
* Returns the version of the underlying libopus library if available, otherwise {@code null}.
*/
public static String getLibopusVersion() {
return isLibopusAvailable() ? OpusDecoder.getLibopusVersion() : null;
}
@Override
protected MediaClock getMediaClock() {
return this;
} }
@Override @Override
...@@ -136,256 +65,9 @@ public final class LibopusAudioTrackRenderer extends TrackRenderer implements Me ...@@ -136,256 +65,9 @@ public final class LibopusAudioTrackRenderer extends TrackRenderer implements Me
} }
@Override @Override
protected void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { protected OpusDecoder createDecoder(List<byte[]> initializationData) throws OpusDecoderException {
if (outputStreamEnded) { return new OpusDecoder(NUM_BUFFERS, NUM_BUFFERS, INITIAL_INPUT_BUFFER_SIZE,
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); 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);
}
});
}
} }
} }
...@@ -18,6 +18,7 @@ package com.google.android.exoplayer.ext.opus; ...@@ -18,6 +18,7 @@ package com.google.android.exoplayer.ext.opus;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.DecoderInputBuffer; import com.google.android.exoplayer.DecoderInputBuffer;
import com.google.android.exoplayer.util.extensions.SimpleDecoder; import com.google.android.exoplayer.util.extensions.SimpleDecoder;
import com.google.android.exoplayer.util.extensions.SimpleOutputBuffer;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
...@@ -27,7 +28,7 @@ import java.util.List; ...@@ -27,7 +28,7 @@ import java.util.List;
* JNI wrapper for the libopus Opus decoder. * JNI wrapper for the libopus Opus decoder.
*/ */
/* package */ final class OpusDecoder extends /* package */ final class OpusDecoder extends
SimpleDecoder<DecoderInputBuffer, OpusOutputBuffer, OpusDecoderException> { SimpleDecoder<DecoderInputBuffer, SimpleOutputBuffer, OpusDecoderException> {
/** /**
* Whether the underlying libopus library is available. * Whether the underlying libopus library is available.
...@@ -77,7 +78,7 @@ import java.util.List; ...@@ -77,7 +78,7 @@ import java.util.List;
*/ */
public OpusDecoder(int numInputBuffers, int numOutputBuffers, int initialInputBufferSize, public OpusDecoder(int numInputBuffers, int numOutputBuffers, int initialInputBufferSize,
List<byte[]> initializationData) throws OpusDecoderException { List<byte[]> initializationData) throws OpusDecoderException {
super(new DecoderInputBuffer[numInputBuffers], new OpusOutputBuffer[numOutputBuffers]); super(new DecoderInputBuffer[numInputBuffers], new SimpleOutputBuffer[numOutputBuffers]);
byte[] headerBytes = initializationData.get(0); byte[] headerBytes = initializationData.get(0);
if (headerBytes.length < 19) { if (headerBytes.length < 19) {
throw new OpusDecoderException("Header size is too small."); throw new OpusDecoderException("Header size is too small.");
...@@ -139,18 +140,13 @@ import java.util.List; ...@@ -139,18 +140,13 @@ import java.util.List;
} }
@Override @Override
public OpusOutputBuffer createOutputBuffer() { public SimpleOutputBuffer createOutputBuffer() {
return new OpusOutputBuffer(this); return new SimpleOutputBuffer(this);
}
@Override
protected void releaseOutputBuffer(OpusOutputBuffer buffer) {
super.releaseOutputBuffer(buffer);
} }
@Override @Override
public OpusDecoderException decode(DecoderInputBuffer inputBuffer, public OpusDecoderException decode(DecoderInputBuffer inputBuffer,
OpusOutputBuffer outputBuffer, boolean reset) { SimpleOutputBuffer outputBuffer, boolean reset) {
if (reset) { if (reset) {
opusReset(nativeDecoderContext); opusReset(nativeDecoderContext);
// When seeking to 0, skip number of samples as specified in opus header. When seeking to // When seeking to 0, skip number of samples as specified in opus header. When seeking to
......
...@@ -15,10 +15,12 @@ ...@@ -15,10 +15,12 @@
*/ */
package com.google.android.exoplayer.ext.opus; package com.google.android.exoplayer.ext.opus;
import com.google.android.exoplayer.util.extensions.AudioDecoderException;
/** /**
* Thrown when an Opus decoder error occurs. * Thrown when an Opus decoder error occurs.
*/ */
public final class OpusDecoderException extends Exception { public final class OpusDecoderException extends AudioDecoderException {
/* package */ OpusDecoderException(String message) { /* package */ OpusDecoderException(String message) {
super(message); super(message);
......
...@@ -59,13 +59,10 @@ public final class VpxOutputBuffer extends OutputBuffer { ...@@ -59,13 +59,10 @@ public final class VpxOutputBuffer extends OutputBuffer {
/* package */ void initForRgbFrame(int width, int height) { /* package */ void initForRgbFrame(int width, int height) {
this.width = width; this.width = width;
this.height = height; this.height = height;
this.yuvPlanes = null;
int minimumRgbSize = width * height * 2; int minimumRgbSize = width * height * 2;
if (data == null || data.capacity() < minimumRgbSize) { initData(minimumRgbSize);
data = ByteBuffer.allocateDirect(minimumRgbSize);
yuvPlanes = null;
}
data.position(0);
data.limit(minimumRgbSize);
} }
/** /**
...@@ -76,13 +73,12 @@ public final class VpxOutputBuffer extends OutputBuffer { ...@@ -76,13 +73,12 @@ public final class VpxOutputBuffer extends OutputBuffer {
this.width = width; this.width = width;
this.height = height; this.height = height;
this.colorspace = colorspace; this.colorspace = colorspace;
int yLength = yStride * height; int yLength = yStride * height;
int uvLength = uvStride * ((height + 1) / 2); int uvLength = uvStride * ((height + 1) / 2);
int minimumYuvSize = yLength + (uvLength * 2); int minimumYuvSize = yLength + (uvLength * 2);
if (data == null || data.capacity() < minimumYuvSize) { initData(minimumYuvSize);
data = ByteBuffer.allocateDirect(minimumYuvSize);
}
data.limit(minimumYuvSize);
if (yuvPlanes == null) { if (yuvPlanes == null) {
yuvPlanes = new ByteBuffer[3]; yuvPlanes = new ByteBuffer[3];
} }
...@@ -104,4 +100,12 @@ public final class VpxOutputBuffer extends OutputBuffer { ...@@ -104,4 +100,12 @@ public final class VpxOutputBuffer extends OutputBuffer {
yuvStrides[2] = uvStride; yuvStrides[2] = uvStride;
} }
private void initData(int size) {
if (data == null || data.capacity() < size) {
data = ByteBuffer.allocateDirect(size);
}
data.position(0);
data.limit(size);
}
} }
/* /*
* Copyright (C) 2014 The Android Open Source Project * Copyright (C) 2016 The Android Open Source Project
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -13,44 +13,15 @@ ...@@ -13,44 +13,15 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer.ext.opus; package com.google.android.exoplayer.util.extensions;
import com.google.android.exoplayer.util.extensions.OutputBuffer;
import java.nio.ByteBuffer;
/** /**
* Buffer for {@link OpusDecoder} output. * Thrown when a decoder error occurs.
*/ */
public final class OpusOutputBuffer extends OutputBuffer { public class AudioDecoderException extends Exception {
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 AudioDecoderException(String detailMessage) {
public void release() { super(detailMessage);
owner.releaseOutputBuffer(this);
} }
} }
/*
* 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);
}
});
}
}
}
...@@ -13,26 +13,24 @@ ...@@ -13,26 +13,24 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer.ext.flac; package com.google.android.exoplayer.util.extensions;
import com.google.android.exoplayer.util.extensions.OutputBuffer;
import java.nio.ByteBuffer; 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; public ByteBuffer data;
/* package */ FlacOutputBuffer(FlacDecoder owner) { public SimpleOutputBuffer(SimpleDecoder<?, SimpleOutputBuffer, ?> owner) {
this.owner = owner; this.owner = owner;
} }
/* package */ void init(int size) { public void init(int size) {
if (data == null || data.capacity() < size) { if (data == null || data.capacity() < size) {
data = ByteBuffer.allocateDirect(size); data = ByteBuffer.allocateDirect(size);
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment