Fix Android video renderer to support video frames
with stride > width.

Recent libvpx update generates output video frames with stride
value greater than width, which was not supported by Android OpenGL
video renderer (Android GLES2 doesn't have GL_UNPACK_ROW_LENGTH
to provide stride information for buffer in glTexImage2D call).

Fix it by implementing native frame copying for Java
VideoRenderer.I420Frame implementation.

BUG=4248
R=braveyao@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/40639004

Cr-Commit-Position: refs/heads/master@{#8252}
git-svn-id: http://webrtc.googlecode.com/svn/trunk@8252 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/talk/app/webrtc/java/android/org/webrtc/VideoRendererGui.java b/talk/app/webrtc/java/android/org/webrtc/VideoRendererGui.java
index 0030d84..67b8c8c 100644
--- a/talk/app/webrtc/java/android/org/webrtc/VideoRendererGui.java
+++ b/talk/app/webrtc/java/android/org/webrtc/VideoRendererGui.java
@@ -555,9 +555,9 @@
       }
       // Check input frame parameters.
       if (frame.yuvFrame) {
-        if (!(frame.yuvStrides[0] == frame.width &&
-            frame.yuvStrides[1] == frame.width / 2 &&
-            frame.yuvStrides[2] == frame.width / 2)) {
+        if (frame.yuvStrides[0] < frame.width ||
+            frame.yuvStrides[1] < frame.width / 2 ||
+            frame.yuvStrides[2] < frame.width / 2) {
           Log.e(TAG, "Incorrect strides " + frame.yuvStrides[0] + ", " +
               frame.yuvStrides[1] + ", " + frame.yuvStrides[2]);
           return;
diff --git a/talk/app/webrtc/java/jni/peerconnection_jni.cc b/talk/app/webrtc/java/jni/peerconnection_jni.cc
index d6346d9..c15d28f 100644
--- a/talk/app/webrtc/java/jni/peerconnection_jni.cc
+++ b/talk/app/webrtc/java/jni/peerconnection_jni.cc
@@ -3223,6 +3223,32 @@
   return (jlong)renderer.release();
 }
 
+JOW(void, VideoRenderer_nativeCopyPlane)(
+    JNIEnv *jni, jclass, jobject j_src_buffer, jint width, jint height,
+    jint src_stride, jobject j_dst_buffer, jint dst_stride) {
+  size_t src_size = jni->GetDirectBufferCapacity(j_src_buffer);
+  size_t dst_size = jni->GetDirectBufferCapacity(j_dst_buffer);
+  CHECK(src_stride >= width) << "Wrong source stride " << src_stride;
+  CHECK(dst_stride >= width) << "Wrong destination stride " << dst_stride;
+  CHECK(src_size >= src_stride * height)
+      << "Insufficient source buffer capacity " << src_size;
+  CHECK(dst_size >= dst_stride * height)
+      << "Isufficient destination buffer capacity " << dst_size;
+  uint8_t *src =
+      reinterpret_cast<uint8_t*>(jni->GetDirectBufferAddress(j_src_buffer));
+  uint8_t *dst =
+      reinterpret_cast<uint8_t*>(jni->GetDirectBufferAddress(j_dst_buffer));
+  if (src_stride == dst_stride) {
+    memcpy(dst, src, src_stride * height);
+  } else {
+    for (int i = 0; i < height; i++) {
+      memcpy(dst, src, width);
+      src += src_stride;
+      dst += dst_stride;
+    }
+  }
+}
+
 JOW(jlong, VideoSource_stop)(JNIEnv* jni, jclass, jlong j_p) {
   cricket::VideoCapturer* capturer =
       reinterpret_cast<VideoSourceInterface*>(j_p)->GetVideoCapturer();
diff --git a/talk/app/webrtc/java/src/org/webrtc/VideoRenderer.java b/talk/app/webrtc/java/src/org/webrtc/VideoRenderer.java
index f700a13..82b9002 100644
--- a/talk/app/webrtc/java/src/org/webrtc/VideoRenderer.java
+++ b/talk/app/webrtc/java/src/org/webrtc/VideoRenderer.java
@@ -61,8 +61,8 @@
       if (yuvPlanes == null) {
         yuvPlanes = new ByteBuffer[3];
         yuvPlanes[0] = ByteBuffer.allocateDirect(yuvStrides[0] * height);
-        yuvPlanes[1] = ByteBuffer.allocateDirect(yuvStrides[1] * height);
-        yuvPlanes[2] = ByteBuffer.allocateDirect(yuvStrides[2] * height);
+        yuvPlanes[1] = ByteBuffer.allocateDirect(yuvStrides[1] * height / 2);
+        yuvPlanes[2] = ByteBuffer.allocateDirect(yuvStrides[2] * height / 2);
       }
       this.yuvPlanes = yuvPlanes;
       this.yuvFrame = true;
@@ -89,14 +89,16 @@
      */
     public I420Frame copyFrom(I420Frame source) {
       if (source.yuvFrame && yuvFrame) {
-        if (!Arrays.equals(yuvStrides, source.yuvStrides) ||
-            width != source.width || height != source.height) {
+        if (width != source.width || height != source.height) {
           throw new RuntimeException("Mismatched dimensions!  Source: " +
               source.toString() + ", destination: " + toString());
         }
-        copyPlane(source.yuvPlanes[0], yuvPlanes[0]);
-        copyPlane(source.yuvPlanes[1], yuvPlanes[1]);
-        copyPlane(source.yuvPlanes[2], yuvPlanes[2]);
+        nativeCopyPlane(source.yuvPlanes[0], width, height,
+            source.yuvStrides[0], yuvPlanes[0], yuvStrides[0]);
+        nativeCopyPlane(source.yuvPlanes[1], width / 2, height / 2,
+            source.yuvStrides[1], yuvPlanes[1], yuvStrides[1]);
+        nativeCopyPlane(source.yuvPlanes[2], width / 2, height / 2,
+            source.yuvStrides[2], yuvPlanes[2], yuvStrides[2]);
         return this;
       } else if (!source.yuvFrame && !yuvFrame) {
         textureObject = source.textureObject;
@@ -109,42 +111,37 @@
     }
 
     public I420Frame copyFrom(byte[] yuvData) {
-        if (yuvData.length < width * height * 3 / 2) {
-          throw new RuntimeException("Wrong arrays size: " + yuvData.length);
-        }
-        if (!yuvFrame) {
-          throw new RuntimeException("Can not feed yuv data to texture frame");
-        }
-        int planeSize = width * height;
-        ByteBuffer[] planes = new ByteBuffer[3];
-        planes[0] = ByteBuffer.wrap(yuvData, 0, planeSize);
-        planes[1] = ByteBuffer.wrap(yuvData, planeSize, planeSize / 4);
-        planes[2] = ByteBuffer.wrap(yuvData, planeSize + planeSize / 4,
-            planeSize / 4);
-        for (int i = 0; i < 3; i++) {
-          yuvPlanes[i].position(0);
-          yuvPlanes[i].put(planes[i]);
-          yuvPlanes[i].position(0);
-          yuvPlanes[i].limit(yuvPlanes[i].capacity());
-        }
-        return this;
+      if (yuvData.length < width * height * 3 / 2) {
+        throw new RuntimeException("Wrong arrays size: " + yuvData.length);
       }
-
+      if (!yuvFrame) {
+        throw new RuntimeException("Can not feed yuv data to texture frame");
+      }
+      int planeSize = width * height;
+      ByteBuffer[] planes = new ByteBuffer[3];
+      planes[0] = ByteBuffer.wrap(yuvData, 0, planeSize);
+      planes[1] = ByteBuffer.wrap(yuvData, planeSize, planeSize / 4);
+      planes[2] = ByteBuffer.wrap(yuvData, planeSize + planeSize / 4,
+          planeSize / 4);
+      for (int i = 0; i < 3; i++) {
+        yuvPlanes[i].position(0);
+        yuvPlanes[i].put(planes[i]);
+        yuvPlanes[i].position(0);
+        yuvPlanes[i].limit(yuvPlanes[i].capacity());
+      }
+      return this;
+    }
 
     @Override
     public String toString() {
       return width + "x" + height + ":" + yuvStrides[0] + ":" + yuvStrides[1] +
           ":" + yuvStrides[2];
     }
+  }
 
-    // Copy the bytes out of |src| and into |dst|, ignoring and overwriting
-    // positon & limit in both buffers.
-    private void copyPlane(ByteBuffer src, ByteBuffer dst) {
-      src.position(0).limit(src.capacity());
-      dst.put(src);
-      dst.position(0).limit(dst.capacity());
-    }
-}
+  // Helper native function to do a video frame plane copying.
+  private static native void nativeCopyPlane(ByteBuffer src, int width,
+      int height, int srcStride, ByteBuffer dst, int dstStride);
 
   /** The real meat of VideoRendererInterface. */
   public static interface Callbacks {