Commit e0649db5 by andrewlewis Committed by Oliver Woodman

Handle input format changes in SimpleDecoderAudioRenderer.

This is implemented in the same way as in MediaCodecRenderer.

Move codec != null guard into feedInputBuffer to handle the case of an input
format change with no dequeued buffers where the codec can't be reinitialized
immediately.

Issue: #2111

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=140864942
parent 7ea16a6a
...@@ -19,6 +19,7 @@ import android.media.PlaybackParams; ...@@ -19,6 +19,7 @@ import android.media.PlaybackParams;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.SystemClock; import android.os.SystemClock;
import android.support.annotation.IntDef;
import com.google.android.exoplayer2.BaseRenderer; import com.google.android.exoplayer2.BaseRenderer;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
...@@ -36,6 +37,8 @@ import com.google.android.exoplayer2.util.MediaClock; ...@@ -36,6 +37,8 @@ import com.google.android.exoplayer2.util.MediaClock;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.TraceUtil;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** /**
* Decodes and renders audio using a {@link SimpleDecoder}. * Decodes and renders audio using a {@link SimpleDecoder}.
...@@ -43,6 +46,27 @@ import com.google.android.exoplayer2.util.Util; ...@@ -43,6 +46,27 @@ import com.google.android.exoplayer2.util.Util;
public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements MediaClock, public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements MediaClock,
AudioTrack.Listener { AudioTrack.Listener {
@Retention(RetentionPolicy.SOURCE)
@IntDef({REINITIALIZATION_STATE_NONE, REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM,
REINITIALIZATION_STATE_WAIT_END_OF_STREAM})
private @interface ReinitializationState {}
/**
* The decoder does not need to be re-initialized.
*/
private static final int REINITIALIZATION_STATE_NONE = 0;
/**
* The input format has changed in a way that requires the decoder to be re-initialized, but we
* haven't yet signaled an end of stream to the existing decoder. We need to do so in order to
* ensure that it outputs any remaining buffers before we release it.
*/
private static final int REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM = 1;
/**
* The input format has changed in a way that requires the decoder to be re-initialized, and we've
* signaled an end of stream to the existing decoder. We're waiting for the decoder to output an
* end of stream signal to indicate that it has output any remaining buffers before we release it.
*/
private static final int REINITIALIZATION_STATE_WAIT_END_OF_STREAM = 2;
private final boolean playClearSamplesWithoutKeys; private final boolean playClearSamplesWithoutKeys;
private final EventDispatcher eventDispatcher; private final EventDispatcher eventDispatcher;
...@@ -59,6 +83,11 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -59,6 +83,11 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
private DrmSession<ExoMediaCrypto> drmSession; private DrmSession<ExoMediaCrypto> drmSession;
private DrmSession<ExoMediaCrypto> pendingDrmSession; private DrmSession<ExoMediaCrypto> pendingDrmSession;
@ReinitializationState
private int decoderReinitializationState;
private boolean decoderReceivedBuffers;
private boolean audioTrackNeedsConfigure;
private long currentPositionUs; private long currentPositionUs;
private boolean allowPositionDiscontinuity; private boolean allowPositionDiscontinuity;
private boolean inputStreamEnded; private boolean inputStreamEnded;
...@@ -117,6 +146,8 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -117,6 +146,8 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
formatHolder = new FormatHolder(); formatHolder = new FormatHolder();
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys; this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
audioSessionId = AudioTrack.SESSION_ID_NOT_SET; audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
decoderReinitializationState = REINITIALIZATION_STATE_NONE;
audioTrackNeedsConfigure = true;
} }
@Override @Override
...@@ -136,38 +167,12 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -136,38 +167,12 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
return; return;
} }
drmSession = pendingDrmSession;
ExoMediaCrypto mediaCrypto = null;
if (drmSession != null) {
@DrmSession.State int drmSessionState = drmSession.getState();
if (drmSessionState == DrmSession.STATE_ERROR) {
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
} else if (drmSessionState == DrmSession.STATE_OPENED
|| drmSessionState == DrmSession.STATE_OPENED_WITH_KEYS) {
mediaCrypto = drmSession.getMediaCrypto();
} else {
// The drm session isn't open yet.
return;
}
}
// If we don't have a decoder yet, we need to instantiate one. // If we don't have a decoder yet, we need to instantiate one.
if (decoder == null) { maybeInitDecoder();
try {
long codecInitializingTimestamp = SystemClock.elapsedRealtime();
TraceUtil.beginSection("createAudioDecoder");
decoder = createDecoder(inputFormat, mediaCrypto);
TraceUtil.endSection();
long codecInitializedTimestamp = SystemClock.elapsedRealtime();
eventDispatcher.decoderInitialized(decoder.getName(), codecInitializedTimestamp,
codecInitializedTimestamp - codecInitializingTimestamp);
decoderCounters.decoderInitCount++;
} catch (AudioDecoderException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
}
// Rendering loop. if (decoder != null) {
try { try {
// Rendering loop.
TraceUtil.beginSection("drainAndFeed"); TraceUtil.beginSection("drainAndFeed");
while (drainOutputBuffer()) {} while (drainOutputBuffer()) {}
while (feedInputBuffer()) {} while (feedInputBuffer()) {}
...@@ -178,6 +183,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -178,6 +183,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
} }
decoderCounters.ensureUpdated(); decoderCounters.ensureUpdated();
} }
}
/** /**
* Creates a decoder for the given format. * Creates a decoder for the given format.
...@@ -205,12 +211,8 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -205,12 +211,8 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
null, null, 0, null); null, null, 0, null);
} }
private boolean drainOutputBuffer() throws AudioDecoderException, private boolean drainOutputBuffer() throws ExoPlaybackException, AudioDecoderException,
AudioTrack.InitializationException, AudioTrack.WriteException { AudioTrack.InitializationException, AudioTrack.WriteException {
if (outputStreamEnded) {
return false;
}
if (outputBuffer == null) { if (outputBuffer == null) {
outputBuffer = decoder.dequeueOutputBuffer(); outputBuffer = decoder.dequeueOutputBuffer();
if (outputBuffer == null) { if (outputBuffer == null) {
...@@ -220,17 +222,29 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -220,17 +222,29 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
} }
if (outputBuffer.isEndOfStream()) { if (outputBuffer.isEndOfStream()) {
outputStreamEnded = true; if (decoderReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) {
audioTrack.handleEndOfStream(); // We're waiting to re-initialize the decoder, and have now processed all final buffers.
releaseDecoder();
maybeInitDecoder();
// The audio track may need to be recreated once the new output format is known.
audioTrackNeedsConfigure = true;
} else {
outputBuffer.release(); outputBuffer.release();
outputBuffer = null; outputBuffer = null;
outputStreamEnded = true;
audioTrack.handleEndOfStream();
}
return false; return false;
} }
if (!audioTrack.isInitialized()) { if (audioTrackNeedsConfigure) {
Format outputFormat = getOutputFormat(); Format outputFormat = getOutputFormat();
audioTrack.configure(outputFormat.sampleMimeType, outputFormat.channelCount, audioTrack.configure(outputFormat.sampleMimeType, outputFormat.channelCount,
outputFormat.sampleRate, outputFormat.pcmEncoding, 0); outputFormat.sampleRate, outputFormat.pcmEncoding, 0);
audioTrackNeedsConfigure = false;
}
if (!audioTrack.isInitialized()) {
if (audioSessionId == AudioTrack.SESSION_ID_NOT_SET) { if (audioSessionId == AudioTrack.SESSION_ID_NOT_SET) {
audioSessionId = audioTrack.initialize(AudioTrack.SESSION_ID_NOT_SET); audioSessionId = audioTrack.initialize(AudioTrack.SESSION_ID_NOT_SET);
eventDispatcher.audioSessionId(audioSessionId); eventDispatcher.audioSessionId(audioSessionId);
...@@ -262,7 +276,9 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -262,7 +276,9 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
} }
private boolean feedInputBuffer() throws AudioDecoderException, ExoPlaybackException { private boolean feedInputBuffer() throws AudioDecoderException, ExoPlaybackException {
if (inputStreamEnded) { if (decoder == null || decoderReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM
|| inputStreamEnded) {
// We need to reinitialize the decoder or the input stream has ended.
return false; return false;
} }
...@@ -273,6 +289,14 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -273,6 +289,14 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
} }
} }
if (decoderReinitializationState == REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM) {
inputBuffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
decoder.queueInputBuffer(inputBuffer);
inputBuffer = null;
decoderReinitializationState = REINITIALIZATION_STATE_WAIT_END_OF_STREAM;
return false;
}
int result; int result;
if (waitingForKeys) { if (waitingForKeys) {
// We've already read an encrypted sample into buffer, and are waiting for keys. // We've already read an encrypted sample into buffer, and are waiting for keys.
...@@ -301,6 +325,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -301,6 +325,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
} }
inputBuffer.flip(); inputBuffer.flip();
decoder.queueInputBuffer(inputBuffer); decoder.queueInputBuffer(inputBuffer);
decoderReceivedBuffers = true;
decoderCounters.inputBufferCount++; decoderCounters.inputBufferCount++;
inputBuffer = null; inputBuffer = null;
return true; return true;
...@@ -318,14 +343,20 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -318,14 +343,20 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
&& (bufferEncrypted || !playClearSamplesWithoutKeys); && (bufferEncrypted || !playClearSamplesWithoutKeys);
} }
private void flushDecoder() { private void flushDecoder() throws ExoPlaybackException {
inputBuffer = null;
waitingForKeys = false; waitingForKeys = false;
if (decoderReinitializationState != REINITIALIZATION_STATE_NONE) {
releaseDecoder();
maybeInitDecoder();
} else {
inputBuffer = null;
if (outputBuffer != null) { if (outputBuffer != null) {
outputBuffer.release(); outputBuffer.release();
outputBuffer = null; outputBuffer = null;
} }
decoder.flush(); decoder.flush();
decoderReceivedBuffers = false;
}
} }
@Override @Override
...@@ -370,7 +401,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -370,7 +401,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
} }
@Override @Override
protected void onPositionReset(long positionUs, boolean joining) { protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
audioTrack.reset(); audioTrack.reset();
currentPositionUs = positionUs; currentPositionUs = positionUs;
allowPositionDiscontinuity = true; allowPositionDiscontinuity = true;
...@@ -393,17 +424,12 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -393,17 +424,12 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
@Override @Override
protected void onDisabled() { protected void onDisabled() {
inputBuffer = null;
outputBuffer = null;
inputFormat = null; inputFormat = null;
audioSessionId = AudioTrack.SESSION_ID_NOT_SET; audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
audioTrackNeedsConfigure = true;
waitingForKeys = false; waitingForKeys = false;
try { try {
if (decoder != null) { releaseDecoder();
decoder.release();
decoder = null;
decoderCounters.decoderReleaseCount++;
}
audioTrack.release(); audioTrack.release();
} finally { } finally {
try { try {
...@@ -425,6 +451,54 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -425,6 +451,54 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
} }
} }
private void maybeInitDecoder() throws ExoPlaybackException {
if (decoder != null) {
return;
}
drmSession = pendingDrmSession;
ExoMediaCrypto mediaCrypto = null;
if (drmSession != null) {
@DrmSession.State int drmSessionState = drmSession.getState();
if (drmSessionState == DrmSession.STATE_ERROR) {
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
} else if (drmSessionState == DrmSession.STATE_OPENED
|| drmSessionState == DrmSession.STATE_OPENED_WITH_KEYS) {
mediaCrypto = drmSession.getMediaCrypto();
} else {
// The drm session isn't open yet.
return;
}
}
try {
long codecInitializingTimestamp = SystemClock.elapsedRealtime();
TraceUtil.beginSection("createAudioDecoder");
decoder = createDecoder(inputFormat, mediaCrypto);
TraceUtil.endSection();
long codecInitializedTimestamp = SystemClock.elapsedRealtime();
eventDispatcher.decoderInitialized(decoder.getName(), codecInitializedTimestamp,
codecInitializedTimestamp - codecInitializingTimestamp);
decoderCounters.decoderInitCount++;
} catch (AudioDecoderException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
}
private void releaseDecoder() {
if (decoder == null) {
return;
}
inputBuffer = null;
outputBuffer = null;
decoder.release();
decoder = null;
decoderCounters.decoderReleaseCount++;
decoderReinitializationState = REINITIALIZATION_STATE_NONE;
decoderReceivedBuffers = false;
}
private boolean readFormat() throws ExoPlaybackException { private boolean readFormat() throws ExoPlaybackException {
int result = readSource(formatHolder, null); int result = readSource(formatHolder, null);
if (result == C.RESULT_FORMAT_READ) { if (result == C.RESULT_FORMAT_READ) {
...@@ -456,6 +530,15 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -456,6 +530,15 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
} }
} }
if (decoderReceivedBuffers) {
// Signal end of stream and wait for any final output buffers before re-initialization.
decoderReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM;
} else {
// There aren't any final output buffers, so release the decoder immediately.
releaseDecoder();
maybeInitDecoder();
}
eventDispatcher.inputFormatChanged(newFormat); eventDispatcher.inputFormatChanged(newFormat);
} }
......
...@@ -472,6 +472,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -472,6 +472,9 @@ 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) {
return;
}
if (format == null) { if (format == null) {
readFormat(); readFormat();
} }
...@@ -479,9 +482,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -479,9 +482,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
if (codec != null) { if (codec != null) {
TraceUtil.beginSection("drainAndFeed"); TraceUtil.beginSection("drainAndFeed");
while (drainOutputBuffer(positionUs, elapsedRealtimeUs)) {} while (drainOutputBuffer(positionUs, elapsedRealtimeUs)) {}
if (codec != null) {
while (feedInputBuffer()) {} while (feedInputBuffer()) {}
}
TraceUtil.endSection(); TraceUtil.endSection();
} else if (format != null) { } else if (format != null) {
skipToKeyframeBefore(positionUs); skipToKeyframeBefore(positionUs);
...@@ -531,10 +532,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -531,10 +532,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
* @throws ExoPlaybackException If an error occurs feeding the input buffer. * @throws ExoPlaybackException If an error occurs feeding the input buffer.
*/ */
private boolean feedInputBuffer() throws ExoPlaybackException { private boolean feedInputBuffer() throws ExoPlaybackException {
if (inputStreamEnded if (codec == null || codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM
|| codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) { || inputStreamEnded) {
// The input stream has ended, or we need to re-initialize the codec but are still waiting // We need to reinitialize the codec or the input stream has ended.
// for the existing codec to output any final output buffers.
return false; return false;
} }
...@@ -848,10 +848,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -848,10 +848,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
private boolean drainOutputBuffer(long positionUs, long elapsedRealtimeUs) private boolean drainOutputBuffer(long positionUs, long elapsedRealtimeUs)
throws ExoPlaybackException { throws ExoPlaybackException {
if (outputStreamEnded) {
return false;
}
if (outputIndex < 0) { if (outputIndex < 0) {
outputIndex = codec.dequeueOutputBuffer(outputBufferInfo, getDequeueOutputBufferTimeoutUs()); outputIndex = codec.dequeueOutputBuffer(outputBufferInfo, getDequeueOutputBufferTimeoutUs());
if (outputIndex >= 0) { if (outputIndex >= 0) {
......
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