Commit 82d33cde by andrewlewis Committed by Oliver Woodman

Add support for draining audio output.

At the end of playback, BufferProcessors need to be drained to process all
remaining data, then the output needs to be written to the AudioTrack before
stop() is called.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=148339194
parent 69bd956b
...@@ -278,18 +278,6 @@ ...@@ -278,18 +278,6 @@
] ]
}, },
{ {
"name": "ClearKey DASH",
"samples": [
{
"name": "Big Buck Bunny (CENC ClearKey)",
"uri": "http://html5.cablelabs.com:8100/cenc/ck/dash.mpd",
"extension": "mpd",
"drm_scheme": "cenc",
"drm_license_url": "https://wasabeef.jp/demos/cenc-ck-dash.json"
}
]
},
{
"name": "SmoothStreaming", "name": "SmoothStreaming",
"samples": [ "samples": [
{ {
......
...@@ -54,8 +54,8 @@ import java.util.ArrayList; ...@@ -54,8 +54,8 @@ import java.util.ArrayList;
* safe to call {@link #handleBuffer(ByteBuffer, long)} after {@link #reset()} without calling * safe to call {@link #handleBuffer(ByteBuffer, long)} after {@link #reset()} without calling
* {@link #configure(String, int, int, int, int)}. * {@link #configure(String, int, int, int, int)}.
* <p> * <p>
* Call {@link #handleEndOfStream()} to play out all data when no more input buffers will be * Call {@link #playToEndOfStream()} repeatedly to play out all data when no more input buffers will
* provided via {@link #handleBuffer(ByteBuffer, long)} until the next {@link #reset}. Call * be provided via {@link #handleBuffer(ByteBuffer, long)} until the next {@link #reset}. Call
* {@link #release()} when the instance is no longer required. * {@link #release()} when the instance is no longer required.
*/ */
public final class AudioTrack { public final class AudioTrack {
...@@ -324,6 +324,8 @@ public final class AudioTrack { ...@@ -324,6 +324,8 @@ public final class AudioTrack {
private ByteBuffer outputBuffer; private ByteBuffer outputBuffer;
private byte[] preV21OutputBuffer; private byte[] preV21OutputBuffer;
private int preV21OutputBufferOffset; private int preV21OutputBufferOffset;
private int drainingBufferProcessorIndex;
private boolean handledEndOfStream;
private boolean playing; private boolean playing;
private int audioSessionId; private int audioSessionId;
...@@ -366,6 +368,7 @@ public final class AudioTrack { ...@@ -366,6 +368,7 @@ public final class AudioTrack {
startMediaTimeState = START_NOT_SET; startMediaTimeState = START_NOT_SET;
streamType = C.STREAM_TYPE_DEFAULT; streamType = C.STREAM_TYPE_DEFAULT;
audioSessionId = C.AUDIO_SESSION_ID_UNSET; audioSessionId = C.AUDIO_SESSION_ID_UNSET;
drainingBufferProcessorIndex = C.INDEX_UNSET;
this.bufferProcessors = new BufferProcessor[0]; this.bufferProcessors = new BufferProcessor[0];
outputBuffers = new ByteBuffer[0]; outputBuffers = new ByteBuffer[0];
} }
...@@ -762,7 +765,8 @@ public final class AudioTrack { ...@@ -762,7 +765,8 @@ public final class AudioTrack {
int count = bufferProcessors.length; int count = bufferProcessors.length;
int index = count; int index = count;
while (index >= 0) { while (index >= 0) {
ByteBuffer input = index > 0 ? outputBuffers[index - 1] : inputBuffer; ByteBuffer input = index > 0 ? outputBuffers[index - 1]
: (inputBuffer != null ? inputBuffer : BufferProcessor.EMPTY_BUFFER);
if (index == count) { if (index == count) {
writeBuffer(input, avSyncPresentationTimeUs); writeBuffer(input, avSyncPresentationTimeUs);
} else { } else {
...@@ -851,14 +855,54 @@ public final class AudioTrack { ...@@ -851,14 +855,54 @@ public final class AudioTrack {
} }
/** /**
* Ensures that the last data passed to {@link #handleBuffer(ByteBuffer, long)} is played in full. * Plays out remaining audio. {@link #isEnded()} will return {@code true} when playback has ended.
*
* @throws WriteException If an error occurs draining data to the track.
*/ */
public void handleEndOfStream() { public void playToEndOfStream() throws WriteException {
if (isInitialized()) { if (handledEndOfStream || !isInitialized()) {
// TODO: Drain buffer processors before stopping the AudioTrack. return;
}
// Drain the buffer processors.
boolean bufferProcessorNeedsEndOfStream = false;
if (drainingBufferProcessorIndex == C.INDEX_UNSET) {
drainingBufferProcessorIndex = passthrough ? bufferProcessors.length : 0;
bufferProcessorNeedsEndOfStream = true;
}
while (drainingBufferProcessorIndex < bufferProcessors.length) {
BufferProcessor bufferProcessor = bufferProcessors[drainingBufferProcessorIndex];
if (bufferProcessorNeedsEndOfStream) {
bufferProcessor.queueEndOfStream();
}
processBuffers(C.TIME_UNSET);
if (!bufferProcessor.isEnded()) {
return;
}
bufferProcessorNeedsEndOfStream = true;
drainingBufferProcessorIndex++;
}
// Finish writing any remaining output to the track.
if (outputBuffer != null) {
writeBuffer(outputBuffer, C.TIME_UNSET);
if (outputBuffer != null) {
return;
}
}
// Drain the track.
audioTrackUtil.handleEndOfStream(getWrittenFrames()); audioTrackUtil.handleEndOfStream(getWrittenFrames());
bytesUntilNextAvSync = 0; bytesUntilNextAvSync = 0;
handledEndOfStream = true;
} }
/**
* Returns whether all buffers passed to {@link #handleBuffer(ByteBuffer, long)} have been
* completely processed and played.
*/
public boolean isEnded() {
return !isInitialized() || (handledEndOfStream && !hasPendingData());
} }
/** /**
...@@ -1003,6 +1047,8 @@ public final class AudioTrack { ...@@ -1003,6 +1047,8 @@ public final class AudioTrack {
bufferProcessor.flush(); bufferProcessor.flush();
outputBuffers[i] = bufferProcessor.getOutput(); outputBuffers[i] = bufferProcessor.getOutput();
} }
handledEndOfStream = false;
drainingBufferProcessorIndex = C.INDEX_UNSET;
avSyncHeader = null; avSyncHeader = null;
bytesUntilNextAvSync = 0; bytesUntilNextAvSync = 0;
startMediaTimeState = START_NOT_SET; startMediaTimeState = START_NOT_SET;
......
...@@ -85,6 +85,15 @@ public interface BufferProcessor { ...@@ -85,6 +85,15 @@ public interface BufferProcessor {
void queueInput(ByteBuffer buffer); void queueInput(ByteBuffer buffer);
/** /**
* Queues an end of stream signal and begins draining any pending output from this processor.
* After this method has been called, {@link #queueInput(ByteBuffer)} may not be called until
* after the next call to {@link #flush()}. Calling {@link #getOutput()} will return any remaining
* output data. Multiple calls may be required to read all of the remaining output data.
* {@link #isEnded()} will return {@code true} once all remaining output data has been read.
*/
void queueEndOfStream();
/**
* Returns a buffer containing processed output data between its position and limit. The buffer * Returns a buffer containing processed output data between its position and limit. The buffer
* will always be a direct byte buffer with native byte order. Calling this method invalidates any * will always be a direct byte buffer with native byte order. Calling this method invalidates any
* previously returned buffer. The buffer will be empty if no output is available. * previously returned buffer. The buffer will be empty if no output is available.
...@@ -94,6 +103,12 @@ public interface BufferProcessor { ...@@ -94,6 +103,12 @@ public interface BufferProcessor {
ByteBuffer getOutput(); ByteBuffer getOutput();
/** /**
* Returns whether this processor will return no more output from {@link #getOutput()} until it
* has been {@link #flush()}ed and more input has been queued.
*/
boolean isEnded();
/**
* Clears any state in preparation for receiving a new stream of buffers. * Clears any state in preparation for receiving a new stream of buffers.
*/ */
void flush(); void flush();
......
...@@ -312,7 +312,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -312,7 +312,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
@Override @Override
public boolean isEnded() { public boolean isEnded() {
return super.isEnded() && !audioTrack.hasPendingData(); return super.isEnded() && audioTrack.isEnded();
} }
@Override @Override
...@@ -361,8 +361,12 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -361,8 +361,12 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
} }
@Override @Override
protected void onOutputStreamEnded() { protected void renderToEndOfStream() throws ExoPlaybackException {
audioTrack.handleEndOfStream(); try {
audioTrack.playToEndOfStream();
} catch (AudioTrack.WriteException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
} }
@Override @Override
......
...@@ -30,6 +30,7 @@ import java.nio.ByteOrder; ...@@ -30,6 +30,7 @@ import java.nio.ByteOrder;
private int encoding; private int encoding;
private ByteBuffer buffer; private ByteBuffer buffer;
private ByteBuffer outputBuffer; private ByteBuffer outputBuffer;
private boolean inputEnded;
/** /**
* Creates a new buffer processor that converts audio data to {@link C#ENCODING_PCM_16BIT}. * Creates a new buffer processor that converts audio data to {@link C#ENCODING_PCM_16BIT}.
...@@ -146,14 +147,26 @@ import java.nio.ByteOrder; ...@@ -146,14 +147,26 @@ import java.nio.ByteOrder;
} }
@Override @Override
public void queueEndOfStream() {
inputEnded = true;
}
@SuppressWarnings("ReferenceEquality")
@Override
public boolean isEnded() {
return inputEnded && outputBuffer == EMPTY_BUFFER;
}
@Override
public void flush() { public void flush() {
outputBuffer = EMPTY_BUFFER; outputBuffer = EMPTY_BUFFER;
inputEnded = false;
} }
@Override @Override
public void release() { public void release() {
flush();
buffer = EMPTY_BUFFER; buffer = EMPTY_BUFFER;
outputBuffer = EMPTY_BUFFER;
} }
} }
...@@ -178,6 +178,11 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -178,6 +178,11 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
@Override @Override
public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
if (outputStreamEnded) { if (outputStreamEnded) {
try {
audioTrack.playToEndOfStream();
} catch (AudioTrack.WriteException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
return; return;
} }
...@@ -280,7 +285,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -280,7 +285,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
outputBuffer.release(); outputBuffer.release();
outputBuffer = null; outputBuffer = null;
outputStreamEnded = true; outputStreamEnded = true;
audioTrack.handleEndOfStream(); audioTrack.playToEndOfStream();
} }
return false; return false;
} }
...@@ -388,7 +393,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -388,7 +393,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
@Override @Override
public boolean isEnded() { public boolean isEnded() {
return outputStreamEnded && !audioTrack.hasPendingData(); return outputStreamEnded && audioTrack.isEnded();
} }
@Override @Override
......
...@@ -480,6 +480,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -480,6 +480,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
@Override @Override
public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
if (outputStreamEnded) { if (outputStreamEnded) {
renderToEndOfStream();
return; return;
} }
if (format == null) { if (format == null) {
...@@ -788,16 +789,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -788,16 +789,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
/** /**
* Called when the output stream ends, meaning that the last output buffer has been processed and
* the {@link MediaCodec#BUFFER_FLAG_END_OF_STREAM} flag has been propagated through the decoder.
* <p>
* The default implementation is a no-op.
*/
protected void onOutputStreamEnded() {
// Do nothing.
}
/**
* Called immediately before an input buffer is queued into the codec. * Called immediately before an input buffer is queued into the codec.
* <p> * <p>
* The default implementation is a no-op. * The default implementation is a no-op.
...@@ -1011,6 +1002,17 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -1011,6 +1002,17 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
long bufferPresentationTimeUs, boolean shouldSkip) throws ExoPlaybackException; long bufferPresentationTimeUs, boolean shouldSkip) throws ExoPlaybackException;
/** /**
* Incrementally renders any remaining output.
* <p>
* The default implementation is a no-op.
*
* @throws ExoPlaybackException Thrown if an error occurs rendering remaining output.
*/
protected void renderToEndOfStream() throws ExoPlaybackException {
// Do nothing.
}
/**
* Processes an end of stream signal. * Processes an end of stream signal.
* *
* @throws ExoPlaybackException If an error occurs processing the signal. * @throws ExoPlaybackException If an error occurs processing the signal.
...@@ -1022,7 +1024,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -1022,7 +1024,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
maybeInitCodec(); maybeInitCodec();
} else { } else {
outputStreamEnded = true; outputStreamEnded = true;
onOutputStreamEnded(); renderToEndOfStream();
} }
} }
......
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