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