1 /* 2 * libjingle 3 * Copyright 2013 Google Inc. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, 9 * this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright notice, 11 * this list of conditions and the following disclaimer in the documentation 12 * and/or other materials provided with the distribution. 13 * 3. The name of the author may not be used to endorse or promote products 14 * derived from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 package org.webrtc; 29 30 import java.nio.ByteBuffer; 31 32 /** 33 * Java version of VideoRendererInterface. In addition to allowing clients to 34 * define their own rendering behavior (by passing in a Callbacks object), this 35 * class also provides a createGui() method for creating a GUI-rendering window 36 * on various platforms. 37 */ 38 public class VideoRenderer { 39 /** 40 * Java version of cricket::VideoFrame. Frames are only constructed from native code and test 41 * code. 42 */ 43 public static class I420Frame { 44 public final int width; 45 public final int height; 46 public final int[] yuvStrides; 47 public ByteBuffer[] yuvPlanes; 48 public final boolean yuvFrame; 49 // Matrix that transforms standard coordinates to their proper sampling locations in 50 // the texture. This transform compensates for any properties of the video source that 51 // cause it to appear different from a normalized texture. This matrix does not take 52 // |rotationDegree| into account. 53 public final float[] samplingMatrix; 54 public int textureId; 55 // Frame pointer in C++. 56 private long nativeFramePointer; 57 58 // rotationDegree is the degree that the frame must be rotated clockwisely 59 // to be rendered correctly. 60 public int rotationDegree; 61 62 /** 63 * Construct a frame of the given dimensions with the specified planar data. 64 */ 65 I420Frame(int width, int height, int rotationDegree, int[] yuvStrides, ByteBuffer[] yuvPlanes, 66 long nativeFramePointer) { 67 this.width = width; 68 this.height = height; 69 this.yuvStrides = yuvStrides; 70 this.yuvPlanes = yuvPlanes; 71 this.yuvFrame = true; 72 this.rotationDegree = rotationDegree; 73 this.nativeFramePointer = nativeFramePointer; 74 if (rotationDegree % 90 != 0) { 75 throw new IllegalArgumentException("Rotation degree not multiple of 90: " + rotationDegree); 76 } 77 // The convention in WebRTC is that the first element in a ByteBuffer corresponds to the 78 // top-left corner of the image, but in glTexImage2D() the first element corresponds to the 79 // bottom-left corner. This discrepancy is corrected by setting a vertical flip as sampling 80 // matrix. 81 samplingMatrix = new float[] { 82 1, 0, 0, 0, 83 0, -1, 0, 0, 84 0, 0, 1, 0, 85 0, 1, 0, 1}; 86 } 87 88 /** 89 * Construct a texture frame of the given dimensions with data in SurfaceTexture 90 */ 91 I420Frame(int width, int height, int rotationDegree, int textureId, float[] samplingMatrix, 92 long nativeFramePointer) { 93 this.width = width; 94 this.height = height; 95 this.yuvStrides = null; 96 this.yuvPlanes = null; 97 this.samplingMatrix = samplingMatrix; 98 this.textureId = textureId; 99 this.yuvFrame = false; 100 this.rotationDegree = rotationDegree; 101 this.nativeFramePointer = nativeFramePointer; 102 if (rotationDegree % 90 != 0) { 103 throw new IllegalArgumentException("Rotation degree not multiple of 90: " + rotationDegree); 104 } 105 } 106 107 public int rotatedWidth() { 108 return (rotationDegree % 180 == 0) ? width : height; 109 } 110 111 public int rotatedHeight() { 112 return (rotationDegree % 180 == 0) ? height : width; 113 } 114 115 @Override 116 public String toString() { 117 return width + "x" + height + ":" + yuvStrides[0] + ":" + yuvStrides[1] + 118 ":" + yuvStrides[2]; 119 } 120 } 121 122 // Helper native function to do a video frame plane copying. 123 public static native void nativeCopyPlane(ByteBuffer src, int width, 124 int height, int srcStride, ByteBuffer dst, int dstStride); 125 126 /** The real meat of VideoRendererInterface. */ 127 public static interface Callbacks { 128 // |frame| might have pending rotation and implementation of Callbacks 129 // should handle that by applying rotation during rendering. The callee 130 // is responsible for signaling when it is done with |frame| by calling 131 // renderFrameDone(frame). 132 public void renderFrame(I420Frame frame); 133 } 134 135 /** 136 * This must be called after every renderFrame() to release the frame. 137 */ 138 public static void renderFrameDone(I420Frame frame) { 139 frame.yuvPlanes = null; 140 frame.textureId = 0; 141 if (frame.nativeFramePointer != 0) { 142 releaseNativeFrame(frame.nativeFramePointer); 143 frame.nativeFramePointer = 0; 144 } 145 } 146 147 // |this| either wraps a native (GUI) renderer or a client-supplied Callbacks 148 // (Java) implementation; this is indicated by |isWrappedVideoRenderer|. 149 long nativeVideoRenderer; 150 private final boolean isWrappedVideoRenderer; 151 152 public static VideoRenderer createGui(int x, int y) { 153 long nativeVideoRenderer = nativeCreateGuiVideoRenderer(x, y); 154 if (nativeVideoRenderer == 0) { 155 return null; 156 } 157 return new VideoRenderer(nativeVideoRenderer); 158 } 159 160 public VideoRenderer(Callbacks callbacks) { 161 nativeVideoRenderer = nativeWrapVideoRenderer(callbacks); 162 isWrappedVideoRenderer = true; 163 } 164 165 private VideoRenderer(long nativeVideoRenderer) { 166 this.nativeVideoRenderer = nativeVideoRenderer; 167 isWrappedVideoRenderer = false; 168 } 169 170 public void dispose() { 171 if (nativeVideoRenderer == 0) { 172 // Already disposed. 173 return; 174 } 175 if (!isWrappedVideoRenderer) { 176 freeGuiVideoRenderer(nativeVideoRenderer); 177 } else { 178 freeWrappedVideoRenderer(nativeVideoRenderer); 179 } 180 nativeVideoRenderer = 0; 181 } 182 183 private static native long nativeCreateGuiVideoRenderer(int x, int y); 184 private static native long nativeWrapVideoRenderer(Callbacks callbacks); 185 186 private static native void freeGuiVideoRenderer(long nativeVideoRenderer); 187 private static native void freeWrappedVideoRenderer(long nativeVideoRenderer); 188 189 private static native void releaseNativeFrame(long nativeFramePointer); 190 } 191