40 | * The object wraps an encoder running on a dedicated thread. The various control messages 41 | * may be sent from arbitrary threads (typically the app UI thread). The encoder thread 42 | * manages both sides of the encoder (feeding and draining); the only external input is 43 | * the GL texture. 44 | *
45 | * The design is complicated slightly by the need to create an EGL context that shares state 46 | * with a view that gets restarted if (say) the device orientation changes. When the view 47 | * in question is a GLSurfaceView, we don't have full control over the EGL context creation 48 | * on that side, so we have to bend a bit backwards here. 49 | *
50 | * To use: 51 | *
60 | * TODO: tweak the API (esp. textureId) so it's less awkward for simple use cases. 61 | */ 62 | public class TextureMovieEncoder implements Runnable { 63 | private static final String TAG = "TextureMovieEncoder"; 64 | private static final boolean VERBOSE = false; 65 | 66 | private static final int MSG_START_RECORDING = 0; 67 | private static final int MSG_STOP_RECORDING = 1; 68 | private static final int MSG_FRAME_AVAILABLE = 2; 69 | private static final int MSG_SET_TEXTURE_ID = 3; 70 | private static final int MSG_UPDATE_SHARED_CONTEXT = 4; 71 | private static final int MSG_QUIT = 5; 72 | 73 | // ----- accessed exclusively by encoder thread ----- 74 | private WindowSurface mInputWindowSurface; 75 | private EglCore mEglCore; 76 | private FullFrameRect mFullScreen; 77 | private int mTextureId; 78 | private int mFrameNum; 79 | private VideoEncoderCore mVideoEncoder; 80 | 81 | // ----- accessed by multiple threads ----- 82 | private volatile EncoderHandler mHandler; 83 | 84 | private final Object mReadyFence = new Object(); // guards ready/running 85 | private boolean mReady; 86 | private boolean mRunning; 87 | 88 | 89 | /** 90 | * Encoder configuration. 91 | *
92 | * Object is immutable, which means we can safely pass it between threads without 93 | * explicit synchronization (and don't need to worry about it getting tweaked out from 94 | * under us). 95 | *
96 | * TODO: make frame rate and iframe interval configurable? Maybe use builder pattern 97 | * with reasonable defaults for those and bit rate. 98 | */ 99 | public static class EncoderConfig { 100 | final File mOutputFile; 101 | final int mWidth; 102 | final int mHeight; 103 | final int mBitRate; 104 | final EGLContext mEglContext; 105 | final FilterType mFilterType; 106 | 107 | public EncoderConfig(File outputFile, int width, int height, int bitRate, EGLContext sharedEglContext, FilterType filterType) { 108 | mOutputFile = outputFile; 109 | mWidth = width; 110 | mHeight = height; 111 | mBitRate = bitRate; 112 | mEglContext = sharedEglContext; 113 | mFilterType = filterType; 114 | } 115 | 116 | @Override 117 | public String toString() { 118 | return "EncoderConfig: " + mWidth + "x" + mHeight + " @" + mBitRate + " to '" + mOutputFile.toString() + "' ctxt=" + mEglContext; 119 | } 120 | } 121 | 122 | 123 | public void startRecording(EncoderConfig config) { 124 | Log.d(TAG, "Encoder: startRecording()"); 125 | synchronized (mReadyFence) { 126 | if (mRunning) { 127 | Log.w(TAG, "Encoder thread already running"); 128 | return; 129 | } 130 | mRunning = true; 131 | new Thread(this, "TextureMovieEncoder").start(); 132 | while (!mReady) { 133 | try { 134 | mReadyFence.wait(); 135 | } catch (InterruptedException ie) { 136 | // ignore 137 | } 138 | } 139 | } 140 | 141 | mHandler.sendMessage(mHandler.obtainMessage(MSG_START_RECORDING, config)); 142 | } 143 | 144 | 145 | public void stopRecording() { 146 | mHandler.sendMessage(mHandler.obtainMessage(MSG_STOP_RECORDING)); 147 | mHandler.sendMessage(mHandler.obtainMessage(MSG_QUIT)); 148 | } 149 | 150 | public void waitForStop() { 151 | synchronized (mReadyFence) { 152 | if (!mRunning) { 153 | return; 154 | } 155 | while (mRunning) { 156 | try { 157 | mReadyFence.wait(); 158 | } catch (InterruptedException ie) { 159 | // ignore 160 | } 161 | } 162 | } 163 | } 164 | 165 | public boolean isRecording() { 166 | synchronized (mReadyFence) { 167 | return mRunning; 168 | } 169 | } 170 | 171 | public void updateSharedContext(EGLContext sharedContext) { 172 | mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_SHARED_CONTEXT, sharedContext)); 173 | } 174 | 175 | public void frameAvailable(SurfaceTexture st) { 176 | synchronized (mReadyFence) { 177 | if (!mReady) { 178 | return; 179 | } 180 | } 181 | float[] transform = new float[16]; // TODO - avoid alloc every frame 182 | st.getTransformMatrix(transform); 183 | long timestamp = st.getTimestamp(); 184 | if (timestamp == 0) { 185 | return; 186 | } 187 | mHandler.sendMessage(mHandler.obtainMessage(MSG_FRAME_AVAILABLE, (int) (timestamp >> 32), (int) timestamp, transform)); 188 | } 189 | 190 | 191 | public void setTextureId(int id) { 192 | synchronized (mReadyFence) { 193 | if (!mReady) { 194 | return; 195 | } 196 | } 197 | mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_TEXTURE_ID, id, 0, null)); 198 | } 199 | 200 | /** 201 | * Encoder thread entry point. Establishes Looper/Handler and waits for messages. 202 | *
203 | *
204 | * @see Thread#run()
205 | */
206 | @Override
207 | public void run() {
208 | // Establish a Looper for this thread, and define a Handler for it.
209 | Looper.prepare();
210 | synchronized (mReadyFence) {
211 | mHandler = new EncoderHandler(this);
212 | mReady = true;
213 | mReadyFence.notify();
214 | }
215 | Looper.loop();
216 |
217 | Log.d(TAG, "Encoder thread exiting");
218 | synchronized (mReadyFence) {
219 | mReady = mRunning = false;
220 | mHandler = null;
221 | mReadyFence.notify();
222 | }
223 | }
224 |
225 |
226 | /**
227 | * Handles encoder state change requests. The handler is created on the encoder thread.
228 | */
229 | private static class EncoderHandler extends Handler {
230 | private WeakReference
312 | * This is useful if the old context we were sharing with went away (maybe a GLSurfaceView
313 | * that got torn down) and we need to hook up with the new one.
314 | */
315 | private void handleUpdateSharedContext(EGLContext newSharedContext) {
316 | Log.d(TAG, "handleUpdatedSharedContext " + newSharedContext);
317 |
318 | // Release the EGLSurface and EGLContext.
319 | mInputWindowSurface.releaseEglSurface();
320 | mFullScreen.release(false);
321 | mEglCore.release();
322 |
323 | // Create a new EGLContext and recreate the window surface.
324 | mEglCore = new EglCore(newSharedContext, EglCore.FLAG_RECORDABLE);
325 | mInputWindowSurface.recreate(mEglCore);
326 | mInputWindowSurface.makeCurrent();
327 |
328 | // Create new programs and such for the new context.
329 | mFullScreen = new FullFrameRect(new Texture2dProgram(Texture2dProgram.ProgramType.TEXTURE_EXT));
330 | }
331 |
332 | private void prepareEncoder(EGLContext sharedContext, int width, int height, int bitRate, File outputFile, FilterType filterType) {
333 | try {
334 | mVideoEncoder = new VideoEncoderCore(width, height, bitRate, outputFile);
335 | } catch (IOException ioe) {
336 | throw new RuntimeException(ioe);
337 | }
338 | mEglCore = new EglCore(sharedContext, EglCore.FLAG_RECORDABLE);
339 | mInputWindowSurface = new WindowSurface(mEglCore, mVideoEncoder.getInputSurface(), true);
340 | mInputWindowSurface.makeCurrent();
341 | mFullScreen = new FullFrameRect(createTextureProgram(filterType));
342 | }
343 |
344 | private Texture2dProgram createTextureProgram(FilterType filterType) {
345 | FilterType.FilterInfo filterInfo = FilterType.getFilterInfo(filterType);
346 | Texture2dProgram.ProgramType programType = filterInfo.programType;
347 | float[] kernel = filterInfo.kernel;
348 | float colorAdj = filterInfo.colorAdj;
349 | Texture2dProgram texture2dProgram = new Texture2dProgram(programType);
350 | if (kernel != null) {
351 | texture2dProgram.setKernel(kernel, colorAdj);
352 | }
353 | return texture2dProgram;
354 | }
355 |
356 | private void releaseEncoder() {
357 | mVideoEncoder.release();
358 | if (mInputWindowSurface != null) {
359 | mInputWindowSurface.release();
360 | mInputWindowSurface = null;
361 | }
362 | if (mFullScreen != null) {
363 | mFullScreen.release(false);
364 | mFullScreen = null;
365 | }
366 | if (mEglCore != null) {
367 | mEglCore.release();
368 | mEglCore = null;
369 | }
370 | }
371 | }
372 |
--------------------------------------------------------------------------------
/app/src/main/java/com/felix/glcamera/VideoEncoderCore.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc. All rights reserved.
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 com.felix.glcamera;
18 |
19 | import android.media.MediaCodec;
20 | import android.media.MediaCodecInfo;
21 | import android.media.MediaFormat;
22 | import android.media.MediaMuxer;
23 | import android.util.Log;
24 | import android.view.Surface;
25 |
26 | import java.io.File;
27 | import java.io.IOException;
28 | import java.nio.ByteBuffer;
29 |
30 | /**
31 | * This class wraps up the core components used for surface-input video encoding.
32 | *
33 | * Once created, frames are fed to the input surface. Remember to provide the presentation
34 | * time stamp, and always call drainEncoder() before swapBuffers() to ensure that the
35 | * producer side doesn't get backed up.
36 | *
37 | * This class is not thread-safe, with one exception: it is valid to use the input surface
38 | * on one thread, and drain the output on a different thread.
39 | */
40 | public class VideoEncoderCore {
41 | private static final String TAG = "VideoEncoderCore";
42 | private static final boolean VERBOSE = false;
43 |
44 | // TODO: these ought to be configurable as well
45 | private static final String MIME_TYPE = "video/avc"; // H.264 Advanced Video Coding
46 | private static final int FRAME_RATE = 30; // 30fps
47 | private static final int IFRAME_INTERVAL = 5; // 5 seconds between I-frames
48 |
49 | private Surface mInputSurface;
50 | private MediaMuxer mMuxer;
51 | private MediaCodec mEncoder;
52 | private MediaCodec.BufferInfo mBufferInfo;
53 | private int mTrackIndex;
54 | private boolean mMuxerStarted;
55 |
56 |
57 | /**
58 | * Configures encoder and muxer state, and prepares the input Surface.
59 | */
60 | public VideoEncoderCore(int width, int height, int bitRate, File outputFile)
61 | throws IOException {
62 | mBufferInfo = new MediaCodec.BufferInfo();
63 |
64 | MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, width, height);
65 |
66 | // Set some properties. Failing to specify some of these can cause the MediaCodec
67 | // configure() call to throw an unhelpful exception.
68 | format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
69 | format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
70 | format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
71 | format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
72 | if (VERBOSE) Log.d(TAG, "format: " + format);
73 |
74 | // Create a MediaCodec encoder, and configure it with our format. Get a Surface
75 | // we can use for input and wrap it with a class that handles the EGL work.
76 | mEncoder = MediaCodec.createEncoderByType(MIME_TYPE);
77 | mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
78 | mInputSurface = mEncoder.createInputSurface();
79 | mEncoder.start();
80 |
81 | // Create a MediaMuxer. We can't add the video track and start() the muxer here,
82 | // because our MediaFormat doesn't have the Magic Goodies. These can only be
83 | // obtained from the encoder after it has started processing data.
84 | //
85 | // We're not actually interested in multiplexing audio. We just want to convert
86 | // the raw H.264 elementary stream we get from MediaCodec into a .mp4 file.
87 | mMuxer = new MediaMuxer(outputFile.toString(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
88 |
89 | mTrackIndex = -1;
90 | mMuxerStarted = false;
91 | }
92 |
93 | /**
94 | * Returns the encoder's input surface.
95 | */
96 | public Surface getInputSurface() {
97 | return mInputSurface;
98 | }
99 |
100 | /**
101 | * Releases encoder resources.
102 | */
103 | public void release() {
104 | if (VERBOSE) Log.d(TAG, "releasing encoder objects");
105 | if (mEncoder != null) {
106 | mEncoder.stop();
107 | mEncoder.release();
108 | mEncoder = null;
109 | }
110 | if (mMuxer != null) {
111 | // TODO: stop() throws an exception if you haven't fed it any data. Keep track
112 | // of frames submitted, and don't call stop() if we haven't written anything.
113 | mMuxer.stop();
114 | mMuxer.release();
115 | mMuxer = null;
116 | }
117 | }
118 |
119 | /**
120 | * Extracts all pending data from the encoder and forwards it to the muxer.
121 | *
122 | * If endOfStream is not set, this returns when there is no more data to drain. If it
123 | * is set, we send EOS to the encoder, and then iterate until we see EOS on the output.
124 | * Calling this with endOfStream set should be done once, right before stopping the muxer.
125 | *
126 | * We're just using the muxer to get a .mp4 file (instead of a raw H.264 stream). We're
127 | * not recording audio.
128 | */
129 | public void drainEncoder(boolean endOfStream) {
130 | final int TIMEOUT_USEC = 10000;
131 | if (VERBOSE) Log.d(TAG, "drainEncoder(" + endOfStream + ")");
132 |
133 | if (endOfStream) {
134 | if (VERBOSE) Log.d(TAG, "sending EOS to encoder");
135 | mEncoder.signalEndOfInputStream();
136 | }
137 |
138 | ByteBuffer[] encoderOutputBuffers = mEncoder.getOutputBuffers();
139 | while (true) {
140 | int encoderStatus = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);
141 | if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
142 | // no output available yet
143 | if (!endOfStream) {
144 | break; // out of while
145 | } else {
146 | if (VERBOSE) Log.d(TAG, "no output available, spinning to await EOS");
147 | }
148 | } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
149 | // not expected for an encoder
150 | encoderOutputBuffers = mEncoder.getOutputBuffers();
151 | } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
152 | // should happen before receiving buffers, and should only happen once
153 | if (mMuxerStarted) {
154 | throw new RuntimeException("format changed twice");
155 | }
156 | MediaFormat newFormat = mEncoder.getOutputFormat();
157 | Log.d(TAG, "encoder output format changed: " + newFormat);
158 |
159 | // now that we have the Magic Goodies, start the muxer
160 | mTrackIndex = mMuxer.addTrack(newFormat);
161 | mMuxer.start();
162 | mMuxerStarted = true;
163 | } else if (encoderStatus < 0) {
164 | Log.w(TAG, "unexpected result from encoder.dequeueOutputBuffer: " +
165 | encoderStatus);
166 | // let's ignore it
167 | } else {
168 | ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
169 | if (encodedData == null) {
170 | throw new RuntimeException("encoderOutputBuffer " + encoderStatus +
171 | " was null");
172 | }
173 |
174 | if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
175 | // The codec config data was pulled out and fed to the muxer when we got
176 | // the INFO_OUTPUT_FORMAT_CHANGED status. Ignore it.
177 | if (VERBOSE) Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG");
178 | mBufferInfo.size = 0;
179 | }
180 |
181 | if (mBufferInfo.size != 0) {
182 | if (!mMuxerStarted) {
183 | throw new RuntimeException("muxer hasn't started");
184 | }
185 |
186 | // adjust the ByteBuffer values to match BufferInfo (not needed?)
187 | encodedData.position(mBufferInfo.offset);
188 | encodedData.limit(mBufferInfo.offset + mBufferInfo.size);
189 |
190 | mMuxer.writeSampleData(mTrackIndex, encodedData, mBufferInfo);
191 | if (VERBOSE) {
192 | Log.d(TAG, "sent " + mBufferInfo.size + " bytes to muxer, ts=" +
193 | mBufferInfo.presentationTimeUs);
194 | }
195 | }
196 |
197 | mEncoder.releaseOutputBuffer(encoderStatus, false);
198 |
199 | if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
200 | if (!endOfStream) {
201 | Log.w(TAG, "reached end of stream unexpectedly");
202 | } else {
203 | if (VERBOSE) Log.d(TAG, "end of stream reached");
204 | }
205 | break; // out of while
206 | }
207 | }
208 | }
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/app/src/main/java/com/felix/glcamera/gles/Drawable2d.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc. All rights reserved.
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 com.felix.glcamera.gles;
18 |
19 | import java.nio.FloatBuffer;
20 |
21 | /**
22 | * Base class for stuff we like to draw.
23 | */
24 | public class Drawable2d {
25 | private static final int SIZEOF_FLOAT = 4;
26 |
27 | /**
28 | * Simple equilateral triangle (1.0 per side). Centered on (0,0).
29 | */
30 | private static final float TRIANGLE_COORDS[] = {
31 | 0.0f, 0.577350269f, // 0 top
32 | -0.5f, -0.288675135f, // 1 bottom left
33 | 0.5f, -0.288675135f // 2 bottom right
34 | };
35 | private static final float TRIANGLE_TEX_COORDS[] = {
36 | 0.5f, 0.0f, // 0 top center
37 | 0.0f, 1.0f, // 1 bottom left
38 | 1.0f, 1.0f, // 2 bottom right
39 | };
40 | private static final FloatBuffer TRIANGLE_BUF =
41 | GlUtil.createFloatBuffer(TRIANGLE_COORDS);
42 | private static final FloatBuffer TRIANGLE_TEX_BUF =
43 | GlUtil.createFloatBuffer(TRIANGLE_TEX_COORDS);
44 |
45 | /**
46 | * Simple square, specified as a triangle strip. The square is centered on (0,0) and has
47 | * a size of 1x1.
48 | *
49 | * Triangles are 0-1-2 and 2-1-3 (counter-clockwise winding).
50 | */
51 | private static final float RECTANGLE_COORDS[] = {
52 | -0.5f, -0.5f, // 0 bottom left
53 | 0.5f, -0.5f, // 1 bottom right
54 | -0.5f, 0.5f, // 2 top left
55 | 0.5f, 0.5f, // 3 top right
56 | };
57 | private static final float RECTANGLE_TEX_COORDS[] = {
58 | 0.0f, 1.0f, // 0 bottom left
59 | 1.0f, 1.0f, // 1 bottom right
60 | 0.0f, 0.0f, // 2 top left
61 | 1.0f, 0.0f // 3 top right
62 | };
63 | private static final FloatBuffer RECTANGLE_BUF =
64 | GlUtil.createFloatBuffer(RECTANGLE_COORDS);
65 | private static final FloatBuffer RECTANGLE_TEX_BUF =
66 | GlUtil.createFloatBuffer(RECTANGLE_TEX_COORDS);
67 |
68 | /**
69 | * A "full" square, extending from -1 to +1 in both dimensions. When the model/view/projection
70 | * matrix is identity, this will exactly cover the viewport.
71 | *
72 | * The texture coordinates are Y-inverted relative to RECTANGLE. (This seems to work out
73 | * right with external textures from SurfaceTexture.)
74 | */
75 | private static final float FULL_RECTANGLE_COORDS[] = {
76 | -1.0f, -1.0f, // 0 bottom left
77 | 1.0f, -1.0f, // 1 bottom right
78 | -1.0f, 1.0f, // 2 top left
79 | 1.0f, 1.0f, // 3 top right
80 | };
81 | private static final float FULL_RECTANGLE_TEX_COORDS[] = {
82 | 0.0f, 0.0f, // 0 bottom left
83 | 1.0f, 0.0f, // 1 bottom right
84 | 0.0f, 1.0f, // 2 top left
85 | 1.0f, 1.0f // 3 top right
86 | };
87 | private static final FloatBuffer FULL_RECTANGLE_BUF =
88 | GlUtil.createFloatBuffer(FULL_RECTANGLE_COORDS);
89 | private static final FloatBuffer FULL_RECTANGLE_TEX_BUF =
90 | GlUtil.createFloatBuffer(FULL_RECTANGLE_TEX_COORDS);
91 |
92 |
93 | private FloatBuffer mVertexArray;
94 | private FloatBuffer mTexCoordArray;
95 | private int mVertexCount;
96 | private int mCoordsPerVertex;
97 | private int mVertexStride;
98 | private int mTexCoordStride;
99 | private Prefab mPrefab;
100 |
101 | /**
102 | * Enum values for constructor.
103 | */
104 | public enum Prefab {
105 | TRIANGLE, RECTANGLE, FULL_RECTANGLE
106 | }
107 |
108 | /**
109 | * Prepares a drawable from a "pre-fabricated" shape definition.
110 | *
111 | * Does no EGL/GL operations, so this can be done at any time.
112 | */
113 | public Drawable2d(Prefab shape) {
114 | switch (shape) {
115 | case TRIANGLE:
116 | mVertexArray = TRIANGLE_BUF;
117 | mTexCoordArray = TRIANGLE_TEX_BUF;
118 | mCoordsPerVertex = 2;
119 | mVertexStride = mCoordsPerVertex * SIZEOF_FLOAT;
120 | mVertexCount = TRIANGLE_COORDS.length / mCoordsPerVertex;
121 | break;
122 | case RECTANGLE:
123 | mVertexArray = RECTANGLE_BUF;
124 | mTexCoordArray = RECTANGLE_TEX_BUF;
125 | mCoordsPerVertex = 2;
126 | mVertexStride = mCoordsPerVertex * SIZEOF_FLOAT;
127 | mVertexCount = RECTANGLE_COORDS.length / mCoordsPerVertex;
128 | break;
129 | case FULL_RECTANGLE:
130 | mVertexArray = FULL_RECTANGLE_BUF;
131 | mTexCoordArray = FULL_RECTANGLE_TEX_BUF;
132 | mCoordsPerVertex = 2;
133 | mVertexStride = mCoordsPerVertex * SIZEOF_FLOAT;
134 | mVertexCount = FULL_RECTANGLE_COORDS.length / mCoordsPerVertex;
135 | break;
136 | default:
137 | throw new RuntimeException("Unknown shape " + shape);
138 | }
139 | mTexCoordStride = 2 * SIZEOF_FLOAT;
140 | mPrefab = shape;
141 | }
142 |
143 | /**
144 | * Returns the array of vertices.
145 | *
146 | * To avoid allocations, this returns internal state. The caller must not modify it.
147 | */
148 | public FloatBuffer getVertexArray() {
149 | return mVertexArray;
150 | }
151 |
152 | /**
153 | * Returns the array of texture coordinates.
154 | *
155 | * To avoid allocations, this returns internal state. The caller must not modify it.
156 | */
157 | public FloatBuffer getTexCoordArray() {
158 | return mTexCoordArray;
159 | }
160 |
161 | /**
162 | * Returns the number of vertices stored in the vertex array.
163 | */
164 | public int getVertexCount() {
165 | return mVertexCount;
166 | }
167 |
168 | /**
169 | * Returns the width, in bytes, of the data for each vertex.
170 | */
171 | public int getVertexStride() {
172 | return mVertexStride;
173 | }
174 |
175 | /**
176 | * Returns the width, in bytes, of the data for each texture coordinate.
177 | */
178 | public int getTexCoordStride() {
179 | return mTexCoordStride;
180 | }
181 |
182 | /**
183 | * Returns the number of position coordinates per vertex. This will be 2 or 3.
184 | */
185 | public int getCoordsPerVertex() {
186 | return mCoordsPerVertex;
187 | }
188 |
189 | @Override
190 | public String toString() {
191 | if (mPrefab != null) {
192 | return "[Drawable2d: " + mPrefab + "]";
193 | } else {
194 | return "[Drawable2d: ...]";
195 | }
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/app/src/main/java/com/felix/glcamera/gles/EglCore.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 Google Inc. All rights reserved.
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 com.felix.glcamera.gles;
18 |
19 | import android.graphics.SurfaceTexture;
20 | import android.opengl.EGL14;
21 | import android.opengl.EGLConfig;
22 | import android.opengl.EGLContext;
23 | import android.opengl.EGLDisplay;
24 | import android.opengl.EGLExt;
25 | import android.opengl.EGLSurface;
26 | import android.util.Log;
27 | import android.view.Surface;
28 |
29 | /**
30 | * Core EGL state (display, context, config).
31 | *
32 | * The EGLContext must only be attached to one thread at a time. This class is not thread-safe.
33 | */
34 | public final class EglCore {
35 | private static final String TAG = "EglCore";
36 |
37 | /**
38 | * Constructor flag: surface must be recordable. This discourages EGL from using a
39 | * pixel format that cannot be converted efficiently to something usable by the video
40 | * encoder.
41 | */
42 | public static final int FLAG_RECORDABLE = 0x01;
43 |
44 | /**
45 | * Constructor flag: ask for GLES3, fall back to GLES2 if not available. Without this
46 | * flag, GLES2 is used.
47 | */
48 | public static final int FLAG_TRY_GLES3 = 0x02;
49 |
50 | // Android-specific extension.
51 | private static final int EGL_RECORDABLE_ANDROID = 0x3142;
52 |
53 | private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
54 | private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
55 | private EGLConfig mEGLConfig = null;
56 | private int mGlVersion = -1;
57 |
58 |
59 | /**
60 | * Prepares EGL display and context.
61 | *
62 | * Equivalent to EglCore(null, 0).
63 | */
64 | public EglCore() {
65 | this(null, 0);
66 | }
67 |
68 | /**
69 | * Prepares EGL display and context.
70 | *
71 | * @param sharedContext The context to share, or null if sharing is not desired.
72 | * @param flags Configuration bit flags, e.g. FLAG_RECORDABLE.
73 | */
74 | public EglCore(EGLContext sharedContext, int flags) {
75 | if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
76 | throw new RuntimeException("EGL already set up");
77 | }
78 |
79 | if (sharedContext == null) {
80 | sharedContext = EGL14.EGL_NO_CONTEXT;
81 | }
82 |
83 | mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
84 | if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
85 | throw new RuntimeException("unable to get EGL14 display");
86 | }
87 | int[] version = new int[2];
88 | if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
89 | mEGLDisplay = null;
90 | throw new RuntimeException("unable to initialize EGL14");
91 | }
92 |
93 | // Try to get a GLES3 context, if requested.
94 | if ((flags & FLAG_TRY_GLES3) != 0) {
95 | //Log.d(TAG, "Trying GLES 3");
96 | EGLConfig config = getConfig(flags, 3);
97 | if (config != null) {
98 | int[] attrib3_list = {
99 | EGL14.EGL_CONTEXT_CLIENT_VERSION, 3,
100 | EGL14.EGL_NONE
101 | };
102 | EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext,
103 | attrib3_list, 0);
104 |
105 | if (EGL14.eglGetError() == EGL14.EGL_SUCCESS) {
106 | //Log.d(TAG, "Got GLES 3 config");
107 | mEGLConfig = config;
108 | mEGLContext = context;
109 | mGlVersion = 3;
110 | }
111 | }
112 | }
113 | if (mEGLContext == EGL14.EGL_NO_CONTEXT) { // GLES 2 only, or GLES 3 attempt failed
114 | //Log.d(TAG, "Trying GLES 2");
115 | EGLConfig config = getConfig(flags, 2);
116 | if (config == null) {
117 | throw new RuntimeException("Unable to find a suitable EGLConfig");
118 | }
119 | int[] attrib2_list = {
120 | EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
121 | EGL14.EGL_NONE
122 | };
123 | EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext,
124 | attrib2_list, 0);
125 | checkEglError("eglCreateContext");
126 | mEGLConfig = config;
127 | mEGLContext = context;
128 | mGlVersion = 2;
129 | }
130 |
131 | // Confirm with query.
132 | int[] values = new int[1];
133 | EGL14.eglQueryContext(mEGLDisplay, mEGLContext, EGL14.EGL_CONTEXT_CLIENT_VERSION,
134 | values, 0);
135 | Log.d(TAG, "EGLContext created, client version " + values[0]);
136 | }
137 |
138 | /**
139 | * Finds a suitable EGLConfig.
140 | *
141 | * @param flags Bit flags from constructor.
142 | * @param version Must be 2 or 3.
143 | */
144 | private EGLConfig getConfig(int flags, int version) {
145 | int renderableType = EGL14.EGL_OPENGL_ES2_BIT;
146 | if (version >= 3) {
147 | renderableType |= EGLExt.EGL_OPENGL_ES3_BIT_KHR;
148 | }
149 |
150 | // The actual surface is generally RGBA or RGBX, so situationally omitting alpha
151 | // doesn't really help. It can also lead to a huge performance hit on glReadPixels()
152 | // when reading into a GL_RGBA buffer.
153 | int[] attribList = {
154 | EGL14.EGL_RED_SIZE, 8,
155 | EGL14.EGL_GREEN_SIZE, 8,
156 | EGL14.EGL_BLUE_SIZE, 8,
157 | EGL14.EGL_ALPHA_SIZE, 8,
158 | //EGL14.EGL_DEPTH_SIZE, 16,
159 | //EGL14.EGL_STENCIL_SIZE, 8,
160 | EGL14.EGL_RENDERABLE_TYPE, renderableType,
161 | EGL14.EGL_NONE, 0, // placeholder for recordable [@-3]
162 | EGL14.EGL_NONE
163 | };
164 | if ((flags & FLAG_RECORDABLE) != 0) {
165 | attribList[attribList.length - 3] = EGL_RECORDABLE_ANDROID;
166 | attribList[attribList.length - 2] = 1;
167 | }
168 | EGLConfig[] configs = new EGLConfig[1];
169 | int[] numConfigs = new int[1];
170 | if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length,
171 | numConfigs, 0)) {
172 | Log.w(TAG, "unable to find RGB8888 / " + version + " EGLConfig");
173 | return null;
174 | }
175 | return configs[0];
176 | }
177 |
178 | /**
179 | * Discards all resources held by this class, notably the EGL context. This must be
180 | * called from the thread where the context was created.
181 | *
182 | * On completion, no context will be current.
183 | */
184 | public void release() {
185 | if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
186 | // Android is unusual in that it uses a reference-counted EGLDisplay. So for
187 | // every eglInitialize() we need an eglTerminate().
188 | EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
189 | EGL14.EGL_NO_CONTEXT);
190 | EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
191 | EGL14.eglReleaseThread();
192 | EGL14.eglTerminate(mEGLDisplay);
193 | }
194 |
195 | mEGLDisplay = EGL14.EGL_NO_DISPLAY;
196 | mEGLContext = EGL14.EGL_NO_CONTEXT;
197 | mEGLConfig = null;
198 | }
199 |
200 | @Override
201 | protected void finalize() throws Throwable {
202 | try {
203 | if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
204 | // We're limited here -- finalizers don't run on the thread that holds
205 | // the EGL state, so if a surface or context is still current on another
206 | // thread we can't fully release it here. Exceptions thrown from here
207 | // are quietly discarded. Complain in the log file.
208 | Log.w(TAG, "WARNING: EglCore was not explicitly released -- state may be leaked");
209 | release();
210 | }
211 | } finally {
212 | super.finalize();
213 | }
214 | }
215 |
216 | /**
217 | * Destroys the specified surface. Note the EGLSurface won't actually be destroyed if it's
218 | * still current in a context.
219 | */
220 | public void releaseSurface(EGLSurface eglSurface) {
221 | EGL14.eglDestroySurface(mEGLDisplay, eglSurface);
222 | }
223 |
224 | /**
225 | * Creates an EGL surface associated with a Surface.
226 | *
227 | * If this is destined for MediaCodec, the EGLConfig should have the "recordable" attribute.
228 | */
229 | public EGLSurface createWindowSurface(Object surface) {
230 | if (!(surface instanceof Surface) && !(surface instanceof SurfaceTexture)) {
231 | throw new RuntimeException("invalid surface: " + surface);
232 | }
233 |
234 | // Create a window surface, and attach it to the Surface we received.
235 | int[] surfaceAttribs = {
236 | EGL14.EGL_NONE
237 | };
238 | EGLSurface eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, surface,
239 | surfaceAttribs, 0);
240 | checkEglError("eglCreateWindowSurface");
241 | if (eglSurface == null) {
242 | throw new RuntimeException("surface was null");
243 | }
244 | return eglSurface;
245 | }
246 |
247 | /**
248 | * Creates an EGL surface associated with an offscreen buffer.
249 | */
250 | public EGLSurface createOffscreenSurface(int width, int height) {
251 | int[] surfaceAttribs = {
252 | EGL14.EGL_WIDTH, width,
253 | EGL14.EGL_HEIGHT, height,
254 | EGL14.EGL_NONE
255 | };
256 | EGLSurface eglSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mEGLConfig,
257 | surfaceAttribs, 0);
258 | checkEglError("eglCreatePbufferSurface");
259 | if (eglSurface == null) {
260 | throw new RuntimeException("surface was null");
261 | }
262 | return eglSurface;
263 | }
264 |
265 | /**
266 | * Makes our EGL context current, using the supplied surface for both "draw" and "read".
267 | */
268 | public void makeCurrent(EGLSurface eglSurface) {
269 | if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
270 | // called makeCurrent() before create?
271 | Log.d(TAG, "NOTE: makeCurrent w/o display");
272 | }
273 | if (!EGL14.eglMakeCurrent(mEGLDisplay, eglSurface, eglSurface, mEGLContext)) {
274 | throw new RuntimeException("eglMakeCurrent failed");
275 | }
276 | }
277 |
278 | /**
279 | * Makes our EGL context current, using the supplied "draw" and "read" surfaces.
280 | */
281 | public void makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) {
282 | if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
283 | // called makeCurrent() before create?
284 | Log.d(TAG, "NOTE: makeCurrent w/o display");
285 | }
286 | if (!EGL14.eglMakeCurrent(mEGLDisplay, drawSurface, readSurface, mEGLContext)) {
287 | throw new RuntimeException("eglMakeCurrent(draw,read) failed");
288 | }
289 | }
290 |
291 | /**
292 | * Makes no context current.
293 | */
294 | public void makeNothingCurrent() {
295 | if (!EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
296 | EGL14.EGL_NO_CONTEXT)) {
297 | throw new RuntimeException("eglMakeCurrent failed");
298 | }
299 | }
300 |
301 | /**
302 | * Calls eglSwapBuffers. Use this to "publish" the current frame.
303 | *
304 | * @return false on failure
305 | */
306 | public boolean swapBuffers(EGLSurface eglSurface) {
307 | return EGL14.eglSwapBuffers(mEGLDisplay, eglSurface);
308 | }
309 |
310 | /**
311 | * Sends the presentation time stamp to EGL. Time is expressed in nanoseconds.
312 | */
313 | public void setPresentationTime(EGLSurface eglSurface, long nsecs) {
314 | EGLExt.eglPresentationTimeANDROID(mEGLDisplay, eglSurface, nsecs);
315 | }
316 |
317 | /**
318 | * Returns true if our context and the specified surface are current.
319 | */
320 | public boolean isCurrent(EGLSurface eglSurface) {
321 | return mEGLContext.equals(EGL14.eglGetCurrentContext()) &&
322 | eglSurface.equals(EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW));
323 | }
324 |
325 | /**
326 | * Performs a simple surface query.
327 | */
328 | public int querySurface(EGLSurface eglSurface, int what) {
329 | int[] value = new int[1];
330 | EGL14.eglQuerySurface(mEGLDisplay, eglSurface, what, value, 0);
331 | return value[0];
332 | }
333 |
334 | /**
335 | * Queries a string value.
336 | */
337 | public String queryString(int what) {
338 | return EGL14.eglQueryString(mEGLDisplay, what);
339 | }
340 |
341 | /**
342 | * Returns the GLES version this context is configured for (currently 2 or 3).
343 | */
344 | public int getGlVersion() {
345 | return mGlVersion;
346 | }
347 |
348 | /**
349 | * Writes the current display, context, and surface to the log.
350 | */
351 | public static void logCurrent(String msg) {
352 | EGLDisplay display;
353 | EGLContext context;
354 | EGLSurface surface;
355 |
356 | display = EGL14.eglGetCurrentDisplay();
357 | context = EGL14.eglGetCurrentContext();
358 | surface = EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW);
359 | Log.i(TAG, "Current EGL (" + msg + "): display=" + display + ", context=" + context +
360 | ", surface=" + surface);
361 | }
362 |
363 | /**
364 | * Checks for EGL errors. Throws an exception if an error has been raised.
365 | */
366 | private void checkEglError(String msg) {
367 | int error;
368 | if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
369 | throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error));
370 | }
371 | }
372 | }
373 |
--------------------------------------------------------------------------------
/app/src/main/java/com/felix/glcamera/gles/EglSurfaceBase.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 Google Inc. All rights reserved.
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 com.felix.glcamera.gles;
18 |
19 | import android.graphics.Bitmap;
20 | import android.opengl.EGL14;
21 | import android.opengl.EGLSurface;
22 | import android.opengl.GLES20;
23 | import android.util.Log;
24 |
25 | import java.io.BufferedOutputStream;
26 | import java.io.File;
27 | import java.io.FileOutputStream;
28 | import java.io.IOException;
29 | import java.nio.ByteBuffer;
30 | import java.nio.ByteOrder;
31 |
32 | /**
33 | * Common base class for EGL surfaces.
34 | *
35 | * There can be multiple surfaces associated with a single context.
36 | */
37 | public class EglSurfaceBase {
38 | private static final String TAG = "EglSurfaceBase";
39 |
40 | // EglCore object we're associated with. It may be associated with multiple surfaces.
41 | protected EglCore mEglCore;
42 |
43 | private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE;
44 | private int mWidth = -1;
45 | private int mHeight = -1;
46 |
47 | protected EglSurfaceBase(EglCore eglCore) {
48 | mEglCore = eglCore;
49 | }
50 |
51 | /**
52 | * Creates a window surface.
53 | *
54 | * @param surface May be a Surface or SurfaceTexture.
55 | */
56 | public void createWindowSurface(Object surface) {
57 | if (mEGLSurface != EGL14.EGL_NO_SURFACE) {
58 | throw new IllegalStateException("surface already created");
59 | }
60 | mEGLSurface = mEglCore.createWindowSurface(surface);
61 |
62 | // Don't cache width/height here, because the size of the underlying surface can change
63 | // out from under us (see e.g. HardwareScalerActivity).
64 | //mWidth = mEglCore.querySurface(mEGLSurface, EGL14.EGL_WIDTH);
65 | //mHeight = mEglCore.querySurface(mEGLSurface, EGL14.EGL_HEIGHT);
66 | }
67 |
68 | /**
69 | * Creates an off-screen surface.
70 | */
71 | public void createOffscreenSurface(int width, int height) {
72 | if (mEGLSurface != EGL14.EGL_NO_SURFACE) {
73 | throw new IllegalStateException("surface already created");
74 | }
75 | mEGLSurface = mEglCore.createOffscreenSurface(width, height);
76 | mWidth = width;
77 | mHeight = height;
78 | }
79 |
80 | /**
81 | * Returns the surface's width, in pixels.
82 | *
83 | * If this is called on a window surface, and the underlying surface is in the process
84 | * of changing size, we may not see the new size right away (e.g. in the "surfaceChanged"
85 | * callback). The size should match after the next buffer swap.
86 | */
87 | public int getWidth() {
88 | if (mWidth < 0) {
89 | return mEglCore.querySurface(mEGLSurface, EGL14.EGL_WIDTH);
90 | } else {
91 | return mWidth;
92 | }
93 | }
94 |
95 | /**
96 | * Returns the surface's height, in pixels.
97 | */
98 | public int getHeight() {
99 | if (mHeight < 0) {
100 | return mEglCore.querySurface(mEGLSurface, EGL14.EGL_HEIGHT);
101 | } else {
102 | return mHeight;
103 | }
104 | }
105 |
106 | /**
107 | * Release the EGL surface.
108 | */
109 | public void releaseEglSurface() {
110 | mEglCore.releaseSurface(mEGLSurface);
111 | mEGLSurface = EGL14.EGL_NO_SURFACE;
112 | mWidth = mHeight = -1;
113 | }
114 |
115 | /**
116 | * Makes our EGL context and surface current.
117 | */
118 | public void makeCurrent() {
119 | mEglCore.makeCurrent(mEGLSurface);
120 | }
121 |
122 | /**
123 | * Makes our EGL context and surface current for drawing, using the supplied surface
124 | * for reading.
125 | */
126 | public void makeCurrentReadFrom(EglSurfaceBase readSurface) {
127 | mEglCore.makeCurrent(mEGLSurface, readSurface.mEGLSurface);
128 | }
129 |
130 | /**
131 | * Calls eglSwapBuffers. Use this to "publish" the current frame.
132 | *
133 | * @return false on failure
134 | */
135 | public boolean swapBuffers() {
136 | boolean result = mEglCore.swapBuffers(mEGLSurface);
137 | if (!result) {
138 | Log.d(TAG, "WARNING: swapBuffers() failed");
139 | }
140 | return result;
141 | }
142 |
143 | /**
144 | * Sends the presentation time stamp to EGL.
145 | *
146 | * @param nsecs Timestamp, in nanoseconds.
147 | */
148 | public void setPresentationTime(long nsecs) {
149 | mEglCore.setPresentationTime(mEGLSurface, nsecs);
150 | }
151 |
152 | /**
153 | * Saves the EGL surface to a file.
154 | *
155 | * Expects that this object's EGL surface is current.
156 | */
157 | public void saveFrame(File file) throws IOException {
158 | if (!mEglCore.isCurrent(mEGLSurface)) {
159 | throw new RuntimeException("Expected EGL context/surface is not current");
160 | }
161 |
162 | // glReadPixels fills in a "direct" ByteBuffer with what is essentially big-endian RGBA
163 | // data (i.e. a byte of red, followed by a byte of green...). While the Bitmap
164 | // constructor that takes an int[] wants little-endian ARGB (blue/red swapped), the
165 | // Bitmap "copy pixels" method wants the same format GL provides.
166 | //
167 | // Ideally we'd have some way to re-use the ByteBuffer, especially if we're calling
168 | // here often.
169 | //
170 | // Making this even more interesting is the upside-down nature of GL, which means
171 | // our output will look upside down relative to what appears on screen if the
172 | // typical GL conventions are used.
173 |
174 | String filename = file.toString();
175 |
176 | int width = getWidth();
177 | int height = getHeight();
178 | ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4);
179 | buf.order(ByteOrder.LITTLE_ENDIAN);
180 | GLES20.glReadPixels(0, 0, width, height,
181 | GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);
182 | GlUtil.checkGlError("glReadPixels");
183 | buf.rewind();
184 |
185 | BufferedOutputStream bos = null;
186 | try {
187 | bos = new BufferedOutputStream(new FileOutputStream(filename));
188 | Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
189 | bmp.copyPixelsFromBuffer(buf);
190 | bmp.compress(Bitmap.CompressFormat.PNG, 90, bos);
191 | bmp.recycle();
192 | } finally {
193 | if (bos != null) bos.close();
194 | }
195 | Log.d(TAG, "Saved " + width + "x" + height + " frame as '" + filename + "'");
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/app/src/main/java/com/felix/glcamera/gles/FullFrameRect.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc. All rights reserved.
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 com.felix.glcamera.gles;
18 |
19 | /**
20 | * This class essentially represents a viewport-sized sprite that will be rendered with
21 | * a texture, usually from an external source like the camera or video decoder.
22 | */
23 | public class FullFrameRect {
24 | private final Drawable2d mRectDrawable = new Drawable2d(Drawable2d.Prefab.FULL_RECTANGLE);
25 | private Texture2dProgram mProgram;
26 |
27 | /**
28 | * Prepares the object.
29 | *
30 | * @param program The program to use. FullFrameRect takes ownership, and will release
31 | * the program when no longer needed.
32 | */
33 | public FullFrameRect(Texture2dProgram program) {
34 | mProgram = program;
35 | }
36 |
37 | /**
38 | * Releases resources.
39 | *
40 | * This must be called with the appropriate EGL context current (i.e. the one that was
41 | * current when the constructor was called). If we're about to destroy the EGL context,
42 | * there's no value in having the caller make it current just to do this cleanup, so you
43 | * can pass a flag that will tell this function to skip any EGL-context-specific cleanup.
44 | */
45 | public void release(boolean doEglCleanup) {
46 | if (mProgram != null) {
47 | if (doEglCleanup) {
48 | mProgram.release();
49 | }
50 | mProgram = null;
51 | }
52 | }
53 |
54 | /**
55 | * Returns the program currently in use.
56 | */
57 | public Texture2dProgram getProgram() {
58 | return mProgram;
59 | }
60 |
61 | /**
62 | * Changes the program. The previous program will be released.
63 | *
64 | * The appropriate EGL context must be current.
65 | */
66 | public void changeProgram(Texture2dProgram program) {
67 | mProgram.release();
68 | mProgram = program;
69 | }
70 |
71 | /**
72 | * Creates a texture object suitable for use with drawFrame().
73 | */
74 | public int createTextureObject() {
75 | return mProgram.createTextureObject();
76 | }
77 |
78 | /**
79 | * Draws a viewport-filling rect, texturing it with the specified texture object.
80 | */
81 | public void drawFrame(int textureId, float[] texMatrix) {
82 | // Use the identity matrix for MVP so our 2x2 FULL_RECTANGLE covers the viewport.
83 | mProgram.draw(GlUtil.IDENTITY_MATRIX,
84 | mRectDrawable.getVertexArray(),
85 | 0,
86 | mRectDrawable.getVertexCount(),
87 | mRectDrawable.getCoordsPerVertex(),
88 | mRectDrawable.getVertexStride(),
89 | texMatrix,
90 | mRectDrawable.getTexCoordArray(),
91 | textureId,
92 | mRectDrawable.getTexCoordStride());
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/app/src/main/java/com/felix/glcamera/gles/GlUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc. All rights reserved.
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 com.felix.glcamera.gles;
18 |
19 | import android.opengl.GLES20;
20 | import android.opengl.GLES30;
21 | import android.opengl.Matrix;
22 | import android.util.Log;
23 |
24 | import java.nio.ByteBuffer;
25 | import java.nio.ByteOrder;
26 | import java.nio.FloatBuffer;
27 |
28 | /**
29 | * Some OpenGL utility functions.
30 | */
31 | public class GlUtil {
32 | private static final String TAG = "GlUtil";
33 |
34 | /** Identity matrix for general use. Don't modify or life will get weird. */
35 | public static final float[] IDENTITY_MATRIX;
36 | static {
37 | IDENTITY_MATRIX = new float[16];
38 | Matrix.setIdentityM(IDENTITY_MATRIX, 0);
39 | }
40 |
41 | private static final int SIZEOF_FLOAT = 4;
42 |
43 |
44 | private GlUtil() {} // do not instantiate
45 |
46 | /**
47 | * Creates a new program from the supplied vertex and fragment shaders.
48 | *
49 | * @return A handle to the program, or 0 on failure.
50 | */
51 | public static int createProgram(String vertexSource, String fragmentSource) {
52 | int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
53 | if (vertexShader == 0) {
54 | return 0;
55 | }
56 | int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
57 | if (pixelShader == 0) {
58 | return 0;
59 | }
60 |
61 | int program = GLES20.glCreateProgram();
62 | checkGlError("glCreateProgram");
63 | if (program == 0) {
64 | Log.e(TAG, "Could not create program");
65 | }
66 | GLES20.glAttachShader(program, vertexShader);
67 | checkGlError("glAttachShader");
68 | GLES20.glAttachShader(program, pixelShader);
69 | checkGlError("glAttachShader");
70 | GLES20.glLinkProgram(program);
71 | int[] linkStatus = new int[1];
72 | GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
73 | if (linkStatus[0] != GLES20.GL_TRUE) {
74 | Log.e(TAG, "Could not link program: ");
75 | Log.e(TAG, GLES20.glGetProgramInfoLog(program));
76 | GLES20.glDeleteProgram(program);
77 | program = 0;
78 | }
79 | return program;
80 | }
81 |
82 | /**
83 | * Compiles the provided shader source.
84 | *
85 | * @return A handle to the shader, or 0 on failure.
86 | */
87 | public static int loadShader(int shaderType, String source) {
88 | int shader = GLES20.glCreateShader(shaderType);
89 | checkGlError("glCreateShader type=" + shaderType);
90 | GLES20.glShaderSource(shader, source);
91 | GLES20.glCompileShader(shader);
92 | int[] compiled = new int[1];
93 | GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
94 | if (compiled[0] == 0) {
95 | Log.e(TAG, "Could not compile shader " + shaderType + ":");
96 | Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader));
97 | GLES20.glDeleteShader(shader);
98 | shader = 0;
99 | }
100 | return shader;
101 | }
102 |
103 | /**
104 | * Checks to see if a GLES error has been raised.
105 | */
106 | public static void checkGlError(String op) {
107 | int error = GLES20.glGetError();
108 | if (error != GLES20.GL_NO_ERROR) {
109 | String msg = op + ": glError 0x" + Integer.toHexString(error);
110 | Log.e(TAG, msg);
111 | throw new RuntimeException(msg);
112 | }
113 | }
114 |
115 | /**
116 | * Checks to see if the location we obtained is valid. GLES returns -1 if a label
117 | * could not be found, but does not set the GL error.
118 | *
119 | * Throws a RuntimeException if the location is invalid.
120 | */
121 | public static void checkLocation(int location, String label) {
122 | if (location < 0) {
123 | throw new RuntimeException("Unable to locate '" + label + "' in program");
124 | }
125 | }
126 |
127 | /**
128 | * Creates a texture from raw data.
129 | *
130 | * @param data Image data, in a "direct" ByteBuffer.
131 | * @param width Texture width, in pixels (not bytes).
132 | * @param height Texture height, in pixels.
133 | * @param format Image data format (use constant appropriate for glTexImage2D(), e.g. GL_RGBA).
134 | * @return Handle to texture.
135 | */
136 | public static int createImageTexture(ByteBuffer data, int width, int height, int format) {
137 | int[] textureHandles = new int[1];
138 | int textureHandle;
139 |
140 | GLES20.glGenTextures(1, textureHandles, 0);
141 | textureHandle = textureHandles[0];
142 | GlUtil.checkGlError("glGenTextures");
143 |
144 | // Bind the texture handle to the 2D texture target.
145 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle);
146 |
147 | // Configure min/mag filtering, i.e. what scaling method do we use if what we're rendering
148 | // is smaller or larger than the source image.
149 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
150 | GLES20.GL_LINEAR);
151 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
152 | GLES20.GL_LINEAR);
153 | GlUtil.checkGlError("loadImageTexture");
154 |
155 | // Load the data from the buffer into the texture handle.
156 | GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, /*level*/ 0, format,
157 | width, height, /*border*/ 0, format, GLES20.GL_UNSIGNED_BYTE, data);
158 | GlUtil.checkGlError("loadImageTexture");
159 |
160 | return textureHandle;
161 | }
162 |
163 | /**
164 | * Allocates a direct float buffer, and populates it with the float array data.
165 | */
166 | public static FloatBuffer createFloatBuffer(float[] coords) {
167 | // Allocate a direct ByteBuffer, using 4 bytes per float, and copy coords into it.
168 | ByteBuffer bb = ByteBuffer.allocateDirect(coords.length * SIZEOF_FLOAT);
169 | bb.order(ByteOrder.nativeOrder());
170 | FloatBuffer fb = bb.asFloatBuffer();
171 | fb.put(coords);
172 | fb.position(0);
173 | return fb;
174 | }
175 |
176 | /**
177 | * Writes GL version info to the log.
178 | */
179 | public static void logVersionInfo() {
180 | Log.i(TAG, "vendor : " + GLES20.glGetString(GLES20.GL_VENDOR));
181 | Log.i(TAG, "renderer: " + GLES20.glGetString(GLES20.GL_RENDERER));
182 | Log.i(TAG, "version : " + GLES20.glGetString(GLES20.GL_VERSION));
183 |
184 | if (false) {
185 | int[] values = new int[1];
186 | GLES30.glGetIntegerv(GLES30.GL_MAJOR_VERSION, values, 0);
187 | int majorVersion = values[0];
188 | GLES30.glGetIntegerv(GLES30.GL_MINOR_VERSION, values, 0);
189 | int minorVersion = values[0];
190 | if (GLES30.glGetError() == GLES30.GL_NO_ERROR) {
191 | Log.i(TAG, "iversion: " + majorVersion + "." + minorVersion);
192 | }
193 | }
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/app/src/main/java/com/felix/glcamera/gles/Texture2dProgram.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc. All rights reserved.
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 com.felix.glcamera.gles;
18 |
19 | import android.opengl.GLES11Ext;
20 | import android.opengl.GLES20;
21 | import android.util.Log;
22 |
23 | import java.nio.FloatBuffer;
24 |
25 | /**
26 | * GL program and supporting functions for textured 2D shapes.
27 | */
28 | public class Texture2dProgram {
29 | private static final String TAG = "Texture2dProgram";
30 |
31 | public enum ProgramType {
32 | TEXTURE_2D, TEXTURE_EXT, TEXTURE_EXT_BW, TEXTURE_EXT_FILT
33 | }
34 |
35 | // Simple vertex shader, used for all programs.
36 | private static final String VERTEX_SHADER =
37 | "uniform mat4 uMVPMatrix;\n" +
38 | "uniform mat4 uTexMatrix;\n" +
39 | "attribute vec4 aPosition;\n" +
40 | "attribute vec4 aTextureCoord;\n" +
41 | "varying vec2 vTextureCoord;\n" +
42 | "void main() {\n" +
43 | " gl_Position = uMVPMatrix * aPosition;\n" +
44 | " vTextureCoord = (uTexMatrix * aTextureCoord).xy;\n" +
45 | "}\n";
46 |
47 | // Simple fragment shader for use with "normal" 2D textures.
48 | private static final String FRAGMENT_SHADER_2D =
49 | "precision mediump float;\n" +
50 | "varying vec2 vTextureCoord;\n" +
51 | "uniform sampler2D sTexture;\n" +
52 | "void main() {\n" +
53 | " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
54 | "}\n";
55 |
56 | // Simple fragment shader for use with external 2D textures (e.g. what we get from
57 | // SurfaceTexture).
58 | private static final String FRAGMENT_SHADER_EXT =
59 | "#extension GL_OES_EGL_image_external : require\n" +
60 | "precision mediump float;\n" +
61 | "varying vec2 vTextureCoord;\n" +
62 | "uniform samplerExternalOES sTexture;\n" +
63 | "void main() {\n" +
64 | " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
65 | "}\n";
66 |
67 | // Fragment shader that converts color to black & white with a simple transformation.
68 | private static final String FRAGMENT_SHADER_EXT_BW =
69 | "#extension GL_OES_EGL_image_external : require\n" +
70 | "precision mediump float;\n" +
71 | "varying vec2 vTextureCoord;\n" +
72 | "uniform samplerExternalOES sTexture;\n" +
73 | "void main() {\n" +
74 | " vec4 tc = texture2D(sTexture, vTextureCoord);\n" +
75 | " float color = tc.r * 0.3 + tc.g * 0.59 + tc.b * 0.11;\n" +
76 | " gl_FragColor = vec4(color, color, color, 1.0);\n" +
77 | "}\n";
78 |
79 | // Fragment shader with a convolution filter. The upper-left half will be drawn normally,
80 | // the lower-right half will have the filter applied, and a thin red line will be drawn
81 | // at the border.
82 | //
83 | // This is not optimized for performance. Some things that might make this faster:
84 | // - Remove the conditionals. They're used to present a half & half view with a red
85 | // stripe across the middle, but that's only useful for a demo.
86 | // - Unroll the loop. Ideally the compiler does this for you when it's beneficial.
87 | // - Bake the filter kernel into the shader, instead of passing it through a uniform
88 | // array. That, combined with loop unrolling, should reduce memory accesses.
89 | public static final int KERNEL_SIZE = 9;
90 | private static final String FRAGMENT_SHADER_EXT_FILT =
91 | "#extension GL_OES_EGL_image_external : require\n" +
92 | "#define KERNEL_SIZE " + KERNEL_SIZE + "\n" +
93 | "precision highp float;\n" +
94 | "varying vec2 vTextureCoord;\n" +
95 | "uniform samplerExternalOES sTexture;\n" +
96 | "uniform float uKernel[KERNEL_SIZE];\n" +
97 | "uniform vec2 uTexOffset[KERNEL_SIZE];\n" +
98 | "uniform float uColorAdjust;\n" +
99 | "void main() {\n" +
100 | " int i = 0;\n" +
101 | " vec4 sum = vec4(0.0);\n" +
102 | " for (i = 0; i < KERNEL_SIZE; i++) {\n" +
103 | " vec4 texc = texture2D(sTexture, vTextureCoord + uTexOffset[i]);\n" +
104 | " sum += texc * uKernel[i];\n" +
105 | " }\n" +
106 | " sum += uColorAdjust;\n" +
107 | " gl_FragColor = sum;\n" +
108 | "}\n";
109 |
110 | private ProgramType mProgramType;
111 |
112 | // Handles to the GL program and various components of it.
113 | private int mProgramHandle;
114 | private int muMVPMatrixLoc;
115 | private int muTexMatrixLoc;
116 | private int muKernelLoc;
117 | private int muTexOffsetLoc;
118 | private int muColorAdjustLoc;
119 | private int maPositionLoc;
120 | private int maTextureCoordLoc;
121 |
122 | private int mTextureTarget;
123 |
124 | private float[] mKernel = new float[KERNEL_SIZE];
125 | private float[] mTexOffset;
126 | private float mColorAdjust;
127 |
128 |
129 | /**
130 | * Prepares the program in the current EGL context.
131 | */
132 | public Texture2dProgram(ProgramType programType) {
133 | mProgramType = programType;
134 | switch (programType) {
135 | case TEXTURE_2D:
136 | mTextureTarget = GLES20.GL_TEXTURE_2D;
137 | mProgramHandle = GlUtil.createProgram(VERTEX_SHADER, FRAGMENT_SHADER_2D);
138 | break;
139 | case TEXTURE_EXT:
140 | mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES;
141 | mProgramHandle = GlUtil.createProgram(VERTEX_SHADER, FRAGMENT_SHADER_EXT);
142 | break;
143 | case TEXTURE_EXT_BW:
144 | mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES;
145 | mProgramHandle = GlUtil.createProgram(VERTEX_SHADER, FRAGMENT_SHADER_EXT_BW);
146 | break;
147 | case TEXTURE_EXT_FILT:
148 | mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES;
149 | mProgramHandle = GlUtil.createProgram(VERTEX_SHADER, FRAGMENT_SHADER_EXT_FILT);
150 | break;
151 | default:
152 | throw new RuntimeException("Unhandled type " + programType);
153 | }
154 | if (mProgramHandle == 0) {
155 | throw new RuntimeException("Unable to create program");
156 | }
157 | Log.d(TAG, "Created program " + mProgramHandle + " (" + programType + ")");
158 |
159 | // get locations of attributes and uniforms
160 |
161 | maPositionLoc = GLES20.glGetAttribLocation(mProgramHandle, "aPosition");
162 | GlUtil.checkLocation(maPositionLoc, "aPosition");
163 | maTextureCoordLoc = GLES20.glGetAttribLocation(mProgramHandle, "aTextureCoord");
164 | GlUtil.checkLocation(maTextureCoordLoc, "aTextureCoord");
165 | muMVPMatrixLoc = GLES20.glGetUniformLocation(mProgramHandle, "uMVPMatrix");
166 | GlUtil.checkLocation(muMVPMatrixLoc, "uMVPMatrix");
167 | muTexMatrixLoc = GLES20.glGetUniformLocation(mProgramHandle, "uTexMatrix");
168 | GlUtil.checkLocation(muTexMatrixLoc, "uTexMatrix");
169 | muKernelLoc = GLES20.glGetUniformLocation(mProgramHandle, "uKernel");
170 | if (muKernelLoc < 0) {
171 | // no kernel in this one
172 | muKernelLoc = -1;
173 | muTexOffsetLoc = -1;
174 | muColorAdjustLoc = -1;
175 | } else {
176 | // has kernel, must also have tex offset and color adj
177 | muTexOffsetLoc = GLES20.glGetUniformLocation(mProgramHandle, "uTexOffset");
178 | GlUtil.checkLocation(muTexOffsetLoc, "uTexOffset");
179 | muColorAdjustLoc = GLES20.glGetUniformLocation(mProgramHandle, "uColorAdjust");
180 | GlUtil.checkLocation(muColorAdjustLoc, "uColorAdjust");
181 |
182 | // initialize default values
183 | setKernel(new float[] {0f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 0f}, 0f);
184 | setTexSize(256, 256);
185 | }
186 | }
187 |
188 | /**
189 | * Releases the program.
190 | *
191 | * The appropriate EGL context must be current (i.e. the one that was used to create
192 | * the program).
193 | */
194 | public void release() {
195 | Log.d(TAG, "deleting program " + mProgramHandle);
196 | GLES20.glDeleteProgram(mProgramHandle);
197 | mProgramHandle = -1;
198 | }
199 |
200 | /**
201 | * Returns the program type.
202 | */
203 | public ProgramType getProgramType() {
204 | return mProgramType;
205 | }
206 |
207 | /**
208 | * Creates a texture object suitable for use with this program.
209 | *
210 | * On exit, the texture will be bound.
211 | */
212 | public int createTextureObject() {
213 | int[] textures = new int[1];
214 | GLES20.glGenTextures(1, textures, 0);
215 | GlUtil.checkGlError("glGenTextures");
216 |
217 | int texId = textures[0];
218 | GLES20.glBindTexture(mTextureTarget, texId);
219 | GlUtil.checkGlError("glBindTexture " + texId);
220 |
221 | GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
222 | GLES20.GL_NEAREST);
223 | GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
224 | GLES20.GL_LINEAR);
225 | GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S,
226 | GLES20.GL_CLAMP_TO_EDGE);
227 | GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T,
228 | GLES20.GL_CLAMP_TO_EDGE);
229 | GlUtil.checkGlError("glTexParameter");
230 |
231 | return texId;
232 | }
233 |
234 | /**
235 | * Configures the convolution filter values.
236 | *
237 | * @param values Normalized filter values; must be KERNEL_SIZE elements.
238 | */
239 | public void setKernel(float[] values, float colorAdj) {
240 | if (values.length != KERNEL_SIZE) {
241 | throw new IllegalArgumentException("Kernel size is " + values.length +
242 | " vs. " + KERNEL_SIZE);
243 | }
244 | System.arraycopy(values, 0, mKernel, 0, KERNEL_SIZE);
245 | mColorAdjust = colorAdj;
246 | //Log.d(TAG, "filt kernel: " + Arrays.toString(mKernel) + ", adj=" + colorAdj);
247 | }
248 |
249 | /**
250 | * Sets the size of the texture. This is used to find adjacent texels when filtering.
251 | */
252 | public void setTexSize(int width, int height) {
253 | float rw = 1.0f / width;
254 | float rh = 1.0f / height;
255 |
256 | // Don't need to create a new array here, but it's syntactically convenient.
257 | mTexOffset = new float[] {
258 | -rw, -rh, 0f, -rh, rw, -rh,
259 | -rw, 0f, 0f, 0f, rw, 0f,
260 | -rw, rh, 0f, rh, rw, rh
261 | };
262 | //Log.d(TAG, "filt size: " + width + "x" + height + ": " + Arrays.toString(mTexOffset));
263 | }
264 |
265 | /**
266 | * Issues the draw call. Does the full setup on every call.
267 | *
268 | * @param mvpMatrix The 4x4 projection matrix.
269 | * @param vertexBuffer Buffer with vertex position data.
270 | * @param firstVertex Index of first vertex to use in vertexBuffer.
271 | * @param vertexCount Number of vertices in vertexBuffer.
272 | * @param coordsPerVertex The number of coordinates per vertex (e.g. x,y is 2).
273 | * @param vertexStride Width, in bytes, of the position data for each vertex (often
274 | * vertexCount * sizeof(float)).
275 | * @param texMatrix A 4x4 transformation matrix for texture coords. (Primarily intended
276 | * for use with SurfaceTexture.)
277 | * @param texBuffer Buffer with vertex texture data.
278 | * @param texStride Width, in bytes, of the texture data for each vertex.
279 | */
280 | public void draw(float[] mvpMatrix, FloatBuffer vertexBuffer, int firstVertex,
281 | int vertexCount, int coordsPerVertex, int vertexStride,
282 | float[] texMatrix, FloatBuffer texBuffer, int textureId, int texStride) {
283 | GlUtil.checkGlError("draw start");
284 |
285 | // Select the program.
286 | GLES20.glUseProgram(mProgramHandle);
287 | GlUtil.checkGlError("glUseProgram");
288 |
289 | // Set the texture.
290 | GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
291 | GLES20.glBindTexture(mTextureTarget, textureId);
292 |
293 | // Copy the model / view / projection matrix over.
294 | GLES20.glUniformMatrix4fv(muMVPMatrixLoc, 1, false, mvpMatrix, 0);
295 | GlUtil.checkGlError("glUniformMatrix4fv");
296 |
297 | // Copy the texture transformation matrix over.
298 | GLES20.glUniformMatrix4fv(muTexMatrixLoc, 1, false, texMatrix, 0);
299 | GlUtil.checkGlError("glUniformMatrix4fv");
300 |
301 | // Enable the "aPosition" vertex attribute.
302 | GLES20.glEnableVertexAttribArray(maPositionLoc);
303 | GlUtil.checkGlError("glEnableVertexAttribArray");
304 |
305 | // Connect vertexBuffer to "aPosition".
306 | GLES20.glVertexAttribPointer(maPositionLoc, coordsPerVertex,
307 | GLES20.GL_FLOAT, false, vertexStride, vertexBuffer);
308 | GlUtil.checkGlError("glVertexAttribPointer");
309 |
310 | // Enable the "aTextureCoord" vertex attribute.
311 | GLES20.glEnableVertexAttribArray(maTextureCoordLoc);
312 | GlUtil.checkGlError("glEnableVertexAttribArray");
313 |
314 | // Connect texBuffer to "aTextureCoord".
315 | GLES20.glVertexAttribPointer(maTextureCoordLoc, 2,
316 | GLES20.GL_FLOAT, false, texStride, texBuffer);
317 | GlUtil.checkGlError("glVertexAttribPointer");
318 |
319 | // Populate the convolution kernel, if present.
320 | if (muKernelLoc >= 0) {
321 | GLES20.glUniform1fv(muKernelLoc, KERNEL_SIZE, mKernel, 0);
322 | GLES20.glUniform2fv(muTexOffsetLoc, KERNEL_SIZE, mTexOffset, 0);
323 | GLES20.glUniform1f(muColorAdjustLoc, mColorAdjust);
324 | }
325 |
326 | // Draw the rect.
327 | GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, firstVertex, vertexCount);
328 | GlUtil.checkGlError("glDrawArrays");
329 |
330 | // Done -- disable vertex array, texture, and program.
331 | GLES20.glDisableVertexAttribArray(maPositionLoc);
332 | GLES20.glDisableVertexAttribArray(maTextureCoordLoc);
333 | GLES20.glBindTexture(mTextureTarget, 0);
334 | GLES20.glUseProgram(0);
335 | }
336 | }
337 |
--------------------------------------------------------------------------------
/app/src/main/java/com/felix/glcamera/gles/WindowSurface.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 Google Inc. All rights reserved.
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 com.felix.glcamera.gles;
18 |
19 | import android.graphics.SurfaceTexture;
20 | import android.view.Surface;
21 |
22 | /**
23 | * Recordable EGL window surface.
24 | *
25 | * It's good practice to explicitly release() the surface, preferably from a "finally" block.
26 | */
27 | public class WindowSurface extends EglSurfaceBase {
28 | private Surface mSurface;
29 | private boolean mReleaseSurface;
30 |
31 | /**
32 | * Associates an EGL surface with the native window surface.
33 | *
34 | * Set releaseSurface to true if you want the Surface to be released when release() is
35 | * called. This is convenient, but can interfere with framework classes that expect to
36 | * manage the Surface themselves (e.g. if you release a SurfaceView's Surface, the
37 | * surfaceDestroyed() callback won't fire).
38 | */
39 | public WindowSurface(EglCore eglCore, Surface surface, boolean releaseSurface) {
40 | super(eglCore);
41 | createWindowSurface(surface);
42 | mSurface = surface;
43 | mReleaseSurface = releaseSurface;
44 | }
45 |
46 | /**
47 | * Associates an EGL surface with the SurfaceTexture.
48 | */
49 | public WindowSurface(EglCore eglCore, SurfaceTexture surfaceTexture) {
50 | super(eglCore);
51 | createWindowSurface(surfaceTexture);
52 | }
53 |
54 | /**
55 | * Releases any resources associated with the EGL surface (and, if configured to do so,
56 | * with the Surface as well).
57 | *
58 | * Does not require that the surface's EGL context be current.
59 | */
60 | public void release() {
61 | releaseEglSurface();
62 | if (mSurface != null) {
63 | if (mReleaseSurface) {
64 | mSurface.release();
65 | }
66 | mSurface = null;
67 | }
68 | }
69 |
70 | /**
71 | * Recreate the EGLSurface, using the new EglBase. The caller should have already
72 | * freed the old EGLSurface with releaseEglSurface().
73 | *
74 | * This is useful when we want to update the EGLSurface associated with a Surface.
75 | * For example, if we want to share with a different EGLContext, which can only
76 | * be done by tearing down and recreating the context. (That's handled by the caller;
77 | * this just creates a new EGLSurface for the Surface we were handed earlier.)
78 | *
79 | * If the previous EGLSurface isn't fully destroyed, e.g. it's still current on a
80 | * context somewhere, the create call will fail with complaints from the Surface
81 | * about already being connected.
82 | */
83 | public void recreate(EglCore newEglCore) {
84 | if (mSurface == null) {
85 | throw new RuntimeException("not yet implemented for SurfaceTexture");
86 | }
87 | mEglCore = newEglCore; // switch to new context
88 | createWindowSurface(mSurface); // create new surface
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/app/src/main/java/com/felix/glcamera/util/CameraUtils.java:
--------------------------------------------------------------------------------
1 | package com.felix.glcamera.util;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.pm.FeatureInfo;
5 | import android.content.pm.PackageManager;
6 | import android.hardware.Camera;
7 | import android.os.Build;
8 |
9 | import java.util.ArrayList;
10 | import java.util.Collections;
11 | import java.util.Comparator;
12 | import java.util.List;
13 |
14 | import static android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT;
15 |
16 |
17 | public class CameraUtils {
18 | private static final String TAG = "CameraUtils";
19 |
20 | public static Camera.Size chooseOptimalSize(List