From eef94d99952f64e543075a417e54d476067f32d2 Mon Sep 17 00:00:00 2001 From: mandermo Date: Thu, 19 Jan 2017 09:02:29 -0800 Subject: [PATCH] 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-Commit-Position: refs/heads/master@{#16167} --- .../api/org/webrtc/VideoFileRenderer.java | 48 +++++++++++++------ .../sdk/android/src/jni/peerconnection_jni.cc | 13 +++++ 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/webrtc/sdk/android/api/org/webrtc/VideoFileRenderer.java b/webrtc/sdk/android/api/org/webrtc/VideoFileRenderer.java index f4ffbcace8..02a4a3fd0f 100644 --- a/webrtc/sdk/android/api/org/webrtc/VideoFileRenderer.java +++ b/webrtc/sdk/android/api/org/webrtc/VideoFileRenderer.java @@ -16,6 +16,7 @@ import java.io.FileOutputStream; 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 @@ public class VideoFileRenderer implements VideoRenderer.Callbacks { 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 rawFrames = new ArrayList<>(); public VideoFileRenderer(String outputFile, int outputFileWidth, int outputFileHeight, final EglBase.Context sharedContext) throws IOException { @@ -40,6 +43,7 @@ public class VideoFileRenderer implements VideoRenderer.Callbacks { throw new IllegalArgumentException("Does not support uneven width or height"); } + this.outputFileName = outputFile; this.outputFileWidth = outputFileWidth; this.outputFileHeight = outputFileHeight; @@ -86,7 +90,7 @@ public class VideoFileRenderer implements VideoRenderer.Callbacks { 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 @@ public class VideoFileRenderer implements VideoRenderer.Callbacks { 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 @@ public class VideoFileRenderer implements VideoRenderer.Callbacks { 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 @@ public class VideoFileRenderer implements VideoRenderer.Callbacks { } }); 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/webrtc/sdk/android/src/jni/peerconnection_jni.cc b/webrtc/sdk/android/src/jni/peerconnection_jni.cc index 4d98414193..8c38a4689b 100644 --- a/webrtc/sdk/android/src/jni/peerconnection_jni.cc +++ b/webrtc/sdk/android/src/jni/peerconnection_jni.cc @@ -2237,6 +2237,19 @@ JOW(void, VideoFileRenderer_nativeI420Scale)( } } +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(j_p)->id());