From 96e490d7fef3b5dee8cdcefb36cf35cf939eee78 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 9 Jan 2018 08:04:36 -0800 Subject: [PATCH] Make it possible to subclass LibvpxVideoRenderer Make LibvpxVideoRenderer non-final and add protected methods to match MediaCodecVideoRenderer. Reorganize methods to separate BaseRenderer, protected and internal methods. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=181320714 --- .../ext/vp9/LibvpxVideoRenderer.java | 740 ++++++++++-------- 1 file changed, 435 insertions(+), 305 deletions(-) diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java index ac944a7b01..108a89f56a 100644 --- a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java +++ b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java @@ -20,7 +20,9 @@ import android.graphics.Canvas; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; +import android.support.annotation.CallSuper; import android.support.annotation.IntDef; +import android.support.annotation.Nullable; import android.view.Surface; import com.google.android.exoplayer2.BaseRenderer; import com.google.android.exoplayer2.C; @@ -43,10 +45,8 @@ import com.google.android.exoplayer2.video.VideoRendererEventListener.EventDispa import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -/** - * Decodes and renders video using the native VP9 decoder. - */ -public final class LibvpxVideoRenderer extends BaseRenderer { +/** Decodes and renders video using the native VP9 decoder. */ +public class LibvpxVideoRenderer extends BaseRenderer { @Retention(RetentionPolicy.SOURCE) @IntDef({REINITIALIZATION_STATE_NONE, REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM, @@ -101,7 +101,6 @@ public final class LibvpxVideoRenderer extends BaseRenderer { private final DecoderInputBuffer flagsOnlyBuffer; private final DrmSessionManager drmSessionManager; - private DecoderCounters decoderCounters; private Format format; private VpxDecoder decoder; private VpxInputBuffer inputBuffer; @@ -132,6 +131,8 @@ public final class LibvpxVideoRenderer extends BaseRenderer { private int consecutiveDroppedFrameCount; private int buffersInCodecCount; + protected DecoderCounters decoderCounters; + /** * @param scaleToFit Whether video frames should be scaled to fit when rendering. * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer @@ -196,6 +197,8 @@ public final class LibvpxVideoRenderer extends BaseRenderer { decoderReinitializationState = REINITIALIZATION_STATE_NONE; } + // BaseRenderer implementation. + @Override public int supportsFormat(Format format) { if (!VpxLibrary.isAvailable() || !MimeTypes.VIDEO_VP9.equalsIgnoreCase(format.sampleMimeType)) { @@ -247,273 +250,6 @@ public final class LibvpxVideoRenderer extends BaseRenderer { } } - private boolean drainOutputBuffer(long positionUs) throws ExoPlaybackException, - VpxDecoderException { - // Acquire outputBuffer either from nextOutputBuffer or from the decoder. - if (outputBuffer == null) { - if (nextOutputBuffer != null) { - outputBuffer = nextOutputBuffer; - nextOutputBuffer = null; - } else { - outputBuffer = decoder.dequeueOutputBuffer(); - } - if (outputBuffer == null) { - return false; - } - decoderCounters.skippedOutputBufferCount += outputBuffer.skippedOutputBufferCount; - buffersInCodecCount -= outputBuffer.skippedOutputBufferCount; - } - - if (nextOutputBuffer == null) { - nextOutputBuffer = decoder.dequeueOutputBuffer(); - } - - if (outputBuffer.isEndOfStream()) { - 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(); - } else { - outputBuffer.release(); - outputBuffer = null; - outputStreamEnded = true; - } - return false; - } - - if (outputMode == VpxDecoder.OUTPUT_MODE_NONE) { - // Skip frames in sync with playback, so we'll be at the right frame if the mode changes. - if (isBufferLate(outputBuffer.timeUs - positionUs)) { - forceRenderFrame = false; - skipBuffer(); - buffersInCodecCount--; - return true; - } - return false; - } - - if (forceRenderFrame) { - forceRenderFrame = false; - renderBuffer(); - buffersInCodecCount--; - return true; - } - - final long nextOutputBufferTimeUs = - nextOutputBuffer != null && !nextOutputBuffer.isEndOfStream() - ? nextOutputBuffer.timeUs : C.TIME_UNSET; - - long earlyUs = outputBuffer.timeUs - positionUs; - if (shouldDropBuffersToKeyframe(earlyUs) && maybeDropBuffersToKeyframe(positionUs)) { - forceRenderFrame = true; - return false; - } else if (shouldDropOutputBuffer( - outputBuffer.timeUs, nextOutputBufferTimeUs, positionUs, joiningDeadlineMs)) { - dropBuffer(); - buffersInCodecCount--; - return true; - } - - // If we have yet to render a frame to the current output (either initially or immediately - // following a seek), render one irrespective of the state or current position. - if (!renderedFirstFrame - || (getState() == STATE_STARTED && earlyUs <= 30000)) { - renderBuffer(); - buffersInCodecCount--; - } - return false; - } - - /** - * Returns whether the current frame should be dropped. - * - * @param outputBufferTimeUs The timestamp of the current output buffer. - * @param nextOutputBufferTimeUs The timestamp of the next output buffer or {@link C#TIME_UNSET} - * if the next output buffer is unavailable. - * @param positionUs The current playback position. - * @param joiningDeadlineMs The joining deadline. - * @return Returns whether to drop the current output buffer. - */ - private boolean shouldDropOutputBuffer(long outputBufferTimeUs, long nextOutputBufferTimeUs, - long positionUs, long joiningDeadlineMs) { - return isBufferLate(outputBufferTimeUs - positionUs) - && (joiningDeadlineMs != C.TIME_UNSET || nextOutputBufferTimeUs != C.TIME_UNSET); - } - - /** - * Returns whether to drop all buffers from the buffer being processed to the keyframe at or after - * the current playback position, if possible. - * - * @param earlyUs The time until the current buffer should be presented in microseconds. A - * negative value indicates that the buffer is late. - */ - private boolean shouldDropBuffersToKeyframe(long earlyUs) { - return isBufferVeryLate(earlyUs); - } - - private void renderBuffer() { - int bufferMode = outputBuffer.mode; - boolean renderRgb = bufferMode == VpxDecoder.OUTPUT_MODE_RGB && surface != null; - boolean renderYuv = bufferMode == VpxDecoder.OUTPUT_MODE_YUV && outputBufferRenderer != null; - if (!renderRgb && !renderYuv) { - dropBuffer(); - } else { - maybeNotifyVideoSizeChanged(outputBuffer.width, outputBuffer.height); - if (renderRgb) { - renderRgbFrame(outputBuffer, scaleToFit); - outputBuffer.release(); - } else /* renderYuv */ { - outputBufferRenderer.setOutputBuffer(outputBuffer); - // The renderer will release the buffer. - } - outputBuffer = null; - consecutiveDroppedFrameCount = 0; - decoderCounters.renderedOutputBufferCount++; - maybeNotifyRenderedFirstFrame(); - } - } - - private void dropBuffer() { - updateDroppedBufferCounters(1); - outputBuffer.release(); - outputBuffer = null; - } - - private boolean maybeDropBuffersToKeyframe(long positionUs) throws ExoPlaybackException { - int droppedSourceBufferCount = skipSource(positionUs); - if (droppedSourceBufferCount == 0) { - return false; - } - decoderCounters.droppedToKeyframeCount++; - // We dropped some buffers to catch up, so update the decoder counters and flush the codec, - // which releases all pending buffers buffers including the current output buffer. - updateDroppedBufferCounters(buffersInCodecCount + droppedSourceBufferCount); - flushDecoder(); - return true; - } - - private void updateDroppedBufferCounters(int droppedBufferCount) { - decoderCounters.droppedBufferCount += droppedBufferCount; - droppedFrames += droppedBufferCount; - consecutiveDroppedFrameCount += droppedBufferCount; - decoderCounters.maxConsecutiveDroppedBufferCount = Math.max(consecutiveDroppedFrameCount, - decoderCounters.maxConsecutiveDroppedBufferCount); - if (droppedFrames >= maxDroppedFramesToNotify) { - maybeNotifyDroppedFrames(); - } - } - - private void skipBuffer() { - decoderCounters.skippedOutputBufferCount++; - outputBuffer.release(); - outputBuffer = null; - } - - private void renderRgbFrame(VpxOutputBuffer outputBuffer, boolean scale) { - if (bitmap == null || bitmap.getWidth() != outputBuffer.width - || bitmap.getHeight() != outputBuffer.height) { - bitmap = Bitmap.createBitmap(outputBuffer.width, outputBuffer.height, Bitmap.Config.RGB_565); - } - bitmap.copyPixelsFromBuffer(outputBuffer.data); - Canvas canvas = surface.lockCanvas(null); - if (scale) { - canvas.scale(((float) canvas.getWidth()) / outputBuffer.width, - ((float) canvas.getHeight()) / outputBuffer.height); - } - canvas.drawBitmap(bitmap, 0, 0, null); - surface.unlockCanvasAndPost(canvas); - } - - private boolean feedInputBuffer() throws VpxDecoderException, ExoPlaybackException { - 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; - } - - if (inputBuffer == null) { - inputBuffer = decoder.dequeueInputBuffer(); - if (inputBuffer == null) { - return false; - } - } - - 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. - result = C.RESULT_BUFFER_READ; - } else { - result = readSource(formatHolder, inputBuffer, false); - } - - if (result == C.RESULT_NOTHING_READ) { - return false; - } - if (result == C.RESULT_FORMAT_READ) { - onInputFormatChanged(formatHolder.format); - return true; - } - if (inputBuffer.isEndOfStream()) { - inputStreamEnded = true; - decoder.queueInputBuffer(inputBuffer); - inputBuffer = null; - return false; - } - boolean bufferEncrypted = inputBuffer.isEncrypted(); - waitingForKeys = shouldWaitForKeys(bufferEncrypted); - if (waitingForKeys) { - return false; - } - inputBuffer.flip(); - inputBuffer.colorInfo = formatHolder.format.colorInfo; - decoder.queueInputBuffer(inputBuffer); - buffersInCodecCount++; - decoderReceivedBuffers = true; - decoderCounters.inputBufferCount++; - inputBuffer = null; - return true; - } - - private boolean shouldWaitForKeys(boolean bufferEncrypted) throws ExoPlaybackException { - if (drmSession == null || (!bufferEncrypted && playClearSamplesWithoutKeys)) { - return false; - } - @DrmSession.State int drmSessionState = drmSession.getState(); - if (drmSessionState == DrmSession.STATE_ERROR) { - throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex()); - } - return drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS; - } - - private void flushDecoder() throws ExoPlaybackException { - waitingForKeys = false; - forceRenderFrame = false; - buffersInCodecCount = 0; - if (decoderReinitializationState != REINITIALIZATION_STATE_NONE) { - releaseDecoder(); - maybeInitDecoder(); - } else { - inputBuffer = null; - if (outputBuffer != null) { - outputBuffer.release(); - outputBuffer = null; - } - if (nextOutputBuffer != null) { - nextOutputBuffer.release(); - nextOutputBuffer = null; - } - decoder.flush(); - decoderReceivedBuffers = false; - } - } @Override public boolean isEnded() { @@ -605,42 +341,53 @@ public final class LibvpxVideoRenderer extends BaseRenderer { } } - private void maybeInitDecoder() throws ExoPlaybackException { - if (decoder != null) { - return; - } + /** + * Called when a decoder has been created and configured. + * + *

The default implementation is a no-op. + * + * @param name The name of the decoder that was initialized. + * @param initializedTimestampMs {@link SystemClock#elapsedRealtime()} when initialization + * finished. + * @param initializationDurationMs The time taken to initialize the decoder, in milliseconds. + */ + @CallSuper + protected void onDecoderInitialized( + String name, long initializedTimestampMs, long initializationDurationMs) { + eventDispatcher.decoderInitialized(name, initializedTimestampMs, initializationDurationMs); + } - drmSession = pendingDrmSession; - ExoMediaCrypto mediaCrypto = null; - if (drmSession != null) { - mediaCrypto = drmSession.getMediaCrypto(); - if (mediaCrypto == null) { - DrmSessionException drmError = drmSession.getError(); - if (drmError != null) { - throw ExoPlaybackException.createForRenderer(drmError, getIndex()); - } - // The drm session isn't open yet. - return; + /** + * Flushes the decoder. + * + * @throws ExoPlaybackException If an error occurs reinitializing a decoder. + */ + @CallSuper + protected void flushDecoder() throws ExoPlaybackException { + waitingForKeys = false; + forceRenderFrame = false; + buffersInCodecCount = 0; + if (decoderReinitializationState != REINITIALIZATION_STATE_NONE) { + releaseDecoder(); + maybeInitDecoder(); + } else { + inputBuffer = null; + if (outputBuffer != null) { + outputBuffer.release(); + outputBuffer = null; } - } - - try { - long codecInitializingTimestamp = SystemClock.elapsedRealtime(); - TraceUtil.beginSection("createVpxDecoder"); - decoder = new VpxDecoder(NUM_INPUT_BUFFERS, NUM_OUTPUT_BUFFERS, INITIAL_INPUT_BUFFER_SIZE, - mediaCrypto, disableLoopFilter); - decoder.setOutputMode(outputMode); - TraceUtil.endSection(); - long codecInitializedTimestamp = SystemClock.elapsedRealtime(); - eventDispatcher.decoderInitialized(decoder.getName(), codecInitializedTimestamp, - codecInitializedTimestamp - codecInitializingTimestamp); - decoderCounters.decoderInitCount++; - } catch (VpxDecoderException e) { - throw ExoPlaybackException.createForRenderer(e, getIndex()); + if (nextOutputBuffer != null) { + nextOutputBuffer.release(); + nextOutputBuffer = null; + } + decoder.flush(); + decoderReceivedBuffers = false; } } - private void releaseDecoder() { + /** Releases the decoder. */ + @CallSuper + protected void releaseDecoder() { if (decoder == null) { return; } @@ -657,7 +404,14 @@ public final class LibvpxVideoRenderer extends BaseRenderer { buffersInCodecCount = 0; } - private void onInputFormatChanged(Format newFormat) throws ExoPlaybackException { + /** + * Called when a new format is read from the upstream source. + * + * @param newFormat The new format. + * @throws ExoPlaybackException If an error occurs (re-)initializing the decoder. + */ + @CallSuper + protected void onInputFormatChanged(Format newFormat) throws ExoPlaybackException { Format oldFormat = format; format = newFormat; @@ -692,6 +446,147 @@ public final class LibvpxVideoRenderer extends BaseRenderer { eventDispatcher.inputFormatChanged(format); } + /** + * Called immediately before an input buffer is queued into the decoder. + * + *

The default implementation is a no-op. + * + * @param buffer The buffer that will be queued. + */ + protected void onQueueInputBuffer(VpxInputBuffer buffer) { + // Do nothing. + } + + /** + * Called when an output buffer is successfully processed. + * + * @param presentationTimeUs The timestamp associated with the output buffer. + */ + @CallSuper + protected void onProcessedOutputBuffer(long presentationTimeUs) { + buffersInCodecCount--; + } + + /** + * Returns whether the current frame should be dropped. + * + * @param outputBufferTimeUs The timestamp of the current output buffer. + * @param nextOutputBufferTimeUs The timestamp of the next output buffer or {@link C#TIME_UNSET} + * if the next output buffer is unavailable. + * @param positionUs The current playback position. + * @param joiningDeadlineMs The joining deadline. + * @return Returns whether to drop the current output buffer. + */ + protected boolean shouldDropOutputBuffer( + long outputBufferTimeUs, + long nextOutputBufferTimeUs, + long positionUs, + long joiningDeadlineMs) { + return isBufferLate(outputBufferTimeUs - positionUs) + && (joiningDeadlineMs != C.TIME_UNSET || nextOutputBufferTimeUs != C.TIME_UNSET); + } + + /** + * Returns whether to drop all buffers from the buffer being processed to the keyframe at or after + * the current playback position, if possible. + * + * @param earlyUs The time until the current buffer should be presented in microseconds. A + * negative value indicates that the buffer is late. + */ + protected boolean shouldDropBuffersToKeyframe(long earlyUs) { + return isBufferVeryLate(earlyUs); + } + + /** + * Skips the specified output buffer and releases it. + * + * @param outputBuffer The output buffer to skip. + */ + protected void skipOutputBuffer(VpxOutputBuffer outputBuffer) { + decoderCounters.skippedOutputBufferCount++; + outputBuffer.release(); + } + + /** + * Drops the specified output buffer and releases it. + * + * @param outputBuffer The output buffer to drop. + */ + protected void dropOutputBuffer(VpxOutputBuffer outputBuffer) { + updateDroppedBufferCounters(1); + outputBuffer.release(); + } + + /** + * Renders the specified output buffer. + * + *

The implementation of this method takes ownership of the output buffer and is responsible + * for calling {@link VpxOutputBuffer#release()} either immediately or in the future. + * + * @param outputBuffer The buffer to render. + */ + protected void renderOutputBuffer(VpxOutputBuffer outputBuffer) { + int bufferMode = outputBuffer.mode; + boolean renderRgb = bufferMode == VpxDecoder.OUTPUT_MODE_RGB && surface != null; + boolean renderYuv = bufferMode == VpxDecoder.OUTPUT_MODE_YUV && outputBufferRenderer != null; + if (!renderRgb && !renderYuv) { + dropOutputBuffer(outputBuffer); + } else { + maybeNotifyVideoSizeChanged(outputBuffer.width, outputBuffer.height); + if (renderRgb) { + renderRgbFrame(outputBuffer, scaleToFit); + outputBuffer.release(); + } else /* renderYuv */ { + outputBufferRenderer.setOutputBuffer(outputBuffer); + // The renderer will release the buffer. + } + consecutiveDroppedFrameCount = 0; + decoderCounters.renderedOutputBufferCount++; + maybeNotifyRenderedFirstFrame(); + } + } + + /** + * Drops frames from the current output buffer to the next keyframe at or before the playback + * position. If no such keyframe exists, as the playback position is inside the same group of + * pictures as the buffer being processed, returns {@code false}. Returns {@code true} otherwise. + * + * @param positionUs The current playback position, in microseconds. + * @return Whether any buffers were dropped. + * @throws ExoPlaybackException If an error occurs flushing the decoder. + */ + protected boolean maybeDropBuffersToKeyframe(long positionUs) throws ExoPlaybackException { + int droppedSourceBufferCount = skipSource(positionUs); + if (droppedSourceBufferCount == 0) { + return false; + } + decoderCounters.droppedToKeyframeCount++; + // We dropped some buffers to catch up, so update the decoder counters and flush the decoder, + // which releases all pending buffers buffers including the current output buffer. + updateDroppedBufferCounters(buffersInCodecCount + droppedSourceBufferCount); + flushDecoder(); + return true; + } + + /** + * Updates decoder counters to reflect that {@code droppedBufferCount} additional buffers were + * dropped. + * + * @param droppedBufferCount The number of additional dropped buffers. + */ + protected void updateDroppedBufferCounters(int droppedBufferCount) { + decoderCounters.droppedBufferCount += droppedBufferCount; + droppedFrames += droppedBufferCount; + consecutiveDroppedFrameCount += droppedBufferCount; + decoderCounters.maxConsecutiveDroppedBufferCount = + Math.max(consecutiveDroppedFrameCount, decoderCounters.maxConsecutiveDroppedBufferCount); + if (droppedFrames >= maxDroppedFramesToNotify) { + maybeNotifyDroppedFrames(); + } + } + + // PlayerMessage.Target implementation. + @Override public void handleMessage(int messageType, Object message) throws ExoPlaybackException { if (messageType == C.MSG_SET_SURFACE) { @@ -703,7 +598,10 @@ public final class LibvpxVideoRenderer extends BaseRenderer { } } - private void setOutput(Surface surface, VpxOutputBufferRenderer outputBufferRenderer) { + // Internal methods. + + private void setOutput( + @Nullable Surface surface, @Nullable VpxOutputBufferRenderer outputBufferRenderer) { // At most one output may be non-null. Both may be null if the output is being cleared. Assertions.checkState(surface == null || outputBufferRenderer == null); if (this.surface != surface || this.outputBufferRenderer != outputBufferRenderer) { @@ -737,6 +635,238 @@ public final class LibvpxVideoRenderer extends BaseRenderer { } } + private void maybeInitDecoder() throws ExoPlaybackException { + if (decoder != null) { + return; + } + + drmSession = pendingDrmSession; + ExoMediaCrypto mediaCrypto = null; + if (drmSession != null) { + mediaCrypto = drmSession.getMediaCrypto(); + if (mediaCrypto == null) { + DrmSessionException drmError = drmSession.getError(); + if (drmError != null) { + throw ExoPlaybackException.createForRenderer(drmError, getIndex()); + } + // The drm session isn't open yet. + return; + } + } + + try { + long decoderInitializingTimestamp = SystemClock.elapsedRealtime(); + TraceUtil.beginSection("createVpxDecoder"); + decoder = + new VpxDecoder( + NUM_INPUT_BUFFERS, + NUM_OUTPUT_BUFFERS, + INITIAL_INPUT_BUFFER_SIZE, + mediaCrypto, + disableLoopFilter); + decoder.setOutputMode(outputMode); + TraceUtil.endSection(); + long decoderInitializedTimestamp = SystemClock.elapsedRealtime(); + onDecoderInitialized( + decoder.getName(), + decoderInitializedTimestamp, + decoderInitializedTimestamp - decoderInitializingTimestamp); + decoderCounters.decoderInitCount++; + } catch (VpxDecoderException e) { + throw ExoPlaybackException.createForRenderer(e, getIndex()); + } + } + + private boolean feedInputBuffer() throws VpxDecoderException, ExoPlaybackException { + 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; + } + + if (inputBuffer == null) { + inputBuffer = decoder.dequeueInputBuffer(); + if (inputBuffer == null) { + return false; + } + } + + 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. + result = C.RESULT_BUFFER_READ; + } else { + result = readSource(formatHolder, inputBuffer, false); + } + + if (result == C.RESULT_NOTHING_READ) { + return false; + } + if (result == C.RESULT_FORMAT_READ) { + onInputFormatChanged(formatHolder.format); + return true; + } + if (inputBuffer.isEndOfStream()) { + inputStreamEnded = true; + decoder.queueInputBuffer(inputBuffer); + inputBuffer = null; + return false; + } + boolean bufferEncrypted = inputBuffer.isEncrypted(); + waitingForKeys = shouldWaitForKeys(bufferEncrypted); + if (waitingForKeys) { + return false; + } + inputBuffer.flip(); + inputBuffer.colorInfo = formatHolder.format.colorInfo; + onQueueInputBuffer(inputBuffer); + decoder.queueInputBuffer(inputBuffer); + buffersInCodecCount++; + decoderReceivedBuffers = true; + decoderCounters.inputBufferCount++; + inputBuffer = null; + return true; + } + + /** + * Attempts to dequeue an output buffer from the decoder and, if successful, passes it to {@link + * #processOutputBuffer(long)}. + * + * @param positionUs The player's current position. + * @return Whether it may be possible to drain more output data. + * @throws ExoPlaybackException If an error occurs draining the output buffer. + */ + private boolean drainOutputBuffer(long positionUs) + throws ExoPlaybackException, VpxDecoderException { + // Acquire outputBuffer either from nextOutputBuffer or from the decoder. + if (outputBuffer == null) { + if (nextOutputBuffer != null) { + outputBuffer = nextOutputBuffer; + nextOutputBuffer = null; + } else { + outputBuffer = decoder.dequeueOutputBuffer(); + } + if (outputBuffer == null) { + return false; + } + decoderCounters.skippedOutputBufferCount += outputBuffer.skippedOutputBufferCount; + buffersInCodecCount -= outputBuffer.skippedOutputBufferCount; + } + + if (nextOutputBuffer == null) { + nextOutputBuffer = decoder.dequeueOutputBuffer(); + } + + if (outputBuffer.isEndOfStream()) { + 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(); + } else { + outputBuffer.release(); + outputBuffer = null; + outputStreamEnded = true; + } + return false; + } + + return processOutputBuffer(positionUs); + } + + /** + * Processes {@link #outputBuffer} by rendering it, skipping it or doing nothing, and returns + * whether it may be possible to process another output buffer. + * + * @param positionUs The player's current position. + * @return Whether it may be possible to drain another output buffer. + * @throws ExoPlaybackException If an error occurs processing the output buffer. + */ + private boolean processOutputBuffer(long positionUs) throws ExoPlaybackException { + if (outputMode == VpxDecoder.OUTPUT_MODE_NONE) { + // Skip frames in sync with playback, so we'll be at the right frame if the mode changes. + if (isBufferLate(outputBuffer.timeUs - positionUs)) { + forceRenderFrame = false; + skipOutputBuffer(outputBuffer); + onProcessedOutputBuffer(outputBuffer.timeUs); + outputBuffer = null; + return true; + } + return false; + } + + if (forceRenderFrame) { + forceRenderFrame = false; + renderOutputBuffer(outputBuffer); + onProcessedOutputBuffer(outputBuffer.timeUs); + outputBuffer = null; + return true; + } + + long nextOutputBufferTimeUs = + nextOutputBuffer != null && !nextOutputBuffer.isEndOfStream() + ? nextOutputBuffer.timeUs + : C.TIME_UNSET; + + long earlyUs = outputBuffer.timeUs - positionUs; + if (shouldDropBuffersToKeyframe(earlyUs) && maybeDropBuffersToKeyframe(positionUs)) { + forceRenderFrame = true; + return false; + } else if (shouldDropOutputBuffer( + outputBuffer.timeUs, nextOutputBufferTimeUs, positionUs, joiningDeadlineMs)) { + dropOutputBuffer(outputBuffer); + onProcessedOutputBuffer(outputBuffer.timeUs); + outputBuffer = null; + return true; + } + + // If we have yet to render a frame to the current output (either initially or immediately + // following a seek), render one irrespective of the state or current position. + if (!renderedFirstFrame || (getState() == STATE_STARTED && earlyUs <= 30000)) { + renderOutputBuffer(outputBuffer); + onProcessedOutputBuffer(outputBuffer.timeUs); + outputBuffer = null; + } + + return false; + } + + private boolean shouldWaitForKeys(boolean bufferEncrypted) throws ExoPlaybackException { + if (drmSession == null || (!bufferEncrypted && playClearSamplesWithoutKeys)) { + return false; + } + @DrmSession.State int drmSessionState = drmSession.getState(); + if (drmSessionState == DrmSession.STATE_ERROR) { + throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex()); + } + return drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS; + } + + private void renderRgbFrame(VpxOutputBuffer outputBuffer, boolean scale) { + if (bitmap == null + || bitmap.getWidth() != outputBuffer.width + || bitmap.getHeight() != outputBuffer.height) { + bitmap = Bitmap.createBitmap(outputBuffer.width, outputBuffer.height, Bitmap.Config.RGB_565); + } + bitmap.copyPixelsFromBuffer(outputBuffer.data); + Canvas canvas = surface.lockCanvas(null); + if (scale) { + canvas.scale( + ((float) canvas.getWidth()) / outputBuffer.width, + ((float) canvas.getHeight()) / outputBuffer.height); + } + canvas.drawBitmap(bitmap, 0, 0, null); + surface.unlockCanvasAndPost(canvas); + } + private void setJoiningDeadlineMs() { joiningDeadlineMs = allowedJoiningTimeMs > 0 ? (SystemClock.elapsedRealtime() + allowedJoiningTimeMs) : C.TIME_UNSET;