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 | |
| 13 | import java.nio.ByteBuffer; |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 14 | |
| 15 | /** |
Niels Möller | 8f59762 | 2016-03-23 10:33:07 +0100 | [diff] [blame] | 16 | * Java version of VideoSinkInterface. In addition to allowing clients to |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 17 | * 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 | */ |
| 21 | public class VideoRenderer { |
Magnus Jedvert | e76fb36 | 2015-10-08 19:05:24 +0200 | [diff] [blame] | 22 | /** |
nisse | acd935b | 2016-11-11 03:55:13 -0800 | [diff] [blame] | 23 | * 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] | 24 | * code. |
| 25 | */ |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 26 | public static class I420Frame { |
| 27 | public final int width; |
| 28 | public final int height; |
| 29 | public final int[] yuvStrides; |
Magnus Jedvert | a6cba3a | 2015-08-29 15:57:43 +0200 | [diff] [blame] | 30 | public ByteBuffer[] yuvPlanes; |
glaznev@webrtc.org | 9967845 | 2014-09-15 17:52:42 +0000 | [diff] [blame] | 31 | public final boolean yuvFrame; |
Per | 488e75f | 2015-11-19 10:43:36 +0100 | [diff] [blame] | 32 | // 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.org | 9967845 | 2014-09-15 17:52:42 +0000 | [diff] [blame] | 37 | public int textureId; |
Magnus Jedvert | 7afc12f | 2015-09-03 12:40:38 +0200 | [diff] [blame] | 38 | // Frame pointer in C++. |
Magnus Jedvert | a6cba3a | 2015-08-29 15:57:43 +0200 | [diff] [blame] | 39 | private long nativeFramePointer; |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 40 | |
guoweis@webrtc.org | 840da7b | 2015-03-18 16:58:13 +0000 | [diff] [blame] | 41 | // rotationDegree is the degree that the frame must be rotated clockwisely |
| 42 | // to be rendered correctly. |
| 43 | public int rotationDegree; |
| 44 | |
Sami Kalliomäki | 7d1f493 | 2017-09-14 12:31:53 +0200 | [diff] [blame] | 45 | // If this I420Frame was constructed from VideoFrame.Buffer, this points to |
| 46 | // the backing buffer. |
| 47 | private final VideoFrame.Buffer backingBuffer; |
| 48 | |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 49 | /** |
Magnus Jedvert | 7afc12f | 2015-09-03 12:40:38 +0200 | [diff] [blame] | 50 | * 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] | 51 | */ |
skvlad | ed6e077 | 2016-11-30 14:40:18 -0800 | [diff] [blame] | 52 | public I420Frame(int width, int height, int rotationDegree, int[] yuvStrides, |
| 53 | ByteBuffer[] yuvPlanes, long nativeFramePointer) { |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 54 | this.width = width; |
| 55 | this.height = height; |
| 56 | this.yuvStrides = yuvStrides; |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 57 | this.yuvPlanes = yuvPlanes; |
glaznev@webrtc.org | 9967845 | 2014-09-15 17:52:42 +0000 | [diff] [blame] | 58 | this.yuvFrame = true; |
guoweis@webrtc.org | 840da7b | 2015-03-18 16:58:13 +0000 | [diff] [blame] | 59 | this.rotationDegree = rotationDegree; |
Magnus Jedvert | a6cba3a | 2015-08-29 15:57:43 +0200 | [diff] [blame] | 60 | this.nativeFramePointer = nativeFramePointer; |
Sami Kalliomäki | 7d1f493 | 2017-09-14 12:31:53 +0200 | [diff] [blame] | 61 | backingBuffer = null; |
Magnus Jedvert | 80cf97c | 2015-06-11 10:08:59 +0200 | [diff] [blame] | 62 | if (rotationDegree % 90 != 0) { |
| 63 | throw new IllegalArgumentException("Rotation degree not multiple of 90: " + rotationDegree); |
| 64 | } |
Per | 488e75f | 2015-11-19 10:43:36 +0100 | [diff] [blame] | 65 | // 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äki | 372e587 | 2017-06-27 17:00:21 +0200 | [diff] [blame] | 69 | samplingMatrix = RendererCommon.verticalFlipMatrix(); |
glaznev@webrtc.org | 9967845 | 2014-09-15 17:52:42 +0000 | [diff] [blame] | 70 | } |
| 71 | |
| 72 | /** |
| 73 | * Construct a texture frame of the given dimensions with data in SurfaceTexture |
| 74 | */ |
skvlad | ed6e077 | 2016-11-30 14:40:18 -0800 | [diff] [blame] | 75 | public I420Frame(int width, int height, int rotationDegree, int textureId, |
| 76 | float[] samplingMatrix, long nativeFramePointer) { |
glaznev@webrtc.org | 9967845 | 2014-09-15 17:52:42 +0000 | [diff] [blame] | 77 | this.width = width; |
| 78 | this.height = height; |
| 79 | this.yuvStrides = null; |
| 80 | this.yuvPlanes = null; |
Per | 488e75f | 2015-11-19 10:43:36 +0100 | [diff] [blame] | 81 | this.samplingMatrix = samplingMatrix; |
glaznev@webrtc.org | 9967845 | 2014-09-15 17:52:42 +0000 | [diff] [blame] | 82 | this.textureId = textureId; |
| 83 | this.yuvFrame = false; |
guoweis@webrtc.org | 840da7b | 2015-03-18 16:58:13 +0000 | [diff] [blame] | 84 | this.rotationDegree = rotationDegree; |
Magnus Jedvert | a6cba3a | 2015-08-29 15:57:43 +0200 | [diff] [blame] | 85 | this.nativeFramePointer = nativeFramePointer; |
Sami Kalliomäki | 7d1f493 | 2017-09-14 12:31:53 +0200 | [diff] [blame] | 86 | backingBuffer = null; |
Magnus Jedvert | 80cf97c | 2015-06-11 10:08:59 +0200 | [diff] [blame] | 87 | if (rotationDegree % 90 != 0) { |
| 88 | throw new IllegalArgumentException("Rotation degree not multiple of 90: " + rotationDegree); |
| 89 | } |
| 90 | } |
| 91 | |
Sami Kalliomäki | bc061b4 | 2017-06-16 10:08:23 +0200 | [diff] [blame] | 92 | /** |
sakal | 836f60c | 2017-07-28 07:12:23 -0700 | [diff] [blame] | 93 | * Construct a frame from VideoFrame.Buffer. |
Sami Kalliomäki | bc061b4 | 2017-06-16 10:08:23 +0200 | [diff] [blame] | 94 | */ |
sakal | 836f60c | 2017-07-28 07:12:23 -0700 | [diff] [blame] | 95 | public I420Frame(int rotationDegree, VideoFrame.Buffer buffer, long nativeFramePointer) { |
| 96 | this.width = buffer.getWidth(); |
| 97 | this.height = buffer.getHeight(); |
Sami Kalliomäki | bc061b4 | 2017-06-16 10:08:23 +0200 | [diff] [blame] | 98 | this.rotationDegree = rotationDegree; |
| 99 | if (rotationDegree % 90 != 0) { |
| 100 | throw new IllegalArgumentException("Rotation degree not multiple of 90: " + rotationDegree); |
| 101 | } |
magjed | 2ab9879 | 2017-09-13 05:20:45 -0700 | [diff] [blame] | 102 | if (buffer instanceof VideoFrame.TextureBuffer |
| 103 | && ((VideoFrame.TextureBuffer) buffer).getType() == VideoFrame.TextureBuffer.Type.OES) { |
Sami Kalliomäki | bc061b4 | 2017-06-16 10:08:23 +0200 | [diff] [blame] | 104 | VideoFrame.TextureBuffer textureBuffer = (VideoFrame.TextureBuffer) buffer; |
| 105 | this.yuvFrame = false; |
| 106 | this.textureId = textureBuffer.getTextureId(); |
sakal | 836f60c | 2017-07-28 07:12:23 -0700 | [diff] [blame] | 107 | this.samplingMatrix = RendererCommon.convertMatrixFromAndroidGraphicsMatrix( |
| 108 | textureBuffer.getTransformMatrix()); |
Sami Kalliomäki | bc061b4 | 2017-06-16 10:08:23 +0200 | [diff] [blame] | 109 | |
| 110 | this.yuvStrides = null; |
| 111 | this.yuvPlanes = null; |
Sami Kalliomäki | 7d1f493 | 2017-09-14 12:31:53 +0200 | [diff] [blame] | 112 | } else if (buffer instanceof VideoFrame.I420Buffer) { |
| 113 | VideoFrame.I420Buffer i420Buffer = (VideoFrame.I420Buffer) buffer; |
Sami Kalliomäki | bc061b4 | 2017-06-16 10:08:23 +0200 | [diff] [blame] | 114 | 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äki | 372e587 | 2017-06-27 17:00:21 +0200 | [diff] [blame] | 119 | // 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. |
sakal | 836f60c | 2017-07-28 07:12:23 -0700 | [diff] [blame] | 123 | this.samplingMatrix = RendererCommon.verticalFlipMatrix(); |
Sami Kalliomäki | bc061b4 | 2017-06-16 10:08:23 +0200 | [diff] [blame] | 124 | |
| 125 | this.textureId = 0; |
Sami Kalliomäki | 7d1f493 | 2017-09-14 12:31:53 +0200 | [diff] [blame] | 126 | } else { |
| 127 | this.yuvFrame = false; |
| 128 | this.textureId = 0; |
| 129 | this.samplingMatrix = null; |
| 130 | this.yuvStrides = null; |
| 131 | this.yuvPlanes = null; |
Sami Kalliomäki | bc061b4 | 2017-06-16 10:08:23 +0200 | [diff] [blame] | 132 | } |
| 133 | this.nativeFramePointer = nativeFramePointer; |
Sami Kalliomäki | 7d1f493 | 2017-09-14 12:31:53 +0200 | [diff] [blame] | 134 | backingBuffer = buffer; |
Sami Kalliomäki | bc061b4 | 2017-06-16 10:08:23 +0200 | [diff] [blame] | 135 | } |
| 136 | |
Magnus Jedvert | 80cf97c | 2015-06-11 10:08:59 +0200 | [diff] [blame] | 137 | 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.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 143 | } |
| 144 | |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 145 | @Override |
| 146 | public String toString() { |
sakal | 71a62b9 | 2017-08-10 02:12:24 -0700 | [diff] [blame] | 147 | 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.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 151 | } |
sakal | 6bdcefc | 2017-08-15 01:56:02 -0700 | [diff] [blame] | 152 | |
Sami Kalliomäki | 7d1f493 | 2017-09-14 12:31:53 +0200 | [diff] [blame] | 153 | /** |
| 154 | * Convert the frame to VideoFrame. It is no longer safe to use the I420Frame after calling |
| 155 | * this. |
| 156 | */ |
sakal | 6bdcefc | 2017-08-15 01:56:02 -0700 | [diff] [blame] | 157 | VideoFrame toVideoFrame() { |
| 158 | final VideoFrame.Buffer buffer; |
Sami Kalliomäki | 7d1f493 | 2017-09-14 12:31:53 +0200 | [diff] [blame] | 159 | 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äki | 48b3c02 | 2017-10-04 17:01:00 +0200 | [diff] [blame] | 166 | buffer = JavaI420Buffer.wrap(width, height, yuvPlanes[0], yuvStrides[0], yuvPlanes[1], |
sakal | 6bdcefc | 2017-08-15 01:56:02 -0700 | [diff] [blame] | 167 | 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.org | f693229 | 2015-02-05 17:29:59 +0000 | [diff] [blame] | 177 | } |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 178 | |
glaznev@webrtc.org | f693229 | 2015-02-05 17:29:59 +0000 | [diff] [blame] | 179 | // Helper native function to do a video frame plane copying. |
sakal | b6760f9 | 2016-09-29 04:12:44 -0700 | [diff] [blame] | 180 | public static native void nativeCopyPlane( |
| 181 | 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] | 182 | |
Niels Möller | 8f59762 | 2016-03-23 10:33:07 +0100 | [diff] [blame] | 183 | /** The real meat of VideoSinkInterface. */ |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 184 | public static interface Callbacks { |
guoweis@webrtc.org | 00c509a | 2015-03-12 21:37:26 +0000 | [diff] [blame] | 185 | // |frame| might have pending rotation and implementation of Callbacks |
Magnus Jedvert | a6cba3a | 2015-08-29 15:57:43 +0200 | [diff] [blame] | 186 | // 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.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 189 | public void renderFrame(I420Frame frame); |
| 190 | } |
| 191 | |
sakal | b6760f9 | 2016-09-29 04:12:44 -0700 | [diff] [blame] | 192 | /** |
| 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 Jedvert | a6cba3a | 2015-08-29 15:57:43 +0200 | [diff] [blame] | 203 | |
magjed | d5031fc | 2015-08-14 03:13:05 -0700 | [diff] [blame] | 204 | long nativeVideoRenderer; |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 205 | |
| 206 | public VideoRenderer(Callbacks callbacks) { |
| 207 | nativeVideoRenderer = nativeWrapVideoRenderer(callbacks); |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 208 | } |
| 209 | |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 210 | public void dispose() { |
magjed | d5031fc | 2015-08-14 03:13:05 -0700 | [diff] [blame] | 211 | if (nativeVideoRenderer == 0) { |
| 212 | // Already disposed. |
| 213 | return; |
| 214 | } |
perkj | 47b6263 | 2016-02-08 01:07:19 -0800 | [diff] [blame] | 215 | |
| 216 | freeWrappedVideoRenderer(nativeVideoRenderer); |
magjed | d5031fc | 2015-08-14 03:13:05 -0700 | [diff] [blame] | 217 | nativeVideoRenderer = 0; |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 218 | } |
| 219 | |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 220 | private static native long nativeWrapVideoRenderer(Callbacks callbacks); |
glaznev@webrtc.org | a59c501 | 2014-09-17 03:26:59 +0000 | [diff] [blame] | 221 | private static native void freeWrappedVideoRenderer(long nativeVideoRenderer); |
Magnus Jedvert | a6cba3a | 2015-08-29 15:57:43 +0200 | [diff] [blame] | 222 | private static native void releaseNativeFrame(long nativeFramePointer); |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 223 | } |