blob: 696683c463bc48eadb73d0598dad069fe3e68848 [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 Jedvertb616d092018-06-07 15:12:58 +020023// Use VideoSink instead.
24@Deprecated
henrike@webrtc.org28e20752013-07-10 00:45:36 +000025public class VideoRenderer {
Magnus Jedverte76fb362015-10-08 19:05:24 +020026 /**
nisseacd935b2016-11-11 03:55:13 -080027 * Java version of webrtc::VideoFrame. Frames are only constructed from native code and test
Magnus Jedverte76fb362015-10-08 19:05:24 +020028 * code.
29 */
Magnus Jedvertb616d092018-06-07 15:12:58 +020030 // Use VideoFrame instead.
31 @Deprecated
henrike@webrtc.org28e20752013-07-10 00:45:36 +000032 public static class I420Frame {
33 public final int width;
34 public final int height;
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010035 @Nullable public final int[] yuvStrides;
36 @Nullable public ByteBuffer[] yuvPlanes;
glaznev@webrtc.org99678452014-09-15 17:52:42 +000037 public final boolean yuvFrame;
Per488e75f2015-11-19 10:43:36 +010038 // Matrix that transforms standard coordinates to their proper sampling locations in
39 // the texture. This transform compensates for any properties of the video source that
40 // cause it to appear different from a normalized texture. This matrix does not take
41 // |rotationDegree| into account.
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010042 @Nullable public final float[] samplingMatrix;
glaznev@webrtc.org99678452014-09-15 17:52:42 +000043 public int textureId;
Magnus Jedvert7afc12f2015-09-03 12:40:38 +020044 // Frame pointer in C++.
Magnus Jedverta6cba3a2015-08-29 15:57:43 +020045 private long nativeFramePointer;
henrike@webrtc.org28e20752013-07-10 00:45:36 +000046
guoweis@webrtc.org840da7b2015-03-18 16:58:13 +000047 // rotationDegree is the degree that the frame must be rotated clockwisely
48 // to be rendered correctly.
49 public int rotationDegree;
50
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +020051 // If this I420Frame was constructed from VideoFrame.Buffer, this points to
52 // the backing buffer.
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010053 @Nullable private final VideoFrame.Buffer backingBuffer;
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +020054
henrike@webrtc.org28e20752013-07-10 00:45:36 +000055 /**
Magnus Jedvert7afc12f2015-09-03 12:40:38 +020056 * Construct a frame of the given dimensions with the specified planar data.
henrike@webrtc.org28e20752013-07-10 00:45:36 +000057 */
skvladed6e0772016-11-30 14:40:18 -080058 public I420Frame(int width, int height, int rotationDegree, int[] yuvStrides,
59 ByteBuffer[] yuvPlanes, long nativeFramePointer) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +000060 this.width = width;
61 this.height = height;
62 this.yuvStrides = yuvStrides;
henrike@webrtc.org28e20752013-07-10 00:45:36 +000063 this.yuvPlanes = yuvPlanes;
glaznev@webrtc.org99678452014-09-15 17:52:42 +000064 this.yuvFrame = true;
guoweis@webrtc.org840da7b2015-03-18 16:58:13 +000065 this.rotationDegree = rotationDegree;
Magnus Jedverta6cba3a2015-08-29 15:57:43 +020066 this.nativeFramePointer = nativeFramePointer;
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +020067 backingBuffer = null;
Magnus Jedvert80cf97c2015-06-11 10:08:59 +020068 if (rotationDegree % 90 != 0) {
69 throw new IllegalArgumentException("Rotation degree not multiple of 90: " + rotationDegree);
70 }
Per488e75f2015-11-19 10:43:36 +010071 // The convention in WebRTC is that the first element in a ByteBuffer corresponds to the
72 // top-left corner of the image, but in glTexImage2D() the first element corresponds to the
73 // bottom-left corner. This discrepancy is corrected by setting a vertical flip as sampling
74 // matrix.
Sami Kalliomäki372e5872017-06-27 17:00:21 +020075 samplingMatrix = RendererCommon.verticalFlipMatrix();
glaznev@webrtc.org99678452014-09-15 17:52:42 +000076 }
77
78 /**
79 * Construct a texture frame of the given dimensions with data in SurfaceTexture
80 */
skvladed6e0772016-11-30 14:40:18 -080081 public I420Frame(int width, int height, int rotationDegree, int textureId,
82 float[] samplingMatrix, long nativeFramePointer) {
glaznev@webrtc.org99678452014-09-15 17:52:42 +000083 this.width = width;
84 this.height = height;
85 this.yuvStrides = null;
86 this.yuvPlanes = null;
Per488e75f2015-11-19 10:43:36 +010087 this.samplingMatrix = samplingMatrix;
glaznev@webrtc.org99678452014-09-15 17:52:42 +000088 this.textureId = textureId;
89 this.yuvFrame = false;
guoweis@webrtc.org840da7b2015-03-18 16:58:13 +000090 this.rotationDegree = rotationDegree;
Magnus Jedverta6cba3a2015-08-29 15:57:43 +020091 this.nativeFramePointer = nativeFramePointer;
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +020092 backingBuffer = null;
Magnus Jedvert80cf97c2015-06-11 10:08:59 +020093 if (rotationDegree % 90 != 0) {
94 throw new IllegalArgumentException("Rotation degree not multiple of 90: " + rotationDegree);
95 }
96 }
97
Sami Kalliomäkibc061b42017-06-16 10:08:23 +020098 /**
sakal836f60c2017-07-28 07:12:23 -070099 * Construct a frame from VideoFrame.Buffer.
Sami Kalliomäkibc061b42017-06-16 10:08:23 +0200100 */
Magnus Jedvert9060eb12017-12-12 12:52:54 +0100101 @CalledByNative("I420Frame")
sakal836f60c2017-07-28 07:12:23 -0700102 public I420Frame(int rotationDegree, VideoFrame.Buffer buffer, long nativeFramePointer) {
103 this.width = buffer.getWidth();
104 this.height = buffer.getHeight();
Sami Kalliomäkibc061b42017-06-16 10:08:23 +0200105 this.rotationDegree = rotationDegree;
106 if (rotationDegree % 90 != 0) {
107 throw new IllegalArgumentException("Rotation degree not multiple of 90: " + rotationDegree);
108 }
magjed2ab98792017-09-13 05:20:45 -0700109 if (buffer instanceof VideoFrame.TextureBuffer
110 && ((VideoFrame.TextureBuffer) buffer).getType() == VideoFrame.TextureBuffer.Type.OES) {
Sami Kalliomäkibc061b42017-06-16 10:08:23 +0200111 VideoFrame.TextureBuffer textureBuffer = (VideoFrame.TextureBuffer) buffer;
112 this.yuvFrame = false;
113 this.textureId = textureBuffer.getTextureId();
sakal836f60c2017-07-28 07:12:23 -0700114 this.samplingMatrix = RendererCommon.convertMatrixFromAndroidGraphicsMatrix(
115 textureBuffer.getTransformMatrix());
Sami Kalliomäkibc061b42017-06-16 10:08:23 +0200116
117 this.yuvStrides = null;
118 this.yuvPlanes = null;
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +0200119 } else if (buffer instanceof VideoFrame.I420Buffer) {
120 VideoFrame.I420Buffer i420Buffer = (VideoFrame.I420Buffer) buffer;
Sami Kalliomäkibc061b42017-06-16 10:08:23 +0200121 this.yuvFrame = true;
122 this.yuvStrides =
123 new int[] {i420Buffer.getStrideY(), i420Buffer.getStrideU(), i420Buffer.getStrideV()};
124 this.yuvPlanes =
125 new ByteBuffer[] {i420Buffer.getDataY(), i420Buffer.getDataU(), i420Buffer.getDataV()};
Sami Kalliomäki372e5872017-06-27 17:00:21 +0200126 // The convention in WebRTC is that the first element in a ByteBuffer corresponds to the
127 // top-left corner of the image, but in glTexImage2D() the first element corresponds to the
128 // bottom-left corner. This discrepancy is corrected by multiplying the sampling matrix with
129 // a vertical flip matrix.
sakal836f60c2017-07-28 07:12:23 -0700130 this.samplingMatrix = RendererCommon.verticalFlipMatrix();
Sami Kalliomäkibc061b42017-06-16 10:08:23 +0200131
132 this.textureId = 0;
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +0200133 } else {
134 this.yuvFrame = false;
135 this.textureId = 0;
136 this.samplingMatrix = null;
137 this.yuvStrides = null;
138 this.yuvPlanes = null;
Sami Kalliomäkibc061b42017-06-16 10:08:23 +0200139 }
140 this.nativeFramePointer = nativeFramePointer;
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +0200141 backingBuffer = buffer;
Sami Kalliomäkibc061b42017-06-16 10:08:23 +0200142 }
143
Magnus Jedvert80cf97c2015-06-11 10:08:59 +0200144 public int rotatedWidth() {
145 return (rotationDegree % 180 == 0) ? width : height;
146 }
147
148 public int rotatedHeight() {
149 return (rotationDegree % 180 == 0) ? height : width;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000150 }
151
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000152 @Override
153 public String toString() {
sakal71a62b92017-08-10 02:12:24 -0700154 final String type = yuvFrame
155 ? "Y: " + yuvStrides[0] + ", U: " + yuvStrides[1] + ", V: " + yuvStrides[2]
156 : "Texture: " + textureId;
157 return width + "x" + height + ", " + type;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000158 }
sakal6bdcefc2017-08-15 01:56:02 -0700159
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +0200160 /**
161 * Convert the frame to VideoFrame. It is no longer safe to use the I420Frame after calling
162 * this.
163 */
sakal6bdcefc2017-08-15 01:56:02 -0700164 VideoFrame toVideoFrame() {
165 final VideoFrame.Buffer buffer;
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +0200166 if (backingBuffer != null) {
167 // We were construted from a VideoFrame.Buffer, just return it.
168 // Make sure webrtc::VideoFrame object is released.
169 backingBuffer.retain();
170 VideoRenderer.renderFrameDone(this);
171 buffer = backingBuffer;
172 } else if (yuvFrame) {
Sami Kalliomäki48b3c022017-10-04 17:01:00 +0200173 buffer = JavaI420Buffer.wrap(width, height, yuvPlanes[0], yuvStrides[0], yuvPlanes[1],
sakal6bdcefc2017-08-15 01:56:02 -0700174 yuvStrides[1], yuvPlanes[2], yuvStrides[2],
175 () -> { VideoRenderer.renderFrameDone(this); });
176 } else {
Magnus Jedvert1d270f82018-04-16 16:28:29 +0200177 // Note: No Handler or YuvConverter means calling toI420 will crash.
sakal6bdcefc2017-08-15 01:56:02 -0700178 buffer = new TextureBufferImpl(width, height, VideoFrame.TextureBuffer.Type.OES, textureId,
Magnus Jedvert1d270f82018-04-16 16:28:29 +0200179 RendererCommon.convertMatrixToAndroidGraphicsMatrix(samplingMatrix), null /* handler */,
180 null /* yuvConverter */, () -> VideoRenderer.renderFrameDone(this));
sakal6bdcefc2017-08-15 01:56:02 -0700181 }
182 return new VideoFrame(buffer, rotationDegree, 0 /* timestampNs */);
183 }
Magnus Jedvert9060eb12017-12-12 12:52:54 +0100184
185 @CalledByNative("I420Frame")
186 static I420Frame createI420Frame(int width, int height, int rotationDegree, int y_stride,
187 ByteBuffer y_buffer, int u_stride, ByteBuffer u_buffer, int v_stride, ByteBuffer v_buffer,
188 long nativeFramePointer) {
189 return new I420Frame(width, height, rotationDegree, new int[] {y_stride, u_stride, v_stride},
190 new ByteBuffer[] {y_buffer, u_buffer, v_buffer}, nativeFramePointer);
191 }
glaznev@webrtc.orgf6932292015-02-05 17:29:59 +0000192 }
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000193
Niels Möller8f597622016-03-23 10:33:07 +0100194 /** The real meat of VideoSinkInterface. */
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000195 public static interface Callbacks {
guoweis@webrtc.org00c509a2015-03-12 21:37:26 +0000196 // |frame| might have pending rotation and implementation of Callbacks
Magnus Jedverta6cba3a2015-08-29 15:57:43 +0200197 // should handle that by applying rotation during rendering. The callee
198 // is responsible for signaling when it is done with |frame| by calling
199 // renderFrameDone(frame).
Magnus Jedvert9060eb12017-12-12 12:52:54 +0100200 @CalledByNative("Callbacks") void renderFrame(I420Frame frame);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000201 }
202
sakalb6760f92016-09-29 04:12:44 -0700203 /**
204 * This must be called after every renderFrame() to release the frame.
205 */
206 public static void renderFrameDone(I420Frame frame) {
207 frame.yuvPlanes = null;
208 frame.textureId = 0;
209 if (frame.nativeFramePointer != 0) {
Magnus Jedvert84d8ae52017-12-20 15:12:10 +0100210 nativeReleaseFrame(frame.nativeFramePointer);
sakalb6760f92016-09-29 04:12:44 -0700211 frame.nativeFramePointer = 0;
212 }
213 }
Magnus Jedverta6cba3a2015-08-29 15:57:43 +0200214
magjedd5031fc2015-08-14 03:13:05 -0700215 long nativeVideoRenderer;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000216
217 public VideoRenderer(Callbacks callbacks) {
Magnus Jedvert84d8ae52017-12-20 15:12:10 +0100218 nativeVideoRenderer = nativeCreateVideoRenderer(callbacks);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000219 }
220
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000221 public void dispose() {
magjedd5031fc2015-08-14 03:13:05 -0700222 if (nativeVideoRenderer == 0) {
223 // Already disposed.
224 return;
225 }
perkj47b62632016-02-08 01:07:19 -0800226
Magnus Jedvert84d8ae52017-12-20 15:12:10 +0100227 nativeFreeWrappedVideoRenderer(nativeVideoRenderer);
magjedd5031fc2015-08-14 03:13:05 -0700228 nativeVideoRenderer = 0;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000229 }
230
Magnus Jedvert84d8ae52017-12-20 15:12:10 +0100231 private static native long nativeCreateVideoRenderer(Callbacks callbacks);
232 private static native void nativeFreeWrappedVideoRenderer(long videoRenderer);
233 private static native void nativeReleaseFrame(long framePointer);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000234}