mirror of
https://github.com/samsonjs/media.git
synced 2026-03-27 09:45:47 +00:00
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
This commit is contained in:
parent
69bd956bd7
commit
82d33cde68
7 changed files with 111 additions and 38 deletions
|
|
@ -277,18 +277,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",
|
||||
"samples": [
|
||||
|
|
|
|||
|
|
@ -54,8 +54,8 @@ import java.util.ArrayList;
|
|||
* safe to call {@link #handleBuffer(ByteBuffer, long)} after {@link #reset()} without calling
|
||||
* {@link #configure(String, int, int, int, int)}.
|
||||
* <p>
|
||||
* Call {@link #handleEndOfStream()} to play out all data when no more input buffers will be
|
||||
* provided via {@link #handleBuffer(ByteBuffer, long)} until the next {@link #reset}. Call
|
||||
* Call {@link #playToEndOfStream()} repeatedly to play out all data when no more input buffers will
|
||||
* be provided via {@link #handleBuffer(ByteBuffer, long)} until the next {@link #reset}. Call
|
||||
* {@link #release()} when the instance is no longer required.
|
||||
*/
|
||||
public final class AudioTrack {
|
||||
|
|
@ -324,6 +324,8 @@ public final class AudioTrack {
|
|||
private ByteBuffer outputBuffer;
|
||||
private byte[] preV21OutputBuffer;
|
||||
private int preV21OutputBufferOffset;
|
||||
private int drainingBufferProcessorIndex;
|
||||
private boolean handledEndOfStream;
|
||||
|
||||
private boolean playing;
|
||||
private int audioSessionId;
|
||||
|
|
@ -366,6 +368,7 @@ public final class AudioTrack {
|
|||
startMediaTimeState = START_NOT_SET;
|
||||
streamType = C.STREAM_TYPE_DEFAULT;
|
||||
audioSessionId = C.AUDIO_SESSION_ID_UNSET;
|
||||
drainingBufferProcessorIndex = C.INDEX_UNSET;
|
||||
this.bufferProcessors = new BufferProcessor[0];
|
||||
outputBuffers = new ByteBuffer[0];
|
||||
}
|
||||
|
|
@ -762,7 +765,8 @@ public final class AudioTrack {
|
|||
int count = bufferProcessors.length;
|
||||
int index = count;
|
||||
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) {
|
||||
writeBuffer(input, avSyncPresentationTimeUs);
|
||||
} else {
|
||||
|
|
@ -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() {
|
||||
if (isInitialized()) {
|
||||
// TODO: Drain buffer processors before stopping the AudioTrack.
|
||||
audioTrackUtil.handleEndOfStream(getWrittenFrames());
|
||||
bytesUntilNextAvSync = 0;
|
||||
public void playToEndOfStream() throws WriteException {
|
||||
if (handledEndOfStream || !isInitialized()) {
|
||||
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());
|
||||
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 {
|
|||
bufferProcessor.flush();
|
||||
outputBuffers[i] = bufferProcessor.getOutput();
|
||||
}
|
||||
handledEndOfStream = false;
|
||||
drainingBufferProcessorIndex = C.INDEX_UNSET;
|
||||
avSyncHeader = null;
|
||||
bytesUntilNextAvSync = 0;
|
||||
startMediaTimeState = START_NOT_SET;
|
||||
|
|
|
|||
|
|
@ -84,6 +84,15 @@ public interface BufferProcessor {
|
|||
*/
|
||||
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
|
||||
* will always be a direct byte buffer with native byte order. Calling this method invalidates any
|
||||
|
|
@ -93,6 +102,12 @@ public interface BufferProcessor {
|
|||
*/
|
||||
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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -312,7 +312,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||
|
||||
@Override
|
||||
public boolean isEnded() {
|
||||
return super.isEnded() && !audioTrack.hasPendingData();
|
||||
return super.isEnded() && audioTrack.isEnded();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -361,8 +361,12 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void onOutputStreamEnded() {
|
||||
audioTrack.handleEndOfStream();
|
||||
protected void renderToEndOfStream() throws ExoPlaybackException {
|
||||
try {
|
||||
audioTrack.playToEndOfStream();
|
||||
} catch (AudioTrack.WriteException e) {
|
||||
throw ExoPlaybackException.createForRenderer(e, getIndex());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import java.nio.ByteOrder;
|
|||
private int encoding;
|
||||
private ByteBuffer buffer;
|
||||
private ByteBuffer outputBuffer;
|
||||
private boolean inputEnded;
|
||||
|
||||
/**
|
||||
* Creates a new buffer processor that converts audio data to {@link C#ENCODING_PCM_16BIT}.
|
||||
|
|
@ -145,15 +146,27 @@ import java.nio.ByteOrder;
|
|||
return outputBuffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queueEndOfStream() {
|
||||
inputEnded = true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("ReferenceEquality")
|
||||
@Override
|
||||
public boolean isEnded() {
|
||||
return inputEnded && outputBuffer == EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
outputBuffer = EMPTY_BUFFER;
|
||||
inputEnded = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
flush();
|
||||
buffer = EMPTY_BUFFER;
|
||||
outputBuffer = EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -178,6 +178,11 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||
@Override
|
||||
public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
|
||||
if (outputStreamEnded) {
|
||||
try {
|
||||
audioTrack.playToEndOfStream();
|
||||
} catch (AudioTrack.WriteException e) {
|
||||
throw ExoPlaybackException.createForRenderer(e, getIndex());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -280,7 +285,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||
outputBuffer.release();
|
||||
outputBuffer = null;
|
||||
outputStreamEnded = true;
|
||||
audioTrack.handleEndOfStream();
|
||||
audioTrack.playToEndOfStream();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -388,7 +393,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||
|
||||
@Override
|
||||
public boolean isEnded() {
|
||||
return outputStreamEnded && !audioTrack.hasPendingData();
|
||||
return outputStreamEnded && audioTrack.isEnded();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -480,6 +480,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||
@Override
|
||||
public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
|
||||
if (outputStreamEnded) {
|
||||
renderToEndOfStream();
|
||||
return;
|
||||
}
|
||||
if (format == null) {
|
||||
|
|
@ -787,16 +788,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||
// Do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* <p>
|
||||
|
|
@ -1010,6 +1001,17 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||
MediaCodec codec, ByteBuffer buffer, int bufferIndex, int bufferFlags,
|
||||
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.
|
||||
*
|
||||
|
|
@ -1022,7 +1024,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||
maybeInitCodec();
|
||||
} else {
|
||||
outputStreamEnded = true;
|
||||
onOutputStreamEnded();
|
||||
renderToEndOfStream();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue