Update JavaI420Buffer.allocate to use native allocations.

This ensures memory is released timely and avoids problems with garbage
collection.

Native buffers don't support array operation, so FileVideoCapturer had
to be update to use FileChannel to write ByteBuffers directly.

Bug: None
Change-Id: I3f63d2adc159e9f39f0c68dd0bd6b1747686584e
Reviewed-on: https://webrtc-review.googlesource.com/55262
Commit-Queue: Sami Kalliomäki <sakal@webrtc.org>
Reviewed-by: Anders Carlsson <andersc@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#22118}
diff --git a/sdk/android/api/org/webrtc/FileVideoCapturer.java b/sdk/android/api/org/webrtc/FileVideoCapturer.java
index 88a0d30..4efed36 100644
--- a/sdk/android/api/org/webrtc/FileVideoCapturer.java
+++ b/sdk/android/api/org/webrtc/FileVideoCapturer.java
@@ -15,6 +15,7 @@
 import java.io.IOException;
 import java.io.RandomAccessFile;
 import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
 import java.nio.charset.Charset;
 import java.util.Timer;
 import java.util.TimerTask;
@@ -33,18 +34,21 @@
   private static class VideoReaderY4M implements VideoReader {
     private static final String TAG = "VideoReaderY4M";
     private static final String Y4M_FRAME_DELIMETER = "FRAME";
+    private static final int FRAME_DELIMETER_LENGTH = Y4M_FRAME_DELIMETER.length() + 1;
 
     private final int frameWidth;
     private final int frameHeight;
     // First char after header
     private final long videoStart;
-    private final RandomAccessFile mediaFileStream;
+    private final RandomAccessFile mediaFile;
+    private final FileChannel mediaFileChannel;
 
     public VideoReaderY4M(String file) throws IOException {
-      mediaFileStream = new RandomAccessFile(file, "r");
+      mediaFile = new RandomAccessFile(file, "r");
+      mediaFileChannel = mediaFile.getChannel();
       StringBuilder builder = new StringBuilder();
       for (;;) {
-        int c = mediaFileStream.read();
+        int c = mediaFile.read();
         if (c == -1) {
           // End of file reached.
           throw new RuntimeException("Found end of file before end of header for file: " + file);
@@ -55,7 +59,7 @@
         }
         builder.append((char) c);
       }
-      videoStart = mediaFileStream.getFilePointer();
+      videoStart = mediaFileChannel.position();
       String header = builder.toString();
       String[] headerTokens = header.split("[ ]");
       int w = 0;
@@ -101,24 +105,24 @@
       final int sizeV = chromaHeight * buffer.getStrideV();
 
       try {
-        byte[] frameDelim = new byte[Y4M_FRAME_DELIMETER.length() + 1];
-        if (mediaFileStream.read(frameDelim) < frameDelim.length) {
+        ByteBuffer frameDelim = ByteBuffer.allocate(FRAME_DELIMETER_LENGTH);
+        if (mediaFileChannel.read(frameDelim) < FRAME_DELIMETER_LENGTH) {
           // We reach end of file, loop
-          mediaFileStream.seek(videoStart);
-          if (mediaFileStream.read(frameDelim) < frameDelim.length) {
+          mediaFileChannel.position(videoStart);
+          if (mediaFileChannel.read(frameDelim) < FRAME_DELIMETER_LENGTH) {
             throw new RuntimeException("Error looping video");
           }
         }
-        String frameDelimStr = new String(frameDelim, Charset.forName("US-ASCII"));
+        String frameDelimStr = new String(frameDelim.array(), Charset.forName("US-ASCII"));
         if (!frameDelimStr.equals(Y4M_FRAME_DELIMETER + "\n")) {
           throw new RuntimeException(
               "Frames should be delimited by FRAME plus newline, found delimter was: '"
               + frameDelimStr + "'");
         }
 
-        mediaFileStream.readFully(dataY.array(), dataY.arrayOffset(), sizeY);
-        mediaFileStream.readFully(dataU.array(), dataU.arrayOffset(), sizeU);
-        mediaFileStream.readFully(dataV.array(), dataV.arrayOffset(), sizeV);
+        mediaFileChannel.read(dataY);
+        mediaFileChannel.read(dataU);
+        mediaFileChannel.read(dataV);
       } catch (IOException e) {
         throw new RuntimeException(e);
       }
@@ -129,7 +133,8 @@
     @Override
     public void close() {
       try {
-        mediaFileStream.close();
+        // Closing a file also closes the channel.
+        mediaFile.close();
       } catch (IOException e) {
         Logging.e(TAG, "Problem closing file", e);
       }
diff --git a/sdk/android/api/org/webrtc/JavaI420Buffer.java b/sdk/android/api/org/webrtc/JavaI420Buffer.java
index 13ed63b..3498faf 100644
--- a/sdk/android/api/org/webrtc/JavaI420Buffer.java
+++ b/sdk/android/api/org/webrtc/JavaI420Buffer.java
@@ -85,7 +85,8 @@
     int uPos = yPos + width * height;
     int vPos = uPos + strideUV * chromaHeight;
 
-    ByteBuffer buffer = ByteBuffer.allocateDirect(width * height + 2 * strideUV * chromaHeight);
+    ByteBuffer buffer =
+        JniCommon.nativeAllocateByteBuffer(width * height + 2 * strideUV * chromaHeight);
 
     buffer.position(yPos);
     buffer.limit(uPos);
@@ -99,8 +100,8 @@
     buffer.limit(vPos + strideUV * chromaHeight);
     ByteBuffer dataV = buffer.slice();
 
-    return new JavaI420Buffer(
-        width, height, dataY, width, dataU, strideUV, dataV, strideUV, null /* releaseCallback */);
+    return new JavaI420Buffer(width, height, dataY, width, dataU, strideUV, dataV, strideUV,
+        () -> { JniCommon.nativeFreeByteBuffer(buffer); });
   }
 
   @Override