Allow custom VPX output buffer renderers.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=109832096
This commit is contained in:
anjalibh 2015-12-09 14:23:36 -08:00 committed by Oliver Woodman
parent 7bc341f385
commit e487fe6fa7
9 changed files with 225 additions and 160 deletions

View File

@ -173,7 +173,7 @@ public class VideoPlayer extends Activity implements OnClickListener,
TrackRenderer videoRenderer = TrackRenderer videoRenderer =
new LibvpxVideoTrackRenderer(sampleSource, true, handler, this, 50); new LibvpxVideoTrackRenderer(sampleSource, true, handler, this, 50);
if (useOpenGL) { if (useOpenGL) {
player.sendMessage(videoRenderer, LibvpxVideoTrackRenderer.MSG_SET_VPX_SURFACE_VIEW, player.sendMessage(videoRenderer, LibvpxVideoTrackRenderer.MSG_SET_OUTPUT_BUFFER_RENDERER,
vpxVideoSurfaceView); vpxVideoSurfaceView);
surfaceView.setVisibility(View.GONE); surfaceView.setVisibility(View.GONE);
} else { } else {
@ -202,7 +202,7 @@ public class VideoPlayer extends Activity implements OnClickListener,
player.addListener(this); player.addListener(this);
mediaController.setMediaPlayer(new PlayerControl(player)); mediaController.setMediaPlayer(new PlayerControl(player));
mediaController.setEnabled(true); mediaController.setEnabled(true);
player.sendMessage(renderers[0], LibvpxVideoTrackRenderer.MSG_SET_VPX_SURFACE_VIEW, player.sendMessage(renderers[0], LibvpxVideoTrackRenderer.MSG_SET_OUTPUT_BUFFER_RENDERER,
vpxVideoSurfaceView); vpxVideoSurfaceView);
player.prepare(renderers); player.prepare(renderers);
player.setPlayWhenReady(true); player.setPlayWhenReady(true);

View File

@ -23,8 +23,7 @@ import com.google.android.exoplayer.MediaFormatHolder;
import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.SampleSourceTrackRenderer; import com.google.android.exoplayer.SampleSourceTrackRenderer;
import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.ext.vp9.VpxDecoderWrapper.InputBuffer; import com.google.android.exoplayer.ext.vp9.VpxDecoderWrapper.VpxInputBuffer;
import com.google.android.exoplayer.ext.vp9.VpxDecoderWrapper.OutputBuffer;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
import android.graphics.Bitmap; import android.graphics.Bitmap;
@ -88,7 +87,12 @@ public final class LibvpxVideoTrackRenderer extends SampleSourceTrackRenderer {
* should be the target {@link Surface}, or null. * should be the target {@link Surface}, or null.
*/ */
public static final int MSG_SET_SURFACE = 1; public static final int MSG_SET_SURFACE = 1;
public static final int MSG_SET_VPX_SURFACE_VIEW = 2; /**
* The type of a message that can be passed to an instance of this class via
* {@link ExoPlayer#sendMessage} or {@link ExoPlayer#blockingSendMessage}. The message object
* should be the target {@link VpxOutputBufferRenderer}, or null.
*/
public static final int MSG_SET_OUTPUT_BUFFER_RENDERER = 2;
public final CodecCounters codecCounters = new CodecCounters(); public final CodecCounters codecCounters = new CodecCounters();
@ -100,14 +104,15 @@ public final class LibvpxVideoTrackRenderer extends SampleSourceTrackRenderer {
private MediaFormat format; private MediaFormat format;
private VpxDecoderWrapper decoder; private VpxDecoderWrapper decoder;
private InputBuffer inputBuffer; private VpxInputBuffer inputBuffer;
private OutputBuffer outputBuffer; private VpxOutputBuffer outputBuffer;
private VpxOutputBuffer renderedOutputBuffer;
private Bitmap bitmap; private Bitmap bitmap;
private boolean drawnToSurface; private boolean drawnToSurface;
private boolean renderedFirstFrame; private boolean renderedFirstFrame;
private Surface surface; private Surface surface;
private VpxVideoSurfaceView vpxVideoSurfaceView; private VpxOutputBufferRenderer outputBufferRenderer;
private int outputMode; private int outputMode;
private boolean inputStreamEnded; private boolean inputStreamEnded;
@ -176,7 +181,13 @@ public final class LibvpxVideoTrackRenderer extends SampleSourceTrackRenderer {
return; return;
} }
sourceIsReady = continueBufferingSource(positionUs); sourceIsReady = continueBufferingSource(positionUs);
try {
checkForDiscontinuity(positionUs); checkForDiscontinuity(positionUs);
} catch (VpxDecoderException e) {
notifyDecoderError(e);
throw new ExoPlaybackException(e);
}
// Try and read a format if we don't have one already. // Try and read a format if we don't have one already.
if (format == null && !readFormat(positionUs)) { if (format == null && !readFormat(positionUs)) {
@ -215,7 +226,8 @@ public final class LibvpxVideoTrackRenderer extends SampleSourceTrackRenderer {
if (outputBuffer.flags == VpxDecoderWrapper.FLAG_END_OF_STREAM) { if (outputBuffer.flags == VpxDecoderWrapper.FLAG_END_OF_STREAM) {
outputStreamEnded = true; outputStreamEnded = true;
releaseOutputBuffer(); releaseOutputBuffer(outputBuffer);
outputBuffer = null;
return; return;
} }
@ -229,7 +241,8 @@ public final class LibvpxVideoTrackRenderer extends SampleSourceTrackRenderer {
if (droppedFrameCount == maxDroppedFrameCountToNotify) { if (droppedFrameCount == maxDroppedFrameCountToNotify) {
notifyAndResetDroppedFrameCount(); notifyAndResetDroppedFrameCount();
} }
releaseOutputBuffer(); releaseOutputBuffer(outputBuffer);
outputBuffer = null;
return; return;
} }
@ -266,18 +279,23 @@ public final class LibvpxVideoTrackRenderer extends SampleSourceTrackRenderer {
drawnToSurface = true; drawnToSurface = true;
notifyDrawnToSurface(surface); notifyDrawnToSurface(surface);
} }
} else if (outputBuffer.mode == VpxDecoder.OUTPUT_MODE_YUV && vpxVideoSurfaceView != null) { } else if (outputBuffer.mode == VpxDecoder.OUTPUT_MODE_YUV && outputBufferRenderer != null) {
vpxVideoSurfaceView.renderFrame(outputBuffer); outputBufferRenderer.setOutputBuffer(outputBuffer);
} }
releaseOutputBuffer(); // Release the output buffer we rendered during the previous cycle, now that we delivered a new
} // buffer.
releaseOutputBuffer(renderedOutputBuffer);
private void releaseOutputBuffer() throws VpxDecoderException { renderedOutputBuffer = outputBuffer;
decoder.releaseOutputBuffer(outputBuffer);
outputBuffer = null; outputBuffer = null;
} }
private void renderRgbFrame(OutputBuffer outputBuffer, boolean scale) { private void releaseOutputBuffer(VpxOutputBuffer buffer) throws VpxDecoderException {
if (buffer != null) {
decoder.releaseOutputBuffer(buffer);
}
}
private void renderRgbFrame(VpxOutputBuffer outputBuffer, boolean scale) {
if (bitmap == null || bitmap.getWidth() != outputBuffer.width if (bitmap == null || bitmap.getWidth() != outputBuffer.width
|| bitmap.getHeight() != outputBuffer.height) { || bitmap.getHeight() != outputBuffer.height) {
bitmap = Bitmap.createBitmap(outputBuffer.width, outputBuffer.height, Bitmap.Config.RGB_565); bitmap = Bitmap.createBitmap(outputBuffer.width, outputBuffer.height, Bitmap.Config.RGB_565);
@ -332,7 +350,7 @@ public final class LibvpxVideoTrackRenderer extends SampleSourceTrackRenderer {
return true; return true;
} }
private void checkForDiscontinuity(long positionUs) { private void checkForDiscontinuity(long positionUs) throws VpxDecoderException {
if (decoder == null) { if (decoder == null) {
return; return;
} }
@ -342,9 +360,12 @@ public final class LibvpxVideoTrackRenderer extends SampleSourceTrackRenderer {
} }
} }
private void flushDecoder() { private void flushDecoder() throws VpxDecoderException {
inputBuffer = null; inputBuffer = null;
VpxOutputBuffer bufferToRelease = outputBuffer;
// Set this to null now because releaseOutputBuffer could throw an exception.
outputBuffer = null; outputBuffer = null;
releaseOutputBuffer(bufferToRelease);
decoder.flush(); decoder.flush();
} }
@ -417,8 +438,8 @@ public final class LibvpxVideoTrackRenderer extends SampleSourceTrackRenderer {
public void handleMessage(int messageType, Object message) throws ExoPlaybackException { public void handleMessage(int messageType, Object message) throws ExoPlaybackException {
if (messageType == MSG_SET_SURFACE) { if (messageType == MSG_SET_SURFACE) {
setSurface((Surface) message); setSurface((Surface) message);
} else if (messageType == MSG_SET_VPX_SURFACE_VIEW) { } else if (messageType == MSG_SET_OUTPUT_BUFFER_RENDERER) {
setVpxVideoSurfaceView((VpxVideoSurfaceView) message); setOutputBufferRenderer((VpxOutputBufferRenderer) message);
} else { } else {
super.handleMessage(messageType, message); super.handleMessage(messageType, message);
} }
@ -429,7 +450,7 @@ public final class LibvpxVideoTrackRenderer extends SampleSourceTrackRenderer {
return; return;
} }
this.surface = surface; this.surface = surface;
vpxVideoSurfaceView = null; outputBufferRenderer = null;
outputMode = (surface != null) ? VpxDecoder.OUTPUT_MODE_RGB : VpxDecoder.OUTPUT_MODE_UNKNOWN; outputMode = (surface != null) ? VpxDecoder.OUTPUT_MODE_RGB : VpxDecoder.OUTPUT_MODE_UNKNOWN;
if (decoder != null) { if (decoder != null) {
decoder.setOutputMode(outputMode); decoder.setOutputMode(outputMode);
@ -437,20 +458,20 @@ public final class LibvpxVideoTrackRenderer extends SampleSourceTrackRenderer {
drawnToSurface = false; drawnToSurface = false;
} }
private void setVpxVideoSurfaceView(VpxVideoSurfaceView vpxVideoSurfaceView) { private void setOutputBufferRenderer(VpxOutputBufferRenderer outputBufferRenderer) {
if (this.vpxVideoSurfaceView == vpxVideoSurfaceView) { if (this.outputBufferRenderer == outputBufferRenderer) {
return; return;
} }
this.vpxVideoSurfaceView = vpxVideoSurfaceView; this.outputBufferRenderer = outputBufferRenderer;
surface = null; surface = null;
outputMode = outputMode = (outputBufferRenderer != null)
(vpxVideoSurfaceView != null) ? VpxDecoder.OUTPUT_MODE_YUV : VpxDecoder.OUTPUT_MODE_UNKNOWN; ? VpxDecoder.OUTPUT_MODE_YUV : VpxDecoder.OUTPUT_MODE_UNKNOWN;
if (decoder != null) { if (decoder != null) {
decoder.setOutputMode(outputMode); decoder.setOutputMode(outputMode);
} }
} }
private void notifyIfVideoSizeChanged(final OutputBuffer outputBuffer) { private void notifyIfVideoSizeChanged(final VpxOutputBuffer outputBuffer) {
if (previousWidth == -1 || previousHeight == -1 if (previousWidth == -1 || previousHeight == -1
|| previousWidth != outputBuffer.width || previousHeight != outputBuffer.height) { || previousWidth != outputBuffer.width || previousHeight != outputBuffer.height) {
previousWidth = outputBuffer.width; previousWidth = outputBuffer.width;

View File

@ -15,8 +15,6 @@
*/ */
package com.google.android.exoplayer.ext.vp9; package com.google.android.exoplayer.ext.vp9;
import com.google.android.exoplayer.ext.vp9.VpxDecoderWrapper.OutputBuffer;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
/** /**
@ -64,7 +62,7 @@ import java.nio.ByteBuffer;
* @return 0 on success with a frame to render. 1 on success without a frame to render. * @return 0 on success with a frame to render. 1 on success without a frame to render.
* @throws VpxDecoderException on decode failure. * @throws VpxDecoderException on decode failure.
*/ */
public int decode(ByteBuffer encoded, int size, OutputBuffer outputBuffer) public int decode(ByteBuffer encoded, int size, VpxOutputBuffer outputBuffer)
throws VpxDecoderException { throws VpxDecoderException {
if (vpxDecode(vpxDecContext, encoded, size) != 0) { if (vpxDecode(vpxDecContext, encoded, size) != 0) {
throw new VpxDecoderException("libvpx decode error: " + vpxGetErrorMessage(vpxDecContext)); throw new VpxDecoderException("libvpx decode error: " + vpxGetErrorMessage(vpxDecContext));
@ -94,7 +92,7 @@ import java.nio.ByteBuffer;
private native long vpxInit(); private native long vpxInit();
private native long vpxClose(long context); private native long vpxClose(long context);
private native long vpxDecode(long context, ByteBuffer encoded, int length); private native long vpxDecode(long context, ByteBuffer encoded, int length);
private native int vpxGetFrame(long context, OutputBuffer outputBuffer); private native int vpxGetFrame(long context, VpxOutputBuffer outputBuffer);
private native String vpxGetErrorMessage(long context); private native String vpxGetErrorMessage(long context);
} }

View File

@ -28,16 +28,19 @@ import java.util.LinkedList;
public static final int FLAG_END_OF_STREAM = 1; public static final int FLAG_END_OF_STREAM = 1;
private static final int INPUT_BUFFER_SIZE = 768 * 1024; // Value based on cs/SoftVpx.cpp. private static final int INPUT_BUFFER_SIZE = 768 * 1024; // Value based on cs/SoftVpx.cpp.
/**
* The total number of output buffers. {@link LibvpxVideoTrackRenderer} may hold on to 2 buffers
* at a time so this value should be high enough considering LibvpxVideoTrackRenderer requirement.
*/
private static final int NUM_BUFFERS = 16; private static final int NUM_BUFFERS = 16;
private final Object lock; private final Object lock;
private final LinkedList<InputBuffer> dequeuedInputBuffers; private final LinkedList<VpxInputBuffer> dequeuedInputBuffers;
private final LinkedList<InputBuffer> queuedInputBuffers; private final LinkedList<VpxInputBuffer> queuedInputBuffers;
private final LinkedList<OutputBuffer> queuedOutputBuffers; private final LinkedList<VpxOutputBuffer> queuedOutputBuffers;
private final LinkedList<OutputBuffer> dequeuedOutputBuffers; private final VpxInputBuffer[] availableInputBuffers;
private final InputBuffer[] availableInputBuffers; private final VpxOutputBuffer[] availableOutputBuffers;
private final OutputBuffer[] availableOutputBuffers;
private int availableInputBufferCount; private int availableInputBufferCount;
private int availableOutputBufferCount; private int availableOutputBufferCount;
@ -57,14 +60,13 @@ import java.util.LinkedList;
dequeuedInputBuffers = new LinkedList<>(); dequeuedInputBuffers = new LinkedList<>();
queuedInputBuffers = new LinkedList<>(); queuedInputBuffers = new LinkedList<>();
queuedOutputBuffers = new LinkedList<>(); queuedOutputBuffers = new LinkedList<>();
dequeuedOutputBuffers = new LinkedList<>(); availableInputBuffers = new VpxInputBuffer[NUM_BUFFERS];
availableInputBuffers = new InputBuffer[NUM_BUFFERS]; availableOutputBuffers = new VpxOutputBuffer[NUM_BUFFERS];
availableOutputBuffers = new OutputBuffer[NUM_BUFFERS];
availableInputBufferCount = NUM_BUFFERS; availableInputBufferCount = NUM_BUFFERS;
availableOutputBufferCount = NUM_BUFFERS; availableOutputBufferCount = NUM_BUFFERS;
for (int i = 0; i < NUM_BUFFERS; i++) { for (int i = 0; i < NUM_BUFFERS; i++) {
availableInputBuffers[i] = new InputBuffer(); availableInputBuffers[i] = new VpxInputBuffer();
availableOutputBuffers[i] = new OutputBuffer(); availableOutputBuffers[i] = new VpxOutputBuffer();
} }
} }
@ -72,13 +74,13 @@ import java.util.LinkedList;
this.outputMode = outputMode; this.outputMode = outputMode;
} }
public InputBuffer dequeueInputBuffer() throws VpxDecoderException { public VpxInputBuffer dequeueInputBuffer() throws VpxDecoderException {
synchronized (lock) { synchronized (lock) {
maybeThrowDecoderError(); maybeThrowDecoderError();
if (availableInputBufferCount == 0) { if (availableInputBufferCount == 0) {
return null; return null;
} }
InputBuffer inputBuffer = availableInputBuffers[--availableInputBufferCount]; VpxInputBuffer inputBuffer = availableInputBuffers[--availableInputBufferCount];
inputBuffer.flags = 0; inputBuffer.flags = 0;
inputBuffer.sampleHolder.clearData(); inputBuffer.sampleHolder.clearData();
dequeuedInputBuffers.addLast(inputBuffer); dequeuedInputBuffers.addLast(inputBuffer);
@ -86,7 +88,7 @@ import java.util.LinkedList;
} }
} }
public void queueInputBuffer(InputBuffer inputBuffer) throws VpxDecoderException { public void queueInputBuffer(VpxInputBuffer inputBuffer) throws VpxDecoderException {
synchronized (lock) { synchronized (lock) {
maybeThrowDecoderError(); maybeThrowDecoderError();
dequeuedInputBuffers.remove(inputBuffer); dequeuedInputBuffers.remove(inputBuffer);
@ -95,22 +97,20 @@ import java.util.LinkedList;
} }
} }
public OutputBuffer dequeueOutputBuffer() throws VpxDecoderException { public VpxOutputBuffer dequeueOutputBuffer() throws VpxDecoderException {
synchronized (lock) { synchronized (lock) {
maybeThrowDecoderError(); maybeThrowDecoderError();
if (queuedOutputBuffers.isEmpty()) { if (queuedOutputBuffers.isEmpty()) {
return null; return null;
} }
OutputBuffer outputBuffer = queuedOutputBuffers.removeFirst(); VpxOutputBuffer outputBuffer = queuedOutputBuffers.removeFirst();
dequeuedOutputBuffers.add(outputBuffer);
return outputBuffer; return outputBuffer;
} }
} }
public void releaseOutputBuffer(OutputBuffer outputBuffer) throws VpxDecoderException { public void releaseOutputBuffer(VpxOutputBuffer outputBuffer) throws VpxDecoderException {
synchronized (lock) { synchronized (lock) {
maybeThrowDecoderError(); maybeThrowDecoderError();
dequeuedOutputBuffers.remove(outputBuffer);
availableOutputBuffers[availableOutputBufferCount++] = outputBuffer; availableOutputBuffers[availableOutputBufferCount++] = outputBuffer;
maybeNotifyDecodeLoop(); maybeNotifyDecodeLoop();
} }
@ -128,9 +128,6 @@ import java.util.LinkedList;
while (!queuedOutputBuffers.isEmpty()) { while (!queuedOutputBuffers.isEmpty()) {
availableOutputBuffers[availableOutputBufferCount++] = queuedOutputBuffers.removeFirst(); availableOutputBuffers[availableOutputBufferCount++] = queuedOutputBuffers.removeFirst();
} }
while (!dequeuedOutputBuffers.isEmpty()) {
availableOutputBuffers[availableOutputBufferCount++] = dequeuedOutputBuffers.removeFirst();
}
} }
} }
@ -187,8 +184,8 @@ import java.util.LinkedList;
private boolean decodeBuffer(VpxDecoder decoder) throws InterruptedException, private boolean decodeBuffer(VpxDecoder decoder) throws InterruptedException,
VpxDecoderException { VpxDecoderException {
InputBuffer inputBuffer; VpxInputBuffer inputBuffer;
OutputBuffer outputBuffer; VpxOutputBuffer outputBuffer;
// Wait until we have an input buffer to decode, and an output buffer to decode into. // Wait until we have an input buffer to decode, and an output buffer to decode into.
synchronized (lock) { synchronized (lock) {
@ -238,7 +235,7 @@ import java.util.LinkedList;
return true; return true;
} }
/* package */ static final class InputBuffer { /* package */ static final class VpxInputBuffer {
public final SampleHolder sampleHolder; public final SampleHolder sampleHolder;
@ -246,75 +243,11 @@ import java.util.LinkedList;
public int height; public int height;
public int flags; public int flags;
public InputBuffer() { public VpxInputBuffer() {
sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT); sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT);
sampleHolder.data = ByteBuffer.allocateDirect(INPUT_BUFFER_SIZE); sampleHolder.data = ByteBuffer.allocateDirect(INPUT_BUFFER_SIZE);
} }
} }
/* package */ static final class OutputBuffer {
public ByteBuffer data;
public long timestampUs;
public int width;
public int height;
public int flags;
public ByteBuffer[] yuvPlanes;
public int[] yuvStrides;
public int mode;
/**
* This method is called from C++ through JNI after decoding is done. It will resize the
* buffer based on the given dimensions.
*/
public void initForRgbFrame(int width, int height) {
this.width = width;
this.height = height;
int minimumRgbSize = width * height * 2;
if (data == null || data.capacity() < minimumRgbSize) {
data = ByteBuffer.allocateDirect(minimumRgbSize);
yuvPlanes = null;
}
data.position(0);
data.limit(minimumRgbSize);
}
/**
* This method is called from C++ through JNI after decoding is done. It will resize the
* buffer based on the given stride.
*/
public void initForYuvFrame(int width, int height, int yStride, int uvStride) {
this.width = width;
this.height = height;
int yLength = yStride * height;
int uvLength = uvStride * ((height + 1) / 2);
int minimumYuvSize = yLength + (uvLength * 2);
if (data == null || data.capacity() < minimumYuvSize) {
data = ByteBuffer.allocateDirect(minimumYuvSize);
}
data.limit(minimumYuvSize);
if (yuvPlanes == null) {
yuvPlanes = new ByteBuffer[3];
}
// Rewrapping has to be done on every frame since the stride might have changed.
data.position(0);
yuvPlanes[0] = data.slice();
yuvPlanes[0].limit(yLength);
data.position(yLength);
yuvPlanes[1] = data.slice();
yuvPlanes[1].limit(uvLength);
data.position(yLength + uvLength);
yuvPlanes[2] = data.slice();
yuvPlanes[2].limit(uvLength);
if (yuvStrides == null) {
yuvStrides = new int[3];
}
yuvStrides[0] = yStride;
yuvStrides[1] = uvStride;
yuvStrides[2] = uvStride;
}
}
} }

View File

@ -0,0 +1,85 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.ext.vp9;
import java.nio.ByteBuffer;
/**
* OutputBuffer for storing the video frame.
*/
public final class VpxOutputBuffer {
public ByteBuffer data;
public long timestampUs;
public int width;
public int height;
public int flags;
public ByteBuffer[] yuvPlanes;
public int[] yuvStrides;
public int mode;
/**
* This method is called from C++ through JNI after decoding is done. It will resize the
* buffer based on the given dimensions.
*/
public void initForRgbFrame(int width, int height) {
this.width = width;
this.height = height;
int minimumRgbSize = width * height * 2;
if (data == null || data.capacity() < minimumRgbSize) {
data = ByteBuffer.allocateDirect(minimumRgbSize);
yuvPlanes = null;
}
data.position(0);
data.limit(minimumRgbSize);
}
/**
* This method is called from C++ through JNI after decoding is done. It will resize the
* buffer based on the given stride.
*/
public void initForYuvFrame(int width, int height, int yStride, int uvStride) {
this.width = width;
this.height = height;
int yLength = yStride * height;
int uvLength = uvStride * ((height + 1) / 2);
int minimumYuvSize = yLength + (uvLength * 2);
if (data == null || data.capacity() < minimumYuvSize) {
data = ByteBuffer.allocateDirect(minimumYuvSize);
}
data.limit(minimumYuvSize);
if (yuvPlanes == null) {
yuvPlanes = new ByteBuffer[3];
}
// Rewrapping has to be done on every frame since the stride might have changed.
data.position(0);
yuvPlanes[0] = data.slice();
yuvPlanes[0].limit(yLength);
data.position(yLength);
yuvPlanes[1] = data.slice();
yuvPlanes[1].limit(uvLength);
data.position(yLength + uvLength);
yuvPlanes[2] = data.slice();
yuvPlanes[2].limit(uvLength);
if (yuvStrides == null) {
yuvStrides = new int[3];
}
yuvStrides[0] = yStride;
yuvStrides[1] = uvStride;
yuvStrides[2] = uvStride;
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.ext.vp9;
/**
* Renders the {@link VpxOutputBuffer}.
*/
public interface VpxOutputBufferRenderer {
/**
* Sets the output buffer to be rendered.
*/
void setOutputBuffer(VpxOutputBuffer outputBuffer);
}

View File

@ -15,8 +15,6 @@
*/ */
package com.google.android.exoplayer.ext.vp9; package com.google.android.exoplayer.ext.vp9;
import com.google.android.exoplayer.ext.vp9.VpxDecoderWrapper.OutputBuffer;
import android.opengl.GLES20; import android.opengl.GLES20;
import android.opengl.GLSurfaceView; import android.opengl.GLSurfaceView;
@ -67,7 +65,7 @@ import javax.microedition.khronos.opengles.GL10;
private int program; private int program;
private int texLocation; private int texLocation;
private FloatBuffer textureCoords; private FloatBuffer textureCoords;
private volatile OutputBuffer outputBuffer; private VpxOutputBuffer outputBuffer;
private int previousWidth; private int previousWidth;
private int previousStride; private int previousStride;
@ -82,7 +80,7 @@ import javax.microedition.khronos.opengles.GL10;
* *
* @param outputBuffer OutputBuffer containing the YUV Frame to be rendered * @param outputBuffer OutputBuffer containing the YUV Frame to be rendered
*/ */
public void setFrame(OutputBuffer outputBuffer) { public synchronized void setFrame(VpxOutputBuffer outputBuffer) {
this.outputBuffer = outputBuffer; this.outputBuffer = outputBuffer;
} }
@ -121,8 +119,8 @@ import javax.microedition.khronos.opengles.GL10;
@Override @Override
public void onDrawFrame(GL10 unused) { public void onDrawFrame(GL10 unused) {
OutputBuffer outputBuffer = this.outputBuffer; synchronized (this) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); VpxOutputBuffer outputBuffer = this.outputBuffer;
if (outputBuffer == null) { if (outputBuffer == null) {
// Nothing to render yet. // Nothing to render yet.
return; return;
@ -132,8 +130,9 @@ import javax.microedition.khronos.opengles.GL10;
GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i); GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[i]); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[i]);
GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1); GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, outputBuffer.yuvStrides[i], GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE,
h, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, outputBuffer.yuvPlanes[i]); outputBuffer.yuvStrides[i], h, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE,
outputBuffer.yuvPlanes[i]);
} }
// Set cropping of stride if either width or stride has changed. // Set cropping of stride if either width or stride has changed.
if (previousWidth != outputBuffer.width || previousStride != outputBuffer.yuvStrides[0]) { if (previousWidth != outputBuffer.width || previousStride != outputBuffer.yuvStrides[0]) {
@ -148,6 +147,8 @@ import javax.microedition.khronos.opengles.GL10;
previousWidth = outputBuffer.width; previousWidth = outputBuffer.width;
previousStride = outputBuffer.yuvStrides[0]; previousStride = outputBuffer.yuvStrides[0];
} }
}
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
checkNoGLES2Error(); checkNoGLES2Error();
} }

