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 | b616d09 | 2018-06-07 15:12:58 +0200 | [diff] [blame] | 23 | // Use VideoSink instead. |
| 24 | @Deprecated |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 25 | public class VideoRenderer { |
Magnus Jedvert | e76fb36 | 2015-10-08 19:05:24 +0200 | [diff] [blame] | 26 | /** |
nisse | acd935b | 2016-11-11 03:55:13 -0800 | [diff] [blame] | 27 | * 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] | 28 | * code. |
| 29 | */ |
Magnus Jedvert | b616d09 | 2018-06-07 15:12:58 +0200 | [diff] [blame] | 30 | // Use VideoFrame instead. |
| 31 | @Deprecated |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 32 | public static class I420Frame { |
| 33 | public final int width; |
| 34 | public final int height; |
Sami Kalliomäki | e7592d8 | 2018-03-22 13:32:44 +0100 | [diff] [blame] | 35 | @Nullable public final int[] yuvStrides; |
| 36 | @Nullable public ByteBuffer[] yuvPlanes; |
glaznev@webrtc.org | 9967845 | 2014-09-15 17:52:42 +0000 | [diff] [blame] | 37 | public final boolean yuvFrame; |
Per | 488e75f | 2015-11-19 10:43:36 +0100 | [diff] [blame] | 38 | // 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äki | e7592d8 | 2018-03-22 13:32:44 +0100 | [diff] [blame] | 42 | @Nullable public final float[] samplingMatrix; |
glaznev@webrtc.org | 9967845 | 2014-09-15 17:52:42 +0000 | [diff] [blame] | 43 | public int textureId; |
Magnus Jedvert | 7afc12f | 2015-09-03 12:40:38 +0200 | [diff] [blame] | 44 | // Frame pointer in C++. |
Magnus Jedvert | a6cba3a | 2015-08-29 15:57:43 +0200 | [diff] [blame] | 45 | private long nativeFramePointer; |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 46 | |
guoweis@webrtc.org | 840da7b | 2015-03-18 16:58:13 +0000 | [diff] [blame] | 47 | // rotationDegree is the degree that the frame must be rotated clockwisely |
| 48 | // to be rendered correctly. |
| 49 | public int rotationDegree; |
| 50 | |
Sami Kalliomäki | 7d1f493 | 2017-09-14 12:31:53 +0200 | [diff] [blame] | 51 | // If this I420Frame was constructed from VideoFrame.Buffer, this points to |
| 52 | // the backing buffer. |
Sami Kalliomäki | e7592d8 | 2018-03-22 13:32:44 +0100 | [diff] [blame] | 53 | @Nullable private final VideoFrame.Buffer backingBuffer; |
Sami Kalliomäki | 7d1f493 | 2017-09-14 12:31:53 +0200 | [diff] [blame] | 54 | |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 55 | /** |
Magnus Jedvert | 7afc12f | 2015-09-03 12:40:38 +0200 | [diff] [blame] | 56 | * 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] | 57 | */ |
skvlad | ed6e077 | 2016-11-30 14:40:18 -0800 | [diff] [blame] | 58 | public I420Frame(int width, int height, int rotationDegree, int[] yuvStrides, |
| 59 | ByteBuffer[] yuvPlanes, long nativeFramePointer) { |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 60 | this.width = width; |
| 61 | this.height = height; |
| 62 | this.yuvStrides = yuvStrides; |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 63 | this.yuvPlanes = yuvPlanes; |
glaznev@webrtc.org | 9967845 | 2014-09-15 17:52:42 +0000 | [diff] [blame] | 64 | this.yuvFrame = true; |
guoweis@webrtc.org | 840da7b | 2015-03-18 16:58:13 +0000 | [diff] [blame] | 65 | this.rotationDegree = rotationDegree; |
Magnus Jedvert | a6cba3a | 2015-08-29 15:57:43 +0200 | [diff] [blame] | 66 | this.nativeFramePointer = nativeFramePointer; |
Sami Kalliomäki | 7d1f493 | 2017-09-14 12:31:53 +0200 | [diff] [blame] | 67 | backingBuffer = null; |
Magnus Jedvert | 80cf97c | 2015-06-11 10:08:59 +0200 | [diff] [blame] | 68 | if (rotationDegree % 90 != 0) { |
| 69 | throw new IllegalArgumentException("Rotation degree not multiple of 90: " + rotationDegree); |
| 70 | } |
Per | 488e75f | 2015-11-19 10:43:36 +0100 | [diff] [blame] | 71 | // 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äki | 372e587 | 2017-06-27 17:00:21 +0200 | [diff] [blame] | 75 | samplingMatrix = RendererCommon.verticalFlipMatrix(); |
glaznev@webrtc.org | 9967845 | 2014-09-15 17:52:42 +0000 | [diff] [blame] | 76 | } |
| 77 | |
| 78 | /** |
| 79 | * Construct a texture frame of the given dimensions with data in SurfaceTexture |
| 80 | */ |
skvlad | ed6e077 | 2016-11-30 14:40:18 -0800 | [diff] [blame] | 81 | public I420Frame(int width, int height, int rotationDegree, int textureId, |
| 82 | float[] samplingMatrix, long nativeFramePointer) { |
glaznev@webrtc.org | 9967845 | 2014-09-15 17:52:42 +0000 | [diff] [blame] | 83 | this.width = width; |
| 84 | this.height = height; |
| 85 | this.yuvStrides = null; |
| 86 | this.yuvPlanes = null; |
Per | 488e75f | 2015-11-19 10:43:36 +0100 | [diff] [blame] | 87 | this.samplingMatrix = samplingMatrix; |
glaznev@webrtc.org | 9967845 | 2014-09-15 17:52:42 +0000 | [diff] [blame] | 88 | this.textureId = textureId; |
| 89 | this.yuvFrame = false; |
guoweis@webrtc.org | 840da7b | 2015-03-18 16:58:13 +0000 | [diff] [blame] | 90 | this.rotationDegree = rotationDegree; |
Magnus Jedvert | a6cba3a | 2015-08-29 15:57:43 +0200 | [diff] [blame] | 91 | this.nativeFramePointer = nativeFramePointer; |
Sami Kalliomäki | 7d1f493 | 2017-09-14 12:31:53 +0200 | [diff] [blame] | 92 | backingBuffer = null; |
Magnus Jedvert | 80cf97c | 2015-06-11 10:08:59 +0200 | [diff] [blame] | 93 | if (rotationDegree % 90 != 0) { |
| 94 | throw new IllegalArgumentException("Rotation degree not multiple of 90: " + rotationDegree); |
| 95 | } |
| 96 | } |
| 97 | |
Sami Kalliomäki | bc061b4 | 2017-06-16 10:08:23 +0200 | [diff] [blame] | 98 | /** |
sakal | 836f60c | 2017-07-28 07:12:23 -0700 | [diff] [blame] | 99 | * Construct a frame from VideoFrame.Buffer. |
Sami Kalliomäki | bc061b4 | 2017-06-16 10:08:23 +0200 | [diff] [blame] | 100 | */ |
Magnus Jedvert | 9060eb1 | 2017-12-12 12:52:54 +0100 | [diff] [blame] | 101 | @CalledByNative("I420Frame") |
sakal | 836f60c | 2017-07-28 07:12:23 -0700 | [diff] [blame] | 102 | public I420Frame(int rotationDegree, VideoFrame.Buffer buffer, long nativeFramePointer) { |
| 103 | this.width = buffer.getWidth(); |
| 104 | this.height = buffer.getHeight(); |
Sami Kalliomäki | bc061b4 | 2017-06-16 10:08:23 +0200 | [diff] [blame] | 105 | this.rotationDegree = rotationDegree; |
| 106 | if (rotationDegree % 90 != 0) { |
| 107 | throw new IllegalArgumentException("Rotation degree not multiple of 90: " + rotationDegree); |
| 108 | } |
magjed | 2ab9879 | 2017-09-13 05:20:45 -0700 | [diff] [blame] | 109 | if (buffer instanceof VideoFrame.TextureBuffer |
| 110 | && ((VideoFrame.TextureBuffer) buffer).getType() == VideoFrame.TextureBuffer.Type.OES) { |
Sami Kalliomäki | bc061b4 | 2017-06-16 10:08:23 +0200 | [diff] [blame] | 111 | VideoFrame.TextureBuffer textureBuffer = (VideoFrame.TextureBuffer) buffer; |
| 112 | this.yuvFrame = false; |
| 113 | this.textureId = textureBuffer.getTextureId(); |
sakal | 836f60c | 2017-07-28 07:12:23 -0700 | [diff] [blame] | 114 | this.samplingMatrix = RendererCommon.convertMatrixFromAndroidGraphicsMatrix( |
| 115 | textureBuffer.getTransformMatrix()); |
Sami Kalliomäki | bc061b4 | 2017-06-16 10:08:23 +0200 | [diff] [blame] | 116 | |
| 117 | this.yuvStrides = null; |
| 118 | this.yuvPlanes = null; |
Sami Kalliomäki | 7d1f493 | 2017-09-14 12:31:53 +0200 | [diff] [blame] | 119 | } else if (buffer instanceof VideoFrame.I420Buffer) { |
| 120 | VideoFrame.I420Buffer i420Buffer = (VideoFrame.I420Buffer) buffer; |
Sami Kalliomäki | bc061b4 | 2017-06-16 10:08:23 +0200 | [diff] [blame] | 121 | 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äki | 372e587 | 2017-06-27 17:00:21 +0200 | [diff] [blame] | 126 | // 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. |
sakal | 836f60c | 2017-07-28 07:12:23 -0700 | [diff] [blame] | 130 | this.samplingMatrix = RendererCommon.verticalFlipMatrix(); |
Sami Kalliomäki | bc061b4 | 2017-06-16 10:08:23 +0200 | [diff] [blame] | 131 | |
| 132 | this.textureId = 0; |
Sami Kalliomäki | 7d1f493 | 2017-09-14 12:31:53 +0200 | [diff] [blame] | 133 | } else { |
| 134 | this.yuvFrame = false; |
| 135 | this.textureId = 0; |
| 136 | this.samplingMatrix = null; |
| 137 | this.yuvStrides = null; |
| 138 | this.yuvPlanes = null; |
Sami Kalliomäki | bc061b4 | 2017-06-16 10:08:23 +0200 | [diff] [blame] | 139 | } |
| 140 | this.nativeFramePointer = nativeFramePointer; |
Sami Kalliomäki | 7d1f493 | 2017-09-14 12:31:53 +0200 | [diff] [blame] | 141 | backingBuffer = buffer; |
Sami Kalliomäki | bc061b4 | 2017-06-16 10:08:23 +0200 | [diff] [blame] | 142 | } |
| 143 | |
Magnus Jedvert | 80cf97c | 2015-06-11 10:08:59 +0200 | [diff] [blame] | 144 | 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.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 150 | } |
| 151 | |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 152 | @Override |
| 153 | public String toString() { |
sakal | 71a62b9 | 2017-08-10 02:12:24 -0700 | [diff] [blame] | 154 | 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.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 158 | } |
sakal | 6bdcefc | 2017-08-15 01:56:02 -0700 | [diff] [blame] | 159 | |
Sami Kalliomäki | 7d1f493 | 2017-09-14 12:31:53 +0200 | [diff] [blame] | 160 | /** |
| 161 | * Convert the frame to VideoFrame. It is no longer safe to use the I420Frame after calling |
| 162 | * this. |
| 163 | */ |
sakal | 6bdcefc | 2017-08-15 01:56:02 -0700 | [diff] [blame] | 164 | VideoFrame toVideoFrame() { |
| 165 | final VideoFrame.Buffer buffer; |
Sami Kalliomäki | 7d1f493 | 2017-09-14 12:31:53 +0200 | [diff] [blame] | 166 | 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äki | 48b3c02 | 2017-10-04 17:01:00 +0200 | [diff] [blame] | 173 | buffer = JavaI420Buffer.wrap(width, height, yuvPlanes[0], yuvStrides[0], yuvPlanes[1], |
sakal | 6bdcefc | 2017-08-15 01:56:02 -0700 | [diff] [blame] | 174 | yuvStrides[1], yuvPlanes[2], yuvStrides[2], |
| 175 | () -> { VideoRenderer.renderFrameDone(this); }); |
| 176 | } else { |
Magnus Jedvert | 1d270f8 | 2018-04-16 16:28:29 +0200 | [diff] [blame] | 177 | // Note: No Handler or YuvConverter means calling toI420 will crash. |
sakal | 6bdcefc | 2017-08-15 01:56:02 -0700 | [diff] [blame] | 178 | buffer = new TextureBufferImpl(width, height, VideoFrame.TextureBuffer.Type.OES, textureId, |
Magnus Jedvert | 1d270f8 | 2018-04-16 16:28:29 +0200 | [diff] [blame] | 179 | RendererCommon.convertMatrixToAndroidGraphicsMatrix(samplingMatrix), null /* handler */, |
| 180 | null /* yuvConverter */, () -> VideoRenderer.renderFrameDone(this)); |
sakal | 6bdcefc | 2017-08-15 01:56:02 -0700 | [diff] [blame] | 181 | } |
| 182 | return new VideoFrame(buffer, rotationDegree, 0 /* timestampNs */); |
| 183 | } |
Magnus Jedvert | 9060eb1 | 2017-12-12 12:52:54 +0100 | [diff] [blame] | 184 | |
| 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.org | f693229 | 2015-02-05 17:29:59 +0000 | [diff] [blame] | 192 | } |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 193 | |
Niels Möller | 8f59762 | 2016-03-23 10:33:07 +0100 | [diff] [blame] | 194 | /** The real meat of VideoSinkInterface. */ |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 195 | public static interface Callbacks { |
guoweis@webrtc.org | 00c509a | 2015-03-12 21:37:26 +0000 | [diff] [blame] | 196 | // |frame| might have pending rotation and implementation of Callbacks |
Magnus Jedvert | a6cba3a | 2015-08-29 15:57:43 +0200 | [diff] [blame] | 197 | // 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 Jedvert | 9060eb1 | 2017-12-12 12:52:54 +0100 | [diff] [blame] | 200 | @CalledByNative("Callbacks") void renderFrame(I420Frame frame); |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 201 | } |
| 202 | |
sakal | b6760f9 | 2016-09-29 04:12:44 -0700 | [diff] [blame] | 203 | /** |
| 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 Jedvert | 84d8ae5 | 2017-12-20 15:12:10 +0100 | [diff] [blame] | 210 | nativeReleaseFrame(frame.nativeFramePointer); |
sakal | b6760f9 | 2016-09-29 04:12:44 -0700 | [diff] [blame] | 211 | frame.nativeFramePointer = 0; |
| 212 | } |
| 213 | } |
Magnus Jedvert | a6cba3a | 2015-08-29 15:57:43 +0200 | [diff] [blame] | 214 | |
magjed | d5031fc | 2015-08-14 03:13:05 -0700 | [diff] [blame] | 215 | long nativeVideoRenderer; |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 216 | |
| 217 | public VideoRenderer(Callbacks callbacks) { |
Magnus Jedvert | 84d8ae5 | 2017-12-20 15:12:10 +0100 | [diff] [blame] | 218 | nativeVideoRenderer = nativeCreateVideoRenderer(callbacks); |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 219 | } |
| 220 | |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 221 | public void dispose() { |
magjed | d5031fc | 2015-08-14 03:13:05 -0700 | [diff] [blame] | 222 | if (nativeVideoRenderer == 0) { |
| 223 | // Already disposed. |
| 224 | return; |
| 225 | } |
perkj | 47b6263 | 2016-02-08 01:07:19 -0800 | [diff] [blame] | 226 | |
Magnus Jedvert | 84d8ae5 | 2017-12-20 15:12:10 +0100 | [diff] [blame] | 227 | nativeFreeWrappedVideoRenderer(nativeVideoRenderer); |
magjed | d5031fc | 2015-08-14 03:13:05 -0700 | [diff] [blame] | 228 | nativeVideoRenderer = 0; |
henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 229 | } |
| 230 | |
Magnus Jedvert | 84d8ae5 | 2017-12-20 15:12:10 +0100 | [diff] [blame] | 231 | 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.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 234 | } |