OLD | NEW |
| (Empty) |
1 /* | |
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 */ | |
10 package org.webrtc; | |
11 | |
12 import android.os.Handler; | |
13 import android.os.HandlerThread; | |
14 import java.io.FileOutputStream; | |
15 import java.io.IOException; | |
16 import java.nio.ByteBuffer; | |
17 import java.util.concurrent.CountDownLatch; | |
18 | |
19 /** | |
20 * Can be used to save the video frames to file. | |
21 */ | |
22 public class VideoFileRenderer implements VideoRenderer.Callbacks { | |
23 private static final String TAG = "VideoFileRenderer"; | |
24 | |
25 private final HandlerThread renderThread; | |
26 private final Object handlerLock = new Object(); | |
27 private final Handler renderThreadHandler; | |
28 private final FileOutputStream videoOutFile; | |
29 private final int outputFileWidth; | |
30 private final int outputFileHeight; | |
31 private final int outputFrameSize; | |
32 private final ByteBuffer outputFrameBuffer; | |
33 private EglBase eglBase; | |
34 private YuvConverter yuvConverter; | |
35 | |
36 public VideoFileRenderer(String outputFile, int outputFileWidth, int outputFil
eHeight, | |
37 final EglBase.Context sharedContext) throws IOException { | |
38 if ((outputFileWidth % 2) == 1 || (outputFileHeight % 2) == 1) { | |
39 throw new IllegalArgumentException("Does not support uneven width or heigh
t"); | |
40 } | |
41 | |
42 this.outputFileWidth = outputFileWidth; | |
43 this.outputFileHeight = outputFileHeight; | |
44 | |
45 outputFrameSize = outputFileWidth * outputFileHeight * 3 / 2; | |
46 outputFrameBuffer = ByteBuffer.allocateDirect(outputFrameSize); | |
47 | |
48 videoOutFile = new FileOutputStream(outputFile); | |
49 videoOutFile.write( | |
50 ("YUV4MPEG2 C420 W" + outputFileWidth + " H" + outputFileHeight + " Ip F
30:1 A1:1\n") | |
51 .getBytes()); | |
52 | |
53 renderThread = new HandlerThread(TAG); | |
54 renderThread.start(); | |
55 renderThreadHandler = new Handler(renderThread.getLooper()); | |
56 | |
57 ThreadUtils.invokeAtFrontUninterruptibly(renderThreadHandler, new Runnable()
{ | |
58 @Override | |
59 public void run() { | |
60 eglBase = EglBase.create(sharedContext, EglBase.CONFIG_PIXEL_BUFFER); | |
61 eglBase.createDummyPbufferSurface(); | |
62 eglBase.makeCurrent(); | |
63 yuvConverter = new YuvConverter(); | |
64 } | |
65 }); | |
66 } | |
67 | |
68 @Override | |
69 public void renderFrame(final VideoRenderer.I420Frame frame) { | |
70 renderThreadHandler.post(new Runnable() { | |
71 @Override | |
72 public void run() { | |
73 renderFrameOnRenderThread(frame); | |
74 } | |
75 }); | |
76 } | |
77 | |
78 private void renderFrameOnRenderThread(VideoRenderer.I420Frame frame) { | |
79 final float frameAspectRatio = (float) frame.rotatedWidth() / (float) frame.
rotatedHeight(); | |
80 | |
81 final float[] rotatedSamplingMatrix = | |
82 RendererCommon.rotateTextureMatrix(frame.samplingMatrix, frame.rotationD
egree); | |
83 final float[] layoutMatrix = RendererCommon.getLayoutMatrix( | |
84 false, frameAspectRatio, (float) outputFileWidth / outputFileHeight); | |
85 final float[] texMatrix = RendererCommon.multiplyMatrices(rotatedSamplingMat
rix, layoutMatrix); | |
86 | |
87 try { | |
88 videoOutFile.write("FRAME\n".getBytes()); | |
89 if (!frame.yuvFrame) { | |
90 yuvConverter.convert(outputFrameBuffer, outputFileWidth, outputFileHeigh
t, outputFileWidth, | |
91 frame.textureId, texMatrix); | |
92 | |
93 int stride = outputFileWidth; | |
94 byte[] data = outputFrameBuffer.array(); | |
95 int offset = outputFrameBuffer.arrayOffset(); | |
96 | |
97 // Write Y | |
98 videoOutFile.write(data, offset, outputFileWidth * outputFileHeight); | |
99 | |
100 // Write U | |
101 for (int r = outputFileHeight; r < outputFileHeight * 3 / 2; ++r) { | |
102 videoOutFile.write(data, offset + r * stride, stride / 2); | |
103 } | |
104 | |
105 // Write V | |
106 for (int r = outputFileHeight; r < outputFileHeight * 3 / 2; ++r) { | |
107 videoOutFile.write(data, offset + r * stride + stride / 2, stride / 2)
; | |
108 } | |
109 } else { | |
110 nativeI420Scale(frame.yuvPlanes[0], frame.yuvStrides[0], frame.yuvPlanes
[1], | |
111 frame.yuvStrides[1], frame.yuvPlanes[2], frame.yuvStrides[2], frame.
width, frame.height, | |
112 outputFrameBuffer, outputFileWidth, outputFileHeight); | |
113 videoOutFile.write( | |
114 outputFrameBuffer.array(), outputFrameBuffer.arrayOffset(), outputFr
ameSize); | |
115 } | |
116 } catch (IOException e) { | |
117 Logging.e(TAG, "Failed to write to file for video out"); | |
118 throw new RuntimeException(e); | |
119 } finally { | |
120 VideoRenderer.renderFrameDone(frame); | |
121 } | |
122 } | |
123 | |
124 /** | |
125 * Release all resources. All already posted frames will be rendered first. | |
126 */ | |
127 public void release() { | |
128 final CountDownLatch cleanupBarrier = new CountDownLatch(1); | |
129 renderThreadHandler.post(new Runnable() { | |
130 @Override | |
131 public void run() { | |
132 try { | |
133 videoOutFile.close(); | |
134 } catch (IOException e) { | |
135 Logging.d(TAG, "Error closing output video file"); | |
136 } | |
137 yuvConverter.release(); | |
138 eglBase.release(); | |
139 renderThread.quit(); | |
140 cleanupBarrier.countDown(); | |
141 } | |
142 }); | |
143 ThreadUtils.awaitUninterruptibly(cleanupBarrier); | |
144 } | |
145 | |
146 public static native void nativeI420Scale(ByteBuffer srcY, int strideY, ByteBu
ffer srcU, | |
147 int strideU, ByteBuffer srcV, int strideV, int width, int height, ByteBuff
er dst, | |
148 int dstWidth, int dstHeight); | |
149 } | |
OLD | NEW |