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