1/*
2 * Copyright (C) 2018 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
17package androidx.heifwriter;
18
19import android.opengl.EGL14;
20import android.opengl.EGLConfig;
21import android.opengl.EGLContext;
22import android.opengl.EGLDisplay;
23import android.opengl.EGLExt;
24import android.opengl.EGLSurface;
25import android.util.Log;
26import android.view.Surface;
27
28import java.util.Objects;
29
30/**
31 * Holds state associated with a Surface used for MediaCodec encoder input.
32 * <p>
33 * The constructor takes a Surface obtained from MediaCodec.createInputSurface(), and uses that
34 * to create an EGL window surface. Calls to eglSwapBuffers() cause a frame of data to be sent
35 * to the video encoder.
36 *
37 * @hide
38 */
39public class EglWindowSurface {
40    private static final String TAG = "EglWindowSurface";
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 Surface mSurface;
48    private int mWidth;
49    private int mHeight;
50
51    /**
52     * Creates an EglWindowSurface from a Surface.
53     */
54    public EglWindowSurface(Surface surface) {
55        if (surface == null) {
56            throw new NullPointerException();
57        }
58        mSurface = surface;
59
60        eglSetup();
61    }
62
63    /**
64     * Prepares EGL. We want a GLES 2.0 context and a surface that supports recording.
65     */
66    private void eglSetup() {
67        mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
68        if (Objects.equals(mEGLDisplay, EGL14.EGL_NO_DISPLAY)) {
69            throw new RuntimeException("unable to get EGL14 display");
70        }
71        int[] version = new int[2];
72        if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
73            mEGLDisplay = null;
74            throw new RuntimeException("unable to initialize EGL14");
75        }
76
77        // Configure EGL for recordable and OpenGL ES 2.0.  We want enough RGB bits
78        // to minimize artifacts from possible YUV conversion.
79        int[] attribList = {
80                EGL14.EGL_RED_SIZE, 8,
81                EGL14.EGL_GREEN_SIZE, 8,
82                EGL14.EGL_BLUE_SIZE, 8,
83                EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
84                EGLExt.EGL_RECORDABLE_ANDROID, 1,
85                EGL14.EGL_NONE
86        };
87        int[] numConfigs = new int[1];
88        if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, mConfigs, 0, mConfigs.length,
89                numConfigs, 0)) {
90            throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config");
91        }
92
93        // Configure context for OpenGL ES 2.0.
94        int[] attrib_list = {
95                EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
96                EGL14.EGL_NONE
97        };
98        mEGLContext = EGL14.eglCreateContext(mEGLDisplay, mConfigs[0], EGL14.EGL_NO_CONTEXT,
99                attrib_list, 0);
100        checkEglError("eglCreateContext");
101        if (mEGLContext == null) {
102            throw new RuntimeException("null context");
103        }
104
105        // Create a window surface, and attach it to the Surface we received.
106        createEGLSurface();
107
108        mWidth = getWidth();
109        mHeight = getHeight();
110    }
111
112    public void updateSize(int width, int height) {
113        if (width != mWidth || height != mHeight) {
114            Log.d(TAG, "re-create EGLSurface");
115            releaseEGLSurface();
116            createEGLSurface();
117            mWidth = getWidth();
118            mHeight = getHeight();
119        }
120    }
121
122    private void createEGLSurface() {
123        int[] surfaceAttribs = {
124                EGL14.EGL_NONE
125        };
126        mEGLSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mConfigs[0], mSurface,
127                surfaceAttribs, 0);
128        checkEglError("eglCreateWindowSurface");
129        if (mEGLSurface == null) {
130            throw new RuntimeException("surface was null");
131        }
132    }
133
134    private void releaseEGLSurface() {
135        if (!Objects.equals(mEGLDisplay, EGL14.EGL_NO_DISPLAY)) {
136            EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
137            mEGLSurface = EGL14.EGL_NO_SURFACE;
138        }
139    }
140
141    /**
142     * Discard all resources held by this class, notably the EGL context. Also releases the
143     * Surface that was passed to our constructor.
144     */
145    public void release() {
146        if (!Objects.equals(mEGLDisplay, EGL14.EGL_NO_DISPLAY)) {
147            EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
148            EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
149            EGL14.eglReleaseThread();
150            EGL14.eglTerminate(mEGLDisplay);
151        }
152
153        mSurface.release();
154
155        mEGLDisplay = EGL14.EGL_NO_DISPLAY;
156        mEGLContext = EGL14.EGL_NO_CONTEXT;
157        mEGLSurface = EGL14.EGL_NO_SURFACE;
158
159        mSurface = null;
160    }
161
162    /**
163     * Makes our EGL context and surface current.
164     */
165    public void makeCurrent() {
166        if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
167            throw new RuntimeException("eglMakeCurrent failed");
168        }
169    }
170
171    /**
172     * Makes our EGL context and surface not current.
173     */
174    public void makeUnCurrent() {
175        if (!EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
176                EGL14.EGL_NO_CONTEXT)) {
177            throw new RuntimeException("eglMakeCurrent failed");
178        }
179    }
180
181    /**
182     * Calls eglSwapBuffers. Use this to "publish" the current frame.
183     */
184    public boolean swapBuffers() {
185        return EGL14.eglSwapBuffers(mEGLDisplay, mEGLSurface);
186    }
187
188    /**
189     * Returns the Surface that the MediaCodec receives buffers from.
190     */
191    public Surface getSurface() {
192        return mSurface;
193    }
194
195    /**
196     * Queries the surface's width.
197     */
198    public int getWidth() {
199        int[] value = new int[1];
200        EGL14.eglQuerySurface(mEGLDisplay, mEGLSurface, EGL14.EGL_WIDTH, value, 0);
201        return value[0];
202    }
203
204    /**
205     * Queries the surface's height.
206     */
207    public int getHeight() {
208        int[] value = new int[1];
209        EGL14.eglQuerySurface(mEGLDisplay, mEGLSurface, EGL14.EGL_HEIGHT, value, 0);
210        return value[0];
211    }
212
213    /**
214     * Sends the presentation time stamp to EGL. Time is expressed in nanoseconds.
215     */
216    public void setPresentationTime(long nsecs) {
217        EGLExt.eglPresentationTimeANDROID(mEGLDisplay, mEGLSurface, nsecs);
218    }
219
220    /**
221     * Checks for EGL errors.
222     */
223    private void checkEglError(String msg) {
224        int error;
225        if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
226            throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error));
227        }
228    }
229}
230