2016-10-18 08:47:51 -07:00
|
|
|
/*
|
|
|
|
|
* Copyright 2016 The WebRTC Project Authors. All rights reserved.
|
|
|
|
|
*
|
|
|
|
|
* Use of this source code is governed by a BSD-style license
|
|
|
|
|
* that can be found in the LICENSE file in the root of the source
|
|
|
|
|
* tree. An additional intellectual property rights grant can be found
|
|
|
|
|
* in the file PATENTS. All contributing project authors may
|
|
|
|
|
* be found in the AUTHORS file in the root of the source tree.
|
|
|
|
|
*/
|
2017-01-11 06:22:56 -08:00
|
|
|
|
2016-10-18 08:47:51 -07:00
|
|
|
package org.webrtc;
|
|
|
|
|
|
|
|
|
|
import android.os.Handler;
|
|
|
|
|
import android.os.HandlerThread;
|
|
|
|
|
import java.io.FileOutputStream;
|
|
|
|
|
import java.io.IOException;
|
2016-10-21 15:05:01 +02:00
|
|
|
import java.nio.ByteBuffer;
|
|
|
|
|
import java.util.concurrent.CountDownLatch;
|
2017-01-19 09:02:29 -08:00
|
|
|
import java.util.ArrayList;
|
2016-10-18 08:47:51 -07:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Can be used to save the video frames to file.
|
|
|
|
|
*/
|
|
|
|
|
public class VideoFileRenderer implements VideoRenderer.Callbacks {
|
Reland of Android Logging.java: Load native library only when needed (patchset #1 id:1 of https://codereview.webrtc.org/2814133002/ )
Reason for revert:
Fix bug in original CL.
Original issue's description:
> Revert of Android Logging.java: Load native library only when needed (patchset #2 id:2 of https://codereview.webrtc.org/2817593003/ )
>
> Reason for revert:
> Change in Logging.java breaking compilation (incorrect reference to enum)
>
> Original issue's description:
> > Reland of Android Logging.java: Load native library only when needed (patchset #1 id:1 of https://codereview.webrtc.org/2816753002/ )
> >
> > Reason for revert:
> > Fix bug in original CL.
> >
> > Original issue's description:
> > > Revert of Android Logging.java: Load native library only when needed (patchset #3 id:40001 of https://codereview.webrtc.org/2803203002/ )
> > >
> > > Reason for revert:
> > > Breaks C++ logs in Java apps.
> > >
> > > Original issue's description:
> > > > Android Logging.java: Load native library only when needed
> > > >
> > > > Logging.java currently always tries to load jingle_peerconnection_so in
> > > > the static section, but some clients don't want to use it. This CL loads
> > > > jingle_peerconnection_so only when a client requests it by calling one
> > > > of:
> > > > * Logging.enableLogThreads
> > > > * Logging.enableLogTimeStamps
> > > > * Logging.enableTracing
> > > > * Logging.enableLogToDebugOutput
> > > >
> > > > BUG=b/36410678
> > > >
> > > > Review-Url: https://codereview.webrtc.org/2803203002
> > > > Cr-Commit-Position: refs/heads/master@{#17647}
> > > > Committed: https://chromium.googlesource.com/external/webrtc/+/dee5eb14e1d4e5bd894772448cc9418e94b3a967
> > >
> > > TBR=sakal@webrtc.org,glaznev@webrtc.org,noahric@chromium.org,magjed@webrtc.org
> > > # Not skipping CQ checks because original CL landed more than 1 days ago.
> > > BUG=b/36410678
> > >
> > > Review-Url: https://codereview.webrtc.org/2816753002
> > > Cr-Commit-Position: refs/heads/master@{#17676}
> > > Committed: https://chromium.googlesource.com/external/webrtc/+/6e4a4427dc58ca252461310981e1f07ed987c334
> >
> > TBR=sakal@webrtc.org,glaznev@webrtc.org,noahric@chromium.org,brandtr@webrtc.org
> > # Skipping CQ checks because original CL landed less than 1 days ago.
> > NOPRESUBMIT=true
> > NOTREECHECKS=true
> > NOTRY=true
> > BUG=b/36410678
> >
> > Review-Url: https://codereview.webrtc.org/2817593003
> > Cr-Commit-Position: refs/heads/master@{#17677}
> > Committed: https://chromium.googlesource.com/external/webrtc/+/297714619fc4252c08ef8a04ac4ca18653c70b39
>
> TBR=sakal@webrtc.org,glaznev@webrtc.org,noahric@chromium.org,brandtr@webrtc.org,magjed@webrtc.org
> # Skipping CQ checks because original CL landed less than 1 days ago.
> NOPRESUBMIT=true
> NOTREECHECKS=true
> NOTRY=true
> BUG=b/36410678
>
> Review-Url: https://codereview.webrtc.org/2814133002
> Cr-Commit-Position: refs/heads/master@{#17678}
> Committed: https://chromium.googlesource.com/external/webrtc/+/06034c7d6401b8e447aac98fd062b87523ae5f4f
TBR=sakal@webrtc.org,glaznev@webrtc.org,noahric@chromium.org,brandtr@webrtc.org,lliuu@webrtc.org
# Skipping CQ checks because original CL landed less than 1 days ago.
BUG=b/36410678
Review-Url: https://codereview.webrtc.org/2813363004
Cr-Commit-Position: refs/heads/master@{#17694}
2017-04-13 04:17:02 -07:00
|
|
|
static {
|
|
|
|
|
System.loadLibrary("jingle_peerconnection_so");
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-18 08:47:51 -07:00
|
|
|
private static final String TAG = "VideoFileRenderer";
|
|
|
|
|
|
|
|
|
|
private final HandlerThread renderThread;
|
|
|
|
|
private final Object handlerLock = new Object();
|
|
|
|
|
private final Handler renderThreadHandler;
|
|
|
|
|
private final FileOutputStream videoOutFile;
|
2017-01-19 09:02:29 -08:00
|
|
|
private final String outputFileName;
|
2016-10-18 08:47:51 -07:00
|
|
|
private final int outputFileWidth;
|
|
|
|
|
private final int outputFileHeight;
|
|
|
|
|
private final int outputFrameSize;
|
|
|
|
|
private final ByteBuffer outputFrameBuffer;
|
2016-10-20 03:19:16 -07:00
|
|
|
private EglBase eglBase;
|
|
|
|
|
private YuvConverter yuvConverter;
|
2017-01-19 09:02:29 -08:00
|
|
|
private ArrayList<ByteBuffer> rawFrames = new ArrayList<>();
|
2016-10-18 08:47:51 -07:00
|
|
|
|
|
|
|
|
public VideoFileRenderer(String outputFile, int outputFileWidth, int outputFileHeight,
|
2016-10-20 03:19:16 -07:00
|
|
|
final EglBase.Context sharedContext) throws IOException {
|
2016-10-18 08:47:51 -07:00
|
|
|
if ((outputFileWidth % 2) == 1 || (outputFileHeight % 2) == 1) {
|
|
|
|
|
throw new IllegalArgumentException("Does not support uneven width or height");
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-19 09:02:29 -08:00
|
|
|
this.outputFileName = outputFile;
|
2016-10-18 08:47:51 -07:00
|
|
|
this.outputFileWidth = outputFileWidth;
|
|
|
|
|
this.outputFileHeight = outputFileHeight;
|
|
|
|
|
|
|
|
|
|
outputFrameSize = outputFileWidth * outputFileHeight * 3 / 2;
|
|
|
|
|
outputFrameBuffer = ByteBuffer.allocateDirect(outputFrameSize);
|
|
|
|
|
|
|
|
|
|
videoOutFile = new FileOutputStream(outputFile);
|
|
|
|
|
videoOutFile.write(
|
|
|
|
|
("YUV4MPEG2 C420 W" + outputFileWidth + " H" + outputFileHeight + " Ip F30:1 A1:1\n")
|
|
|
|
|
.getBytes());
|
|
|
|
|
|
|
|
|
|
renderThread = new HandlerThread(TAG);
|
|
|
|
|
renderThread.start();
|
|
|
|
|
renderThreadHandler = new Handler(renderThread.getLooper());
|
2016-10-20 03:19:16 -07:00
|
|
|
|
|
|
|
|
ThreadUtils.invokeAtFrontUninterruptibly(renderThreadHandler, new Runnable() {
|
|
|
|
|
@Override
|
|
|
|
|
public void run() {
|
|
|
|
|
eglBase = EglBase.create(sharedContext, EglBase.CONFIG_PIXEL_BUFFER);
|
|
|
|
|
eglBase.createDummyPbufferSurface();
|
|
|
|
|
eglBase.makeCurrent();
|
|
|
|
|
yuvConverter = new YuvConverter();
|
|
|
|
|
}
|
|
|
|
|
});
|
2016-10-18 08:47:51 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void renderFrame(final VideoRenderer.I420Frame frame) {
|
|
|
|
|
renderThreadHandler.post(new Runnable() {
|
|
|
|
|
@Override
|
|
|
|
|
public void run() {
|
|
|
|
|
renderFrameOnRenderThread(frame);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void renderFrameOnRenderThread(VideoRenderer.I420Frame frame) {
|
|
|
|
|
final float frameAspectRatio = (float) frame.rotatedWidth() / (float) frame.rotatedHeight();
|
|
|
|
|
|
|
|
|
|
final float[] rotatedSamplingMatrix =
|
|
|
|
|
RendererCommon.rotateTextureMatrix(frame.samplingMatrix, frame.rotationDegree);
|
|
|
|
|
final float[] layoutMatrix = RendererCommon.getLayoutMatrix(
|
|
|
|
|
false, frameAspectRatio, (float) outputFileWidth / outputFileHeight);
|
|
|
|
|
final float[] texMatrix = RendererCommon.multiplyMatrices(rotatedSamplingMatrix, layoutMatrix);
|
|
|
|
|
|
|
|
|
|
try {
|
2017-01-19 09:02:29 -08:00
|
|
|
ByteBuffer buffer = nativeCreateNativeByteBuffer(outputFrameSize);
|
2016-10-18 08:47:51 -07:00
|
|
|
if (!frame.yuvFrame) {
|
|
|
|
|
yuvConverter.convert(outputFrameBuffer, outputFileWidth, outputFileHeight, outputFileWidth,
|
|
|
|
|
frame.textureId, texMatrix);
|
|
|
|
|
|
|
|
|
|
int stride = outputFileWidth;
|
|
|
|
|
byte[] data = outputFrameBuffer.array();
|
|
|
|
|
int offset = outputFrameBuffer.arrayOffset();
|
|
|
|
|
|
|
|
|
|
// Write Y
|
2017-01-19 09:02:29 -08:00
|
|
|
buffer.put(data, offset, outputFileWidth * outputFileHeight);
|
2016-10-18 08:47:51 -07:00
|
|
|
|
|
|
|
|
// Write U
|
|
|
|
|
for (int r = outputFileHeight; r < outputFileHeight * 3 / 2; ++r) {
|
2017-01-19 09:02:29 -08:00
|
|
|
buffer.put(data, offset + r * stride, stride / 2);
|
2016-10-18 08:47:51 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write V
|
|
|
|
|
for (int r = outputFileHeight; r < outputFileHeight * 3 / 2; ++r) {
|
2017-01-19 09:02:29 -08:00
|
|
|
buffer.put(data, offset + r * stride + stride / 2, stride / 2);
|
2016-10-18 08:47:51 -07:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
nativeI420Scale(frame.yuvPlanes[0], frame.yuvStrides[0], frame.yuvPlanes[1],
|
|
|
|
|
frame.yuvStrides[1], frame.yuvPlanes[2], frame.yuvStrides[2], frame.width, frame.height,
|
|
|
|
|
outputFrameBuffer, outputFileWidth, outputFileHeight);
|
2017-01-19 09:02:29 -08:00
|
|
|
|
|
|
|
|
buffer.put(outputFrameBuffer.array(), outputFrameBuffer.arrayOffset(), outputFrameSize);
|
2016-10-18 08:47:51 -07:00
|
|
|
}
|
2017-01-19 09:02:29 -08:00
|
|
|
buffer.rewind();
|
|
|
|
|
rawFrames.add(buffer);
|
2016-10-18 08:47:51 -07:00
|
|
|
} finally {
|
|
|
|
|
VideoRenderer.renderFrameDone(frame);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-21 15:05:01 +02:00
|
|
|
/**
|
|
|
|
|
* Release all resources. All already posted frames will be rendered first.
|
|
|
|
|
*/
|
2016-10-18 08:47:51 -07:00
|
|
|
public void release() {
|
2016-10-21 15:05:01 +02:00
|
|
|
final CountDownLatch cleanupBarrier = new CountDownLatch(1);
|
|
|
|
|
renderThreadHandler.post(new Runnable() {
|
2016-10-18 08:47:51 -07:00
|
|
|
@Override
|
|
|
|
|
public void run() {
|
2016-10-20 03:19:16 -07:00
|
|
|
yuvConverter.release();
|
|
|
|
|
eglBase.release();
|
|
|
|
|
renderThread.quit();
|
2016-10-21 15:05:01 +02:00
|
|
|
cleanupBarrier.countDown();
|
2016-10-18 08:47:51 -07:00
|
|
|
}
|
|
|
|
|
});
|
2016-10-21 15:05:01 +02:00
|
|
|
ThreadUtils.awaitUninterruptibly(cleanupBarrier);
|
2017-01-19 09:02:29 -08:00
|
|
|
try {
|
|
|
|
|
for (ByteBuffer buffer : rawFrames) {
|
|
|
|
|
videoOutFile.write("FRAME\n".getBytes());
|
|
|
|
|
|
|
|
|
|
byte[] data = new byte[outputFrameSize];
|
|
|
|
|
buffer.get(data);
|
|
|
|
|
|
|
|
|
|
videoOutFile.write(data);
|
|
|
|
|
|
|
|
|
|
nativeFreeNativeByteBuffer(buffer);
|
|
|
|
|
}
|
|
|
|
|
videoOutFile.close();
|
|
|
|
|
Logging.d(TAG, "Video written to disk as " + outputFileName + ". Number frames are "
|
|
|
|
|
+ rawFrames.size() + " and the dimension of the frames are " + outputFileWidth + "x"
|
|
|
|
|
+ outputFileHeight + ".");
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
Logging.e(TAG, "Error writing video to disk", e);
|
|
|
|
|
}
|
2016-10-18 08:47:51 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static native void nativeI420Scale(ByteBuffer srcY, int strideY, ByteBuffer srcU,
|
|
|
|
|
int strideU, ByteBuffer srcV, int strideV, int width, int height, ByteBuffer dst,
|
|
|
|
|
int dstWidth, int dstHeight);
|
2017-01-19 09:02:29 -08:00
|
|
|
|
|
|
|
|
public static native ByteBuffer nativeCreateNativeByteBuffer(int size);
|
|
|
|
|
|
|
|
|
|
public static native void nativeFreeNativeByteBuffer(ByteBuffer buffer);
|
2016-10-18 08:47:51 -07:00
|
|
|
}
|