| 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 |