mirror of
https://github.com/samsonjs/media.git
synced 2026-04-05 11:15:46 +00:00
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
This commit is contained in:
parent
7ea16a6ae2
commit
e0649db513
2 changed files with 150 additions and 71 deletions
|
|
@ -19,6 +19,7 @@ import android.media.PlaybackParams;
|
|||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import android.support.annotation.IntDef;
|
||||
import com.google.android.exoplayer2.BaseRenderer;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
|
|
@ -36,6 +37,8 @@ import com.google.android.exoplayer2.util.MediaClock;
|
|||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import com.google.android.exoplayer2.util.TraceUtil;
|
||||
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}.
|
||||
|
|
@ -43,6 +46,27 @@ import com.google.android.exoplayer2.util.Util;
|
|||
public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements MediaClock,
|
||||
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 EventDispatcher eventDispatcher;
|
||||
|
|
@ -59,6 +83,11 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||
private DrmSession<ExoMediaCrypto> drmSession;
|
||||
private DrmSession<ExoMediaCrypto> pendingDrmSession;
|
||||
|
||||
@ReinitializationState
|
||||
private int decoderReinitializationState;
|
||||
private boolean decoderReceivedBuffers;
|
||||
private boolean audioTrackNeedsConfigure;
|
||||
|
||||
private long currentPositionUs;
|
||||
private boolean allowPositionDiscontinuity;
|
||||
private boolean inputStreamEnded;
|
||||
|
|
@ -117,6 +146,8 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||
formatHolder = new FormatHolder();
|
||||
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
|
||||
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
|
||||
decoderReinitializationState = REINITIALIZATION_STATE_NONE;
|
||||
audioTrackNeedsConfigure = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -136,47 +167,22 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||
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 (decoder == null) {
|
||||
maybeInitDecoder();
|
||||
|
||||
if (decoder != null) {
|
||||
try {
|
||||
long codecInitializingTimestamp = SystemClock.elapsedRealtime();
|
||||
TraceUtil.beginSection("createAudioDecoder");
|
||||
decoder = createDecoder(inputFormat, mediaCrypto);
|
||||
// Rendering loop.
|
||||
TraceUtil.beginSection("drainAndFeed");
|
||||
while (drainOutputBuffer()) {}
|
||||
while (feedInputBuffer()) {}
|
||||
TraceUtil.endSection();
|
||||
long codecInitializedTimestamp = SystemClock.elapsedRealtime();
|
||||
eventDispatcher.decoderInitialized(decoder.getName(), codecInitializedTimestamp,
|
||||
codecInitializedTimestamp - codecInitializingTimestamp);
|
||||
decoderCounters.decoderInitCount++;
|
||||
} catch (AudioDecoderException e) {
|
||||
} catch (AudioTrack.InitializationException | AudioTrack.WriteException
|
||||
| AudioDecoderException e) {
|
||||
throw ExoPlaybackException.createForRenderer(e, getIndex());
|
||||
}
|
||||
decoderCounters.ensureUpdated();
|
||||
}
|
||||
|
||||
// Rendering loop.
|
||||
try {
|
||||
TraceUtil.beginSection("drainAndFeed");
|
||||
while (drainOutputBuffer()) {}
|
||||
while (feedInputBuffer()) {}
|
||||
TraceUtil.endSection();
|
||||
} catch (AudioTrack.InitializationException | AudioTrack.WriteException
|
||||
| AudioDecoderException e) {
|
||||
throw ExoPlaybackException.createForRenderer(e, getIndex());
|
||||
}
|
||||
decoderCounters.ensureUpdated();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -205,12 +211,8 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||
null, null, 0, null);
|
||||
}
|
||||
|
||||
private boolean drainOutputBuffer() throws AudioDecoderException,
|
||||
private boolean drainOutputBuffer() throws ExoPlaybackException, AudioDecoderException,
|
||||
AudioTrack.InitializationException, AudioTrack.WriteException {
|
||||
if (outputStreamEnded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (outputBuffer == null) {
|
||||
outputBuffer = decoder.dequeueOutputBuffer();
|
||||
if (outputBuffer == null) {
|
||||
|
|
@ -220,17 +222,29 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||
}
|
||||
|
||||
if (outputBuffer.isEndOfStream()) {
|
||||
outputStreamEnded = true;
|
||||
audioTrack.handleEndOfStream();
|
||||
outputBuffer.release();
|
||||
outputBuffer = null;
|
||||
if (decoderReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) {
|
||||
// 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 = null;
|
||||
outputStreamEnded = true;
|
||||
audioTrack.handleEndOfStream();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!audioTrack.isInitialized()) {
|
||||
if (audioTrackNeedsConfigure) {
|
||||
Format outputFormat = getOutputFormat();
|
||||
audioTrack.configure(outputFormat.sampleMimeType, outputFormat.channelCount,
|
||||
outputFormat.sampleRate, outputFormat.pcmEncoding, 0);
|
||||
audioTrackNeedsConfigure = false;
|
||||
}
|
||||
|
||||
if (!audioTrack.isInitialized()) {
|
||||
if (audioSessionId == AudioTrack.SESSION_ID_NOT_SET) {
|
||||
audioSessionId = audioTrack.initialize(AudioTrack.SESSION_ID_NOT_SET);
|
||||
eventDispatcher.audioSessionId(audioSessionId);
|
||||
|
|
@ -262,7 +276,9 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
if (waitingForKeys) {
|
||||
// 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
|
|||
}
|
||||
inputBuffer.flip();
|
||||
decoder.queueInputBuffer(inputBuffer);
|
||||
decoderReceivedBuffers = true;
|
||||
decoderCounters.inputBufferCount++;
|
||||
inputBuffer = null;
|
||||
return true;
|
||||
|
|
@ -318,14 +343,20 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||
&& (bufferEncrypted || !playClearSamplesWithoutKeys);
|
||||
}
|
||||
|
||||
private void flushDecoder() {
|
||||
inputBuffer = null;
|
||||
private void flushDecoder() throws ExoPlaybackException {
|
||||
waitingForKeys = false;
|
||||
if (outputBuffer != null) {
|
||||
outputBuffer.release();
|
||||
outputBuffer = null;
|
||||
if (decoderReinitializationState != REINITIALIZATION_STATE_NONE) {
|
||||
releaseDecoder();
|
||||
maybeInitDecoder();
|
||||
} else {
|
||||
inputBuffer = null;
|
||||
if (outputBuffer != null) {
|
||||
outputBuffer.release();
|
||||
outputBuffer = null;
|
||||
}
|
||||
decoder.flush();
|
||||
decoderReceivedBuffers = false;
|
||||
}
|
||||
decoder.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -370,7 +401,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void onPositionReset(long positionUs, boolean joining) {
|
||||
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
|
||||
audioTrack.reset();
|
||||
currentPositionUs = positionUs;
|
||||
allowPositionDiscontinuity = true;
|
||||
|
|
@ -393,17 +424,12 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||
|
||||
@Override
|
||||
protected void onDisabled() {
|
||||
inputBuffer = null;
|
||||
outputBuffer = null;
|
||||
inputFormat = null;
|
||||
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
|
||||
audioTrackNeedsConfigure = true;
|
||||
waitingForKeys = false;
|
||||
try {
|
||||
if (decoder != null) {
|
||||
decoder.release();
|
||||
decoder = null;
|
||||
decoderCounters.decoderReleaseCount++;
|
||||
}
|
||||
releaseDecoder();
|
||||
audioTrack.release();
|
||||
} finally {
|
||||
try {
|
||||
|
|
@ -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 {
|
||||
int result = readSource(formatHolder, null);
|
||||
if (result == C.RESULT_FORMAT_READ) {
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -472,6 +472,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||
|
||||
@Override
|
||||
public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
|
||||
if (outputStreamEnded) {
|
||||
return;
|
||||
}
|
||||
if (format == null) {
|
||||
readFormat();
|
||||
}
|
||||
|
|
@ -479,9 +482,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||
if (codec != null) {
|
||||
TraceUtil.beginSection("drainAndFeed");
|
||||
while (drainOutputBuffer(positionUs, elapsedRealtimeUs)) {}
|
||||
if (codec != null) {
|
||||
while (feedInputBuffer()) {}
|
||||
}
|
||||
while (feedInputBuffer()) {}
|
||||
TraceUtil.endSection();
|
||||
} else if (format != null) {
|
||||
skipToKeyframeBefore(positionUs);
|
||||
|
|
@ -531,10 +532,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||
* @throws ExoPlaybackException If an error occurs feeding the input buffer.
|
||||
*/
|
||||
private boolean feedInputBuffer() throws ExoPlaybackException {
|
||||
if (inputStreamEnded
|
||||
|| codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) {
|
||||
// The input stream has ended, or we need to re-initialize the codec but are still waiting
|
||||
// for the existing codec to output any final output buffers.
|
||||
if (codec == null || codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM
|
||||
|| inputStreamEnded) {
|
||||
// We need to reinitialize the codec or the input stream has ended.
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -848,10 +848,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||
@SuppressWarnings("deprecation")
|
||||
private boolean drainOutputBuffer(long positionUs, long elapsedRealtimeUs)
|
||||
throws ExoPlaybackException {
|
||||
if (outputStreamEnded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (outputIndex < 0) {
|
||||
outputIndex = codec.dequeueOutputBuffer(outputBufferInfo, getDequeueOutputBufferTimeoutUs());
|
||||
if (outputIndex >= 0) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue