blob: 06b433c47d893cee8885f918da79d972681691c8 [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 {
26 private static final String TAG = "VideoFileRenderer";
27
mandermo64e1a322016-10-18 08:47:51 -070028 private final HandlerThread renderThread;
29 private final Object handlerLock = new Object();
30 private final Handler renderThreadHandler;
31 private final FileOutputStream videoOutFile;
mandermoeef94d92017-01-19 09:02:29 -080032 private final String outputFileName;
mandermo64e1a322016-10-18 08:47:51 -070033 private final int outputFileWidth;
34 private final int outputFileHeight;
35 private final int outputFrameSize;
36 private final ByteBuffer outputFrameBuffer;
magjed1cb48232016-10-20 03:19:16 -070037 private EglBase eglBase;
38 private YuvConverter yuvConverter;
mandermoeef94d92017-01-19 09:02:29 -080039 private ArrayList<ByteBuffer> rawFrames = new ArrayList<>();
mandermo64e1a322016-10-18 08:47:51 -070040
41 public VideoFileRenderer(String outputFile, int outputFileWidth, int outputFileHeight,
magjed1cb48232016-10-20 03:19:16 -070042 final EglBase.Context sharedContext) throws IOException {
mandermo64e1a322016-10-18 08:47:51 -070043 if ((outputFileWidth % 2) == 1 || (outputFileHeight % 2) == 1) {
44 throw new IllegalArgumentException("Does not support uneven width or height");
45 }
mandermo64e1a322016-10-18 08:47:51 -070046
mandermoeef94d92017-01-19 09:02:29 -080047 this.outputFileName = outputFile;
mandermo64e1a322016-10-18 08:47:51 -070048 this.outputFileWidth = outputFileWidth;
49 this.outputFileHeight = outputFileHeight;
50
51 outputFrameSize = outputFileWidth * outputFileHeight * 3 / 2;
52 outputFrameBuffer = ByteBuffer.allocateDirect(outputFrameSize);
53
54 videoOutFile = new FileOutputStream(outputFile);
55 videoOutFile.write(
56 ("YUV4MPEG2 C420 W" + outputFileWidth + " H" + outputFileHeight + " Ip F30:1 A1:1\n")
Sami Kalliomäkibde473e2017-10-30 13:34:41 +010057 .getBytes(Charset.forName("US-ASCII")));
mandermo64e1a322016-10-18 08:47:51 -070058
59 renderThread = new HandlerThread(TAG);
60 renderThread.start();
61 renderThreadHandler = new Handler(renderThread.getLooper());
magjed1cb48232016-10-20 03:19:16 -070062
63 ThreadUtils.invokeAtFrontUninterruptibly(renderThreadHandler, new Runnable() {
64 @Override
65 public void run() {
66 eglBase = EglBase.create(sharedContext, EglBase.CONFIG_PIXEL_BUFFER);
67 eglBase.createDummyPbufferSurface();
68 eglBase.makeCurrent();
69 yuvConverter = new YuvConverter();
70 }
71 });
mandermo64e1a322016-10-18 08:47:51 -070072 }
73
74 @Override
75 public void renderFrame(final VideoRenderer.I420Frame frame) {
76 renderThreadHandler.post(new Runnable() {
77 @Override
78 public void run() {
79 renderFrameOnRenderThread(frame);
80 }
81 });
82 }
83
Sami Kalliomäki9828beb2017-10-26 16:21:22 +020084 // TODO(sakal): yuvConverter.convert is deprecated. This will be removed once this file is updated
85 // to implement VideoSink instead of VideoRenderer.Callbacks.
86 @SuppressWarnings("deprecation")
mandermo64e1a322016-10-18 08:47:51 -070087 private void renderFrameOnRenderThread(VideoRenderer.I420Frame frame) {
88 final float frameAspectRatio = (float) frame.rotatedWidth() / (float) frame.rotatedHeight();
89
90 final float[] rotatedSamplingMatrix =
91 RendererCommon.rotateTextureMatrix(frame.samplingMatrix, frame.rotationDegree);
92 final float[] layoutMatrix = RendererCommon.getLayoutMatrix(
93 false, frameAspectRatio, (float) outputFileWidth / outputFileHeight);
94 final float[] texMatrix = RendererCommon.multiplyMatrices(rotatedSamplingMatrix, layoutMatrix);
95
96 try {
Sami Kalliomäkicb98b112017-10-16 11:20:26 +020097 ByteBuffer buffer = JniCommon.allocateNativeByteBuffer(outputFrameSize);
mandermo64e1a322016-10-18 08:47:51 -070098 if (!frame.yuvFrame) {
99 yuvConverter.convert(outputFrameBuffer, outputFileWidth, outputFileHeight, outputFileWidth,
100 frame.textureId, texMatrix);
101
102 int stride = outputFileWidth;
103 byte[] data = outputFrameBuffer.array();
104 int offset = outputFrameBuffer.arrayOffset();
105
106 // Write Y
mandermoeef94d92017-01-19 09:02:29 -0800107 buffer.put(data, offset, outputFileWidth * outputFileHeight);
mandermo64e1a322016-10-18 08:47:51 -0700108
109 // Write U
110 for (int r = outputFileHeight; r < outputFileHeight * 3 / 2; ++r) {
mandermoeef94d92017-01-19 09:02:29 -0800111 buffer.put(data, offset + r * stride, stride / 2);
mandermo64e1a322016-10-18 08:47:51 -0700112 }
113
114 // Write V
115 for (int r = outputFileHeight; r < outputFileHeight * 3 / 2; ++r) {
mandermoeef94d92017-01-19 09:02:29 -0800116 buffer.put(data, offset + r * stride + stride / 2, stride / 2);
mandermo64e1a322016-10-18 08:47:51 -0700117 }
118 } else {
119 nativeI420Scale(frame.yuvPlanes[0], frame.yuvStrides[0], frame.yuvPlanes[1],
120 frame.yuvStrides[1], frame.yuvPlanes[2], frame.yuvStrides[2], frame.width, frame.height,
121 outputFrameBuffer, outputFileWidth, outputFileHeight);
mandermoeef94d92017-01-19 09:02:29 -0800122
123 buffer.put(outputFrameBuffer.array(), outputFrameBuffer.arrayOffset(), outputFrameSize);
mandermo64e1a322016-10-18 08:47:51 -0700124 }
mandermoeef94d92017-01-19 09:02:29 -0800125 buffer.rewind();
126 rawFrames.add(buffer);
mandermo64e1a322016-10-18 08:47:51 -0700127 } finally {
128 VideoRenderer.renderFrameDone(frame);
129 }
130 }
131
Magnus Jedvert894c4002016-10-21 15:05:01 +0200132 /**
133 * Release all resources. All already posted frames will be rendered first.
134 */
mandermo64e1a322016-10-18 08:47:51 -0700135 public void release() {
Magnus Jedvert894c4002016-10-21 15:05:01 +0200136 final CountDownLatch cleanupBarrier = new CountDownLatch(1);
137 renderThreadHandler.post(new Runnable() {
mandermo64e1a322016-10-18 08:47:51 -0700138 @Override
139 public void run() {
magjed1cb48232016-10-20 03:19:16 -0700140 yuvConverter.release();
141 eglBase.release();
142 renderThread.quit();
Magnus Jedvert894c4002016-10-21 15:05:01 +0200143 cleanupBarrier.countDown();
mandermo64e1a322016-10-18 08:47:51 -0700144 }
145 });
Magnus Jedvert894c4002016-10-21 15:05:01 +0200146 ThreadUtils.awaitUninterruptibly(cleanupBarrier);
mandermoeef94d92017-01-19 09:02:29 -0800147 try {
148 for (ByteBuffer buffer : rawFrames) {
Sami Kalliomäkibde473e2017-10-30 13:34:41 +0100149 videoOutFile.write("FRAME\n".getBytes(Charset.forName("US-ASCII")));
mandermoeef94d92017-01-19 09:02:29 -0800150
151 byte[] data = new byte[outputFrameSize];
152 buffer.get(data);
153
154 videoOutFile.write(data);
155
Sami Kalliomäkicb98b112017-10-16 11:20:26 +0200156 JniCommon.freeNativeByteBuffer(buffer);
mandermoeef94d92017-01-19 09:02:29 -0800157 }
158 videoOutFile.close();
159 Logging.d(TAG, "Video written to disk as " + outputFileName + ". Number frames are "
160 + rawFrames.size() + " and the dimension of the frames are " + outputFileWidth + "x"
161 + outputFileHeight + ".");
162 } catch (IOException e) {
163 Logging.e(TAG, "Error writing video to disk", e);
164 }
mandermo64e1a322016-10-18 08:47:51 -0700165 }
166
167 public static native void nativeI420Scale(ByteBuffer srcY, int strideY, ByteBuffer srcU,
168 int strideU, ByteBuffer srcV, int strideV, int width, int height, ByteBuffer dst,
169 int dstWidth, int dstHeight);
mandermo64e1a322016-10-18 08:47:51 -0700170}