blob: 20ca839495595eedf9a4d57dfde966ebd2d60f2a [file] [log] [blame]
mandermo64e1a322016-10-18 08:47:51 -07001/*
2 * Copyright 2016 The WebRTC Project Authors. All rights reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
sakale1674ef2017-01-11 06:22:56 -080010
mandermo64e1a322016-10-18 08:47:51 -070011package org.webrtc;
12
13import android.os.Handler;
14import android.os.HandlerThread;
mandermo64e1a322016-10-18 08:47:51 -070015import java.io.FileOutputStream;
16import java.io.IOException;
Magnus Jedvert894c4002016-10-21 15:05:01 +020017import java.nio.ByteBuffer;
Sami Kalliomäkibde473e2017-10-30 13:34:41 +010018import java.nio.charset.Charset;
mandermoeef94d92017-01-19 09:02:29 -080019import java.util.ArrayList;
Sami Kalliomäkicb98b112017-10-16 11:20:26 +020020import java.util.concurrent.CountDownLatch;
mandermo64e1a322016-10-18 08:47:51 -070021
22/**
23 * Can be used to save the video frames to file.
24 */
Magnus Jedvert84d8ae52017-12-20 15:12:10 +010025@JNINamespace("webrtc::jni")
mandermo64e1a322016-10-18 08:47:51 -070026public class VideoFileRenderer implements VideoRenderer.Callbacks {
27 private static final String TAG = "VideoFileRenderer";
28
mandermo64e1a322016-10-18 08:47:51 -070029 private final HandlerThread renderThread;
30 private final Object handlerLock = new Object();
31 private final Handler renderThreadHandler;
32 private final FileOutputStream videoOutFile;
mandermoeef94d92017-01-19 09:02:29 -080033 private final String outputFileName;
mandermo64e1a322016-10-18 08:47:51 -070034 private final int outputFileWidth;
35 private final int outputFileHeight;
36 private final int outputFrameSize;
37 private final ByteBuffer outputFrameBuffer;
magjed1cb48232016-10-20 03:19:16 -070038 private EglBase eglBase;
39 private YuvConverter yuvConverter;
mandermoeef94d92017-01-19 09:02:29 -080040 private ArrayList<ByteBuffer> rawFrames = new ArrayList<>();
mandermo64e1a322016-10-18 08:47:51 -070041
42 public VideoFileRenderer(String outputFile, int outputFileWidth, int outputFileHeight,
magjed1cb48232016-10-20 03:19:16 -070043 final EglBase.Context sharedContext) throws IOException {
mandermo64e1a322016-10-18 08:47:51 -070044 if ((outputFileWidth % 2) == 1 || (outputFileHeight % 2) == 1) {
45 throw new IllegalArgumentException("Does not support uneven width or height");
46 }
mandermo64e1a322016-10-18 08:47:51 -070047
mandermoeef94d92017-01-19 09:02:29 -080048 this.outputFileName = outputFile;
mandermo64e1a322016-10-18 08:47:51 -070049 this.outputFileWidth = outputFileWidth;
50 this.outputFileHeight = outputFileHeight;
51
52 outputFrameSize = outputFileWidth * outputFileHeight * 3 / 2;
53 outputFrameBuffer = ByteBuffer.allocateDirect(outputFrameSize);
54
55 videoOutFile = new FileOutputStream(outputFile);
56 videoOutFile.write(
57 ("YUV4MPEG2 C420 W" + outputFileWidth + " H" + outputFileHeight + " Ip F30:1 A1:1\n")
Sami Kalliomäkibde473e2017-10-30 13:34:41 +010058 .getBytes(Charset.forName("US-ASCII")));
mandermo64e1a322016-10-18 08:47:51 -070059
60 renderThread = new HandlerThread(TAG);
61 renderThread.start();
62 renderThreadHandler = new Handler(renderThread.getLooper());
magjed1cb48232016-10-20 03:19:16 -070063
64 ThreadUtils.invokeAtFrontUninterruptibly(renderThreadHandler, new Runnable() {
65 @Override
66 public void run() {
67 eglBase = EglBase.create(sharedContext, EglBase.CONFIG_PIXEL_BUFFER);
68 eglBase.createDummyPbufferSurface();
69 eglBase.makeCurrent();
70 yuvConverter = new YuvConverter();
71 }
72 });
mandermo64e1a322016-10-18 08:47:51 -070073 }
74
75 @Override
76 public void renderFrame(final VideoRenderer.I420Frame frame) {
77 renderThreadHandler.post(new Runnable() {
78 @Override
79 public void run() {
80 renderFrameOnRenderThread(frame);
81 }
82 });
83 }
84
Sami Kalliomäki9828beb2017-10-26 16:21:22 +020085 // TODO(sakal): yuvConverter.convert is deprecated. This will be removed once this file is updated
86 // to implement VideoSink instead of VideoRenderer.Callbacks.
87 @SuppressWarnings("deprecation")
mandermo64e1a322016-10-18 08:47:51 -070088 private void renderFrameOnRenderThread(VideoRenderer.I420Frame frame) {
89 final float frameAspectRatio = (float) frame.rotatedWidth() / (float) frame.rotatedHeight();
90
91 final float[] rotatedSamplingMatrix =
92 RendererCommon.rotateTextureMatrix(frame.samplingMatrix, frame.rotationDegree);
93 final float[] layoutMatrix = RendererCommon.getLayoutMatrix(
94 false, frameAspectRatio, (float) outputFileWidth / outputFileHeight);
95 final float[] texMatrix = RendererCommon.multiplyMatrices(rotatedSamplingMatrix, layoutMatrix);
96
97 try {
Magnus Jedvert84d8ae52017-12-20 15:12:10 +010098 ByteBuffer buffer = JniCommon.nativeAllocateByteBuffer(outputFrameSize);
mandermo64e1a322016-10-18 08:47:51 -070099 if (!frame.yuvFrame) {
100 yuvConverter.convert(outputFrameBuffer, outputFileWidth, outputFileHeight, outputFileWidth,
101 frame.textureId, texMatrix);
102
103 int stride = outputFileWidth;
104 byte[] data = outputFrameBuffer.array();
105 int offset = outputFrameBuffer.arrayOffset();
106
107 // Write Y
mandermoeef94d92017-01-19 09:02:29 -0800108 buffer.put(data, offset, outputFileWidth * outputFileHeight);
mandermo64e1a322016-10-18 08:47:51 -0700109
110 // Write U
111 for (int r = outputFileHeight; r < outputFileHeight * 3 / 2; ++r) {
mandermoeef94d92017-01-19 09:02:29 -0800112 buffer.put(data, offset + r * stride, stride / 2);
mandermo64e1a322016-10-18 08:47:51 -0700113 }
114
115 // Write V
116 for (int r = outputFileHeight; r < outputFileHeight * 3 / 2; ++r) {
mandermoeef94d92017-01-19 09:02:29 -0800117 buffer.put(data, offset + r * stride + stride / 2, stride / 2);
mandermo64e1a322016-10-18 08:47:51 -0700118 }
119 } else {
120 nativeI420Scale(frame.yuvPlanes[0], frame.yuvStrides[0], frame.yuvPlanes[1],
121 frame.yuvStrides[1], frame.yuvPlanes[2], frame.yuvStrides[2], frame.width, frame.height,
122 outputFrameBuffer, outputFileWidth, outputFileHeight);
mandermoeef94d92017-01-19 09:02:29 -0800123
124 buffer.put(outputFrameBuffer.array(), outputFrameBuffer.arrayOffset(), outputFrameSize);
mandermo64e1a322016-10-18 08:47:51 -0700125 }
mandermoeef94d92017-01-19 09:02:29 -0800126 buffer.rewind();
127 rawFrames.add(buffer);
mandermo64e1a322016-10-18 08:47:51 -0700128 } finally {
129 VideoRenderer.renderFrameDone(frame);
130 }
131 }
132
Magnus Jedvert894c4002016-10-21 15:05:01 +0200133 /**
134 * Release all resources. All already posted frames will be rendered first.
135 */
mandermo64e1a322016-10-18 08:47:51 -0700136 public void release() {
Magnus Jedvert894c4002016-10-21 15:05:01 +0200137 final CountDownLatch cleanupBarrier = new CountDownLatch(1);
138 renderThreadHandler.post(new Runnable() {
mandermo64e1a322016-10-18 08:47:51 -0700139 @Override
140 public void run() {
magjed1cb48232016-10-20 03:19:16 -0700141 yuvConverter.release();
142 eglBase.release();
143 renderThread.quit();
Magnus Jedvert894c4002016-10-21 15:05:01 +0200144 cleanupBarrier.countDown();
mandermo64e1a322016-10-18 08:47:51 -0700145 }
146 });
Magnus Jedvert894c4002016-10-21 15:05:01 +0200147 ThreadUtils.awaitUninterruptibly(cleanupBarrier);
mandermoeef94d92017-01-19 09:02:29 -0800148 try {
149 for (ByteBuffer buffer : rawFrames) {
Sami Kalliomäkibde473e2017-10-30 13:34:41 +0100150 videoOutFile.write("FRAME\n".getBytes(Charset.forName("US-ASCII")));
mandermoeef94d92017-01-19 09:02:29 -0800151
152 byte[] data = new byte[outputFrameSize];
153 buffer.get(data);
154
155 videoOutFile.write(data);
156
Magnus Jedvert84d8ae52017-12-20 15:12:10 +0100157 JniCommon.nativeFreeByteBuffer(buffer);
mandermoeef94d92017-01-19 09:02:29 -0800158 }
159 videoOutFile.close();
160 Logging.d(TAG, "Video written to disk as " + outputFileName + ". Number frames are "
161 + rawFrames.size() + " and the dimension of the frames are " + outputFileWidth + "x"
162 + outputFileHeight + ".");
163 } catch (IOException e) {
164 Logging.e(TAG, "Error writing video to disk", e);
165 }
mandermo64e1a322016-10-18 08:47:51 -0700166 }
167
168 public static native void nativeI420Scale(ByteBuffer srcY, int strideY, ByteBuffer srcU,
169 int strideU, ByteBuffer srcV, int strideV, int width, int height, ByteBuffer dst,
170 int dstWidth, int dstHeight);
mandermo64e1a322016-10-18 08:47:51 -0700171}