blob: c599491f04e2cf420dcf2f0c8cc91f8cab6be71d [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äki75db5522018-01-22 14:19:16 +010020import java.util.concurrent.BlockingQueue;
Sami Kalliomäkicb98b112017-10-16 11:20:26 +020021import java.util.concurrent.CountDownLatch;
Sami Kalliomäki75db5522018-01-22 14:19:16 +010022import java.util.concurrent.LinkedBlockingQueue;
mandermo64e1a322016-10-18 08:47:51 -070023
24/**
25 * Can be used to save the video frames to file.
26 */
Magnus Jedvert84d8ae52017-12-20 15:12:10 +010027@JNINamespace("webrtc::jni")
Magnus Jedverte987f2b2018-04-23 16:14:47 +020028public class VideoFileRenderer implements VideoSink {
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;
mandermo64e1a322016-10-18 08:47:51 -070032 private final Handler renderThreadHandler;
33 private final FileOutputStream videoOutFile;
mandermoeef94d92017-01-19 09:02:29 -080034 private final String outputFileName;
mandermo64e1a322016-10-18 08:47:51 -070035 private final int outputFileWidth;
36 private final int outputFileHeight;
37 private final int outputFrameSize;
38 private final ByteBuffer outputFrameBuffer;
magjed1cb48232016-10-20 03:19:16 -070039 private EglBase eglBase;
40 private YuvConverter yuvConverter;
mandermoeef94d92017-01-19 09:02:29 -080041 private ArrayList<ByteBuffer> rawFrames = new ArrayList<>();
mandermo64e1a322016-10-18 08:47:51 -070042
43 public VideoFileRenderer(String outputFile, int outputFileWidth, int outputFileHeight,
magjed1cb48232016-10-20 03:19:16 -070044 final EglBase.Context sharedContext) throws IOException {
mandermo64e1a322016-10-18 08:47:51 -070045 if ((outputFileWidth % 2) == 1 || (outputFileHeight % 2) == 1) {
46 throw new IllegalArgumentException("Does not support uneven width or height");
47 }
mandermo64e1a322016-10-18 08:47:51 -070048
mandermoeef94d92017-01-19 09:02:29 -080049 this.outputFileName = outputFile;
mandermo64e1a322016-10-18 08:47:51 -070050 this.outputFileWidth = outputFileWidth;
51 this.outputFileHeight = outputFileHeight;
52
53 outputFrameSize = outputFileWidth * outputFileHeight * 3 / 2;
54 outputFrameBuffer = ByteBuffer.allocateDirect(outputFrameSize);
55
56 videoOutFile = new FileOutputStream(outputFile);
57 videoOutFile.write(
58 ("YUV4MPEG2 C420 W" + outputFileWidth + " H" + outputFileHeight + " Ip F30:1 A1:1\n")
Sami Kalliomäkibde473e2017-10-30 13:34:41 +010059 .getBytes(Charset.forName("US-ASCII")));
mandermo64e1a322016-10-18 08:47:51 -070060
61 renderThread = new HandlerThread(TAG);
62 renderThread.start();
63 renderThreadHandler = new Handler(renderThread.getLooper());
magjed1cb48232016-10-20 03:19:16 -070064
65 ThreadUtils.invokeAtFrontUninterruptibly(renderThreadHandler, new Runnable() {
66 @Override
67 public void run() {
68 eglBase = EglBase.create(sharedContext, EglBase.CONFIG_PIXEL_BUFFER);
69 eglBase.createDummyPbufferSurface();
70 eglBase.makeCurrent();
71 yuvConverter = new YuvConverter();
72 }
73 });
mandermo64e1a322016-10-18 08:47:51 -070074 }
75
76 @Override
Sami Kalliomäki75db5522018-01-22 14:19:16 +010077 public void onFrame(VideoFrame frame) {
78 frame.retain();
79 renderThreadHandler.post(() -> renderFrameOnRenderThread(frame));
80 }
mandermo64e1a322016-10-18 08:47:51 -070081
Sami Kalliomäki75db5522018-01-22 14:19:16 +010082 private void renderFrameOnRenderThread(VideoFrame frame) {
83 final VideoFrame.Buffer buffer = frame.getBuffer();
mandermo64e1a322016-10-18 08:47:51 -070084
Sami Kalliomäki75db5522018-01-22 14:19:16 +010085 // If the frame is rotated, it will be applied after cropAndScale. Therefore, if the frame is
86 // rotated by 90 degrees, swap width and height.
87 final int targetWidth = frame.getRotation() % 180 == 0 ? outputFileWidth : outputFileHeight;
88 final int targetHeight = frame.getRotation() % 180 == 0 ? outputFileHeight : outputFileWidth;
mandermo64e1a322016-10-18 08:47:51 -070089
Sami Kalliomäki75db5522018-01-22 14:19:16 +010090 final float frameAspectRatio = (float) buffer.getWidth() / (float) buffer.getHeight();
91 final float fileAspectRatio = (float) targetWidth / (float) targetHeight;
mandermo64e1a322016-10-18 08:47:51 -070092
Sami Kalliomäki75db5522018-01-22 14:19:16 +010093 // Calculate cropping to equalize the aspect ratio.
94 int cropWidth = buffer.getWidth();
95 int cropHeight = buffer.getHeight();
96 if (fileAspectRatio > frameAspectRatio) {
Oleh Prypin72467c22018-03-09 10:19:54 +010097 cropHeight = (int) (cropHeight * (frameAspectRatio / fileAspectRatio));
Sami Kalliomäki75db5522018-01-22 14:19:16 +010098 } else {
Oleh Prypin72467c22018-03-09 10:19:54 +010099 cropWidth = (int) (cropWidth * (fileAspectRatio / frameAspectRatio));
mandermo64e1a322016-10-18 08:47:51 -0700100 }
Sami Kalliomäki75db5522018-01-22 14:19:16 +0100101
102 final int cropX = (buffer.getWidth() - cropWidth) / 2;
103 final int cropY = (buffer.getHeight() - cropHeight) / 2;
104
105 final VideoFrame.Buffer scaledBuffer =
106 buffer.cropAndScale(cropX, cropY, cropWidth, cropHeight, targetWidth, targetHeight);
107 frame.release();
108
109 final VideoFrame.I420Buffer i420 = scaledBuffer.toI420();
110 scaledBuffer.release();
111
112 ByteBuffer byteBuffer = JniCommon.nativeAllocateByteBuffer(outputFrameSize);
113 YuvHelper.I420Rotate(i420.getDataY(), i420.getStrideY(), i420.getDataU(), i420.getStrideU(),
114 i420.getDataV(), i420.getStrideV(), byteBuffer, i420.getWidth(), i420.getHeight(),
115 frame.getRotation());
116 i420.release();
117
118 byteBuffer.rewind();
119 rawFrames.add(byteBuffer);
mandermo64e1a322016-10-18 08:47:51 -0700120 }
121
Magnus Jedvert894c4002016-10-21 15:05:01 +0200122 /**
123 * Release all resources. All already posted frames will be rendered first.
124 */
mandermo64e1a322016-10-18 08:47:51 -0700125 public void release() {
Magnus Jedvert894c4002016-10-21 15:05:01 +0200126 final CountDownLatch cleanupBarrier = new CountDownLatch(1);
Sami Kalliomäki75db5522018-01-22 14:19:16 +0100127 renderThreadHandler.post(() -> {
128 yuvConverter.release();
129 eglBase.release();
130 renderThread.quit();
131 cleanupBarrier.countDown();
mandermo64e1a322016-10-18 08:47:51 -0700132 });
Magnus Jedvert894c4002016-10-21 15:05:01 +0200133 ThreadUtils.awaitUninterruptibly(cleanupBarrier);
mandermoeef94d92017-01-19 09:02:29 -0800134 try {
135 for (ByteBuffer buffer : rawFrames) {
Sami Kalliomäkibde473e2017-10-30 13:34:41 +0100136 videoOutFile.write("FRAME\n".getBytes(Charset.forName("US-ASCII")));
mandermoeef94d92017-01-19 09:02:29 -0800137
138 byte[] data = new byte[outputFrameSize];
139 buffer.get(data);
140
141 videoOutFile.write(data);
142
Magnus Jedvert84d8ae52017-12-20 15:12:10 +0100143 JniCommon.nativeFreeByteBuffer(buffer);
mandermoeef94d92017-01-19 09:02:29 -0800144 }
145 videoOutFile.close();
Sami Kalliomäki75db5522018-01-22 14:19:16 +0100146 Logging.d(TAG,
147 "Video written to disk as " + outputFileName + ". Number frames are " + rawFrames.size()
148 + " and the dimension of the frames are " + outputFileWidth + "x" + outputFileHeight
149 + ".");
mandermoeef94d92017-01-19 09:02:29 -0800150 } catch (IOException e) {
151 Logging.e(TAG, "Error writing video to disk", e);
152 }
mandermo64e1a322016-10-18 08:47:51 -0700153 }
mandermo64e1a322016-10-18 08:47:51 -0700154}