View File

@ -15,8 +15,6 @@
*/ */
package com.google.android.exoplayer.ext.vp9; package com.google.android.exoplayer.ext.vp9;
import com.google.android.exoplayer.ext.vp9.VpxDecoderWrapper.OutputBuffer;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.opengl.GLSurfaceView; import android.opengl.GLSurfaceView;
@ -26,7 +24,7 @@ import android.util.AttributeSet;
* A GLSurfaceView extension that scales itself to the given aspect ratio. * A GLSurfaceView extension that scales itself to the given aspect ratio.
*/ */
@TargetApi(11) @TargetApi(11)
public class VpxVideoSurfaceView extends GLSurfaceView { public class VpxVideoSurfaceView extends GLSurfaceView implements VpxOutputBufferRenderer {
private final VpxRenderer renderer; private final VpxRenderer renderer;
@ -43,7 +41,8 @@ public class VpxVideoSurfaceView extends GLSurfaceView {
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
} }
public void renderFrame(OutputBuffer outputBuffer) { @Override
public void setOutputBuffer(VpxOutputBuffer outputBuffer) {
renderer.setFrame(outputBuffer); renderer.setFrame(outputBuffer);
requestRender(); requestRender();
} }

View File

@ -44,7 +44,7 @@
Java_com_google_android_exoplayer_ext_vp9_VpxDecoder_ ## NAME \ Java_com_google_android_exoplayer_ext_vp9_VpxDecoder_ ## NAME \
(JNIEnv* env, jobject thiz, ##__VA_ARGS__)\ (JNIEnv* env, jobject thiz, ##__VA_ARGS__)\
// JNI references for OutputBuffer class. // JNI references for VpxOutputBuffer class.
static jmethodID initForRgbFrame; static jmethodID initForRgbFrame;
static jmethodID initForYuvFrame; static jmethodID initForYuvFrame;
static jfieldID dataField; static jfieldID dataField;
@ -69,7 +69,7 @@ FUNC(jlong, vpxInit) {
// Populate JNI References. // Populate JNI References.
const jclass outputBufferClass = env->FindClass( const jclass outputBufferClass = env->FindClass(
"com/google/android/exoplayer/ext/vp9/VpxDecoderWrapper$OutputBuffer"); "com/google/android/exoplayer/ext/vp9/VpxOutputBuffer");
initForYuvFrame = env->GetMethodID(outputBufferClass, "initForYuvFrame", initForYuvFrame = env->GetMethodID(outputBufferClass, "initForYuvFrame",
"(IIII)V"); "(IIII)V");
initForRgbFrame = env->GetMethodID(outputBufferClass, "initForRgbFrame", initForRgbFrame = env->GetMethodID(outputBufferClass, "initForRgbFrame",