blob: 92cdeb451384ce99c5fefe550a27b82ba2766cb0 [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 Jedverte987f2b2018-04-23 16:14:47 +020027public class VideoFileRenderer implements VideoSink {
mandermo64e1a322016-10-18 08:47:51 -070028 private static final String TAG = "VideoFileRenderer";
29
mandermo64e1a322016-10-18 08:47:51 -070030 private final HandlerThread renderThread;
mandermo64e1a322016-10-18 08:47:51 -070031 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;
Sami Kalliomäkicd513752018-05-29 13:46:20 +020040 private int frameCount;
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
Sami Kalliomäki75db5522018-01-22 14:19:16 +010076 public void onFrame(VideoFrame frame) {
77 frame.retain();
78 renderThreadHandler.post(() -> renderFrameOnRenderThread(frame));
79 }
mandermo64e1a322016-10-18 08:47:51 -070080
Sami Kalliomäki75db5522018-01-22 14:19:16 +010081 private void renderFrameOnRenderThread(VideoFrame frame) {
82 final VideoFrame.Buffer buffer = frame.getBuffer();
mandermo64e1a322016-10-18 08:47:51 -070083
Sami Kalliomäki75db5522018-01-22 14:19:16 +010084 // If the frame is rotated, it will be applied after cropAndScale. Therefore, if the frame is
85 // rotated by 90 degrees, swap width and height.
86 final int targetWidth = frame.getRotation() % 180 == 0 ? outputFileWidth : outputFileHeight;
87 final int targetHeight = frame.getRotation() % 180 == 0 ? outputFileHeight : outputFileWidth;
mandermo64e1a322016-10-18 08:47:51 -070088
Sami Kalliomäki75db5522018-01-22 14:19:16 +010089 final float frameAspectRatio = (float) buffer.getWidth() / (float) buffer.getHeight();
90 final float fileAspectRatio = (float) targetWidth / (float) targetHeight;
mandermo64e1a322016-10-18 08:47:51 -070091
Sami Kalliomäki75db5522018-01-22 14:19:16 +010092 // Calculate cropping to equalize the aspect ratio.
93 int cropWidth = buffer.getWidth();
94 int cropHeight = buffer.getHeight();
95 if (fileAspectRatio > frameAspectRatio) {
Oleh Prypin72467c22018-03-09 10:19:54 +010096 cropHeight = (int) (cropHeight * (frameAspectRatio / fileAspectRatio));
Sami Kalliomäki75db5522018-01-22 14:19:16 +010097 } else {
Oleh Prypin72467c22018-03-09 10:19:54 +010098 cropWidth = (int) (cropWidth * (fileAspectRatio / frameAspectRatio));
mandermo64e1a322016-10-18 08:47:51 -070099 }
Sami Kalliomäki75db5522018-01-22 14:19:16 +0100100
101 final int cropX = (buffer.getWidth() - cropWidth) / 2;
102 final int cropY = (buffer.getHeight() - cropHeight) / 2;
103
104 final VideoFrame.Buffer scaledBuffer =
105 buffer.cropAndScale(cropX, cropY, cropWidth, cropHeight, targetWidth, targetHeight);
106 frame.release();
107
108 final VideoFrame.I420Buffer i420 = scaledBuffer.toI420();
109 scaledBuffer.release();
110
Sami Kalliomäki75db5522018-01-22 14:19:16 +0100111 YuvHelper.I420Rotate(i420.getDataY(), i420.getStrideY(), i420.getDataU(), i420.getStrideU(),
Sami Kalliomäkicd513752018-05-29 13:46:20 +0200112 i420.getDataV(), i420.getStrideV(), outputFrameBuffer, i420.getWidth(), i420.getHeight(),
Sami Kalliomäki75db5522018-01-22 14:19:16 +0100113 frame.getRotation());
114 i420.release();
115
Sami Kalliomäkicd513752018-05-29 13:46:20 +0200116 try {
117 videoOutFile.write("FRAME\n".getBytes(Charset.forName("US-ASCII")));
118 videoOutFile.write(
119 outputFrameBuffer.array(), outputFrameBuffer.arrayOffset(), outputFrameSize);
120 } catch (IOException e) {
121 throw new RuntimeException("Error writing video to disk", e);
122 }
123 frameCount++;
mandermo64e1a322016-10-18 08:47:51 -0700124 }
125
Magnus Jedvert894c4002016-10-21 15:05:01 +0200126 /**
127 * Release all resources. All already posted frames will be rendered first.
128 */
mandermo64e1a322016-10-18 08:47:51 -0700129 public void release() {
Magnus Jedvert894c4002016-10-21 15:05:01 +0200130 final CountDownLatch cleanupBarrier = new CountDownLatch(1);
Sami Kalliomäki75db5522018-01-22 14:19:16 +0100131 renderThreadHandler.post(() -> {
132 yuvConverter.release();
133 eglBase.release();
134 renderThread.quit();
135 cleanupBarrier.countDown();
mandermo64e1a322016-10-18 08:47:51 -0700136 });
Magnus Jedvert894c4002016-10-21 15:05:01 +0200137 ThreadUtils.awaitUninterruptibly(cleanupBarrier);
mandermoeef94d92017-01-19 09:02:29 -0800138 try {
mandermoeef94d92017-01-19 09:02:29 -0800139 videoOutFile.close();
Sami Kalliomäki75db5522018-01-22 14:19:16 +0100140 Logging.d(TAG,
Sami Kalliomäkicd513752018-05-29 13:46:20 +0200141 "Video written to disk as " + outputFileName + ". The number of frames is " + frameCount
142 + " and the dimensions of the frames are " + outputFileWidth + "x" + outputFileHeight
Sami Kalliomäki75db5522018-01-22 14:19:16 +0100143 + ".");
mandermoeef94d92017-01-19 09:02:29 -0800144 } catch (IOException e) {
Sami Kalliomäkicd513752018-05-29 13:46:20 +0200145 throw new RuntimeException("Error closing output file", e);
mandermoeef94d92017-01-19 09:02:29 -0800146 }
mandermo64e1a322016-10-18 08:47:51 -0700147 }
mandermo64e1a322016-10-18 08:47:51 -0700148}