Android: Add tests for VideoFrame.Buffer.toI420() and cropAndScale()

This CL adds tests that are primarily targeting
VideoFrame.Buffer.toI420() and cropAndScale(), but includes the whole
chain for YuvConverter, GlRectDrawer, and VideoFrameDrawer.

It also includes a couple of fixes to bugs that were exposed by the new
tests.

Bug: webrtc:9186, webrtc:9391
Change-Id: I5eb62979a8fd8def28c3cb2e82dcede57c42216f
Reviewed-on: https://webrtc-review.googlesource.com/83163
Commit-Queue: Magnus Jedvert <magjed@webrtc.org>
Reviewed-by: Sami Kalliomäki <sakal@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#23611}
diff --git a/sdk/android/api/org/webrtc/JavaI420Buffer.java b/sdk/android/api/org/webrtc/JavaI420Buffer.java
index 19dd3e9..7231fda 100644
--- a/sdk/android/api/org/webrtc/JavaI420Buffer.java
+++ b/sdk/android/api/org/webrtc/JavaI420Buffer.java
@@ -39,6 +39,15 @@
     this.refCountDelegate = new RefCountDelegate(releaseCallback);
   }
 
+  private static void checkCapacity(ByteBuffer data, int width, int height, int stride) {
+    // The last row does not necessarily need padding.
+    final int minCapacity = stride * (height - 1) + width;
+    if (data.capacity() < minCapacity) {
+      throw new IllegalArgumentException(
+          "Buffer must be at least " + minCapacity + " bytes, but was " + data.capacity());
+    }
+  }
+
   /** Wraps existing ByteBuffers into JavaI420Buffer object without copying the contents. */
   public static JavaI420Buffer wrap(int width, int height, ByteBuffer dataY, int strideY,
       ByteBuffer dataU, int strideU, ByteBuffer dataV, int strideV, Runnable releaseCallback) {
@@ -55,19 +64,11 @@
     dataU = dataU.slice();
     dataV = dataV.slice();
 
+    final int chromaWidth = (width + 1) / 2;
     final int chromaHeight = (height + 1) / 2;
-    final int minCapacityY = strideY * height;
-    final int minCapacityU = strideU * chromaHeight;
-    final int minCapacityV = strideV * chromaHeight;
-    if (dataY.capacity() < minCapacityY) {
-      throw new IllegalArgumentException("Y-buffer must be at least " + minCapacityY + " bytes.");
-    }
-    if (dataU.capacity() < minCapacityU) {
-      throw new IllegalArgumentException("U-buffer must be at least " + minCapacityU + " bytes.");
-    }
-    if (dataV.capacity() < minCapacityV) {
-      throw new IllegalArgumentException("V-buffer must be at least " + minCapacityV + " bytes.");
-    }
+    checkCapacity(dataY, width, height, strideY);
+    checkCapacity(dataU, chromaWidth, chromaHeight, strideU);
+    checkCapacity(dataV, chromaWidth, chromaHeight, strideV);
 
     return new JavaI420Buffer(
         width, height, dataY, strideY, dataU, strideU, dataV, strideV, releaseCallback);
diff --git a/sdk/android/api/org/webrtc/TextureBufferImpl.java b/sdk/android/api/org/webrtc/TextureBufferImpl.java
index 1903658..c34728a 100644
--- a/sdk/android/api/org/webrtc/TextureBufferImpl.java
+++ b/sdk/android/api/org/webrtc/TextureBufferImpl.java
@@ -85,7 +85,10 @@
   public VideoFrame.Buffer cropAndScale(
       int cropX, int cropY, int cropWidth, int cropHeight, int scaleWidth, int scaleHeight) {
     final Matrix newMatrix = new Matrix(transformMatrix);
-    newMatrix.preTranslate(cropX / (float) width, cropY / (float) height);
+    // In WebRTC, Y=0 is the top row, while in OpenGL Y=0 is the bottom row. This means that the Y
+    // direction is effectively reversed.
+    final int cropYFromBottom = height - (cropY + cropHeight);
+    newMatrix.preTranslate(cropX / (float) width, cropYFromBottom / (float) height);
     newMatrix.preScale(cropWidth / (float) width, cropHeight / (float) height);
 
     retain();
diff --git a/sdk/android/api/org/webrtc/VideoFrame.java b/sdk/android/api/org/webrtc/VideoFrame.java
index b496f00..6d19260 100644
--- a/sdk/android/api/org/webrtc/VideoFrame.java
+++ b/sdk/android/api/org/webrtc/VideoFrame.java
@@ -204,9 +204,8 @@
       dataV.position(cropX / 2 + cropY / 2 * buffer.getStrideV());
 
       buffer.retain();
-      return JavaI420Buffer.wrap(buffer.getWidth(), buffer.getHeight(), dataY.slice(),
-          buffer.getStrideY(), dataU.slice(), buffer.getStrideU(), dataV.slice(),
-          buffer.getStrideV(), buffer::release);
+      return JavaI420Buffer.wrap(scaleWidth, scaleHeight, dataY.slice(), buffer.getStrideY(),
+          dataU.slice(), buffer.getStrideU(), dataV.slice(), buffer.getStrideV(), buffer::release);
     }
 
     JavaI420Buffer newBuffer = JavaI420Buffer.allocate(scaleWidth, scaleHeight);
diff --git a/sdk/android/api/org/webrtc/YuvConverter.java b/sdk/android/api/org/webrtc/YuvConverter.java
index 2d8e548..f7922d6 100644
--- a/sdk/android/api/org/webrtc/YuvConverter.java
+++ b/sdk/android/api/org/webrtc/YuvConverter.java
@@ -286,7 +286,7 @@
     // Y'UV444 to RGB888, see
     // https://en.wikipedia.org/wiki/YUV#Y.27UV444_to_RGB888_conversion.
     // We use the ITU-R coefficients for U and V */
-    GLES20.glUniform4f(coeffsLoc, 0.299f, 0.587f, 0.114f, 0.0f);
+    GLES20.glUniform4f(coeffsLoc, 0.2987856f, 0.5871095f, 0.1141049f, 0.0f);
     GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
 
     // Draw U
@@ -294,12 +294,12 @@
     // Matrix * (1;0;0;0) / (width / 2). Note that opengl uses column major order.
     GLES20.glUniform2f(
         xUnitLoc, 2.0f * transformMatrix[0] / width, 2.0f * transformMatrix[1] / width);
-    GLES20.glUniform4f(coeffsLoc, -0.169f, -0.331f, 0.499f, 0.5f);
+    GLES20.glUniform4f(coeffsLoc, -0.168805420f, -0.3317003f, 0.5005057f, 0.5f);
     GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
 
     // Draw V
     GLES20.glViewport(stride / 8, height, uv_width, uv_height);
-    GLES20.glUniform4f(coeffsLoc, 0.499f, -0.418f, -0.0813f, 0.5f);
+    GLES20.glUniform4f(coeffsLoc, 0.4997964f, -0.4184672f, -0.0813292f, 0.5f);
     GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
 
     GLES20.glReadPixels(