blob: 182093c9321ffff9dbb9376118432e4a6f1848fb [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;
henrike@webrtc.org28e20752013-07-10 00:45:36 +000014
15/**
Niels Möller8f597622016-03-23 10:33:07 +010016 * Java version of VideoSinkInterface. In addition to allowing clients to
henrike@webrtc.org28e20752013-07-10 00:45:36 +000017 * define their own rendering behavior (by passing in a Callbacks object), this
18 * class also provides a createGui() method for creating a GUI-rendering window
19 * on various platforms.
20 */
21public class VideoRenderer {
Magnus Jedverte76fb362015-10-08 19:05:24 +020022 /**
nisseacd935b2016-11-11 03:55:13 -080023 * Java version of webrtc::VideoFrame. Frames are only constructed from native code and test
Magnus Jedverte76fb362015-10-08 19:05:24 +020024 * code.
25 */
henrike@webrtc.org28e20752013-07-10 00:45:36 +000026 public static class I420Frame {
27 public final int width;
28 public final int height;
29 public final int[] yuvStrides;
Magnus Jedverta6cba3a2015-08-29 15:57:43 +020030 public ByteBuffer[] yuvPlanes;
glaznev@webrtc.org99678452014-09-15 17:52:42 +000031 public final boolean yuvFrame;
Per488e75f2015-11-19 10:43:36 +010032 // Matrix that transforms standard coordinates to their proper sampling locations in
33 // the texture. This transform compensates for any properties of the video source that
34 // cause it to appear different from a normalized texture. This matrix does not take
35 // |rotationDegree| into account.
36 public final float[] samplingMatrix;
glaznev@webrtc.org99678452014-09-15 17:52:42 +000037 public int textureId;
Magnus Jedvert7afc12f2015-09-03 12:40:38 +020038 // Frame pointer in C++.
Magnus Jedverta6cba3a2015-08-29 15:57:43 +020039 private long nativeFramePointer;
henrike@webrtc.org28e20752013-07-10 00:45:36 +000040
guoweis@webrtc.org840da7b2015-03-18 16:58:13 +000041 // rotationDegree is the degree that the frame must be rotated clockwisely
42 // to be rendered correctly.
43 public int rotationDegree;
44
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +020045 // If this I420Frame was constructed from VideoFrame.Buffer, this points to
46 // the backing buffer.
47 private final VideoFrame.Buffer backingBuffer;
48
henrike@webrtc.org28e20752013-07-10 00:45:36 +000049 /**
Magnus Jedvert7afc12f2015-09-03 12:40:38 +020050 * Construct a frame of the given dimensions with the specified planar data.
henrike@webrtc.org28e20752013-07-10 00:45:36 +000051 */
skvladed6e0772016-11-30 14:40:18 -080052 public I420Frame(int width, int height, int rotationDegree, int[] yuvStrides,
53 ByteBuffer[] yuvPlanes, long nativeFramePointer) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +000054 this.width = width;
55 this.height = height;
56 this.yuvStrides = yuvStrides;
henrike@webrtc.org28e20752013-07-10 00:45:36 +000057 this.yuvPlanes = yuvPlanes;
glaznev@webrtc.org99678452014-09-15 17:52:42 +000058 this.yuvFrame = true;
guoweis@webrtc.org840da7b2015-03-18 16:58:13 +000059 this.rotationDegree = rotationDegree;
Magnus Jedverta6cba3a2015-08-29 15:57:43 +020060 this.nativeFramePointer = nativeFramePointer;
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +020061 backingBuffer = null;
Magnus Jedvert80cf97c2015-06-11 10:08:59 +020062 if (rotationDegree % 90 != 0) {
63 throw new IllegalArgumentException("Rotation degree not multiple of 90: " + rotationDegree);
64 }
Per488e75f2015-11-19 10:43:36 +010065 // The convention in WebRTC is that the first element in a ByteBuffer corresponds to the
66 // top-left corner of the image, but in glTexImage2D() the first element corresponds to the
67 // bottom-left corner. This discrepancy is corrected by setting a vertical flip as sampling
68 // matrix.
Sami Kalliomäki372e5872017-06-27 17:00:21 +020069 samplingMatrix = RendererCommon.verticalFlipMatrix();
glaznev@webrtc.org99678452014-09-15 17:52:42 +000070 }
71
72 /**
73 * Construct a texture frame of the given dimensions with data in SurfaceTexture
74 */
skvladed6e0772016-11-30 14:40:18 -080075 public I420Frame(int width, int height, int rotationDegree, int textureId,
76 float[] samplingMatrix, long nativeFramePointer) {
glaznev@webrtc.org99678452014-09-15 17:52:42 +000077 this.width = width;
78 this.height = height;
79 this.yuvStrides = null;
80 this.yuvPlanes = null;
Per488e75f2015-11-19 10:43:36 +010081 this.samplingMatrix = samplingMatrix;
glaznev@webrtc.org99678452014-09-15 17:52:42 +000082 this.textureId = textureId;
83 this.yuvFrame = false;
guoweis@webrtc.org840da7b2015-03-18 16:58:13 +000084 this.rotationDegree = rotationDegree;
Magnus Jedverta6cba3a2015-08-29 15:57:43 +020085 this.nativeFramePointer = nativeFramePointer;
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +020086 backingBuffer = null;
Magnus Jedvert80cf97c2015-06-11 10:08:59 +020087 if (rotationDegree % 90 != 0) {
88 throw new IllegalArgumentException("Rotation degree not multiple of 90: " + rotationDegree);
89 }
90 }
91
Sami Kalliomäkibc061b42017-06-16 10:08:23 +020092 /**
sakal836f60c2017-07-28 07:12:23 -070093 * Construct a frame from VideoFrame.Buffer.
Sami Kalliomäkibc061b42017-06-16 10:08:23 +020094 */
sakal836f60c2017-07-28 07:12:23 -070095 public I420Frame(int rotationDegree, VideoFrame.Buffer buffer, long nativeFramePointer) {
96 this.width = buffer.getWidth();
97 this.height = buffer.getHeight();
Sami Kalliomäkibc061b42017-06-16 10:08:23 +020098 this.rotationDegree = rotationDegree;
99 if (rotationDegree % 90 != 0) {
100 throw new IllegalArgumentException("Rotation degree not multiple of 90: " + rotationDegree);
101 }
magjed2ab98792017-09-13 05:20:45 -0700102 if (buffer instanceof VideoFrame.TextureBuffer
103 && ((VideoFrame.TextureBuffer) buffer).getType() == VideoFrame.TextureBuffer.Type.OES) {
Sami Kalliomäkibc061b42017-06-16 10:08:23 +0200104 VideoFrame.TextureBuffer textureBuffer = (VideoFrame.TextureBuffer) buffer;
105 this.yuvFrame = false;
106 this.textureId = textureBuffer.getTextureId();
sakal836f60c2017-07-28 07:12:23 -0700107 this.samplingMatrix = RendererCommon.convertMatrixFromAndroidGraphicsMatrix(
108 textureBuffer.getTransformMatrix());
Sami Kalliomäkibc061b42017-06-16 10:08:23 +0200109
110 this.yuvStrides = null;
111 this.yuvPlanes = null;
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +0200112 } else if (buffer instanceof VideoFrame.I420Buffer) {
113 VideoFrame.I420Buffer i420Buffer = (VideoFrame.I420Buffer) buffer;
Sami Kalliomäkibc061b42017-06-16 10:08:23 +0200114 this.yuvFrame = true;
115 this.yuvStrides =
116 new int[] {i420Buffer.getStrideY(), i420Buffer.getStrideU(), i420Buffer.getStrideV()};
117 this.yuvPlanes =
118 new ByteBuffer[] {i420Buffer.getDataY(), i420Buffer.getDataU(), i420Buffer.getDataV()};
Sami Kalliomäki372e5872017-06-27 17:00:21 +0200119 // The convention in WebRTC is that the first element in a ByteBuffer corresponds to the
120 // top-left corner of the image, but in glTexImage2D() the first element corresponds to the
121 // bottom-left corner. This discrepancy is corrected by multiplying the sampling matrix with
122 // a vertical flip matrix.
sakal836f60c2017-07-28 07:12:23 -0700123 this.samplingMatrix = RendererCommon.verticalFlipMatrix();
Sami Kalliomäkibc061b42017-06-16 10:08:23 +0200124
125 this.textureId = 0;
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +0200126 } else {
127 this.yuvFrame = false;
128 this.textureId = 0;
129 this.samplingMatrix = null;
130 this.yuvStrides = null;
131 this.yuvPlanes = null;
Sami Kalliomäkibc061b42017-06-16 10:08:23 +0200132 }
133 this.nativeFramePointer = nativeFramePointer;
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +0200134 backingBuffer = buffer;
Sami Kalliomäkibc061b42017-06-16 10:08:23 +0200135 }
136
Magnus Jedvert80cf97c2015-06-11 10:08:59 +0200137 public int rotatedWidth() {
138 return (rotationDegree % 180 == 0) ? width : height;
139 }
140
141 public int rotatedHeight() {
142 return (rotationDegree % 180 == 0) ? height : width;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000143 }
144
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000145 @Override
146 public String toString() {
sakal71a62b92017-08-10 02:12:24 -0700147 final String type = yuvFrame
148 ? "Y: " + yuvStrides[0] + ", U: " + yuvStrides[1] + ", V: " + yuvStrides[2]
149 : "Texture: " + textureId;
150 return width + "x" + height + ", " + type;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000151 }
sakal6bdcefc2017-08-15 01:56:02 -0700152
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +0200153 /**
154 * Convert the frame to VideoFrame. It is no longer safe to use the I420Frame after calling
155 * this.
156 */
sakal6bdcefc2017-08-15 01:56:02 -0700157 VideoFrame toVideoFrame() {
158 final VideoFrame.Buffer buffer;
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +0200159 if (backingBuffer != null) {
160 // We were construted from a VideoFrame.Buffer, just return it.
161 // Make sure webrtc::VideoFrame object is released.
162 backingBuffer.retain();
163 VideoRenderer.renderFrameDone(this);
164 buffer = backingBuffer;
165 } else if (yuvFrame) {
Sami Kalliomäki48b3c022017-10-04 17:01:00 +0200166 buffer = JavaI420Buffer.wrap(width, height, yuvPlanes[0], yuvStrides[0], yuvPlanes[1],
sakal6bdcefc2017-08-15 01:56:02 -0700167 yuvStrides[1], yuvPlanes[2], yuvStrides[2],
168 () -> { VideoRenderer.renderFrameDone(this); });
169 } else {
170 // Note: surfaceTextureHelper being null means calling toI420 will crash.
171 buffer = new TextureBufferImpl(width, height, VideoFrame.TextureBuffer.Type.OES, textureId,
172 RendererCommon.convertMatrixToAndroidGraphicsMatrix(samplingMatrix),
173 null /* surfaceTextureHelper */, () -> { VideoRenderer.renderFrameDone(this); });
174 }
175 return new VideoFrame(buffer, rotationDegree, 0 /* timestampNs */);
176 }
glaznev@webrtc.orgf6932292015-02-05 17:29:59 +0000177 }
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000178
glaznev@webrtc.orgf6932292015-02-05 17:29:59 +0000179 // Helper native function to do a video frame plane copying.
sakalb6760f92016-09-29 04:12:44 -0700180 public static native void nativeCopyPlane(
181 ByteBuffer src, int width, int height, int srcStride, ByteBuffer dst, int dstStride);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000182
Niels Möller8f597622016-03-23 10:33:07 +0100183 /** The real meat of VideoSinkInterface. */
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000184 public static interface Callbacks {
guoweis@webrtc.org00c509a2015-03-12 21:37:26 +0000185 // |frame| might have pending rotation and implementation of Callbacks
Magnus Jedverta6cba3a2015-08-29 15:57:43 +0200186 // should handle that by applying rotation during rendering. The callee
187 // is responsible for signaling when it is done with |frame| by calling
188 // renderFrameDone(frame).
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000189 public void renderFrame(I420Frame frame);
190 }
191
sakalb6760f92016-09-29 04:12:44 -0700192 /**
193 * This must be called after every renderFrame() to release the frame.
194 */
195 public static void renderFrameDone(I420Frame frame) {
196 frame.yuvPlanes = null;
197 frame.textureId = 0;
198 if (frame.nativeFramePointer != 0) {
199 releaseNativeFrame(frame.nativeFramePointer);
200 frame.nativeFramePointer = 0;
201 }
202 }
Magnus Jedverta6cba3a2015-08-29 15:57:43 +0200203
magjedd5031fc2015-08-14 03:13:05 -0700204 long nativeVideoRenderer;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000205
206 public VideoRenderer(Callbacks callbacks) {
207 nativeVideoRenderer = nativeWrapVideoRenderer(callbacks);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000208 }
209
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000210 public void dispose() {
magjedd5031fc2015-08-14 03:13:05 -0700211 if (nativeVideoRenderer == 0) {
212 // Already disposed.
213 return;
214 }
perkj47b62632016-02-08 01:07:19 -0800215
216 freeWrappedVideoRenderer(nativeVideoRenderer);
magjedd5031fc2015-08-14 03:13:05 -0700217 nativeVideoRenderer = 0;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000218 }
219
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000220 private static native long nativeWrapVideoRenderer(Callbacks callbacks);
glaznev@webrtc.orga59c5012014-09-17 03:26:59 +0000221 private static native void freeWrappedVideoRenderer(long nativeVideoRenderer);
Magnus Jedverta6cba3a2015-08-29 15:57:43 +0200222 private static native void releaseNativeFrame(long nativeFramePointer);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000223}