blob: 2f2e422618e5c6f02f25b6eabebd9cd281d56909 [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 */
25public class VideoFileRenderer implements VideoRenderer.Callbacks {
magjed282d49f2017-04-13 04:17:02 -070026 static {
27 System.loadLibrary("jingle_peerconnection_so");
28 }
29
mandermo64e1a322016-10-18 08:47:51 -070030 private static final String TAG = "VideoFileRenderer";
31
mandermo64e1a322016-10-18 08:47:51 -070032 private final HandlerThread renderThread;
33 private final Object handlerLock = new Object();
34 private final Handler renderThreadHandler;
35 private final FileOutputStream videoOutFile;
mandermoeef94d92017-01-19 09:02:29 -080036 private final String outputFileName;
mandermo64e1a322016-10-18 08:47:51 -070037 private final int outputFileWidth;
38 private final int outputFileHeight;
39 private final int outputFrameSize;
40 private final ByteBuffer outputFrameBuffer;
magjed1cb48232016-10-20 03:19:16 -070041 private EglBase eglBase;
42 private YuvConverter yuvConverter;
mandermoeef94d92017-01-19 09:02:29 -080043 private ArrayList<ByteBuffer> rawFrames = new ArrayList<>();
mandermo64e1a322016-10-18 08:47:51 -070044
45 public VideoFileRenderer(String outputFile, int outputFileWidth, int outputFileHeight,
magjed1cb48232016-10-20 03:19:16 -070046 final EglBase.Context sharedContext) throws IOException {
mandermo64e1a322016-10-18 08:47:51 -070047 if ((outputFileWidth % 2) == 1 || (outputFileHeight % 2) == 1) {
48 throw new IllegalArgumentException("Does not support uneven width or height");
49 }
mandermo64e1a322016-10-18 08:47:51 -070050
mandermoeef94d92017-01-19 09:02:29 -080051 this.outputFileName = outputFile;
mandermo64e1a322016-10-18 08:47:51 -070052 this.outputFileWidth = outputFileWidth;
53 this.outputFileHeight = outputFileHeight;
54
55 outputFrameSize = outputFileWidth * outputFileHeight * 3 / 2;
56 outputFrameBuffer = ByteBuffer.allocateDirect(outputFrameSize);
57
58 videoOutFile = new FileOutputStream(outputFile);
59 videoOutFile.write(
60 ("YUV4MPEG2 C420 W" + outputFileWidth + " H" + outputFileHeight + " Ip F30:1 A1:1\n")
Sami Kalliomäkibde473e2017-10-30 13:34:41 +010061 .getBytes(Charset.forName("US-ASCII")));
mandermo64e1a322016-10-18 08:47:51 -070062
63 renderThread = new HandlerThread(TAG);
64 renderThread.start();
65 renderThreadHandler = new Handler(renderThread.getLooper());
magjed1cb48232016-10-20 03:19:16 -070066
67 ThreadUtils.invokeAtFrontUninterruptibly(renderThreadHandler, new Runnable() {
68 @Override
69 public void run() {
70 eglBase = EglBase.create(sharedContext, EglBase.CONFIG_PIXEL_BUFFER);
71 eglBase.createDummyPbufferSurface();
72 eglBase.makeCurrent();
73 yuvConverter = new YuvConverter();
74 }
75 });
mandermo64e1a322016-10-18 08:47:51 -070076 }
77
78 @Override
79 public void renderFrame(final VideoRenderer.I420Frame frame) {
80 renderThreadHandler.post(new Runnable() {
81 @Override
82 public void run() {
83 renderFrameOnRenderThread(frame);
84 }
85 });
86 }
87
Sami Kalliomäki9828beb2017-10-26 16:21:22 +020088 // TODO(sakal): yuvConverter.convert is deprecated. This will be removed once this file is updated
89 // to implement VideoSink instead of VideoRenderer.Callbacks.
90 @SuppressWarnings("deprecation")
mandermo64e1a322016-10-18 08:47:51 -070091 private void renderFrameOnRenderThread(VideoRenderer.I420Frame frame) {
92 final float frameAspectRatio = (float) frame.rotatedWidth() / (float) frame.rotatedHeight();
93
94 final float[] rotatedSamplingMatrix =
95 RendererCommon.rotateTextureMatrix(frame.samplingMatrix, frame.rotationDegree);
96 final float[] layoutMatrix = RendererCommon.getLayoutMatrix(
97 false, frameAspectRatio, (float) outputFileWidth / outputFileHeight);
98 final float[] texMatrix = RendererCommon.multiplyMatrices(rotatedSamplingMatrix, layoutMatrix);
99
100 try {
Sami Kalliomäkicb98b112017-10-16 11:20:26 +0200101 ByteBuffer buffer = JniCommon.allocateNativeByteBuffer(outputFrameSize);
mandermo64e1a322016-10-18 08:47:51 -0700102 if (!frame.yuvFrame) {
103 yuvConverter.convert(outputFrameBuffer, outputFileWidth, outputFileHeight, outputFileWidth,
104 frame.textureId, texMatrix);
105
106 int stride = outputFileWidth;
107 byte[] data = outputFrameBuffer.array();
108 int offset = outputFrameBuffer.arrayOffset();
109
110 // Write Y
mandermoeef94d92017-01-19 09:02:29 -0800111 buffer.put(data, offset, outputFileWidth * outputFileHeight);
mandermo64e1a322016-10-18 08:47:51 -0700112
113 // Write U
114 for (int r = outputFileHeight; r < outputFileHeight * 3 / 2; ++r) {
mandermoeef94d92017-01-19 09:02:29 -0800115 buffer.put(data, offset + r * stride, stride / 2);
mandermo64e1a322016-10-18 08:47:51 -0700116 }
117
118 // Write V
119 for (int r = outputFileHeight; r < outputFileHeight * 3 / 2; ++r) {
mandermoeef94d92017-01-19 09:02:29 -0800120 buffer.put(data, offset + r * stride + stride / 2, stride / 2);
mandermo64e1a322016-10-18 08:47:51 -0700121 }
122 } else {
123 nativeI420Scale(frame.yuvPlanes[0], frame.yuvStrides[0], frame.yuvPlanes[1],
124 frame.yuvStrides[1], frame.yuvPlanes[2], frame.yuvStrides[2], frame.width, frame.height,
125 outputFrameBuffer, outputFileWidth, outputFileHeight);
mandermoeef94d92017-01-19 09:02:29 -0800126
127 buffer.put(outputFrameBuffer.array(), outputFrameBuffer.arrayOffset(), outputFrameSize);
mandermo64e1a322016-10-18 08:47:51 -0700128 }
mandermoeef94d92017-01-19 09:02:29 -0800129 buffer.rewind();
130 rawFrames.add(buffer);
mandermo64e1a322016-10-18 08:47:51 -0700131 } finally {
132 VideoRenderer.renderFrameDone(frame);
133 }
134 }
135
Magnus Jedvert894c4002016-10-21 15:05:01 +0200136 /**
137 * Release all resources. All already posted frames will be rendered first.
138 */
mandermo64e1a322016-10-18 08:47:51 -0700139 public void release() {
Magnus Jedvert894c4002016-10-21 15:05:01 +0200140 final CountDownLatch cleanupBarrier = new CountDownLatch(1);
141 renderThreadHandler.post(new Runnable() {
mandermo64e1a322016-10-18 08:47:51 -0700142 @Override
143 public void run() {
magjed1cb48232016-10-20 03:19:16 -0700144 yuvConverter.release();
145 eglBase.release();
146 renderThread.quit();
Magnus Jedvert894c4002016-10-21 15:05:01 +0200147 cleanupBarrier.countDown();
mandermo64e1a322016-10-18 08:47:51 -0700148 }
149 });
Magnus Jedvert894c4002016-10-21 15:05:01 +0200150 ThreadUtils.awaitUninterruptibly(cleanupBarrier);
mandermoeef94d92017-01-19 09:02:29 -0800151 try {
152 for (ByteBuffer buffer : rawFrames) {
Sami Kalliomäkibde473e2017-10-30 13:34:41 +0100153 videoOutFile.write("FRAME\n".getBytes(Charset.forName("US-ASCII")));
mandermoeef94d92017-01-19 09:02:29 -0800154
155 byte[] data = new byte[outputFrameSize];
156 buffer.get(data);
157
158 videoOutFile.write(data);
159
Sami Kalliomäkicb98b112017-10-16 11:20:26 +0200160 JniCommon.freeNativeByteBuffer(buffer);
mandermoeef94d92017-01-19 09:02:29 -0800161 }
162 videoOutFile.close();
163 Logging.d(TAG, "Video written to disk as " + outputFileName + ". Number frames are "
164 + rawFrames.size() + " and the dimension of the frames are " + outputFileWidth + "x"
165 + outputFileHeight + ".");
166 } catch (IOException e) {
167 Logging.e(TAG, "Error writing video to disk", e);
168 }
mandermo64e1a322016-10-18 08:47:51 -0700169 }
170
171 public static native void nativeI420Scale(ByteBuffer srcY, int strideY, ByteBuffer srcU,
172 int strideU, ByteBuffer srcV, int strideV, int width, int height, ByteBuffer dst,
173 int dstWidth, int dstHeight);
mandermo64e1a322016-10-18 08:47:51 -0700174}