1 /* 2 * Copyright 2018 The WebRTC project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 package org.webrtc; 12 13 import static org.hamcrest.Matchers.greaterThanOrEqualTo; 14 import static org.hamcrest.Matchers.lessThanOrEqualTo; 15 import static org.junit.Assert.assertEquals; 16 import static org.junit.Assert.assertThat; 17 18 import android.graphics.Matrix; 19 import android.opengl.GLES20; 20 import android.os.Handler; 21 import android.os.HandlerThread; 22 import androidx.test.filters.SmallTest; 23 import java.nio.ByteBuffer; 24 import java.util.ArrayList; 25 import java.util.Arrays; 26 import java.util.Collection; 27 import java.util.Collections; 28 import java.util.List; 29 import org.junit.BeforeClass; 30 import org.junit.Test; 31 import org.junit.runner.RunWith; 32 import org.junit.runners.Parameterized; 33 import org.junit.runners.Parameterized.Parameters; 34 import org.webrtc.VideoFrame; 35 36 /** 37 * Test VideoFrame buffers of different kind of formats: I420, RGB, OES, NV12, NV21, and verify 38 * toI420() and cropAndScale() behavior. Creating RGB/OES textures involves VideoFrameDrawer and 39 * GlRectDrawer and we are testing the full chain I420 -> OES/RGB texture -> I420, with and without 40 * cropping in the middle. Reading textures back to I420 also exercises the YuvConverter code. 41 */ 42 @RunWith(Parameterized.class) 43 public class VideoFrameBufferTest { 44 /** 45 * These tests are parameterized on this enum which represents the different VideoFrame.Buffers. 46 */ 47 private static enum BufferType { I420_JAVA, I420_NATIVE, RGB_TEXTURE, OES_TEXTURE, NV21, NV12 } 48 49 @Parameters(name = "{0}") parameters()50 public static Collection<BufferType> parameters() { 51 return Arrays.asList(BufferType.values()); 52 } 53 54 @BeforeClass setUp()55 public static void setUp() { 56 // Needed for JniCommon.nativeAllocateByteBuffer() to work, which is used from JavaI420Buffer. 57 System.loadLibrary(TestConstants.NATIVE_LIBRARY); 58 } 59 60 private final BufferType bufferType; 61 VideoFrameBufferTest(BufferType bufferType)62 public VideoFrameBufferTest(BufferType bufferType) { 63 this.bufferType = bufferType; 64 } 65 66 /** 67 * Create a VideoFrame.Buffer of the given type with the same pixel content as the given I420 68 * buffer. 69 */ createBufferWithType( BufferType bufferType, VideoFrame.I420Buffer i420Buffer)70 private static VideoFrame.Buffer createBufferWithType( 71 BufferType bufferType, VideoFrame.I420Buffer i420Buffer) { 72 VideoFrame.Buffer buffer; 73 switch (bufferType) { 74 case I420_JAVA: 75 buffer = i420Buffer; 76 buffer.retain(); 77 assertEquals(VideoFrameBufferType.I420, buffer.getBufferType()); 78 assertEquals(VideoFrameBufferType.I420, nativeGetBufferType(buffer)); 79 return buffer; 80 case I420_NATIVE: 81 buffer = nativeGetNativeI420Buffer(i420Buffer); 82 assertEquals(VideoFrameBufferType.I420, buffer.getBufferType()); 83 assertEquals(VideoFrameBufferType.I420, nativeGetBufferType(buffer)); 84 return buffer; 85 case RGB_TEXTURE: 86 buffer = createRgbTextureBuffer(/* eglContext= */ null, i420Buffer); 87 assertEquals(VideoFrameBufferType.NATIVE, buffer.getBufferType()); 88 assertEquals(VideoFrameBufferType.NATIVE, nativeGetBufferType(buffer)); 89 return buffer; 90 case OES_TEXTURE: 91 buffer = createOesTextureBuffer(/* eglContext= */ null, i420Buffer); 92 assertEquals(VideoFrameBufferType.NATIVE, buffer.getBufferType()); 93 assertEquals(VideoFrameBufferType.NATIVE, nativeGetBufferType(buffer)); 94 return buffer; 95 case NV21: 96 buffer = createNV21Buffer(i420Buffer); 97 assertEquals(VideoFrameBufferType.NATIVE, buffer.getBufferType()); 98 assertEquals(VideoFrameBufferType.NATIVE, nativeGetBufferType(buffer)); 99 return buffer; 100 case NV12: 101 buffer = createNV12Buffer(i420Buffer); 102 assertEquals(VideoFrameBufferType.NATIVE, buffer.getBufferType()); 103 assertEquals(VideoFrameBufferType.NATIVE, nativeGetBufferType(buffer)); 104 return buffer; 105 default: 106 throw new IllegalArgumentException("Unknown buffer type: " + bufferType); 107 } 108 } 109 createBufferToTest(VideoFrame.I420Buffer i420Buffer)110 private VideoFrame.Buffer createBufferToTest(VideoFrame.I420Buffer i420Buffer) { 111 return createBufferWithType(this.bufferType, i420Buffer); 112 } 113 114 /** 115 * Creates a 16x16 I420 buffer that varies smoothly and spans all RGB values. 116 */ createTestI420Buffer()117 public static VideoFrame.I420Buffer createTestI420Buffer() { 118 final int width = 16; 119 final int height = 16; 120 final int[] yData = new int[] {156, 162, 167, 172, 177, 182, 187, 193, 199, 203, 209, 214, 219, 121 224, 229, 235, 147, 152, 157, 162, 168, 173, 178, 183, 188, 193, 199, 205, 210, 215, 220, 122 225, 138, 143, 148, 153, 158, 163, 168, 174, 180, 184, 190, 195, 200, 205, 211, 216, 128, 123 133, 138, 144, 149, 154, 159, 165, 170, 175, 181, 186, 191, 196, 201, 206, 119, 124, 129, 124 134, 140, 145, 150, 156, 161, 166, 171, 176, 181, 187, 192, 197, 109, 114, 119, 126, 130, 125 136, 141, 146, 151, 156, 162, 167, 172, 177, 182, 187, 101, 105, 111, 116, 121, 126, 132, 126 137, 142, 147, 152, 157, 162, 168, 173, 178, 90, 96, 101, 107, 112, 117, 122, 127, 132, 138, 127 143, 148, 153, 158, 163, 168, 82, 87, 92, 97, 102, 107, 113, 118, 123, 128, 133, 138, 144, 128 149, 154, 159, 72, 77, 83, 88, 93, 98, 103, 108, 113, 119, 124, 129, 134, 139, 144, 150, 63, 129 68, 73, 78, 83, 89, 94, 99, 104, 109, 114, 119, 125, 130, 135, 140, 53, 58, 64, 69, 74, 79, 130 84, 89, 95, 100, 105, 110, 115, 120, 126, 131, 44, 49, 54, 59, 64, 70, 75, 80, 85, 90, 95, 131 101, 106, 111, 116, 121, 34, 40, 45, 50, 55, 60, 65, 71, 76, 81, 86, 91, 96, 101, 107, 113, 132 25, 30, 35, 40, 46, 51, 56, 61, 66, 71, 77, 82, 87, 92, 98, 103, 16, 21, 26, 31, 36, 41, 46, 133 52, 57, 62, 67, 72, 77, 83, 89, 94}; 134 final int[] uData = new int[] {110, 113, 116, 118, 120, 123, 125, 128, 113, 116, 118, 120, 123, 135 125, 128, 130, 116, 118, 120, 123, 125, 128, 130, 132, 118, 120, 123, 125, 128, 130, 132, 136 135, 120, 123, 125, 128, 130, 132, 135, 138, 123, 125, 128, 130, 132, 135, 138, 139, 125, 137 128, 130, 132, 135, 138, 139, 142, 128, 130, 132, 135, 138, 139, 142, 145}; 138 final int[] vData = new int[] {31, 45, 59, 73, 87, 100, 114, 127, 45, 59, 73, 87, 100, 114, 128, 139 141, 59, 73, 87, 100, 114, 127, 141, 155, 73, 87, 100, 114, 127, 141, 155, 168, 87, 100, 140 114, 128, 141, 155, 168, 182, 100, 114, 128, 141, 155, 168, 182, 197, 114, 127, 141, 155, 141 168, 182, 196, 210, 127, 141, 155, 168, 182, 196, 210, 224}; 142 return JavaI420Buffer.wrap(width, height, toByteBuffer(yData), 143 /* strideY= */ width, toByteBuffer(uData), /* strideU= */ width / 2, toByteBuffer(vData), 144 /* strideV= */ width / 2, 145 /* releaseCallback= */ null); 146 } 147 148 /** 149 * Create an RGB texture buffer available in `eglContext` with the same pixel content as the given 150 * I420 buffer. 151 */ createRgbTextureBuffer( EglBase.Context eglContext, VideoFrame.I420Buffer i420Buffer)152 public static VideoFrame.TextureBuffer createRgbTextureBuffer( 153 EglBase.Context eglContext, VideoFrame.I420Buffer i420Buffer) { 154 final int width = i420Buffer.getWidth(); 155 final int height = i420Buffer.getHeight(); 156 157 final HandlerThread renderThread = new HandlerThread("RGB texture thread"); 158 renderThread.start(); 159 final Handler renderThreadHandler = new Handler(renderThread.getLooper()); 160 return ThreadUtils.invokeAtFrontUninterruptibly(renderThreadHandler, () -> { 161 // Create EGL base with a pixel buffer as display output. 162 final EglBase eglBase = EglBase.create(eglContext, EglBase.CONFIG_PIXEL_BUFFER); 163 eglBase.createDummyPbufferSurface(); 164 eglBase.makeCurrent(); 165 166 final GlTextureFrameBuffer textureFrameBuffer = new GlTextureFrameBuffer(GLES20.GL_RGBA); 167 textureFrameBuffer.setSize(width, height); 168 169 GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, textureFrameBuffer.getFrameBufferId()); 170 drawI420Buffer(i420Buffer); 171 GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); 172 173 final YuvConverter yuvConverter = new YuvConverter(); 174 return new TextureBufferImpl(width, height, VideoFrame.TextureBuffer.Type.RGB, 175 textureFrameBuffer.getTextureId(), 176 /* transformMatrix= */ new Matrix(), renderThreadHandler, yuvConverter, 177 /* releaseCallback= */ () -> renderThreadHandler.post(() -> { 178 textureFrameBuffer.release(); 179 yuvConverter.release(); 180 eglBase.release(); 181 renderThread.quit(); 182 })); 183 }); 184 } 185 186 /** 187 * Create an OES texture buffer available in `eglContext` with the same pixel content as the given 188 * I420 buffer. 189 */ 190 public static VideoFrame.TextureBuffer createOesTextureBuffer( 191 EglBase.Context eglContext, VideoFrame.I420Buffer i420Buffer) { 192 final int width = i420Buffer.getWidth(); 193 final int height = i420Buffer.getHeight(); 194 195 // Create resources for generating OES textures. 196 final SurfaceTextureHelper surfaceTextureHelper = 197 SurfaceTextureHelper.create("SurfaceTextureHelper test", eglContext); 198 surfaceTextureHelper.setTextureSize(width, height); 199 200 final HandlerThread renderThread = new HandlerThread("OES texture thread"); 201 renderThread.start(); 202 final Handler renderThreadHandler = new Handler(renderThread.getLooper()); 203 final VideoFrame.TextureBuffer oesBuffer = 204 ThreadUtils.invokeAtFrontUninterruptibly(renderThreadHandler, () -> { 205 // Create EGL base with the SurfaceTexture as display output. 206 final EglBase eglBase = EglBase.create(eglContext, EglBase.CONFIG_PLAIN); 207 eglBase.createSurface(surfaceTextureHelper.getSurfaceTexture()); 208 eglBase.makeCurrent(); 209 assertEquals(width, eglBase.surfaceWidth()); 210 assertEquals(height, eglBase.surfaceHeight()); 211 212 final SurfaceTextureHelperTest.MockTextureListener listener = 213 new SurfaceTextureHelperTest.MockTextureListener(); 214 surfaceTextureHelper.startListening(listener); 215 216 // Draw the frame and block until an OES texture is delivered. 217 drawI420Buffer(i420Buffer); 218 eglBase.swapBuffers(); 219 final VideoFrame.TextureBuffer textureBuffer = listener.waitForTextureBuffer(); 220 surfaceTextureHelper.stopListening(); 221 surfaceTextureHelper.dispose(); 222 223 return textureBuffer; 224 }); 225 renderThread.quit(); 226 227 return oesBuffer; 228 } 229 230 /** Create an NV21Buffer with the same pixel content as the given I420 buffer. */ 231 public static NV21Buffer createNV21Buffer(VideoFrame.I420Buffer i420Buffer) { 232 final int width = i420Buffer.getWidth(); 233 final int height = i420Buffer.getHeight(); 234 final int chromaStride = width; 235 final int chromaWidth = (width + 1) / 2; 236 final int chromaHeight = (height + 1) / 2; 237 final int ySize = width * height; 238 239 final ByteBuffer nv21Buffer = ByteBuffer.allocateDirect(ySize + chromaStride * chromaHeight); 240 // We don't care what the array offset is since we only want an array that is direct. 241 @SuppressWarnings("ByteBufferBackingArray") final byte[] nv21Data = nv21Buffer.array(); 242 243 for (int y = 0; y < height; ++y) { 244 for (int x = 0; x < width; ++x) { 245 final byte yValue = i420Buffer.getDataY().get(y * i420Buffer.getStrideY() + x); 246 nv21Data[y * width + x] = yValue; 247 } 248 } 249 for (int y = 0; y < chromaHeight; ++y) { 250 for (int x = 0; x < chromaWidth; ++x) { 251 final byte uValue = i420Buffer.getDataU().get(y * i420Buffer.getStrideU() + x); 252 final byte vValue = i420Buffer.getDataV().get(y * i420Buffer.getStrideV() + x); 253 nv21Data[ySize + y * chromaStride + 2 * x + 0] = vValue; 254 nv21Data[ySize + y * chromaStride + 2 * x + 1] = uValue; 255 } 256 } 257 return new NV21Buffer(nv21Data, width, height, /* releaseCallback= */ null); 258 } 259 260 /** Create an NV12Buffer with the same pixel content as the given I420 buffer. */ 261 public static NV12Buffer createNV12Buffer(VideoFrame.I420Buffer i420Buffer) { 262 final int width = i420Buffer.getWidth(); 263 final int height = i420Buffer.getHeight(); 264 final int chromaStride = width; 265 final int chromaWidth = (width + 1) / 2; 266 final int chromaHeight = (height + 1) / 2; 267 final int ySize = width * height; 268 269 final ByteBuffer nv12Buffer = ByteBuffer.allocateDirect(ySize + chromaStride * chromaHeight); 270 271 for (int y = 0; y < height; ++y) { 272 for (int x = 0; x < width; ++x) { 273 final byte yValue = i420Buffer.getDataY().get(y * i420Buffer.getStrideY() + x); 274 nv12Buffer.put(y * width + x, yValue); 275 } 276 } 277 for (int y = 0; y < chromaHeight; ++y) { 278 for (int x = 0; x < chromaWidth; ++x) { 279 final byte uValue = i420Buffer.getDataU().get(y * i420Buffer.getStrideU() + x); 280 final byte vValue = i420Buffer.getDataV().get(y * i420Buffer.getStrideV() + x); 281 nv12Buffer.put(ySize + y * chromaStride + 2 * x + 0, uValue); 282 nv12Buffer.put(ySize + y * chromaStride + 2 * x + 1, vValue); 283 } 284 } 285 return new NV12Buffer(width, height, /* stride= */ width, /* sliceHeight= */ height, nv12Buffer, 286 /* releaseCallback */ null); 287 } 288 289 /** Print the ByteBuffer plane to the StringBuilder. */ 290 private static void printPlane( 291 StringBuilder stringBuilder, int width, int height, ByteBuffer plane, int stride) { 292 for (int y = 0; y < height; ++y) { 293 for (int x = 0; x < width; ++x) { 294 final int value = plane.get(y * stride + x) & 0xFF; 295 if (x != 0) { 296 stringBuilder.append(", "); 297 } 298 stringBuilder.append(value); 299 } 300 stringBuilder.append("\n"); 301 } 302 } 303 304 /** Convert the pixel content of an I420 buffer to a string representation. */ 305 private static String i420BufferToString(VideoFrame.I420Buffer buffer) { 306 final StringBuilder stringBuilder = new StringBuilder(); 307 stringBuilder.append( 308 "I420 buffer with size: " + buffer.getWidth() + "x" + buffer.getHeight() + ".\n"); 309 stringBuilder.append("Y-plane:\n"); 310 printPlane(stringBuilder, buffer.getWidth(), buffer.getHeight(), buffer.getDataY(), 311 buffer.getStrideY()); 312 final int chromaWidth = (buffer.getWidth() + 1) / 2; 313 final int chromaHeight = (buffer.getHeight() + 1) / 2; 314 stringBuilder.append("U-plane:\n"); 315 printPlane(stringBuilder, chromaWidth, chromaHeight, buffer.getDataU(), buffer.getStrideU()); 316 stringBuilder.append("V-plane:\n"); 317 printPlane(stringBuilder, chromaWidth, chromaHeight, buffer.getDataV(), buffer.getStrideV()); 318 return stringBuilder.toString(); 319 } 320 321 /** 322 * Assert that the given I420 buffers are almost identical, allowing for some difference due to 323 * numerical errors. It has limits for both overall PSNR and maximum individual pixel difference. 324 */ 325 public static void assertAlmostEqualI420Buffers( 326 VideoFrame.I420Buffer bufferA, VideoFrame.I420Buffer bufferB) { 327 final int diff = maxDiff(bufferA, bufferB); 328 assertThat("Pixel difference too high: " + diff + "." 329 + "\nBuffer A: " + i420BufferToString(bufferA) 330 + "Buffer B: " + i420BufferToString(bufferB), 331 diff, lessThanOrEqualTo(4)); 332 final double psnr = calculatePsnr(bufferA, bufferB); 333 assertThat("PSNR too low: " + psnr + "." 334 + "\nBuffer A: " + i420BufferToString(bufferA) 335 + "Buffer B: " + i420BufferToString(bufferB), 336 psnr, greaterThanOrEqualTo(50.0)); 337 } 338 339 /** Returns a flattened list of pixel differences for two ByteBuffer planes. */ 340 private static List<Integer> getPixelDiffs( 341 int width, int height, ByteBuffer planeA, int strideA, ByteBuffer planeB, int strideB) { 342 List<Integer> res = new ArrayList<>(); 343 for (int y = 0; y < height; ++y) { 344 for (int x = 0; x < width; ++x) { 345 final int valueA = planeA.get(y * strideA + x) & 0xFF; 346 final int valueB = planeB.get(y * strideB + x) & 0xFF; 347 res.add(Math.abs(valueA - valueB)); 348 } 349 } 350 return res; 351 } 352 353 /** Returns a flattened list of pixel differences for two I420 buffers. */ 354 private static List<Integer> getPixelDiffs( 355 VideoFrame.I420Buffer bufferA, VideoFrame.I420Buffer bufferB) { 356 assertEquals(bufferA.getWidth(), bufferB.getWidth()); 357 assertEquals(bufferA.getHeight(), bufferB.getHeight()); 358 final int width = bufferA.getWidth(); 359 final int height = bufferA.getHeight(); 360 final int chromaWidth = (width + 1) / 2; 361 final int chromaHeight = (height + 1) / 2; 362 final List<Integer> diffs = getPixelDiffs(width, height, bufferA.getDataY(), 363 bufferA.getStrideY(), bufferB.getDataY(), bufferB.getStrideY()); 364 diffs.addAll(getPixelDiffs(chromaWidth, chromaHeight, bufferA.getDataU(), bufferA.getStrideU(), 365 bufferB.getDataU(), bufferB.getStrideU())); 366 diffs.addAll(getPixelDiffs(chromaWidth, chromaHeight, bufferA.getDataV(), bufferA.getStrideV(), 367 bufferB.getDataV(), bufferB.getStrideV())); 368 return diffs; 369 } 370 371 /** Returns the maximum pixel difference from any of the Y/U/V planes in the given buffers. */ 372 private static int maxDiff(VideoFrame.I420Buffer bufferA, VideoFrame.I420Buffer bufferB) { 373 return Collections.max(getPixelDiffs(bufferA, bufferB)); 374 } 375 376 /** 377 * Returns the PSNR given a sum of squared error and the number of measurements that were added. 378 */ 379 private static double sseToPsnr(long sse, int count) { 380 if (sse == 0) { 381 return Double.POSITIVE_INFINITY; 382 } 383 final double meanSquaredError = (double) sse / (double) count; 384 final double maxPixelValue = 255.0; 385 return 10.0 * Math.log10(maxPixelValue * maxPixelValue / meanSquaredError); 386 } 387 388 /** Returns the PSNR of the given I420 buffers. */ 389 private static double calculatePsnr( 390 VideoFrame.I420Buffer bufferA, VideoFrame.I420Buffer bufferB) { 391 final List<Integer> pixelDiffs = getPixelDiffs(bufferA, bufferB); 392 long sse = 0; 393 for (int pixelDiff : pixelDiffs) { 394 sse += pixelDiff * pixelDiff; 395 } 396 return sseToPsnr(sse, pixelDiffs.size()); 397 } 398 399 /** 400 * Convert an int array to a byte array and make sure the values are within the range [0, 255]. 401 */ 402 private static byte[] toByteArray(int[] array) { 403 final byte[] res = new byte[array.length]; 404 for (int i = 0; i < array.length; ++i) { 405 final int value = array[i]; 406 assertThat(value, greaterThanOrEqualTo(0)); 407 assertThat(value, lessThanOrEqualTo(255)); 408 res[i] = (byte) value; 409 } 410 return res; 411 } 412 413 /** Convert a byte array to a direct ByteBuffer. */ 414 private static ByteBuffer toByteBuffer(int[] array) { 415 final ByteBuffer buffer = ByteBuffer.allocateDirect(array.length); 416 buffer.put(toByteArray(array)); 417 buffer.rewind(); 418 return buffer; 419 } 420 421 /** 422 * Draw an I420 buffer on the currently bound frame buffer, allocating and releasing any 423 * resources necessary. 424 */ 425 private static void drawI420Buffer(VideoFrame.I420Buffer i420Buffer) { 426 final GlRectDrawer drawer = new GlRectDrawer(); 427 final VideoFrameDrawer videoFrameDrawer = new VideoFrameDrawer(); 428 videoFrameDrawer.drawFrame( 429 new VideoFrame(i420Buffer, /* rotation= */ 0, /* timestampNs= */ 0), drawer); 430 videoFrameDrawer.release(); 431 drawer.release(); 432 } 433 434 /** 435 * Helper function that tests cropAndScale() with the given cropping and scaling parameters, and 436 * compares the pixel content against a reference I420 buffer. 437 */ 438 private void testCropAndScale( 439 int cropX, int cropY, int cropWidth, int cropHeight, int scaleWidth, int scaleHeight) { 440 final VideoFrame.I420Buffer referenceI420Buffer = createTestI420Buffer(); 441 final VideoFrame.Buffer bufferToTest = createBufferToTest(referenceI420Buffer); 442 443 final VideoFrame.Buffer croppedReferenceBuffer = referenceI420Buffer.cropAndScale( 444 cropX, cropY, cropWidth, cropHeight, scaleWidth, scaleHeight); 445 referenceI420Buffer.release(); 446 final VideoFrame.I420Buffer croppedReferenceI420Buffer = croppedReferenceBuffer.toI420(); 447 croppedReferenceBuffer.release(); 448 449 final VideoFrame.Buffer croppedBufferToTest = 450 bufferToTest.cropAndScale(cropX, cropY, cropWidth, cropHeight, scaleWidth, scaleHeight); 451 bufferToTest.release(); 452 453 final VideoFrame.I420Buffer croppedOutputI420Buffer = croppedBufferToTest.toI420(); 454 croppedBufferToTest.release(); 455 456 assertAlmostEqualI420Buffers(croppedReferenceI420Buffer, croppedOutputI420Buffer); 457 croppedReferenceI420Buffer.release(); 458 croppedOutputI420Buffer.release(); 459 } 460 461 @Test 462 @SmallTest 463 /** Test calling toI420() and comparing pixel content against I420 reference. */ 464 public void testToI420() { 465 final VideoFrame.I420Buffer referenceI420Buffer = createTestI420Buffer(); 466 final VideoFrame.Buffer bufferToTest = createBufferToTest(referenceI420Buffer); 467 468 final VideoFrame.I420Buffer outputI420Buffer = bufferToTest.toI420(); 469 bufferToTest.release(); 470 471 assertEquals(VideoFrameBufferType.I420, nativeGetBufferType(outputI420Buffer)); 472 assertAlmostEqualI420Buffers(referenceI420Buffer, outputI420Buffer); 473 referenceI420Buffer.release(); 474 outputI420Buffer.release(); 475 } 476 477 @Test 478 @SmallTest 479 /** Pure 2x scaling with no cropping. */ 480 public void testScale2x() { 481 testCropAndScale(0 /* cropX= */, 0 /* cropY= */, /* cropWidth= */ 16, /* cropHeight= */ 16, 482 /* scaleWidth= */ 8, /* scaleHeight= */ 8); 483 } 484 485 @Test 486 @SmallTest 487 /** Test cropping only X direction, with no scaling. */ 488 public void testCropX() { 489 testCropAndScale(8 /* cropX= */, 0 /* cropY= */, /* cropWidth= */ 8, /* cropHeight= */ 16, 490 /* scaleWidth= */ 8, /* scaleHeight= */ 16); 491 } 492 493 @Test 494 @SmallTest 495 /** Test cropping only Y direction, with no scaling. */ 496 public void testCropY() { 497 testCropAndScale(0 /* cropX= */, 8 /* cropY= */, /* cropWidth= */ 16, /* cropHeight= */ 8, 498 /* scaleWidth= */ 16, /* scaleHeight= */ 8); 499 } 500 501 @Test 502 @SmallTest 503 /** Test center crop, with no scaling. */ 504 public void testCenterCrop() { 505 testCropAndScale(4 /* cropX= */, 4 /* cropY= */, /* cropWidth= */ 8, /* cropHeight= */ 8, 506 /* scaleWidth= */ 8, /* scaleHeight= */ 8); 507 } 508 509 @Test 510 @SmallTest 511 /** Test non-center crop for right bottom corner, with no scaling. */ 512 public void testRightBottomCornerCrop() { 513 testCropAndScale(8 /* cropX= */, 8 /* cropY= */, /* cropWidth= */ 8, /* cropHeight= */ 8, 514 /* scaleWidth= */ 8, /* scaleHeight= */ 8); 515 } 516 517 @Test 518 @SmallTest 519 /** Test combined cropping and scaling. */ 520 public void testCropAndScale() { 521 testCropAndScale(4 /* cropX= */, 4 /* cropY= */, /* cropWidth= */ 12, /* cropHeight= */ 12, 522 /* scaleWidth= */ 8, /* scaleHeight= */ 8); 523 } 524 525 @VideoFrameBufferType private static native int nativeGetBufferType(VideoFrame.Buffer buffer); 526 527 /** Returns the copy of I420Buffer using WrappedNativeI420Buffer. */ 528 private static native VideoFrame.Buffer nativeGetNativeI420Buffer( 529 VideoFrame.I420Buffer i420Buffer); 530 } 531