blob: 3dd3f1aae9e6c546b559bc803aa04dfe2771aa5d [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
13import java.nio.ByteBuffer;
Magnus Jedvert9060eb12017-12-12 12:52:54 +010014import org.webrtc.VideoFrame;
henrike@webrtc.org28e20752013-07-10 00:45:36 +000015
16/**
Niels Möller8f597622016-03-23 10:33:07 +010017 * Java version of VideoSinkInterface. In addition to allowing clients to
henrike@webrtc.org28e20752013-07-10 00:45:36 +000018 * define their own rendering behavior (by passing in a Callbacks object), this
19 * class also provides a createGui() method for creating a GUI-rendering window
20 * on various platforms.
21 */
Magnus Jedvert84d8ae52017-12-20 15:12:10 +010022@JNINamespace("webrtc::jni")
henrike@webrtc.org28e20752013-07-10 00:45:36 +000023public class VideoRenderer {
Magnus Jedverte76fb362015-10-08 19:05:24 +020024 /**
nisseacd935b2016-11-11 03:55:13 -080025 * Java version of webrtc::VideoFrame. Frames are only constructed from native code and test
Magnus Jedverte76fb362015-10-08 19:05:24 +020026 * code.
27 */
henrike@webrtc.org28e20752013-07-10 00:45:36 +000028 public static class I420Frame {
29 public final int width;
30 public final int height;
31 public final int[] yuvStrides;
Magnus Jedverta6cba3a2015-08-29 15:57:43 +020032 public ByteBuffer[] yuvPlanes;
glaznev@webrtc.org99678452014-09-15 17:52:42 +000033 public final boolean yuvFrame;
Per488e75f2015-11-19 10:43:36 +010034 // Matrix that transforms standard coordinates to their proper sampling locations in
35 // the texture. This transform compensates for any properties of the video source that
36 // cause it to appear different from a normalized texture. This matrix does not take
37 // |rotationDegree| into account.
38 public final float[] samplingMatrix;
glaznev@webrtc.org99678452014-09-15 17:52:42 +000039 public int textureId;
Magnus Jedvert7afc12f2015-09-03 12:40:38 +020040 // Frame pointer in C++.
Magnus Jedverta6cba3a2015-08-29 15:57:43 +020041 private long nativeFramePointer;
henrike@webrtc.org28e20752013-07-10 00:45:36 +000042
guoweis@webrtc.org840da7b2015-03-18 16:58:13 +000043 // rotationDegree is the degree that the frame must be rotated clockwisely
44 // to be rendered correctly.
45 public int rotationDegree;
46
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +020047 // If this I420Frame was constructed from VideoFrame.Buffer, this points to
48 // the backing buffer.
49 private final VideoFrame.Buffer backingBuffer;
50
henrike@webrtc.org28e20752013-07-10 00:45:36 +000051 /**
Magnus Jedvert7afc12f2015-09-03 12:40:38 +020052 * Construct a frame of the given dimensions with the specified planar data.
henrike@webrtc.org28e20752013-07-10 00:45:36 +000053 */
skvladed6e0772016-11-30 14:40:18 -080054 public I420Frame(int width, int height, int rotationDegree, int[] yuvStrides,
55 ByteBuffer[] yuvPlanes, long nativeFramePointer) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +000056 this.width = width;
57 this.height = height;
58 this.yuvStrides = yuvStrides;
henrike@webrtc.org28e20752013-07-10 00:45:36 +000059 this.yuvPlanes = yuvPlanes;
glaznev@webrtc.org99678452014-09-15 17:52:42 +000060 this.yuvFrame = true;
guoweis@webrtc.org840da7b2015-03-18 16:58:13 +000061 this.rotationDegree = rotationDegree;
Magnus Jedverta6cba3a2015-08-29 15:57:43 +020062 this.nativeFramePointer = nativeFramePointer;
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +020063 backingBuffer = null;
Magnus Jedvert80cf97c2015-06-11 10:08:59 +020064 if (rotationDegree % 90 != 0) {
65 throw new IllegalArgumentException("Rotation degree not multiple of 90: " + rotationDegree);
66 }
Per488e75f2015-11-19 10:43:36 +010067 // The convention in WebRTC is that the first element in a ByteBuffer corresponds to the
68 // top-left corner of the image, but in glTexImage2D() the first element corresponds to the
69 // bottom-left corner. This discrepancy is corrected by setting a vertical flip as sampling
70 // matrix.
Sami Kalliomäki372e5872017-06-27 17:00:21 +020071 samplingMatrix = RendererCommon.verticalFlipMatrix();
glaznev@webrtc.org99678452014-09-15 17:52:42 +000072 }
73
74 /**
75 * Construct a texture frame of the given dimensions with data in SurfaceTexture
76 */
skvladed6e0772016-11-30 14:40:18 -080077 public I420Frame(int width, int height, int rotationDegree, int textureId,
78 float[] samplingMatrix, long nativeFramePointer) {
glaznev@webrtc.org99678452014-09-15 17:52:42 +000079 this.width = width;
80 this.height = height;
81 this.yuvStrides = null;
82 this.yuvPlanes = null;
Per488e75f2015-11-19 10:43:36 +010083 this.samplingMatrix = samplingMatrix;
glaznev@webrtc.org99678452014-09-15 17:52:42 +000084 this.textureId = textureId;
85 this.yuvFrame = false;
guoweis@webrtc.org840da7b2015-03-18 16:58:13 +000086 this.rotationDegree = rotationDegree;
Magnus Jedverta6cba3a2015-08-29 15:57:43 +020087 this.nativeFramePointer = nativeFramePointer;
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +020088 backingBuffer = null;
Magnus Jedvert80cf97c2015-06-11 10:08:59 +020089 if (rotationDegree % 90 != 0) {
90 throw new IllegalArgumentException("Rotation degree not multiple of 90: " + rotationDegree);
91 }
92 }
93
Sami Kalliomäkibc061b42017-06-16 10:08:23 +020094 /**
sakal836f60c2017-07-28 07:12:23 -070095 * Construct a frame from VideoFrame.Buffer.
Sami Kalliomäkibc061b42017-06-16 10:08:23 +020096 */
Magnus Jedvert9060eb12017-12-12 12:52:54 +010097 @CalledByNative("I420Frame")
sakal836f60c2017-07-28 07:12:23 -070098 public I420Frame(int rotationDegree, VideoFrame.Buffer buffer, long nativeFramePointer) {
99 this.width = buffer.getWidth();
100 this.height = buffer.getHeight();
Sami Kalliomäkibc061b42017-06-16 10:08:23 +0200101 this.rotationDegree = rotationDegree;
102 if (rotationDegree % 90 != 0) {
103 throw new IllegalArgumentException("Rotation degree not multiple of 90: " + rotationDegree);
104 }
magjed2ab98792017-09-13 05:20:45 -0700105 if (buffer instanceof VideoFrame.TextureBuffer
106 && ((VideoFrame.TextureBuffer) buffer).getType() == VideoFrame.TextureBuffer.Type.OES) {
Sami Kalliomäkibc061b42017-06-16 10:08:23 +0200107 VideoFrame.TextureBuffer textureBuffer = (VideoFrame.TextureBuffer) buffer;
108 this.yuvFrame = false;
109 this.textureId = textureBuffer.getTextureId();
sakal836f60c2017-07-28 07:12:23 -0700110 this.samplingMatrix = RendererCommon.convertMatrixFromAndroidGraphicsMatrix(
111 textureBuffer.getTransformMatrix());
Sami Kalliomäkibc061b42017-06-16 10:08:23 +0200112
113 this.yuvStrides = null;
114 this.yuvPlanes = null;
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +0200115 } else if (buffer instanceof VideoFrame.I420Buffer) {
116 VideoFrame.I420Buffer i420Buffer = (VideoFrame.I420Buffer) buffer;
Sami Kalliomäkibc061b42017-06-16 10:08:23 +0200117 this.yuvFrame = true;
118 this.yuvStrides =
119 new int[] {i420Buffer.getStrideY(), i420Buffer.getStrideU(), i420Buffer.getStrideV()};
120 this.yuvPlanes =
121 new ByteBuffer[] {i420Buffer.getDataY(), i420Buffer.getDataU(), i420Buffer.getDataV()};
Sami Kalliomäki372e5872017-06-27 17:00:21 +0200122 // The convention in WebRTC is that the first element in a ByteBuffer corresponds to the
123 // top-left corner of the image, but in glTexImage2D() the first element corresponds to the
124 // bottom-left corner. This discrepancy is corrected by multiplying the sampling matrix with
125 // a vertical flip matrix.
sakal836f60c2017-07-28 07:12:23 -0700126 this.samplingMatrix = RendererCommon.verticalFlipMatrix();
Sami Kalliomäkibc061b42017-06-16 10:08:23 +0200127
128 this.textureId = 0;
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +0200129 } else {
130 this.yuvFrame = false;
131 this.textureId = 0;
132 this.samplingMatrix = null;
133 this.yuvStrides = null;
134 this.yuvPlanes = null;
Sami Kalliomäkibc061b42017-06-16 10:08:23 +0200135 }
136 this.nativeFramePointer = nativeFramePointer;
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +0200137 backingBuffer = buffer;
Sami Kalliomäkibc061b42017-06-16 10:08:23 +0200138 }
139
Magnus Jedvert80cf97c2015-06-11 10:08:59 +0200140 public int rotatedWidth() {
141 return (rotationDegree % 180 == 0) ? width : height;
142 }
143
144 public int rotatedHeight() {
145 return (rotationDegree % 180 == 0) ? height : width;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000146 }
147
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000148 @Override
149 public String toString() {
sakal71a62b92017-08-10 02:12:24 -0700150 final String type = yuvFrame
151 ? "Y: " + yuvStrides[0] + ", U: " + yuvStrides[1] + ", V: " + yuvStrides[2]
152 : "Texture: " + textureId;
153 return width + "x" + height + ", " + type;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000154 }
sakal6bdcefc2017-08-15 01:56:02 -0700155
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +0200156 /**
157 * Convert the frame to VideoFrame. It is no longer safe to use the I420Frame after calling
158 * this.
159 */
sakal6bdcefc2017-08-15 01:56:02 -0700160 VideoFrame toVideoFrame() {
161 final VideoFrame.Buffer buffer;
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +0200162 if (backingBuffer != null) {
163 // We were construted from a VideoFrame.Buffer, just return it.
164 // Make sure webrtc::VideoFrame object is released.
165 backingBuffer.retain();
166 VideoRenderer.renderFrameDone(this);
167 buffer = backingBuffer;
168 } else if (yuvFrame) {
Sami Kalliomäki48b3c022017-10-04 17:01:00 +0200169 buffer = JavaI420Buffer.wrap(width, height, yuvPlanes[0], yuvStrides[0], yuvPlanes[1],
sakal6bdcefc2017-08-15 01:56:02 -0700170 yuvStrides[1], yuvPlanes[2], yuvStrides[2],
171 () -> { VideoRenderer.renderFrameDone(this); });
172 } else {
173 // Note: surfaceTextureHelper being null means calling toI420 will crash.
174 buffer = new TextureBufferImpl(width, height, VideoFrame.TextureBuffer.Type.OES, textureId,
175 RendererCommon.convertMatrixToAndroidGraphicsMatrix(samplingMatrix),
176 null /* surfaceTextureHelper */, () -> { VideoRenderer.renderFrameDone(this); });
177 }
178 return new VideoFrame(buffer, rotationDegree, 0 /* timestampNs */);
179 }
Magnus Jedvert9060eb12017-12-12 12:52:54 +0100180
181 @CalledByNative("I420Frame")
182 static I420Frame createI420Frame(int width, int height, int rotationDegree, int y_stride,
183 ByteBuffer y_buffer, int u_stride, ByteBuffer u_buffer, int v_stride, ByteBuffer v_buffer,
184 long nativeFramePointer) {
185 return new I420Frame(width, height, rotationDegree, new int[] {y_stride, u_stride, v_stride},
186 new ByteBuffer[] {y_buffer, u_buffer, v_buffer}, nativeFramePointer);
187 }
188
189 @CalledByNative("I420Frame")
190 static I420Frame createTextureFrame(int width, int height, int rotationDegree, int textureId,
191 float[] samplingMatrix, long nativeFramePointer) {
192 return new I420Frame(
193 width, height, rotationDegree, textureId, samplingMatrix, nativeFramePointer);
194 }
glaznev@webrtc.orgf6932292015-02-05 17:29:59 +0000195 }
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000196
glaznev@webrtc.orgf6932292015-02-05 17:29:59 +0000197 // Helper native function to do a video frame plane copying.
Magnus Jedvert84d8ae52017-12-20 15:12:10 +0100198 static native void nativeCopyPlane(
sakalb6760f92016-09-29 04:12:44 -0700199 ByteBuffer src, int width, int height, int srcStride, ByteBuffer dst, int dstStride);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000200
Niels Möller8f597622016-03-23 10:33:07 +0100201 /** The real meat of VideoSinkInterface. */
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000202 public static interface Callbacks {
guoweis@webrtc.org00c509a2015-03-12 21:37:26 +0000203 // |frame| might have pending rotation and implementation of Callbacks
Magnus Jedverta6cba3a2015-08-29 15:57:43 +0200204 // should handle that by applying rotation during rendering. The callee
205 // is responsible for signaling when it is done with |frame| by calling
206 // renderFrameDone(frame).
Magnus Jedvert9060eb12017-12-12 12:52:54 +0100207 @CalledByNative("Callbacks") void renderFrame(I420Frame frame);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000208 }
209
sakalb6760f92016-09-29 04:12:44 -0700210 /**
211 * This must be called after every renderFrame() to release the frame.
212 */
213 public static void renderFrameDone(I420Frame frame) {
214 frame.yuvPlanes = null;
215 frame.textureId = 0;
216 if (frame.nativeFramePointer != 0) {
Magnus Jedvert84d8ae52017-12-20 15:12:10 +0100217 nativeReleaseFrame(frame.nativeFramePointer);
sakalb6760f92016-09-29 04:12:44 -0700218 frame.nativeFramePointer = 0;
219 }
220 }
Magnus Jedverta6cba3a2015-08-29 15:57:43 +0200221
magjedd5031fc2015-08-14 03:13:05 -0700222 long nativeVideoRenderer;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000223
224 public VideoRenderer(Callbacks callbacks) {
Magnus Jedvert84d8ae52017-12-20 15:12:10 +0100225 nativeVideoRenderer = nativeCreateVideoRenderer(callbacks);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000226 }
227
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000228 public void dispose() {
magjedd5031fc2015-08-14 03:13:05 -0700229 if (nativeVideoRenderer == 0) {
230 // Already disposed.
231 return;
232 }
perkj47b62632016-02-08 01:07:19 -0800233
Magnus Jedvert84d8ae52017-12-20 15:12:10 +0100234 nativeFreeWrappedVideoRenderer(nativeVideoRenderer);
magjedd5031fc2015-08-14 03:13:05 -0700235 nativeVideoRenderer = 0;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000236 }
237
Magnus Jedvert84d8ae52017-12-20 15:12:10 +0100238 private static native long nativeCreateVideoRenderer(Callbacks callbacks);
239 private static native void nativeFreeWrappedVideoRenderer(long videoRenderer);
240 private static native void nativeReleaseFrame(long framePointer);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000241}