blob: f781679b3c937da7d90763761eb0f4e9cf50dbdd [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 */
23public 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;
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010031 @Nullable public final int[] yuvStrides;
32 @Nullable 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.
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010038 @Nullable 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.
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010049 @Nullable private final VideoFrame.Buffer backingBuffer;
Sami Kalliomäki7d1f4932017-09-14 12:31:53 +020050
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 {
Magnus Jedvert1d270f82018-04-16 16:28:29 +0200173 // Note: No Handler or YuvConverter means calling toI420 will crash.
sakal6bdcefc2017-08-15 01:56:02 -0700174 buffer = new TextureBufferImpl(width, height, VideoFrame.TextureBuffer.Type.OES, textureId,
Magnus Jedvert1d270f82018-04-16 16:28:29 +0200175 RendererCommon.convertMatrixToAndroidGraphicsMatrix(samplingMatrix), null /* handler */,
176 null /* yuvConverter */, () -> VideoRenderer.renderFrameDone(this));
sakal6bdcefc2017-08-15 01:56:02 -0700177 }
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 }
glaznev@webrtc.orgf6932292015-02-05 17:29:59 +0000188 }
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000189
Niels Möller8f597622016-03-23 10:33:07 +0100190 /** The real meat of VideoSinkInterface. */
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000191 public static interface Callbacks {
guoweis@webrtc.org00c509a2015-03-12 21:37:26 +0000192 // |frame| might have pending rotation and implementation of Callbacks
Magnus Jedverta6cba3a2015-08-29 15:57:43 +0200193 // should handle that by applying rotation during rendering. The callee
194 // is responsible for signaling when it is done with |frame| by calling
195 // renderFrameDone(frame).
Magnus Jedvert9060eb12017-12-12 12:52:54 +0100196 @CalledByNative("Callbacks") void renderFrame(I420Frame frame);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000197 }
198
sakalb6760f92016-09-29 04:12:44 -0700199 /**
200 * This must be called after every renderFrame() to release the frame.
201 */
202 public static void renderFrameDone(I420Frame frame) {
203 frame.yuvPlanes = null;
204 frame.textureId = 0;
205 if (frame.nativeFramePointer != 0) {
Magnus Jedvert84d8ae52017-12-20 15:12:10 +0100206 nativeReleaseFrame(frame.nativeFramePointer);
sakalb6760f92016-09-29 04:12:44 -0700207 frame.nativeFramePointer = 0;
208 }
209 }
Magnus Jedverta6cba3a2015-08-29 15:57:43 +0200210
magjedd5031fc2015-08-14 03:13:05 -0700211 long nativeVideoRenderer;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000212
213 public VideoRenderer(Callbacks callbacks) {
Magnus Jedvert84d8ae52017-12-20 15:12:10 +0100214 nativeVideoRenderer = nativeCreateVideoRenderer(callbacks);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000215 }
216
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000217 public void dispose() {
magjedd5031fc2015-08-14 03:13:05 -0700218 if (nativeVideoRenderer == 0) {
219 // Already disposed.
220 return;
221 }
perkj47b62632016-02-08 01:07:19 -0800222
Magnus Jedvert84d8ae52017-12-20 15:12:10 +0100223 nativeFreeWrappedVideoRenderer(nativeVideoRenderer);
magjedd5031fc2015-08-14 03:13:05 -0700224 nativeVideoRenderer = 0;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000225 }
226
Magnus Jedvert84d8ae52017-12-20 15:12:10 +0100227 private static native long nativeCreateVideoRenderer(Callbacks callbacks);
228 private static native void nativeFreeWrappedVideoRenderer(long videoRenderer);
229 private static native void nativeReleaseFrame(long framePointer);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000230}