2013-07-10 00:45:36 +00:00
|
|
|
/*
|
2016-02-10 07:54:43 -08:00
|
|
|
* Copyright 2013 The WebRTC project authors. All Rights Reserved.
|
2013-07-10 00:45:36 +00:00
|
|
|
*
|
2016-02-10 07:54:43 -08:00
|
|
|
* 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.
|
2013-07-10 00:45:36 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
package org.webrtc;
|
|
|
|
|
|
|
|
|
|
import java.nio.ByteBuffer;
|
|
|
|
|
|
|
|
|
|
/**
|
2016-03-23 10:33:07 +01:00
|
|
|
* Java version of VideoSinkInterface. In addition to allowing clients to
|
2013-07-10 00:45:36 +00:00
|
|
|
* define their own rendering behavior (by passing in a Callbacks object), this
|
|
|
|
|
* class also provides a createGui() method for creating a GUI-rendering window
|
|
|
|
|
* on various platforms.
|
|
|
|
|
*/
|
|
|
|
|
public class VideoRenderer {
|
2015-10-08 19:05:24 +02:00
|
|
|
/**
|
Reland of Delete all use of cricket::VideoFrame and cricket::WebRtcVideoFrame. (patchset #1 id:1 of https://codereview.webrtc.org/2471783002/ )
Reason for revert:
Relanding after known downstream breakages have been fixed.
Original issue's description:
> Revert of Delete all use of cricket::VideoFrame and cricket::WebRtcVideoFrame. (patchset #7 id:120001 of https://codereview.webrtc.org/2383093002/ )
>
> Reason for revert:
> Breaks chrome, see https://build.chromium.org/p/chromium.webrtc.fyi/builders/Mac%20Builder/builds/19019/steps/compile/logs/stdio
>
> Analysis: Chrome uses cricket::VideoFrame, without explicitly including webrtc/media/base/videoframe.h, and breaks when that file is no longer included by any other webrtc headers. Will reland after updating Chrome.
>
> Original issue's description:
> > Delete all use of cricket::VideoFrame and cricket::WebRtcVideoFrame.
> >
> > Replaced with webrtc::VideoFrame.
> >
> > TBR=mflodman@webrtc.org
> > BUG=webrtc:5682
> >
> > Committed: https://crrev.com/45c8b8940042bd2574c39920804ade8343cefdba
> > Cr-Commit-Position: refs/heads/master@{#14885}
>
> TBR=perkj@webrtc.org,pthatcher@webrtc.org,tkchin@webrtc.org,mflodman@webrtc.org,stefan@webrtc.org
> # Skipping CQ checks because original CL landed less than 1 days ago.
> NOPRESUBMIT=true
> NOTREECHECKS=true
> NOTRY=true
> BUG=webrtc:5682
>
> Committed: https://crrev.com/7341ab8e2505c9763d208e069bda269018357e7d
> Cr-Commit-Position: refs/heads/master@{#14886}
TBR=perkj@webrtc.org,pthatcher@webrtc.org,tkchin@webrtc.org,mflodman@webrtc.org,stefan@webrtc.org
# Not skipping CQ checks because original CL landed more than 1 days ago.
BUG=webrtc:5682
Review-Url: https://codereview.webrtc.org/2487633002
Cr-Commit-Position: refs/heads/master@{#15039}
2016-11-11 03:55:13 -08:00
|
|
|
* Java version of webrtc::VideoFrame. Frames are only constructed from native code and test
|
2015-10-08 19:05:24 +02:00
|
|
|
* code.
|
|
|
|
|
*/
|
2013-07-10 00:45:36 +00:00
|
|
|
public static class I420Frame {
|
|
|
|
|
public final int width;
|
|
|
|
|
public final int height;
|
|
|
|
|
public final int[] yuvStrides;
|
2015-08-29 15:57:43 +02:00
|
|
|
public ByteBuffer[] yuvPlanes;
|
2014-09-15 17:52:42 +00:00
|
|
|
public final boolean yuvFrame;
|
2015-11-19 10:43:36 +01:00
|
|
|
// Matrix that transforms standard coordinates to their proper sampling locations in
|
|
|
|
|
// the texture. This transform compensates for any properties of the video source that
|
|
|
|
|
// cause it to appear different from a normalized texture. This matrix does not take
|
|
|
|
|
// |rotationDegree| into account.
|
|
|
|
|
public final float[] samplingMatrix;
|
2014-09-15 17:52:42 +00:00
|
|
|
public int textureId;
|
2015-09-03 12:40:38 +02:00
|
|
|
// Frame pointer in C++.
|
2015-08-29 15:57:43 +02:00
|
|
|
private long nativeFramePointer;
|
2013-07-10 00:45:36 +00:00
|
|
|
|
2015-03-18 16:58:13 +00:00
|
|
|
// rotationDegree is the degree that the frame must be rotated clockwisely
|
|
|
|
|
// to be rendered correctly.
|
|
|
|
|
public int rotationDegree;
|
|
|
|
|
|
2017-09-14 12:31:53 +02:00
|
|
|
// If this I420Frame was constructed from VideoFrame.Buffer, this points to
|
|
|
|
|
// the backing buffer.
|
|
|
|
|
private final VideoFrame.Buffer backingBuffer;
|
|
|
|
|
|
2013-07-10 00:45:36 +00:00
|
|
|
/**
|
2015-09-03 12:40:38 +02:00
|
|
|
* Construct a frame of the given dimensions with the specified planar data.
|
2013-07-10 00:45:36 +00:00
|
|
|
*/
|
2016-11-30 14:40:18 -08:00
|
|
|
public I420Frame(int width, int height, int rotationDegree, int[] yuvStrides,
|
|
|
|
|
ByteBuffer[] yuvPlanes, long nativeFramePointer) {
|
2013-07-10 00:45:36 +00:00
|
|
|
this.width = width;
|
|
|
|
|
this.height = height;
|
|
|
|
|
this.yuvStrides = yuvStrides;
|
|
|
|
|
this.yuvPlanes = yuvPlanes;
|
2014-09-15 17:52:42 +00:00
|
|
|
this.yuvFrame = true;
|
2015-03-18 16:58:13 +00:00
|
|
|
this.rotationDegree = rotationDegree;
|
2015-08-29 15:57:43 +02:00
|
|
|
this.nativeFramePointer = nativeFramePointer;
|
2017-09-14 12:31:53 +02:00
|
|
|
backingBuffer = null;
|
2015-06-11 10:08:59 +02:00
|
|
|
if (rotationDegree % 90 != 0) {
|
|
|
|
|
throw new IllegalArgumentException("Rotation degree not multiple of 90: " + rotationDegree);
|
|
|
|
|
}
|
2015-11-19 10:43:36 +01:00
|
|
|
// The convention in WebRTC is that the first element in a ByteBuffer corresponds to the
|
|
|
|
|
// top-left corner of the image, but in glTexImage2D() the first element corresponds to the
|
|
|
|
|
// bottom-left corner. This discrepancy is corrected by setting a vertical flip as sampling
|
|
|
|
|
// matrix.
|
2017-06-27 17:00:21 +02:00
|
|
|
samplingMatrix = RendererCommon.verticalFlipMatrix();
|
2014-09-15 17:52:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Construct a texture frame of the given dimensions with data in SurfaceTexture
|
|
|
|
|
*/
|
2016-11-30 14:40:18 -08:00
|
|
|
public I420Frame(int width, int height, int rotationDegree, int textureId,
|
|
|
|
|
float[] samplingMatrix, long nativeFramePointer) {
|
2014-09-15 17:52:42 +00:00
|
|
|
this.width = width;
|
|
|
|
|
this.height = height;
|
|
|
|
|
this.yuvStrides = null;
|
|
|
|
|
this.yuvPlanes = null;
|
2015-11-19 10:43:36 +01:00
|
|
|
this.samplingMatrix = samplingMatrix;
|
2014-09-15 17:52:42 +00:00
|
|
|
this.textureId = textureId;
|
|
|
|
|
this.yuvFrame = false;
|
2015-03-18 16:58:13 +00:00
|
|
|
this.rotationDegree = rotationDegree;
|
2015-08-29 15:57:43 +02:00
|
|
|
this.nativeFramePointer = nativeFramePointer;
|
2017-09-14 12:31:53 +02:00
|
|
|
backingBuffer = null;
|
2015-06-11 10:08:59 +02:00
|
|
|
if (rotationDegree % 90 != 0) {
|
|
|
|
|
throw new IllegalArgumentException("Rotation degree not multiple of 90: " + rotationDegree);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-16 10:08:23 +02:00
|
|
|
/**
|
2017-07-28 07:12:23 -07:00
|
|
|
* Construct a frame from VideoFrame.Buffer.
|
2017-06-16 10:08:23 +02:00
|
|
|
*/
|
2017-07-28 07:12:23 -07:00
|
|
|
public I420Frame(int rotationDegree, VideoFrame.Buffer buffer, long nativeFramePointer) {
|
|
|
|
|
this.width = buffer.getWidth();
|
|
|
|
|
this.height = buffer.getHeight();
|
2017-06-16 10:08:23 +02:00
|
|
|
this.rotationDegree = rotationDegree;
|
|
|
|
|
if (rotationDegree % 90 != 0) {
|
|
|
|
|
throw new IllegalArgumentException("Rotation degree not multiple of 90: " + rotationDegree);
|
|
|
|
|
}
|
2017-09-13 05:20:45 -07:00
|
|
|
if (buffer instanceof VideoFrame.TextureBuffer
|
|
|
|
|
&& ((VideoFrame.TextureBuffer) buffer).getType() == VideoFrame.TextureBuffer.Type.OES) {
|
2017-06-16 10:08:23 +02:00
|
|
|
VideoFrame.TextureBuffer textureBuffer = (VideoFrame.TextureBuffer) buffer;
|
|
|
|
|
this.yuvFrame = false;
|
|
|
|
|
this.textureId = textureBuffer.getTextureId();
|
2017-07-28 07:12:23 -07:00
|
|
|
this.samplingMatrix = RendererCommon.convertMatrixFromAndroidGraphicsMatrix(
|
|
|
|
|
textureBuffer.getTransformMatrix());
|
2017-06-16 10:08:23 +02:00
|
|
|
|
|
|
|
|
this.yuvStrides = null;
|
|
|
|
|
this.yuvPlanes = null;
|
2017-09-14 12:31:53 +02:00
|
|
|
} else if (buffer instanceof VideoFrame.I420Buffer) {
|
|
|
|
|
VideoFrame.I420Buffer i420Buffer = (VideoFrame.I420Buffer) buffer;
|
2017-06-16 10:08:23 +02:00
|
|
|
this.yuvFrame = true;
|
|
|
|
|
this.yuvStrides =
|
|
|
|
|
new int[] {i420Buffer.getStrideY(), i420Buffer.getStrideU(), i420Buffer.getStrideV()};
|
|
|
|
|
this.yuvPlanes =
|
|
|
|
|
new ByteBuffer[] {i420Buffer.getDataY(), i420Buffer.getDataU(), i420Buffer.getDataV()};
|
2017-06-27 17:00:21 +02:00
|
|
|
// The convention in WebRTC is that the first element in a ByteBuffer corresponds to the
|
|
|
|
|
// top-left corner of the image, but in glTexImage2D() the first element corresponds to the
|
|
|
|
|
// bottom-left corner. This discrepancy is corrected by multiplying the sampling matrix with
|
|
|
|
|
// a vertical flip matrix.
|
2017-07-28 07:12:23 -07:00
|
|
|
this.samplingMatrix = RendererCommon.verticalFlipMatrix();
|
2017-06-16 10:08:23 +02:00
|
|
|
|
|
|
|
|
this.textureId = 0;
|
2017-09-14 12:31:53 +02:00
|
|
|
} else {
|
|
|
|
|
this.yuvFrame = false;
|
|
|
|
|
this.textureId = 0;
|
|
|
|
|
this.samplingMatrix = null;
|
|
|
|
|
this.yuvStrides = null;
|
|
|
|
|
this.yuvPlanes = null;
|
2017-06-16 10:08:23 +02:00
|
|
|
}
|
|
|
|
|
this.nativeFramePointer = nativeFramePointer;
|
2017-09-14 12:31:53 +02:00
|
|
|
backingBuffer = buffer;
|
2017-06-16 10:08:23 +02:00
|
|
|
}
|
|
|
|
|
|
2015-06-11 10:08:59 +02:00
|
|
|
public int rotatedWidth() {
|
|
|
|
|
return (rotationDegree % 180 == 0) ? width : height;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int rotatedHeight() {
|
|
|
|
|
return (rotationDegree % 180 == 0) ? height : width;
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public String toString() {
|
2017-08-10 02:12:24 -07:00
|
|
|
final String type = yuvFrame
|
|
|
|
|
? "Y: " + yuvStrides[0] + ", U: " + yuvStrides[1] + ", V: " + yuvStrides[2]
|
|
|
|
|
: "Texture: " + textureId;
|
|
|
|
|
return width + "x" + height + ", " + type;
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
2017-08-15 01:56:02 -07:00
|
|
|
|
2017-09-14 12:31:53 +02:00
|
|
|
/**
|
|
|
|
|
* Convert the frame to VideoFrame. It is no longer safe to use the I420Frame after calling
|
|
|
|
|
* this.
|
|
|
|
|
*/
|
2017-08-15 01:56:02 -07:00
|
|
|
VideoFrame toVideoFrame() {
|
|
|
|
|
final VideoFrame.Buffer buffer;
|
2017-09-14 12:31:53 +02:00
|
|
|
if (backingBuffer != null) {
|
|
|
|
|
// We were construted from a VideoFrame.Buffer, just return it.
|
|
|
|
|
// Make sure webrtc::VideoFrame object is released.
|
|
|
|
|
backingBuffer.retain();
|
|
|
|
|
VideoRenderer.renderFrameDone(this);
|
|
|
|
|
buffer = backingBuffer;
|
|
|
|
|
} else if (yuvFrame) {
|
2017-08-15 01:56:02 -07:00
|
|
|
buffer = new I420BufferImpl(width, height, yuvPlanes[0], yuvStrides[0], yuvPlanes[1],
|
|
|
|
|
yuvStrides[1], yuvPlanes[2], yuvStrides[2],
|
|
|
|
|
() -> { VideoRenderer.renderFrameDone(this); });
|
|
|
|
|
} else {
|
|
|
|
|
// Note: surfaceTextureHelper being null means calling toI420 will crash.
|
|
|
|
|
buffer = new TextureBufferImpl(width, height, VideoFrame.TextureBuffer.Type.OES, textureId,
|
|
|
|
|
RendererCommon.convertMatrixToAndroidGraphicsMatrix(samplingMatrix),
|
|
|
|
|
null /* surfaceTextureHelper */, () -> { VideoRenderer.renderFrameDone(this); });
|
|
|
|
|
}
|
|
|
|
|
return new VideoFrame(buffer, rotationDegree, 0 /* timestampNs */);
|
|
|
|
|
}
|
2015-02-05 17:29:59 +00:00
|
|
|
}
|
2013-07-10 00:45:36 +00:00
|
|
|
|
2015-02-05 17:29:59 +00:00
|
|
|
// Helper native function to do a video frame plane copying.
|
2015-09-03 12:40:38 +02:00
|
|
|
public static native void nativeCopyPlane(
|
2015-02-05 17:29:59 +00:00
|
|
|
ByteBuffer src, int width, int height, int srcStride, ByteBuffer dst, int dstStride);
|
2013-07-10 00:45:36 +00:00
|
|
|
|
2016-03-23 10:33:07 +01:00
|
|
|
/** The real meat of VideoSinkInterface. */
|
2013-07-10 00:45:36 +00:00
|
|
|
public static interface Callbacks {
|
2015-03-12 21:37:26 +00:00
|
|
|
// |frame| might have pending rotation and implementation of Callbacks
|
2015-08-29 15:57:43 +02:00
|
|
|
// should handle that by applying rotation during rendering. The callee
|
|
|
|
|
// is responsible for signaling when it is done with |frame| by calling
|
|
|
|
|
// renderFrameDone(frame).
|
2013-07-10 00:45:36 +00:00
|
|
|
public void renderFrame(I420Frame frame);
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-29 15:57:43 +02:00
|
|
|
/**
|
|
|
|
|
* This must be called after every renderFrame() to release the frame.
|
|
|
|
|
*/
|
|
|
|
|
public static void renderFrameDone(I420Frame frame) {
|
|
|
|
|
frame.yuvPlanes = null;
|
|
|
|
|
frame.textureId = 0;
|
|
|
|
|
if (frame.nativeFramePointer != 0) {
|
|
|
|
|
releaseNativeFrame(frame.nativeFramePointer);
|
|
|
|
|
frame.nativeFramePointer = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-14 03:13:05 -07:00
|
|
|
long nativeVideoRenderer;
|
2013-07-10 00:45:36 +00:00
|
|
|
|
|
|
|
|
public VideoRenderer(Callbacks callbacks) {
|
|
|
|
|
nativeVideoRenderer = nativeWrapVideoRenderer(callbacks);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void dispose() {
|
2015-08-14 03:13:05 -07:00
|
|
|
if (nativeVideoRenderer == 0) {
|
|
|
|
|
// Already disposed.
|
|
|
|
|
return;
|
|
|
|
|
}
|
2016-02-08 01:07:19 -08:00
|
|
|
|
|
|
|
|
freeWrappedVideoRenderer(nativeVideoRenderer);
|
2015-08-14 03:13:05 -07:00
|
|
|
nativeVideoRenderer = 0;
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static native long nativeWrapVideoRenderer(Callbacks callbacks);
|
2014-09-17 03:26:59 +00:00
|
|
|
private static native void freeWrappedVideoRenderer(long nativeVideoRenderer);
|
2015-08-29 15:57:43 +02:00
|
|
|
private static native void releaseNativeFrame(long nativeFramePointer);
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|