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 */ I420Frame(int width, int height, int rotationDegree, int[] yuvStrides, ByteBuffer[] yuvPlanes, long nativeFramePointer)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 */ I420Frame(int width, int height, int rotationDegree, int textureId, float[] samplingMatrix, long nativeFramePointer)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 rotatedWidth()107 public int rotatedWidth() { 108 return (rotationDegree % 180 == 0) ? width : height; 109 } 110 rotatedHeight()111 public int rotatedHeight() { 112 return (rotationDegree % 180 == 0) ? height : width; 113 } 114 115 @Override toString()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. nativeCopyPlane(ByteBuffer src, int width, int height, int srcStride, ByteBuffer dst, int dstStride)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). renderFrame(I420Frame frame)132 public void renderFrame(I420Frame frame); 133 } 134 135 /** 136 * This must be called after every renderFrame() to release the frame. 137 */ renderFrameDone(I420Frame frame)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 createGui(int x, int y)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 VideoRenderer(Callbacks callbacks)160 public VideoRenderer(Callbacks callbacks) { 161 nativeVideoRenderer = nativeWrapVideoRenderer(callbacks); 162 isWrappedVideoRenderer = true; 163 } 164 VideoRenderer(long nativeVideoRenderer)165 private VideoRenderer(long nativeVideoRenderer) { 166 this.nativeVideoRenderer = nativeVideoRenderer; 167 isWrappedVideoRenderer = false; 168 } 169 dispose()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 nativeCreateGuiVideoRenderer(int x, int y)183 private static native long nativeCreateGuiVideoRenderer(int x, int y); nativeWrapVideoRenderer(Callbacks callbacks)184 private static native long nativeWrapVideoRenderer(Callbacks callbacks); 185 freeGuiVideoRenderer(long nativeVideoRenderer)186 private static native void freeGuiVideoRenderer(long nativeVideoRenderer); freeWrappedVideoRenderer(long nativeVideoRenderer)187 private static native void freeWrappedVideoRenderer(long nativeVideoRenderer); 188 releaseNativeFrame(long nativeFramePointer)189 private static native void releaseNativeFrame(long nativeFramePointer); 190 } 191