| 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);
|
| + }
|
| + }
|
| +}
|
|
|