henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 1 | /* |
kjellander | b24317b | 2016-02-10 07:54:43 -0800 | [diff] [blame] | 2 | * Copyright 2013 The WebRTC project authors. All Rights Reserved. |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 3 | * |
kjellander | b24317b | 2016-02-10 07:54:43 -0800 | [diff] [blame] | 4 | * 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.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 9 | */ |
| 10 | |
| 11 | package org.webrtc; |
| 12 | |
Sami Kalliomäki | e7592d8 | 2018-03-22 13:32:44 +0100 | [diff] [blame] | 13 | import javax.annotation.Nullable; |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 14 | import java.nio.ByteBuffer; |
Magnus Jedvert | 9060eb1 | 2017-12-12 12:52:54 +0100 | [diff] [blame] | 15 | import org.webrtc.VideoFrame; |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 16 | |
| 17 | /** |
Niels Möller | 8f59762 | 2016-03-23 10:33:07 +0100 | [diff] [blame] | 18 | * Java version of VideoSinkInterface. In addition to allowing clients to |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 19 | * 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 Jedvert | 84d8ae5 | 2017-12-20 15:12:10 +0100 | [diff] [blame] | 23 | @JNINamespace("webrtc::jni") |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 24 | public class VideoRenderer { |
Magnus Jedvert | e76fb36 | 2015-10-08 19:05:24 +0200 | [diff] [blame] | 25 | /** |
nisse | acd935b | 2016-11-11 03:55:13 -0800 | [diff] [blame] | 26 | * Java version of webrtc::VideoFrame. Frames are only constructed from native code and test |
Magnus Jedvert | e76fb36 | 2015-10-08 19:05:24 +0200 | [diff] [blame] | 27 | * code. |
| 28 | */ |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 29 | public static class I420Frame { |
| 30 | public final int width; |
| 31 | public final int height; |
Sami Kalliomäki | e7592d8 | 2018-03-22 13:32:44 +0100 | [diff] [blame] | 32 | @Nullable public final int[] yuvStrides; |
| 33 | @Nullable public ByteBuffer[] yuvPlanes; |
glaznev@webrtc.org | 9967845 | 2014-09-15 17:52:42 +0000 | [diff] [blame] | 34 | public final boolean yuvFrame; |
Per | 488e75f | 2015-11-19 10:43:36 +0100 | [diff] [blame] | 35 | // 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äki | e7592d8 | 2018-03-22 13:32:44 +0100 | [diff] [blame] | 39 | @Nullable public final float[] samplingMatrix; |
glaznev@webrtc.org | 9967845 | 2014-09-15 17:52:42 +0000 | [diff] [blame] | 40 | public int textureId; |
Magnus Jedvert | 7afc12f | 2015-09-03 12:40:38 +0200 | [diff] [blame] | 41 | // Frame pointer in C++. |
Magnus Jedvert | a6cba3a | 2015-08-29 15:57:43 +0200 | [diff] [blame] | 42 | private long nativeFramePointer; |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 43 | |
guoweis@webrtc.org | 840da7b | 2015-03-18 16:58:13 +0000 | [diff] [blame] | 44 | // rotationDegree is the degree that the frame must be rotated clockwisely |
| 45 | // to be rendered correctly. |
| 46 | public int rotationDegree; |
| 47 | |
Sami Kalliomäki | 7d1f493 | 2017-09-14 12:31:53 +0200 | [diff] [blame] | 48 | // If this I420Frame was constructed from VideoFrame.Buffer, this points to |
| 49 | // the backing buffer. |
Sami Kalliomäki | e7592d8 | 2018-03-22 13:32:44 +0100 | [diff] [blame] | 50 | @Nullable private final VideoFrame.Buffer backingBuffer; |
Sami Kalliomäki | 7d1f493 | 2017-09-14 12:31:53 +0200 | [diff] [blame] | 51 | |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 52 | /** |
Magnus Jedvert | 7afc12f | 2015-09-03 12:40:38 +0200 | [diff] [blame] | 53 | * Construct a frame of the given dimensions with the specified planar data. |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 54 | */ |
skvlad | ed6e077 | 2016-11-30 14:40:18 -0800 | [diff] [blame] | 55 | public I420Frame(int width, int height, int rotationDegree, int[] yuvStrides, |
| 56 | ByteBuffer[] yuvPlanes, long nativeFramePointer) { |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 57 | this.width = width; |
| 58 | this.height = height; |
| 59 | this.yuvStrides = yuvStrides; |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 60 | this.yuvPlanes = yuvPlanes; |
glaznev@webrtc.org | 9967845 | 2014-09-15 17:52:42 +0000 | [diff] [blame] | 61 | this.yuvFrame = true; |
guoweis@webrtc.org | 840da7b | 2015-03-18 16:58:13 +0000 | [diff] [blame] | 62 | this.rotationDegree = rotationDegree; |
Magnus Jedvert | a6cba3a | 2015-08-29 15:57:43 +0200 | [diff] [blame] | 63 | this.nativeFramePointer = nativeFramePointer; |
Sami Kalliomäki | 7d1f493 | 2017-09-14 12:31:53 +0200 | [diff] [blame] | 64 | backingBuffer = null; |
Magnus Jedvert | 80cf97c | 2015-06-11 10:08:59 +0200 | [diff] [blame] | 65 | if (rotationDegree % 90 != 0) { |
| 66 | throw new IllegalArgumentException("Rotation degree not multiple of 90: " + rotationDegree); |
| 67 | } |
Per | 488e75f | 2015-11-19 10:43:36 +0100 | [diff] [blame] | 68 | // 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äki | 372e587 | 2017-06-27 17:00:21 +0200 | [diff] [blame] | 72 | samplingMatrix = RendererCommon.verticalFlipMatrix(); |
glaznev@webrtc.org | 9967845 | 2014-09-15 17:52:42 +0000 | [diff] [blame] | 73 | } |
| 74 | |
| 75 | /** |
| 76 | * Construct a texture frame of the given dimensions with data in SurfaceTexture |
| 77 | */ |
skvlad | ed6e077 | 2016-11-30 14:40:18 -0800 | [diff] [blame] | 78 | public I420Frame(int width, int height, int rotationDegree, int textureId, |
| 79 | float[] samplingMatrix, long nativeFramePointer) { |
glaznev@webrtc.org | 9967845 | 2014-09-15 17:52:42 +0000 | [diff] [blame] | 80 | this.width = width; |
| 81 | this.height = height; |
| 82 | this.yuvStrides = null; |
| 83 | this.yuvPlanes = null; |
Per | 488e75f | 2015-11-19 10:43:36 +0100 | [diff] [blame] | 84 | this.samplingMatrix = samplingMatrix; |
glaznev@webrtc.org | 9967845 | 2014-09-15 17:52:42 +0000 | [diff] [blame] | 85 | this.textureId = textureId; |
| 86 | this.yuvFrame = false; |
guoweis@webrtc.org | 840da7b | 2015-03-18 16:58:13 +0000 | [diff] [blame] | 87 | this.rotationDegree = rotationDegree; |
Magnus Jedvert | a6cba3a | 2015-08-29 15:57:43 +0200 | [diff] [blame] | 88 | this.nativeFramePointer = nativeFramePointer; |
Sami Kalliomäki | 7d1f493 | 2017-09-14 12:31:53 +0200 | [diff] [blame] | 89 | backingBuffer = null; |
Magnus Jedvert | 80cf97c | 2015-06-11 10:08:59 +0200 | [diff] [blame] | 90 | if (rotationDegree % 90 != 0) { |
| 91 | throw new IllegalArgumentException("Rotation degree not multiple of 90: " + rotationDegree); |
| 92 | } |
| 93 | } |
| 94 | |
Sami Kalliomäki | bc061b4 | 2017-06-16 10:08:23 +0200 | [diff] [blame] | 95 | /** |
sakal | 836f60c | 2017-07-28 07:12:23 -0700 | [diff] [blame] | 96 | * Construct a frame from VideoFrame.Buffer. |
Sami Kalliomäki | bc061b4 | 2017-06-16 10:08:23 +0200 | [diff] [blame] | 97 | */ |
Magnus Jedvert | 9060eb1 | 2017-12-12 12:52:54 +0100 | [diff] [blame] | 98 | @CalledByNative("I420Frame") |
sakal | 836f60c | 2017-07-28 07:12:23 -0700 | [diff] [blame] | 99 | public I420Frame(int rotationDegree, VideoFrame.Buffer buffer, long nativeFramePointer) { |
| 100 | this.width = buffer.getWidth(); |
| 101 | this.height = buffer.getHeight(); |
Sami Kalliomäki | bc061b4 | 2017-06-16 10:08:23 +0200 | [diff] [blame] | 102 | this.rotationDegree = rotationDegree; |
| 103 | if (rotationDegree % 90 != 0) { |
| 104 | throw new IllegalArgumentException("Rotation degree not multiple of 90: " + rotationDegree); |
| 105 | } |
magjed | 2ab9879 | 2017-09-13 05:20:45 -0700 | [diff] [blame] | 106 | if (buffer instanceof VideoFrame.TextureBuffer |
| 107 | && ((VideoFrame.TextureBuffer) buffer).getType() == VideoFrame.TextureBuffer.Type.OES) { |
Sami Kalliomäki | bc061b4 | 2017-06-16 10:08:23 +0200 | [diff] [blame] | 108 | VideoFrame.TextureBuffer textureBuffer = (VideoFrame.TextureBuffer) buffer; |
| 109 | this.yuvFrame = false; |
| 110 | this.textureId = textureBuffer.getTextureId(); |
sakal | 836f60c | 2017-07-28 07:12:23 -0700 | [diff] [blame] | 111 | this.samplingMatrix = RendererCommon.convertMatrixFromAndroidGraphicsMatrix( |
| 112 | textureBuffer.getTransformMatrix()); |
Sami Kalliomäki | bc061b4 | 2017-06-16 10:08:23 +0200 | [diff] [blame] | 113 | |
| 114 | this.yuvStrides = null; |
| 115 | this.yuvPlanes = null; |
Sami Kalliomäki | 7d1f493 | 2017-09-14 12:31:53 +0200 | [diff] [blame] | 116 | } else if (buffer instanceof VideoFrame.I420Buffer) { |
| 117 | VideoFrame.I420Buffer i420Buffer = (VideoFrame.I420Buffer) buffer; |
Sami Kalliomäki | bc061b4 | 2017-06-16 10:08:23 +0200 | [diff] [blame] | 118 | 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äki | 372e587 | 2017-06-27 17:00:21 +0200 | [diff] [blame] | 123 | // 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. |
sakal | 836f60c | 2017-07-28 07:12:23 -0700 | [diff] [blame] | 127 | this.samplingMatrix = RendererCommon.verticalFlipMatrix(); |
Sami Kalliomäki | bc061b4 | 2017-06-16 10:08:23 +0200 | [diff] [blame] | 128 | |
| 129 | this.textureId = 0; |
Sami Kalliomäki | 7d1f493 | 2017-09-14 12:31:53 +0200 | [diff] [blame] | 130 | } else { |
| 131 | this.yuvFrame = false; |
| 132 | this.textureId = 0; |
| 133 | this.samplingMatrix = null; |
| 134 | this.yuvStrides = null; |
| 135 | this.yuvPlanes = null; |
Sami Kalliomäki | bc061b4 | 2017-06-16 10:08:23 +0200 | [diff] [blame] | 136 | } |
| 137 | this.nativeFramePointer = nativeFramePointer; |
Sami Kalliomäki | 7d1f493 | 2017-09-14 12:31:53 +0200 | [diff] [blame] | 138 | backingBuffer = buffer; |
Sami Kalliomäki | bc061b4 | 2017-06-16 10:08:23 +0200 | [diff] [blame] | 139 | } |
| 140 | |
Magnus Jedvert | 80cf97c | 2015-06-11 10:08:59 +0200 | [diff] [blame] | 141 | 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.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 147 | } |
| 148 | |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 149 | @Override |
| 150 | public String toString() { |
sakal | 71a62b9 | 2017-08-10 02:12:24 -0700 | [diff] [blame] | 151 | 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.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 155 | } |
sakal | 6bdcefc | 2017-08-15 01:56:02 -0700 | [diff] [blame] | 156 | |
Sami Kalliomäki | 7d1f493 | 2017-09-14 12:31:53 +0200 | [diff] [blame] | 157 | /** |
| 158 | * Convert the frame to VideoFrame. It is no longer safe to use the I420Frame after calling |
| 159 | * this. |
| 160 | */ |
sakal | 6bdcefc | 2017-08-15 01:56:02 -0700 | [diff] [blame] | 161 | VideoFrame toVideoFrame() { |
| 162 | final VideoFrame.Buffer buffer; |
Sami Kalliomäki | 7d1f493 | 2017-09-14 12:31:53 +0200 | [diff] [blame] | 163 | 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äki | 48b3c02 | 2017-10-04 17:01:00 +0200 | [diff] [blame] | 170 | buffer = JavaI420Buffer.wrap(width, height, yuvPlanes[0], yuvStrides[0], yuvPlanes[1], |
sakal | 6bdcefc | 2017-08-15 01:56:02 -0700 | [diff] [blame] | 171 | yuvStrides[1], yuvPlanes[2], yuvStrides[2], |
| 172 | () -> { VideoRenderer.renderFrameDone(this); }); |
| 173 | } else { |
Sami Kalliomäki | 64051d4 | 2018-04-16 13:37:07 +0000 | [diff] [blame^] | 174 | // Note: surfaceTextureHelper being null means calling toI420 will crash. |
sakal | 6bdcefc | 2017-08-15 01:56:02 -0700 | [diff] [blame] | 175 | buffer = new TextureBufferImpl(width, height, VideoFrame.TextureBuffer.Type.OES, textureId, |
Sami Kalliomäki | 64051d4 | 2018-04-16 13:37:07 +0000 | [diff] [blame^] | 176 | RendererCommon.convertMatrixToAndroidGraphicsMatrix(samplingMatrix), |
| 177 | null /* surfaceTextureHelper */, () -> { VideoRenderer.renderFrameDone(this); }); |
sakal | 6bdcefc | 2017-08-15 01:56:02 -0700 | [diff] [blame] | 178 | } |
| 179 | return new VideoFrame(buffer, rotationDegree, 0 /* timestampNs */); |
| 180 | } |
Magnus Jedvert | 9060eb1 | 2017-12-12 12:52:54 +0100 | [diff] [blame] | 181 | |
| 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.org | f693229 | 2015-02-05 17:29:59 +0000 | [diff] [blame] | 196 | } |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 197 | |
glaznev@webrtc.org | f693229 | 2015-02-05 17:29:59 +0000 | [diff] [blame] | 198 | // Helper native function to do a video frame plane copying. |
Magnus Jedvert | 84d8ae5 | 2017-12-20 15:12:10 +0100 | [diff] [blame] | 199 | static native void nativeCopyPlane( |
sakal | b6760f9 | 2016-09-29 04:12:44 -0700 | [diff] [blame] | 200 | ByteBuffer src, int width, int height, int srcStride, ByteBuffer dst, int dstStride); |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 201 | |
Niels Möller | 8f59762 | 2016-03-23 10:33:07 +0100 | [diff] [blame] | 202 | /** The real meat of VideoSinkInterface. */ |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 203 | public static interface Callbacks { |
guoweis@webrtc.org | 00c509a | 2015-03-12 21:37:26 +0000 | [diff] [blame] | 204 | // |frame| might have pending rotation and implementation of Callbacks |
Magnus Jedvert | a6cba3a | 2015-08-29 15:57:43 +0200 | [diff] [blame] | 205 | // 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 Jedvert | 9060eb1 | 2017-12-12 12:52:54 +0100 | [diff] [blame] | 208 | @CalledByNative("Callbacks") void renderFrame(I420Frame frame); |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 209 | } |
| 210 | |
sakal | b6760f9 | 2016-09-29 04:12:44 -0700 | [diff] [blame] | 211 | /** |
| 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 Jedvert | 84d8ae5 | 2017-12-20 15:12:10 +0100 | [diff] [blame] | 218 | nativeReleaseFrame(frame.nativeFramePointer); |
sakal | b6760f9 | 2016-09-29 04:12:44 -0700 | [diff] [blame] | 219 | frame.nativeFramePointer = 0; |
| 220 | } |
| 221 | } |
Magnus Jedvert | a6cba3a | 2015-08-29 15:57:43 +0200 | [diff] [blame] | 222 | |
magjed | d5031fc | 2015-08-14 03:13:05 -0700 | [diff] [blame] | 223 | long nativeVideoRenderer; |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 224 | |
| 225 | public VideoRenderer(Callbacks callbacks) { |
Magnus Jedvert | 84d8ae5 | 2017-12-20 15:12:10 +0100 | [diff] [blame] | 226 | nativeVideoRenderer = nativeCreateVideoRenderer(callbacks); |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 227 | } |
| 228 | |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 229 | public void dispose() { |
magjed | d5031fc | 2015-08-14 03:13:05 -0700 | [diff] [blame] | 230 | if (nativeVideoRenderer == 0) { |
| 231 | // Already disposed. |
| 232 | return; |
| 233 | } |
perkj | 47b6263 | 2016-02-08 01:07:19 -0800 | [diff] [blame] | 234 | |
Magnus Jedvert | 84d8ae5 | 2017-12-20 15:12:10 +0100 | [diff] [blame] | 235 | nativeFreeWrappedVideoRenderer(nativeVideoRenderer); |
magjed | d5031fc | 2015-08-14 03:13:05 -0700 | [diff] [blame] | 236 | nativeVideoRenderer = 0; |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 237 | } |
| 238 | |
Magnus Jedvert | 84d8ae5 | 2017-12-20 15:12:10 +0100 | [diff] [blame] | 239 | 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.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 242 | } |