blob: f10ad20f581cc8d2e1fb9b624a773b479a11fbb6 [file] [log] [blame]
henrike@webrtc.org28e20752013-07-10 00:45:36 +00001/*
kjellanderb24317b2016-02-10 07:54:43 -08002 * Copyright 2013 The WebRTC project authors. All Rights Reserved.
henrike@webrtc.org28e20752013-07-10 00:45:36 +00003 *
kjellanderb24317b2016-02-10 07:54:43 -08004 * 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.
henrike@webrtc.org28e20752013-07-10 00:45:36 +00009 */
10
11package org.webrtc;
12
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010013import javax.annotation.Nullable;
henrike@webrtc.org28e20752013-07-10 00:45:36 +000014import java.nio.ByteBuffer;
Magnus Jedvert9060eb12017-12-12 12:52:54 +010015import org.webrtc.VideoFrame;
henrike@webrtc.org28e20752013-07-10 00:45:36 +000016
17/**
Niels Möller8f597622016-03-23 10:33:07 +010018 * Java version of VideoSinkInterface. In addition to allowing clients to
henrike@webrtc.org28e20752013-07-10 00:45:36 +000019 * define their own rendering behavior (by passing in a Callbacks object), this
20 * class also provides a createGui() method for creating a GUI-rendering window
21 * on various platforms.
22 */
Magnus Jedvert84d8ae52017-12-20 15:12:10 +010023@JNINamespace("webrtc::jni")
henrike@webrtc.org28e20752013-07-10 00:45:36 +000024public class VideoRenderer {
Magnus Jedverte76fb362015-10-08 19:05:24 +020025 /**
nisseacd935b2016-11-11 03:55:13 -080026 * Java version of webrtc::VideoFrame. Frames are only constructed from native code and test
Magnus Jedverte76fb362015-10-08 19:05:24 +020027 * code.
28 */
henrike@webrtc.org28e20752013-07-10 00:45:36 +000029 public static class I420Frame {
30 public final int width;
31 public final int height;
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010032 @Nullable public final int[] yuvStrides;
33 @Nullable public ByteBuffer[] yuvPlanes;
glaznev@webrtc.org99678452014-09-15 17:52:42 +000034 public final boolean yuvFrame;
Per488e75f2015-11-19 10:43:36 +010035 // Matrix that transforms standard coordinates to their proper sampling locations in
36 // the texture. This transform compensates for any properties of the video source that
37 // cause it to appear different from a normalized texture. This matrix does not take
38 // |rotationDegree| into account.
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010039 @Nullable public final float[] samplingMatrix;
glaznev@webrtc.org99678452014-09-15 17:52:42 +000040 public int textureId;
Magnus Jedvert7afc12f2015-09-03 12:40:38 +020041 // Frame pointer in C++.
Magnus Jedverta6cba3a2015-08-29 15:57:43 +020042 private long nativeFramePointer;
henrike@webrtc.org28e20752013-07-10 00:45:36 +000043
guoweis@webrtc.org840da7b2015-03-18 16:58:13 +000044 // rotationDegree is the degree that the frame must be rotated clockwisely
45 // to be rendered correctly.
46 public int rotationDegree;
47
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +020048 // If this I420Frame was constructed from VideoFrame.Buffer, this points to
49 // the backing buffer.
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010050 @Nullable private final VideoFrame.Buffer backingBuffer;
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +020051
henrike@webrtc.org28e20752013-07-10 00:45:36 +000052 /**
Magnus Jedvert7afc12f2015-09-03 12:40:38 +020053 * Construct a frame of the given dimensions with the specified planar data.
henrike@webrtc.org28e20752013-07-10 00:45:36 +000054 */
skvladed6e0772016-11-30 14:40:18 -080055 public I420Frame(int width, int height, int rotationDegree, int[] yuvStrides,
56 ByteBuffer[] yuvPlanes, long nativeFramePointer) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +000057 this.width = width;
58 this.height = height;
59 this.yuvStrides = yuvStrides;
henrike@webrtc.org28e20752013-07-10 00:45:36 +000060 this.yuvPlanes = yuvPlanes;
glaznev@webrtc.org99678452014-09-15 17:52:42 +000061 this.yuvFrame = true;
guoweis@webrtc.org840da7b2015-03-18 16:58:13 +000062 this.rotationDegree = rotationDegree;
Magnus Jedverta6cba3a2015-08-29 15:57:43 +020063 this.nativeFramePointer = nativeFramePointer;
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +020064 backingBuffer = null;
Magnus Jedvert80cf97c2015-06-11 10:08:59 +020065 if (rotationDegree % 90 != 0) {
66 throw new IllegalArgumentException("Rotation degree not multiple of 90: " + rotationDegree);
67 }
Per488e75f2015-11-19 10:43:36 +010068 // The convention in WebRTC is that the first element in a ByteBuffer corresponds to the
69 // top-left corner of the image, but in glTexImage2D() the first element corresponds to the
70 // bottom-left corner. This discrepancy is corrected by setting a vertical flip as sampling
71 // matrix.
Sami Kalliomäki372e5872017-06-27 17:00:21 +020072 samplingMatrix = RendererCommon.verticalFlipMatrix();
glaznev@webrtc.org99678452014-09-15 17:52:42 +000073 }
74
75 /**
76 * Construct a texture frame of the given dimensions with data in SurfaceTexture
77 */
skvladed6e0772016-11-30 14:40:18 -080078 public I420Frame(int width, int height, int rotationDegree, int textureId,
79 float[] samplingMatrix, long nativeFramePointer) {
glaznev@webrtc.org99678452014-09-15 17:52:42 +000080 this.width = width;
81 this.height = height;
82 this.yuvStrides = null;
83 this.yuvPlanes = null;
Per488e75f2015-11-19 10:43:36 +010084 this.samplingMatrix = samplingMatrix;
glaznev@webrtc.org99678452014-09-15 17:52:42 +000085 this.textureId = textureId;
86 this.yuvFrame = false;
guoweis@webrtc.org840da7b2015-03-18 16:58:13 +000087 this.rotationDegree = rotationDegree;
Magnus Jedverta6cba3a2015-08-29 15:57:43 +020088 this.nativeFramePointer = nativeFramePointer;
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +020089 backingBuffer = null;
Magnus Jedvert80cf97c2015-06-11 10:08:59 +020090 if (rotationDegree % 90 != 0) {
91 throw new IllegalArgumentException("Rotation degree not multiple of 90: " + rotationDegree);
92 }
93 }
94
Sami Kalliomäkibc061b42017-06-16 10:08:23 +020095 /**
sakal836f60c2017-07-28 07:12:23 -070096 * Construct a frame from VideoFrame.Buffer.
Sami Kalliomäkibc061b42017-06-16 10:08:23 +020097 */
Magnus Jedvert9060eb12017-12-12 12:52:54 +010098 @CalledByNative("I420Frame")
sakal836f60c2017-07-28 07:12:23 -070099 public I420Frame(int rotationDegree, VideoFrame.Buffer buffer, long nativeFramePointer) {
100 this.width = buffer.getWidth();
101 this.height = buffer.getHeight();
Sami Kalliomäkibc061b42017-06-16 10:08:23 +0200102 this.rotationDegree = rotationDegree;
103 if (rotationDegree % 90 != 0) {
104 throw new IllegalArgumentException("Rotation degree not multiple of 90: " + rotationDegree);
105 }
magjed2ab98792017-09-13 05:20:45 -0700106 if (buffer instanceof VideoFrame.TextureBuffer
107 && ((VideoFrame.TextureBuffer) buffer).getType() == VideoFrame.TextureBuffer.Type.OES) {
Sami Kalliomäkibc061b42017-06-16 10:08:23 +0200108 VideoFrame.TextureBuffer textureBuffer = (VideoFrame.TextureBuffer) buffer;
109 this.yuvFrame = false;
110 this.textureId = textureBuffer.getTextureId();
sakal836f60c2017-07-28 07:12:23 -0700111 this.samplingMatrix = RendererCommon.convertMatrixFromAndroidGraphicsMatrix(
112 textureBuffer.getTransformMatrix());
Sami Kalliomäkibc061b42017-06-16 10:08:23 +0200113
114 this.yuvStrides = null;
115 this.yuvPlanes = null;
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +0200116 } else if (buffer instanceof VideoFrame.I420Buffer) {
117 VideoFrame.I420Buffer i420Buffer = (VideoFrame.I420Buffer) buffer;
Sami Kalliomäkibc061b42017-06-16 10:08:23 +0200118 this.yuvFrame = true;
119 this.yuvStrides =
120 new int[] {i420Buffer.getStrideY(), i420Buffer.getStrideU(), i420Buffer.getStrideV()};
121 this.yuvPlanes =
122 new ByteBuffer[] {i420Buffer.getDataY(), i420Buffer.getDataU(), i420Buffer.getDataV()};
Sami Kalliomäki372e5872017-06-27 17:00:21 +0200123 // The convention in WebRTC is that the first element in a ByteBuffer corresponds to the
124 // top-left corner of the image, but in glTexImage2D() the first element corresponds to the
125 // bottom-left corner. This discrepancy is corrected by multiplying the sampling matrix with
126 // a vertical flip matrix.
sakal836f60c2017-07-28 07:12:23 -0700127 this.samplingMatrix = RendererCommon.verticalFlipMatrix();
Sami Kalliomäkibc061b42017-06-16 10:08:23 +0200128
129 this.textureId = 0;
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +0200130 } else {
131 this.yuvFrame = false;
132 this.textureId = 0;
133 this.samplingMatrix = null;
134 this.yuvStrides = null;
135 this.yuvPlanes = null;
Sami Kalliomäkibc061b42017-06-16 10:08:23 +0200136 }
137 this.nativeFramePointer = nativeFramePointer;
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +0200138 backingBuffer = buffer;
Sami Kalliomäkibc061b42017-06-16 10:08:23 +0200139 }
140
Magnus Jedvert80cf97c2015-06-11 10:08:59 +0200141 public int rotatedWidth() {
142 return (rotationDegree % 180 == 0) ? width : height;
143 }
144
145 public int rotatedHeight() {
146 return (rotationDegree % 180 == 0) ? height : width;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000147 }
148
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000149 @Override
150 public String toString() {
sakal71a62b92017-08-10 02:12:24 -0700151 final String type = yuvFrame
152 ? "Y: " + yuvStrides[0] + ", U: " + yuvStrides[1] + ", V: " + yuvStrides[2]
153 : "Texture: " + textureId;
154 return width + "x" + height + ", " + type;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000155 }
sakal6bdcefc2017-08-15 01:56:02 -0700156
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +0200157 /**
158 * Convert the frame to VideoFrame. It is no longer safe to use the I420Frame after calling
159 * this.
160 */
sakal6bdcefc2017-08-15 01:56:02 -0700161 VideoFrame toVideoFrame() {
162 final VideoFrame.Buffer buffer;
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +0200163 if (backingBuffer != null) {
164 // We were construted from a VideoFrame.Buffer, just return it.
165 // Make sure webrtc::VideoFrame object is released.
166 backingBuffer.retain();
167 VideoRenderer.renderFrameDone(this);
168 buffer = backingBuffer;
169 } else if (yuvFrame) {
Sami Kalliomäki48b3c022017-10-04 17:01:00 +0200170 buffer = JavaI420Buffer.wrap(width, height, yuvPlanes[0], yuvStrides[0], yuvPlanes[1],
sakal6bdcefc2017-08-15 01:56:02 -0700171 yuvStrides[1], yuvPlanes[2], yuvStrides[2],
172 () -> { VideoRenderer.renderFrameDone(this); });
173 } else {
174 // Note: surfaceTextureHelper being null means calling toI420 will crash.
175 buffer = new TextureBufferImpl(width, height, VideoFrame.TextureBuffer.Type.OES, textureId,
176 RendererCommon.convertMatrixToAndroidGraphicsMatrix(samplingMatrix),
177 null /* surfaceTextureHelper */, () -> { VideoRenderer.renderFrameDone(this); });
178 }
179 return new VideoFrame(buffer, rotationDegree, 0 /* timestampNs */);
180 }
Magnus Jedvert9060eb12017-12-12 12:52:54 +0100181
182 @CalledByNative("I420Frame")
183 static I420Frame createI420Frame(int width, int height, int rotationDegree, int y_stride,
184 ByteBuffer y_buffer, int u_stride, ByteBuffer u_buffer, int v_stride, ByteBuffer v_buffer,
185 long nativeFramePointer) {
186 return new I420Frame(width, height, rotationDegree, new int[] {y_stride, u_stride, v_stride},
187 new ByteBuffer[] {y_buffer, u_buffer, v_buffer}, nativeFramePointer);
188 }
189
190 @CalledByNative("I420Frame")
191 static I420Frame createTextureFrame(int width, int height, int rotationDegree, int textureId,
192 float[] samplingMatrix, long nativeFramePointer) {
193 return new I420Frame(
194 width, height, rotationDegree, textureId, samplingMatrix, nativeFramePointer);
195 }
glaznev@webrtc.orgf6932292015-02-05 17:29:59 +0000196 }
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000197
glaznev@webrtc.orgf6932292015-02-05 17:29:59 +0000198 // Helper native function to do a video frame plane copying.
Magnus Jedvert84d8ae52017-12-20 15:12:10 +0100199 static native void nativeCopyPlane(
sakalb6760f92016-09-29 04:12:44 -0700200 ByteBuffer src, int width, int height, int srcStride, ByteBuffer dst, int dstStride);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000201
Niels Möller8f597622016-03-23 10:33:07 +0100202 /** The real meat of VideoSinkInterface. */
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000203 public static interface Callbacks {
guoweis@webrtc.org00c509a2015-03-12 21:37:26 +0000204 // |frame| might have pending rotation and implementation of Callbacks
Magnus Jedverta6cba3a2015-08-29 15:57:43 +0200205 // should handle that by applying rotation during rendering. The callee
206 // is responsible for signaling when it is done with |frame| by calling
207 // renderFrameDone(frame).
Magnus Jedvert9060eb12017-12-12 12:52:54 +0100208 @CalledByNative("Callbacks") void renderFrame(I420Frame frame);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000209 }
210
sakalb6760f92016-09-29 04:12:44 -0700211 /**
212 * This must be called after every renderFrame() to release the frame.
213 */
214 public static void renderFrameDone(I420Frame frame) {
215 frame.yuvPlanes = null;
216 frame.textureId = 0;
217 if (frame.nativeFramePointer != 0) {
Magnus Jedvert84d8ae52017-12-20 15:12:10 +0100218 nativeReleaseFrame(frame.nativeFramePointer);
sakalb6760f92016-09-29 04:12:44 -0700219 frame.nativeFramePointer = 0;
220 }
221 }
Magnus Jedverta6cba3a2015-08-29 15:57:43 +0200222
magjedd5031fc2015-08-14 03:13:05 -0700223 long nativeVideoRenderer;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000224
225 public VideoRenderer(Callbacks callbacks) {
Magnus Jedvert84d8ae52017-12-20 15:12:10 +0100226 nativeVideoRenderer = nativeCreateVideoRenderer(callbacks);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000227 }
228
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000229 public void dispose() {
magjedd5031fc2015-08-14 03:13:05 -0700230 if (nativeVideoRenderer == 0) {
231 // Already disposed.
232 return;
233 }
perkj47b62632016-02-08 01:07:19 -0800234
Magnus Jedvert84d8ae52017-12-20 15:12:10 +0100235 nativeFreeWrappedVideoRenderer(nativeVideoRenderer);
magjedd5031fc2015-08-14 03:13:05 -0700236 nativeVideoRenderer = 0;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000237 }
238
Magnus Jedvert84d8ae52017-12-20 15:12:10 +0100239 private static native long nativeCreateVideoRenderer(Callbacks callbacks);
240 private static native void nativeFreeWrappedVideoRenderer(long videoRenderer);
241 private static native void nativeReleaseFrame(long framePointer);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000242}