1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.media.cts;
18 
19 
20 import android.media.MediaCodec;
21 import android.opengl.EGL14;
22 import android.opengl.EGLConfig;
23 import android.opengl.EGLContext;
24 import android.opengl.EGLDisplay;
25 import android.opengl.EGLExt;
26 import android.opengl.EGLSurface;
27 import android.opengl.GLES20;
28 import android.util.Log;
29 import android.view.Surface;
30 
31 
32 /**
33  * Holds state associated with a Surface used for MediaCodec encoder input.
34  * <p>
35  * The constructor takes a Surface obtained from MediaCodec.createInputSurface(), and uses that
36  * to create an EGL window surface.  Calls to eglSwapBuffers() cause a frame of data to be sent
37  * to the video encoder.
38  */
39 class InputSurface implements InputSurfaceInterface {
40     private static final String TAG = "InputSurface";
41 
42     private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
43     private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
44     private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE;
45     private EGLConfig[] mConfigs = new EGLConfig[1];
46 
47     private boolean mReleaseSurface;
48     private Surface mSurface;
49     private int mWidth;
50     private int mHeight;
51 
52     /**
53      * Creates an InputSurface from a Surface.
54      */
InputSurface(Surface surface, boolean releaseSurface)55     public InputSurface(Surface surface, boolean releaseSurface) {
56         if (surface == null) {
57             throw new NullPointerException();
58         }
59         mSurface = surface;
60         mReleaseSurface = releaseSurface;
61 
62         eglSetup();
63     }
64 
65     /**
66      * Creates an InputSurface from a Surface.
67      */
InputSurface(Surface surface)68     public InputSurface(Surface surface) {
69         this(surface, true);
70     }
71 
72     /**
73      * Prepares EGL.  We want a GLES 2.0 context and a surface that supports recording.
74      */
eglSetup()75     private void eglSetup() {
76         mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
77         if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
78             throw new RuntimeException("unable to get EGL14 display");
79         }
80         int[] version = new int[2];
81         if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
82             mEGLDisplay = null;
83             throw new RuntimeException("unable to initialize EGL14");
84         }
85 
86         // Configure EGL for recordable and OpenGL ES 2.0.  We want enough RGB bits
87         // to minimize artifacts from possible YUV conversion.
88         int[] attribList = {
89                 EGL14.EGL_RED_SIZE, 8,
90                 EGL14.EGL_GREEN_SIZE, 8,
91                 EGL14.EGL_BLUE_SIZE, 8,
92                 EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
93                 EGLExt.EGL_RECORDABLE_ANDROID, 1,
94                 EGL14.EGL_NONE
95         };
96         int[] numConfigs = new int[1];
97         if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, mConfigs, 0, mConfigs.length,
98                 numConfigs, 0)) {
99             throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config");
100         }
101 
102         // Configure context for OpenGL ES 2.0.
103         int[] attrib_list = {
104                 EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
105                 EGL14.EGL_NONE
106         };
107         mEGLContext = EGL14.eglCreateContext(mEGLDisplay, mConfigs[0], EGL14.EGL_NO_CONTEXT,
108                 attrib_list, 0);
109         checkEglError("eglCreateContext");
110         if (mEGLContext == null) {
111             throw new RuntimeException("null context");
112         }
113 
114         // Create a window surface, and attach it to the Surface we received.
115         createEGLSurface();
116 
117         mWidth = getWidth();
118         mHeight = getHeight();
119     }
120 
121     @Override
updateSize(int width, int height)122     public void updateSize(int width, int height) {
123         if (width != mWidth || height != mHeight) {
124             Log.d(TAG, "re-create EGLSurface");
125             releaseEGLSurface();
126             createEGLSurface();
127             mWidth = getWidth();
128             mHeight = getHeight();
129         }
130     }
131 
createEGLSurface()132     private void createEGLSurface() {
133         //EGLConfig[] configs = new EGLConfig[1];
134         int[] surfaceAttribs = {
135                 EGL14.EGL_NONE
136         };
137         mEGLSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mConfigs[0], mSurface,
138                 surfaceAttribs, 0);
139         checkEglError("eglCreateWindowSurface");
140         if (mEGLSurface == null) {
141             throw new RuntimeException("surface was null");
142         }
143     }
releaseEGLSurface()144     private void releaseEGLSurface() {
145         if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
146             EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
147             mEGLSurface = EGL14.EGL_NO_SURFACE;
148         }
149     }
150     /**
151      * Discard all resources held by this class, notably the EGL context.  Also releases the
152      * Surface that was passed to our constructor.
153      */
154     @Override
release()155     public void release() {
156         if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
157             EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
158             EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
159             EGL14.eglReleaseThread();
160             EGL14.eglTerminate(mEGLDisplay);
161         }
162 
163         if (mReleaseSurface) {
164             mSurface.release();
165         }
166 
167         mEGLDisplay = EGL14.EGL_NO_DISPLAY;
168         mEGLContext = EGL14.EGL_NO_CONTEXT;
169         mEGLSurface = EGL14.EGL_NO_SURFACE;
170 
171         mSurface = null;
172     }
173 
174     /**
175      * Makes our EGL context and surface current.
176      */
177     @Override
makeCurrent()178     public void makeCurrent() {
179         if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
180             throw new RuntimeException("eglMakeCurrent failed");
181         }
182     }
183 
makeUnCurrent()184     public void makeUnCurrent() {
185         if (!EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
186                 EGL14.EGL_NO_CONTEXT)) {
187             throw new RuntimeException("eglMakeCurrent failed");
188         }
189     }
190 
191     /**
192      * Calls eglSwapBuffers.  Use this to "publish" the current frame.
193      */
194     @Override
swapBuffers()195     public boolean swapBuffers() {
196         return EGL14.eglSwapBuffers(mEGLDisplay, mEGLSurface);
197     }
198 
199     /**
200      * Returns the Surface that the MediaCodec receives buffers from.
201      */
getSurface()202     public Surface getSurface() {
203         return mSurface;
204     }
205 
206     /**
207      * Queries the surface's width.
208      */
getWidth()209     public int getWidth() {
210         int[] value = new int[1];
211         EGL14.eglQuerySurface(mEGLDisplay, mEGLSurface, EGL14.EGL_WIDTH, value, 0);
212         return value[0];
213     }
214 
215     /**
216      * Queries the surface's height.
217      */
getHeight()218     public int getHeight() {
219         int[] value = new int[1];
220         EGL14.eglQuerySurface(mEGLDisplay, mEGLSurface, EGL14.EGL_HEIGHT, value, 0);
221         return value[0];
222     }
223 
224     /**
225      * Sends the presentation time stamp to EGL.  Time is expressed in nanoseconds.
226      */
227     @Override
setPresentationTime(long nsecs)228     public void setPresentationTime(long nsecs) {
229         EGLExt.eglPresentationTimeANDROID(mEGLDisplay, mEGLSurface, nsecs);
230     }
231 
232     /**
233      * Checks for EGL errors.
234      */
checkEglError(String msg)235     private void checkEglError(String msg) {
236         int error;
237         if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
238             throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error));
239         }
240     }
241 
242     @Override
configure(MediaCodec codec)243     public void configure(MediaCodec codec) {
244         codec.setInputSurface(mSurface);
245     }
246 
247     @Override
configure(NdkMediaCodec codec)248     public void configure(NdkMediaCodec codec) {
249         codec.setInputSurface(mSurface);
250     }
251 
252     /**
253      * Clears the surface to black.
254      * <p>
255      * Ported from https://github.com/google/grafika
256      */
clearSurface(Surface surface)257     public static void clearSurface(Surface surface) {
258         // We need to do this with OpenGL ES (*not* Canvas -- the "software render" bits
259         // are sticky).  We can't stay connected to the Surface after we're done because
260         // that'd prevent the video encoder from attaching.
261         //
262         // If the Surface is resized to be larger, the new portions will be black, so
263         // clearing to something other than black may look weird unless we do the clear
264         // post-resize.
265         InputSurface win = new InputSurface(surface, false /* release */);
266         win.makeCurrent();
267         GLES20.glClearColor(0, 0, 0, 0);
268         GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
269         win.swapBuffers();
270         win.release();
271     }
272 }
273