Index: webrtc/api/androidtests/src/org/webrtc/EglRendererTest.java |
diff --git a/webrtc/api/androidtests/src/org/webrtc/EglRendererTest.java b/webrtc/api/androidtests/src/org/webrtc/EglRendererTest.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..1ac577c64f7ed2fe44a7b12ae1c57913988acd64 |
--- /dev/null |
+++ b/webrtc/api/androidtests/src/org/webrtc/EglRendererTest.java |
@@ -0,0 +1,251 @@ |
+/* |
+ * Copyright 2016 The WebRTC Project Authors. All rights reserved. |
+ * |
+ * Use of this source code is governed by a BSD-style license |
+ * that can be found in the LICENSE file in the root of the source |
+ * tree. An additional intellectual property rights grant can be found |
+ * in the file PATENTS. All contributing project authors may |
+ * be found in the AUTHORS file in the root of the source tree. |
+ */ |
+ |
+package org.webrtc; |
+ |
+import android.graphics.Bitmap; |
+import android.test.ActivityInstrumentationTestCase2; |
+import android.test.suitebuilder.annotation.SmallTest; |
+import android.view.SurfaceHolder; |
+import android.view.SurfaceView; |
+import java.nio.ByteBuffer; |
+import java.util.ArrayList; |
+import java.util.Arrays; |
+import java.util.concurrent.CountDownLatch; |
+import org.webrtc.test.EmptyActivity; |
+ |
+public class EglRendererTest extends ActivityInstrumentationTestCase2<EmptyActivity> { |
+ final String TAG = "EglRendererTest"; |
+ final int RENDER_WAIT_MS = 100; |
+ final int TEST_FRAME_WIDTH = 4; |
+ final int TEST_FRAME_HEIGHT = 4; |
+ // Some arbitrary frames |
+ final ByteBuffer[][] TEST_FRAMES = { |
+ { |
+ ByteBuffer.wrap( |
+ new byte[] {11, 12, 13, 14, 15, 16, 17, 18, 19, 110, 111, 112, 113, 114, 115, 116}), |
+ ByteBuffer.wrap(new byte[] {117, 118, 119, 120}), |
+ ByteBuffer.wrap(new byte[] {121, 122, 123, 124}), |
+ }, |
+ { |
+ ByteBuffer.wrap(new byte[] {-11, -12, -13, -14, -15, -16, -17, -18, -19, -110, -111, -112, |
+ -113, -114, -115, -116}), |
+ ByteBuffer.wrap(new byte[] {-121, -122, -123, -124}), |
+ ByteBuffer.wrap(new byte[] {-117, -118, -119, -120}), |
+ }, |
+ { |
+ ByteBuffer.wrap(new byte[] {-11, -12, -13, -14, -15, -16, -17, -18, -19, -110, -111, -112, |
+ -113, -114, -115, -116}), |
+ ByteBuffer.wrap(new byte[] {117, 118, 119, 120}), |
+ ByteBuffer.wrap(new byte[] {121, 122, 123, 124}), |
+ }, |
+ }; |
+ |
+ private static class TestFrameListener implements EglRenderer.FrameListener { |
+ final private ArrayList<Bitmap> bitmaps = new ArrayList<Bitmap>(); |
+ |
+ @Override |
+ public synchronized void onFrame(Bitmap bitmap) { |
+ bitmaps.add(bitmap); |
+ } |
+ |
+ public synchronized ArrayList<Bitmap> getBitmaps() { |
+ return new ArrayList<Bitmap>(bitmaps); |
+ } |
+ } |
+ |
+ final TestFrameListener testFrameListener = new TestFrameListener(); |
+ |
+ EglRenderer eglRenderer; |
+ SurfaceView surfaceView; |
+ CountDownLatch surfaceReadyLatch = new CountDownLatch(1); |
+ |
+ public EglRendererTest() { |
+ super(EmptyActivity.class); |
+ } |
+ |
+ @Override |
+ protected void setUp() throws Exception { |
+ PeerConnectionFactory.initializeAndroidGlobals(getInstrumentation().getTargetContext(), |
+ true /* initializeAudio */, true /* initializeVideo */, true /* videoHwAcceleration */); |
+ eglRenderer = new EglRenderer("TestRenderer: "); |
+ eglRenderer.init(null, EglBase.CONFIG_RGBA, new GlRectDrawer()); |
+ getActivity().runOnUiThread(new Runnable() { |
+ @Override |
+ public void run() { |
+ surfaceView = new SurfaceView(getInstrumentation().getTargetContext()); |
+ surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() { |
+ @Override |
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { |
+ eglRenderer.surfaceSizeChanged(width, height); |
+ } |
+ |
+ @Override |
+ public void surfaceCreated(SurfaceHolder holder) { |
+ eglRenderer.createEglSurface(holder.getSurface()); |
+ surfaceReadyLatch.countDown(); |
+ } |
+ |
+ @Override |
+ public void surfaceDestroyed(SurfaceHolder holder) { |
+ eglRenderer.releaseEglSurface(); |
+ } |
+ }); |
+ getActivity().setContentView(surfaceView); |
+ } |
+ }); |
+ } |
+ |
+ @Override |
+ public void tearDown() { |
+ eglRenderer.release(); |
+ } |
+ |
+ private void waitForSurfaceReady() throws InterruptedException { |
+ surfaceReadyLatch.await(); |
+ } |
+ |
+ private void checkBitmap(Bitmap bitmap, float scale) { |
+ assertNotNull(bitmap); |
+ assertEquals((int) (TEST_FRAME_WIDTH * scale), bitmap.getWidth()); |
+ assertEquals((int) (TEST_FRAME_HEIGHT * scale), bitmap.getHeight()); |
+ } |
+ |
+ private float linearSample(ByteBuffer data, int x, int y) { |
+ final int stride = TEST_FRAME_WIDTH / 2; |
+ |
+ float coordX = (x + 0.5f) / TEST_FRAME_WIDTH * (TEST_FRAME_WIDTH / 2f); |
+ float coordY = (y + 0.5f) / TEST_FRAME_HEIGHT * (TEST_FRAME_HEIGHT / 2f); |
+ |
+ int lowX = (int) Math.floor(coordX - 0.5f); |
+ int lowY = (int) Math.floor(coordY - 0.5f); |
+ int highX = lowX + 1; |
+ int highY = lowY + 1; |
+ |
+ float highSampleX = coordX - lowX - 0.5f; |
+ float highSampleY = coordY - lowY - 0.5f; |
+ float lowSampleX = 1.f - highSampleX; |
+ float lowSampleY = 1.f - highSampleY; |
+ |
+ lowX = Math.max(0, lowY); |
+ lowY = Math.max(0, lowY); |
+ highX = Math.min(TEST_FRAME_WIDTH / 2 - 1, highX); |
+ highY = Math.min(TEST_FRAME_HEIGHT / 2 - 1, highY); |
+ |
+ float lowYValue = (data.get(lowY * stride + lowX) & 0xFF) * lowSampleX |
+ + (data.get(lowY * stride + highX) & 0xFF) * highSampleX; |
+ float highYValue = (data.get(highY * stride + lowX) & 0xFF) * lowSampleX |
+ + (data.get(highY * stride + highX) & 0xFF) * highSampleX; |
+ |
+ return (lowSampleY * lowYValue + highSampleY * highYValue) / 255.f - 0.5f; |
+ } |
+ |
+ private byte[] convertYUVFrameToRBG(ByteBuffer[] yuvFrame) { |
+ final byte[] argbFrame = new byte[TEST_FRAME_WIDTH * TEST_FRAME_HEIGHT * 4]; |
+ final int argbStride = TEST_FRAME_WIDTH * 4; |
+ final int yStride = TEST_FRAME_WIDTH; |
+ |
+ final int vStride = TEST_FRAME_WIDTH / 2; |
+ |
+ for (int y = 0; y < TEST_FRAME_HEIGHT; y++) { |
+ for (int x = 0; x < TEST_FRAME_WIDTH; x++) { |
+ final int x2 = x / 2; |
+ final int y2 = y / 2; |
+ |
+ final float yC = (yuvFrame[0].get(y * yStride + x) & 0xFF) / 255f; |
+ final float uC = linearSample(yuvFrame[1], x, y); |
+ final float vC = linearSample(yuvFrame[2], x, y); |
+ final float rC = Math.max(0, Math.min(1, yC + 1.403f * vC)); |
+ final float gC = Math.max(0, Math.min(1, yC - 0.344f * uC - 0.714f * vC)); |
+ final float bC = Math.max(0, Math.min(1, yC + 1.77f * uC)); |
+ |
+ argbFrame[y * argbStride + x * 4 + 0] = (byte) (int) Math.round(rC * 255f); |
+ argbFrame[y * argbStride + x * 4 + 1] = (byte) (int) Math.round(gC * 255f); |
+ argbFrame[y * argbStride + x * 4 + 2] = (byte) (int) Math.round(bC * 255f); |
+ argbFrame[y * argbStride + x * 4 + 3] = (byte) 255; |
+ } |
+ } |
+ |
+ return argbFrame; |
+ } |
+ |
+ private void checkBitmapContent(Bitmap bitmap, int frame) { |
+ checkBitmap(bitmap, 1.f); |
+ |
+ byte[] expectedARGB = convertYUVFrameToRBG(TEST_FRAMES[frame]); |
+ ByteBuffer bitmapBuffer = ByteBuffer.allocateDirect(bitmap.getByteCount()); |
+ bitmap.copyPixelsToBuffer(bitmapBuffer); |
+ |
+ Logging.d(TAG, "Expected bitmap content: " + Arrays.toString(expectedARGB)); |
+ Logging.d(TAG, "Bitmap content: " + Arrays.toString(bitmapBuffer.array())); |
+ for (int i = 0; i < expectedARGB.length; i++) { |
+ int expected = expectedARGB[i] & 0xFF; |
+ int value = bitmapBuffer.get(i) & 0xFF; |
+ // Due to unknown conversion differences check value matches +-2. |
+ if (value < expected - 2 || value > expected + 2) { |
+ fail("Frame doesn't match original frame on byte " + i + ". Expected: " + expected |
+ + " Result: " + value); |
+ } |
+ } |
+ } |
+ |
+ private void feedFrame(int i) { |
+ eglRenderer.renderFrame(new VideoRenderer.I420Frame(TEST_FRAME_WIDTH, TEST_FRAME_HEIGHT, 0, |
+ new int[] {TEST_FRAME_WIDTH, TEST_FRAME_WIDTH / 2, TEST_FRAME_WIDTH / 2}, TEST_FRAMES[i], |
+ 0)); |
+ } |
+ |
+ @SmallTest |
+ public void testAddFrameListener() throws Exception { |
+ waitForSurfaceReady(); |
+ |
+ eglRenderer.addFrameListener(testFrameListener, 0.f); |
+ feedFrame(0); |
+ feedFrame(1); |
+ Thread.sleep(RENDER_WAIT_MS); |
+ assertEquals(1, testFrameListener.getBitmaps().size()); |
+ assertNull(testFrameListener.getBitmaps().get(0)); |
+ eglRenderer.addFrameListener(testFrameListener, 0.f); |
+ feedFrame(2); |
+ Thread.sleep(RENDER_WAIT_MS); |
+ assertEquals(2, testFrameListener.getBitmaps().size()); |
+ assertNull(testFrameListener.getBitmaps().get(1)); |
+ } |
+ |
+ @SmallTest |
+ public void testAddFrameListenerBitmap() throws Exception { |
+ waitForSurfaceReady(); |
+ |
+ eglRenderer.addFrameListener(testFrameListener, 1.f); |
+ feedFrame(0); |
+ Thread.sleep(RENDER_WAIT_MS); |
+ assertEquals(1, testFrameListener.getBitmaps().size()); |
+ checkBitmapContent(testFrameListener.getBitmaps().get(0), 0); |
+ eglRenderer.addFrameListener(testFrameListener, 1.f); |
+ feedFrame(1); |
+ Thread.sleep(RENDER_WAIT_MS); |
+ assertEquals(2, testFrameListener.getBitmaps().size()); |
+ checkBitmapContent(testFrameListener.getBitmaps().get(1), 1); |
+ } |
+ |
+ @SmallTest |
+ public void testAddFrameListenerBitmapScale() throws Exception { |
+ waitForSurfaceReady(); |
+ |
+ for (int i = 0; i < 3; ++i) { |
+ float scale = i * 0.5f + 0.5f; |
+ eglRenderer.addFrameListener(testFrameListener, scale); |
+ feedFrame(i); |
+ Thread.sleep(RENDER_WAIT_MS); |
+ assertEquals(i + 1, testFrameListener.getBitmaps().size()); |
+ checkBitmap(testFrameListener.getBitmaps().get(i), scale); |
+ } |
+ } |
+} |