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
This commit is contained in:
andrewlewis 2018-01-09 08:04:36 -08:00 committed by Oliver Woodman
parent ff1bb2f702
commit 96e490d7fe

View File

@ -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<ExoMediaCrypto> 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.
*
* <p>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.
*
* <p>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.
*
* <p>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;