Video collected by VideoFileRenderer is first saved on the native heap, then saved to disk during release.

BUG=webrtc:6545

Review-Url: https://codereview.webrtc.org/2576283004
Cr-Original-Commit-Position: refs/heads/master@{#16167}
Cr-Mirrored-From: https://chromium.googlesource.com/external/webrtc
Cr-Mirrored-Commit: eef94d99952f64e543075a417e54d476067f32d2
diff --git a/sdk/android/api/org/webrtc/VideoFileRenderer.java b/sdk/android/api/org/webrtc/VideoFileRenderer.java
index f4ffbca..02a4a3f 100644
--- a/sdk/android/api/org/webrtc/VideoFileRenderer.java
+++ b/sdk/android/api/org/webrtc/VideoFileRenderer.java
@@ -16,6 +16,7 @@
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.util.concurrent.CountDownLatch;
+import java.util.ArrayList;
 
 /**
  * Can be used to save the video frames to file.
@@ -27,12 +28,14 @@
   private final Object handlerLock = new Object();
   private final Handler renderThreadHandler;
   private final FileOutputStream videoOutFile;
+  private final String outputFileName;
   private final int outputFileWidth;
   private final int outputFileHeight;
   private final int outputFrameSize;
   private final ByteBuffer outputFrameBuffer;
   private EglBase eglBase;
   private YuvConverter yuvConverter;
+  private ArrayList<ByteBuffer> rawFrames = new ArrayList<>();
 
   public VideoFileRenderer(String outputFile, int outputFileWidth, int outputFileHeight,
       final EglBase.Context sharedContext) throws IOException {
@@ -40,6 +43,7 @@
       throw new IllegalArgumentException("Does not support uneven width or height");
     }
 
+    this.outputFileName = outputFile;
     this.outputFileWidth = outputFileWidth;
     this.outputFileHeight = outputFileHeight;
 
@@ -86,7 +90,7 @@
     final float[] texMatrix = RendererCommon.multiplyMatrices(rotatedSamplingMatrix, layoutMatrix);
 
     try {
-      videoOutFile.write("FRAME\n".getBytes());
+      ByteBuffer buffer = nativeCreateNativeByteBuffer(outputFrameSize);
       if (!frame.yuvFrame) {
         yuvConverter.convert(outputFrameBuffer, outputFileWidth, outputFileHeight, outputFileWidth,
             frame.textureId, texMatrix);
@@ -96,27 +100,26 @@
         int offset = outputFrameBuffer.arrayOffset();
 
         // Write Y
-        videoOutFile.write(data, offset, outputFileWidth * outputFileHeight);
+        buffer.put(data, offset, outputFileWidth * outputFileHeight);
 
         // Write U
         for (int r = outputFileHeight; r < outputFileHeight * 3 / 2; ++r) {
-          videoOutFile.write(data, offset + r * stride, stride / 2);
+          buffer.put(data, offset + r * stride, stride / 2);
         }
 
         // Write V
         for (int r = outputFileHeight; r < outputFileHeight * 3 / 2; ++r) {
-          videoOutFile.write(data, offset + r * stride + stride / 2, stride / 2);
+          buffer.put(data, offset + r * stride + stride / 2, stride / 2);
         }
       } else {
         nativeI420Scale(frame.yuvPlanes[0], frame.yuvStrides[0], frame.yuvPlanes[1],
             frame.yuvStrides[1], frame.yuvPlanes[2], frame.yuvStrides[2], frame.width, frame.height,
             outputFrameBuffer, outputFileWidth, outputFileHeight);
-        videoOutFile.write(
-            outputFrameBuffer.array(), outputFrameBuffer.arrayOffset(), outputFrameSize);
+
+        buffer.put(outputFrameBuffer.array(), outputFrameBuffer.arrayOffset(), outputFrameSize);
       }
-    } catch (IOException e) {
-      Logging.e(TAG, "Failed to write to file for video out");
-      throw new RuntimeException(e);
+      buffer.rewind();
+      rawFrames.add(buffer);
     } finally {
       VideoRenderer.renderFrameDone(frame);
     }
@@ -130,11 +133,6 @@
     renderThreadHandler.post(new Runnable() {
       @Override
       public void run() {
-        try {
-          videoOutFile.close();
-        } catch (IOException e) {
-          Logging.d(TAG, "Error closing output video file");
-        }
         yuvConverter.release();
         eglBase.release();
         renderThread.quit();
@@ -142,9 +140,31 @@
       }
     });
     ThreadUtils.awaitUninterruptibly(cleanupBarrier);
+    try {
+      for (ByteBuffer buffer : rawFrames) {
+        videoOutFile.write("FRAME\n".getBytes());
+
+        byte[] data = new byte[outputFrameSize];
+        buffer.get(data);
+
+        videoOutFile.write(data);
+
+        nativeFreeNativeByteBuffer(buffer);
+      }
+      videoOutFile.close();
+      Logging.d(TAG, "Video written to disk as " + outputFileName + ". Number frames are "
+              + rawFrames.size() + " and the dimension of the frames are " + outputFileWidth + "x"
+              + outputFileHeight + ".");
+    } catch (IOException e) {
+      Logging.e(TAG, "Error writing video to disk", e);
+    }
   }
 
   public static native void nativeI420Scale(ByteBuffer srcY, int strideY, ByteBuffer srcU,
       int strideU, ByteBuffer srcV, int strideV, int width, int height, ByteBuffer dst,
       int dstWidth, int dstHeight);
+
+  public static native ByteBuffer nativeCreateNativeByteBuffer(int size);
+
+  public static native void nativeFreeNativeByteBuffer(ByteBuffer buffer);
 }
diff --git a/sdk/android/src/jni/peerconnection_jni.cc b/sdk/android/src/jni/peerconnection_jni.cc
index 4d98414..8c38a46 100644
--- a/sdk/android/src/jni/peerconnection_jni.cc
+++ b/sdk/android/src/jni/peerconnection_jni.cc
@@ -2237,6 +2237,19 @@
   }
 }
 
+JOW(jobject, VideoFileRenderer_nativeCreateNativeByteBuffer)
+(JNIEnv* jni, jclass, jint size) {
+  void* new_data = ::operator new(size);
+  jobject byte_buffer = jni->NewDirectByteBuffer(new_data, size);
+  return byte_buffer;
+}
+
+JOW(void, VideoFileRenderer_nativeFreeNativeByteBuffer)
+(JNIEnv* jni, jclass, jobject byte_buffer) {
+  void* data = jni->GetDirectBufferAddress(byte_buffer);
+  ::operator delete(data);
+}
+
 JOW(jstring, MediaStreamTrack_nativeId)(JNIEnv* jni, jclass, jlong j_p) {
   return JavaStringFromStdString(
       jni, reinterpret_cast<MediaStreamTrackInterface*>(j_p)->id());