blob: e7a6471124a9cd53f4c9d1fe0e74089e4169a89a [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;
mandermoeef94d92017-01-19 09:02:29 -080018import java.util.ArrayList;
Sami Kalliomäkicb98b112017-10-16 11:20:26 +020019import java.util.concurrent.CountDownLatch;
mandermo64e1a322016-10-18 08:47:51 -070020
21/**
22 * Can be used to save the video frames to file.
23 */
24public class VideoFileRenderer implements VideoRenderer.Callbacks {
magjed282d49f2017-04-13 04:17:02 -070025 static {
26 System.loadLibrary("jingle_peerconnection_so");
27 }
28
mandermo64e1a322016-10-18 08:47:51 -070029 private static final String TAG = "VideoFileRenderer";
30
mandermo64e1a322016-10-18 08:47:51 -070031 private final HandlerThread renderThread;
32 private final Object handlerLock = new Object();
33 private final Handler renderThreadHandler;
34 private final FileOutputStream videoOutFile;
mandermoeef94d92017-01-19 09:02:29 -080035 private final String outputFileName;
mandermo64e1a322016-10-18 08:47:51 -070036 private final int outputFileWidth;
37 private final int outputFileHeight;
38 private final int outputFrameSize;
39 private final ByteBuffer outputFrameBuffer;
magjed1cb48232016-10-20 03:19:16 -070040 private EglBase eglBase;
41 private YuvConverter yuvConverter;
mandermoeef94d92017-01-19 09:02:29 -080042 private ArrayList<ByteBuffer> rawFrames = new ArrayList<>();
mandermo64e1a322016-10-18 08:47:51 -070043
44 public VideoFileRenderer(String outputFile, int outputFileWidth, int outputFileHeight,
magjed1cb48232016-10-20 03:19:16 -070045 final EglBase.Context sharedContext) throws IOException {
mandermo64e1a322016-10-18 08:47:51 -070046 if ((outputFileWidth % 2) == 1 || (outputFileHeight % 2) == 1) {
47 throw new IllegalArgumentException("Does not support uneven width or height");
48 }
mandermo64e1a322016-10-18 08:47:51 -070049
mandermoeef94d92017-01-19 09:02:29 -080050 this.outputFileName = outputFile;
mandermo64e1a322016-10-18 08:47:51 -070051 this.outputFileWidth = outputFileWidth;
52 this.outputFileHeight = outputFileHeight;
53
54 outputFrameSize = outputFileWidth * outputFileHeight * 3 / 2;
55 outputFrameBuffer = ByteBuffer.allocateDirect(outputFrameSize);
56
57 videoOutFile = new FileOutputStream(outputFile);
58 videoOutFile.write(
59 ("YUV4MPEG2 C420 W" + outputFileWidth + " H" + outputFileHeight + " Ip F30:1 A1:1\n")
60 .getBytes());
61
62 renderThread = new HandlerThread(TAG);
63 renderThread.start();
64 renderThreadHandler = new Handler(renderThread.getLooper());
magjed1cb48232016-10-20 03:19:16 -070065
66 ThreadUtils.invokeAtFrontUninterruptibly(renderThreadHandler, new Runnable() {
67 @Override
68 public void run() {
69 eglBase = EglBase.create(sharedContext, EglBase.CONFIG_PIXEL_BUFFER);
70 eglBase.createDummyPbufferSurface();
71 eglBase.makeCurrent();
72 yuvConverter = new YuvConverter();
73 }
74 });
mandermo64e1a322016-10-18 08:47:51 -070075 }
76
77 @Override
78 public void renderFrame(final VideoRenderer.I420Frame frame) {
79 renderThreadHandler.post(new Runnable() {
80 @Override
81 public void run() {
82 renderFrameOnRenderThread(frame);
83 }
84 });
85 }
86
Sami Kalliomäki9828beb2017-10-26 16:21:22 +020087 // TODO(sakal): yuvConverter.convert is deprecated. This will be removed once this file is updated
88 // to implement VideoSink instead of VideoRenderer.Callbacks.
89 @SuppressWarnings("deprecation")
mandermo64e1a322016-10-18 08:47:51 -070090 private void renderFrameOnRenderThread(VideoRenderer.I420Frame frame) {
91 final float frameAspectRatio = (float) frame.rotatedWidth() / (float) frame.rotatedHeight();
92
93 final float[] rotatedSamplingMatrix =
94 RendererCommon.rotateTextureMatrix(frame.samplingMatrix, frame.rotationDegree);
95 final float[] layoutMatrix = RendererCommon.getLayoutMatrix(
96 false, frameAspectRatio, (float) outputFileWidth / outputFileHeight);
97 final float[] texMatrix = RendererCommon.multiplyMatrices(rotatedSamplingMatrix, layoutMatrix);
98
99 try {
Sami Kalliomäkicb98b112017-10-16 11:20:26 +0200100 ByteBuffer buffer = JniCommon.allocateNativeByteBuffer(outputFrameSize);
mandermo64e1a322016-10-18 08:47:51 -0700101 if (!frame.yuvFrame) {
102 yuvConverter.convert(outputFrameBuffer, outputFileWidth, outputFileHeight, outputFileWidth,
103 frame.textureId, texMatrix);
104
105 int stride = outputFileWidth;
106 byte[] data = outputFrameBuffer.array();
107 int offset = outputFrameBuffer.arrayOffset();
108
109 // Write Y
mandermoeef94d92017-01-19 09:02:29 -0800110 buffer.put(data, offset, outputFileWidth * outputFileHeight);
mandermo64e1a322016-10-18 08:47:51 -0700111
112 // Write U
113 for (int r = outputFileHeight; r < outputFileHeight * 3 / 2; ++r) {
mandermoeef94d92017-01-19 09:02:29 -0800114 buffer.put(data, offset + r * stride, stride / 2);
mandermo64e1a322016-10-18 08:47:51 -0700115 }
116
117 // Write V
118 for (int r = outputFileHeight; r < outputFileHeight * 3 / 2; ++r) {
mandermoeef94d92017-01-19 09:02:29 -0800119 buffer.put(data, offset + r * stride + stride / 2, stride / 2);
mandermo64e1a322016-10-18 08:47:51 -0700120 }
121 } else {
122 nativeI420Scale(frame.yuvPlanes[0], frame.yuvStrides[0], frame.yuvPlanes[1],
123 frame.yuvStrides[1], frame.yuvPlanes[2], frame.yuvStrides[2], frame.width, frame.height,
124 outputFrameBuffer, outputFileWidth, outputFileHeight);
mandermoeef94d92017-01-19 09:02:29 -0800125
126 buffer.put(outputFrameBuffer.array(), outputFrameBuffer.arrayOffset(), outputFrameSize);
mandermo64e1a322016-10-18 08:47:51 -0700127 }
mandermoeef94d92017-01-19 09:02:29 -0800128 buffer.rewind();
129 rawFrames.add(buffer);
mandermo64e1a322016-10-18 08:47:51 -0700130 } finally {
131 VideoRenderer.renderFrameDone(frame);
132 }
133 }
134
Magnus Jedvert894c4002016-10-21 15:05:01 +0200135 /**
136 * Release all resources. All already posted frames will be rendered first.
137 */
mandermo64e1a322016-10-18 08:47:51 -0700138 public void release() {
Magnus Jedvert894c4002016-10-21 15:05:01 +0200139 final CountDownLatch cleanupBarrier = new CountDownLatch(1);
140 renderThreadHandler.post(new Runnable() {
mandermo64e1a322016-10-18 08:47:51 -0700141 @Override
142 public void run() {
magjed1cb48232016-10-20 03:19:16 -0700143 yuvConverter.release();
144 eglBase.release();
145 renderThread.quit();
Magnus Jedvert894c4002016-10-21 15:05:01 +0200146 cleanupBarrier.countDown();
mandermo64e1a322016-10-18 08:47:51 -0700147 }
148 });
Magnus Jedvert894c4002016-10-21 15:05:01 +0200149 ThreadUtils.awaitUninterruptibly(cleanupBarrier);
mandermoeef94d92017-01-19 09:02:29 -0800150 try {
151 for (ByteBuffer buffer : rawFrames) {
152 videoOutFile.write("FRAME\n".getBytes());
153
154 byte[] data = new byte[outputFrameSize];
155 buffer.get(data);
156
157 videoOutFile.write(data);
158
Sami Kalliomäkicb98b112017-10-16 11:20:26 +0200159 JniCommon.freeNativeByteBuffer(buffer);
mandermoeef94d92017-01-19 09:02:29 -0800160 }
161 videoOutFile.close();
162 Logging.d(TAG, "Video written to disk as " + outputFileName + ". Number frames are "
163 + rawFrames.size() + " and the dimension of the frames are " + outputFileWidth + "x"
164 + outputFileHeight + ".");
165 } catch (IOException e) {
166 Logging.e(TAG, "Error writing video to disk", e);
167 }
mandermo64e1a322016-10-18 08:47:51 -0700168 }
169
170 public static native void nativeI420Scale(ByteBuffer srcY, int strideY, ByteBuffer srcU,
171 int strideU, ByteBuffer srcV, int strideV, int width, int height, ByteBuffer dst,
172 int dstWidth, int dstHeight);
mandermo64e1a322016-10-18 08:47:51 -0700173}