OpenGL.ES在Android上的简单实践:19
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
OpenGL.ES在Android上的简单实践:19
OpenGL.ES在Android上的简单实践:
19-水印录制(EGL+摄像头预览
GL_TEXTURE_EXTERNAL_OES)
0、补充EglSurfaceBase
在自己实际运用中,发现EglSurfaceBase还是缺了对原生的surface的管理,对整体的理解好像总缺了点啥。
所以在EglSurfaceBase的基础上,派生出了WindowSurface。
代码超级简单的,但从理解学习上就完全不同一个台阶了。
1.public class WindowSurface extends EglSurfaceBase {
2.
3.private Surface mSurface;
4.private boolean bReleaseSurface;
5.
6.//将native的surface 与 EGL关联起来
7.public WindowSurface(EglCore eglCore, Surface surface, boolean isReleaseSurface) {
8.super(eglCore);
9.createWindowSurface(surface);
10.mSurface = surface;
11.bReleaseSurface = isReleaseSurface;
12.}
13.//将SurfaceTexture 与 EGL关联起来
14.protected WindowSurface(EglCore eglCore, SurfaceTexture surfaceTexture) {
15.super(eglCore);
16.createWindowSurface(surfaceTexture);
17.}
18.//释放当前EGL上下文关联的 surface
19.public void release() {
20.releaseEglSurface();
21.if (mSurface != null
22.&& bReleaseSurface) {
23.mSurface.release();
24.mSurface = null;
25.}
26.}
27.// That's All.
28.}
那么接下来,我们就要快速开始预览摄像头。
从前篇的ContinuousRecordActivity开始代码:
1.public class ContinuousRecordActivity extends Activity implements SurfaceHolder.Callback {
2.public static final String TAG = "ContinuousRecord";
3.// 因为Androi的摄像头默认是横着方向,所以width>height
4.private static final int VIDEO_WIDTH = 1280;
5.private static final int VIDEO_HEIGHT = 720;
6.private static final int DESIRED_PREVIEW_FPS = 15;
7.private Camera mCamera;
8.SurfaceView sv;
9.
10.@Override
11.protected void onCreate(Bundle savedInstanceState) {
12.super.onCreate(savedInstanceState);
13.setContentView(yout.continuous_record);
14.
15.sv = (SurfaceView) findViewById(R.id.continuousRecord_surfaceView);
16.SurfaceHolder sh = sv.getHolder();
17.sh.addCallback(this);
18.}
19.
20.@Override
21.protected void onResume() {
22.super.onResume();
23.openCamera(VIDEO_WIDTH, VIDEO_HEIGHT, DESIRED_PREVIEW_FPS);
24.}
25.@Override
26.protected void onPause() {
27.super.onPause();
28.releaseCamera();
29.}
30.
31.private EglCore mEglCore;
32.private WindowSurface mDisplaySurface;
33.
34.@Override
35.public void surfaceCreated(SurfaceHolder surfaceHolder) {
36.Log.d(TAG, "surfaceCreated holder=" + surfaceHolder);
37.// 准备好EGL环境,创建渲染介质mDisplaySurface
38.mEglCore = new EglCore(null, EglCore.FLAG_RECORDABLE);
39.mDisplaySurface = new WindowSurface(mEglCore,
surfaceHolder.getSurface(), false);
40.mDisplaySurface.makeCurrent();
41.
42.//mTextureId = createTextureObject();
43.//mCameraTexture = new SurfaceTexture(mT extureId);
44.//mCameraTexture.setOnFrameAvailableListener(n ew SurfaceTexture.OnFrameAvailableListener() {
45.// @Override
46.// public void onFrameAvailable(SurfaceTexture surfaceTexture) {
47.//
Handler.sendEmptyMessage(MSG_FRAME_AVAILABLE);
48.// }
49.//});
50.
51.try {
52.Log.d(TAG, "starting camera preview");
53.//mCamera.setPreviewTexture(mCameraTexture);
54.mCamera.startPreview();
55.} catch (IOException ioe) {
56.throw new RuntimeException(ioe);
57.}
58.}
59.
60.... ... ...
61.(省略 openCamera releaseCamera surfaceChanged 和 surfaceDestroy等不是重点的代码,节省篇幅。
)
62.(如有需要请follow github)
63.}
我们先分析以上代码,首先我们运用前篇文章的EGL,在合适时间(surfaceCreated)创建了能自己管控的EGL,并把surface和EGL 绑定成我们这个项目的OpenGL渲染界面EGLSurface。
接下来我们看看注释部分的代码,要先理解好这部分,我们才能继续下去。
在我们创建好EGLSurface后,我们要创建一个纹理对象并保存其纹理ID->mTextureId;这个纹理有啥子用?它是用来创建SurfaceTexture对象的,而这个SurfaceTexture又是啥子用啊?这是用来承接摄像头mCamera的预览帧。
啥,风太大听不懂?额,逻辑道理确实就是这样,详情请参考这里。
重点查阅以下这段内容:
首先,SurfaceTexture从图像流(来自Camera预览,视频解码,GL绘制场景等)中获得帧数据,当调用updateTexImage()时,根据内容流中最近的图像更新SurfaceT exture对应的GL纹理对象,接下来,就可以像操作普通GL纹理一样操作它了。
SurfaceTexture.OnFrameAvailableListener用于让SurfaceTexture 的使用者知道有新数据到来。
所以我们可以这样理解,就是我们通过这个SurfaceTexture从Camera接取预览帧的图像流,然后我们就可以通过其绑定的GL纹理对象,直接按照纹理使用绘制了。
但这个纹理比较特殊,我们来show下代码:
1.public class GlUtil {
2.public static final String TAG = "ZZR-GL";
3.
4.public static void checkGlError(String op) {
5.int error = GLES20.glGetError();
6.if (error != GLES20.GL_NO_ERROR) {
7.String msg = op + ": glError 0x" +
Integer.toHexString(error);
8.Log.e(TAG, msg);
9.throw new RuntimeException(msg);
10.}
11.}
12.
13.public static int createExternalTextureObject() {
14.int[] textures = new int[1];
15.GLES20.glGenTextures(1, textures, 0);
16.GlUtil.checkGlError("glGenTextures");
17.
18.int texId = textures[0];
19.GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EX TERNAL_OES, texId);
20.GlUtil.checkGlError("glBindTexture " + texId);
21.
22.GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_ EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
23.GLES20.GL_NEAREST);
24.GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_ EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
25.GLES20.GL_LINEAR);
26.GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_E XTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S,
27.GLES20.GL_CLAMP_TO_EDGE);
28.GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_E XTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T,
29.GLES20.GL_CLAMP_TO_EDGE);
30.GlUtil.checkGlError("glTexParameter");
31.
32.return texId;
33.}
34.}
我们之前创建的纹理类型都是GLES20.GL_TEXTURE_2D; 这里我们要使用GLES11Ext.GL_TEXTURE_EXTERNAL_OES; 这也是Android 平台下特有的类型,意思是这纹理数据是额外存储到其他地方(内存or流),并不是在显存的环境上。
这个区别很重要,因为这个类型的纹理是不能和存储在显存的纹理在同一管线(shader)上渲染的,会出现问题。
什么问题?最直接就是黑屏什么的,这个往后在讨论。
通过createExternalTextureObject我们已经得到了创建SurfaceTexture的纹理对象,现在我们放开代码注释,并通过Handler通信机制看看OnFrameAvailable的回调是否能正常触发?
1.private static class MainHandler extends Handler {
2.private WeakReference<ContinuousRecordActivity> mWeakActivity;
3.public static final int MSG_FRAME_AVAILABLE = 1;
4.MainHandler(ContinuousRecordActivity activity) {
5.mWeakActivity = new WeakReference<ContinuousRecordActivity>(activity);
6.}
7.@Override
8.public void handleMessage(Message msg) {
9.ContinuousRecordActivity activity = mWeakActivity.get();
10.if (activity == null) {
11.Log.d(TAG, "Got message for dead activity");
12.return;
13.}
14.switch (msg.what) {
15.case MSG_FRAME_AVAILABLE:
16.activity.drawFrame();
17.break;
18.default:
19.super.handleMessage(msg);
20.break;
21.}
22.}
23.}
24.
25.@Override
26.public void surfaceCreated(SurfaceHolder surfaceHolder) {
27.Log.d(TAG, "surfaceCreated holder=" + surfaceHolder);
28.// 准备好EGL环境,创建渲染介质mDisplaySurface
29.mEglCore = new EglCore(null, EglCore.FLAG_RECORDABLE);
30.mDisplaySurface = new WindowSurface(mEglCore, surfaceHolder.getSurface(), false);
31.mDisplaySurface.makeCurrent();
32.
33.mTextureId = GlUtil.createExternalTextureObject();
34.mCameraTexture = new SurfaceTexture(mT extureId);
35.mCameraTexture.setOnFrameAvailableListener(ne w SurfaceTexture.OnFrameAvailableListener() {
36.@Override
37.public void onFrameAvailable(SurfaceTexture surfaceTexture) {
38.mHandler.sendEmptyMessage(MainHandler.MSG_ FRAME_AVAILABLE);
39.}
40.});
41.
42.try {
43.Log.d(TAG, "starting camera preview");
44.mCamera.setPreviewTexture(mCameraTexture);
45.mCamera.startPreview();
46.} catch (IOException ioe) {
47.throw new RuntimeException(ioe);
48.}
49.}
50.
51.private void drawFrame() {
52.if (mEglCore == null) {
53.Log.d(TAG, "Skipping drawFrame after shutdown");
54.return;
55.}
56.Log.d(TAG, " MSG_FRAME_AVAILABLE");
57.mDisplaySurface.makeCurrent();
58.GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
59.GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
60.mDisplaySurface.swapBuffers();
61.}
我们分析一下drawFrame方法,首先我们用EglSurface.makeCurrent锁定了渲染的介质,然后我们就可以做自己的GL.draw的操作了,这里我们就是最简单的draw指令,清空颜色缓冲区和深度缓冲区,并把界面画成0xFF00FF的颜色值。
然后我们调用swapBuffers交换读写的渲染介质,让Android系统把画面渲染出来。
然后,,,你们看到0xFF00FF的屏幕了吗?奸笑.jpg
1.05-21 18:29:47.215 28583-28583/org.zzrblog.blogapp D/ZZR-GL: Trying GLES 2
2.05-21 18:29:47.217 28583-28583/org.zzrblog.blogapp D/ZZR-GL: Got GLES 2 config
3.05-21 18:29:47.219 28583-28583/org.zzrblog.blogapp D/ContinuousRecord: starting camera preview
4.05-21 18:29:47.299 28583-28616/org.zzrblog.blogapp D/OpenGLRenderer: endAllActiveAnimators on 0x72bd2d9000 (RippleDrawable) with handle 0x72bcd54c80
5.05-21 18:29:47.599 28583-28583/org.zzrblog.blogapp D/ContinuousRecord: MSG_FRAME_AVAILABLE
此时我们看看日志输出,我们创建了GLES2的EGL环境,打开摄像头预览,就只有一次MSG_FRAME_AVAILABLE预览的信息到达?这个是Android的Camera.SurfaceTexture的机制了,系统既然通知(OnFrameAvailableListener)告诉你,预览帧图像已经给你了,你是不是已经也有个责任告诉系统你已经使用这帧图像呢?是的,我们需要加上一句updateTexImage我们已经使用你的帧图像了。
修改drawFrame代码
1.private void drawFrame() {
2.... ...
3.mDisplaySurface.makeCurrent();
4.GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
5.GLES20.glClearColor(1.0f, 0.0f, 1.0f, 1.0f);
6.mCameraTexture.updateTexImage(); //告诉Android.Camera已经使用帧图像了。
7.// 怀疑还是不动的同学,不妨通过记录来帧的次数分别glClearColor不同的颜色 ?
8.mDisplaySurface.swapBuffers();
9.}
1、画出预览帧
好了,基本框架已经出来了。
我们现在就想想怎么画出一张矩形帧图?有了以前的教学,我想这个任务不难吧。
两个步骤,ShaderProgram 和对应的顶点模型。
我们先来看看这次的渲染管线程序FrameRectSProgram
1.public class FrameRectSProgram extends ShaderProgram {
2.
3.private static final String VERTEX_SHADER =
4."uniform mat4 uMVPMatrix;\n" +
5."attribute vec4 aPosition;\n" +
6."uniform mat4 uTexMatrix;\n" +
7."attribute vec4 aTextureCoord;\n" +
8."varying vec2 vTextureCoord;\n" +
9."void main() {\n" +
10." gl_Position = uMVPMatrix * aPosition;\n" +
11." vTextureCoord = (uTexMatrix * aTextureCoord).xy;\n" +
12."}\n";
13.
14.private static final String FRAGMENT_SHADER_EXT =
15."#extension GL_OES_EGL_image_external : require\n" +
16."precision mediump float;\n" +
17."varying vec2 vTextureCoord;\n" +
18."uniform samplerExternalOES sTexture;\n" +
19."void main() {\n" +
20." gl_FragColor = texture2D(sTexture,
vTextureCoord);\n" +
21."}\n";
22.
23.public FrameRectSProgram() {
24.super(VERTEX_SHADER,
FRAGMENT_SHADER_EXT);
25.
26.uMVPMatrixLoc = GLES20.glGetUniformLocation(programId, "uMVPMatrix");
27.GlUtil.checkLocation(uMVPMatrixLoc, "uMVPMatrix");
28.aPositionLoc = GLES20.glGetAttribLocation(programId, "aPosition");
29.GlUtil.checkLocation(aPositionLoc, "aPosition");
30.uTexMatrixLoc = GLES20.glGetUniformLocation(programId, "uTexMatrix");
31.GlUtil.checkLocation(uTexMatrixLoc, "uTexMatrix");
32.aTextureCoordLoc = GLES20.glGetAttribLocation(programId, "aTextureCoord");
33.GlUtil.checkLocation(aTextureCoordLoc, "aTextureCoord");
34.}
35.
36.int uMVPMatrixLoc;
37.int aPositionLoc;
38.int uTexMatrixLoc;
39.int aTextureCoordLoc;
40.}
我们还是沿用以前的基类模板代码ShaderProgram,派生出这次渲染预览帧的FrameRectSProgram;
先看顶点着色器,除了以前标准的顶点坐标aPosition,纹理坐标aTextureCoord,MVP三大矩阵的核矩阵uMVPMatrix之外,这下多了一个纹理矩阵uTexMatrix ?这个矩阵可以理解为是操作纹理变换的矩阵,之后我们就知道为啥要有这么一个矩阵。
再到片段着色器,多了一句#extension~ 还多了一个纹理类型samplerExternalOES ,这些都是与上面介绍的Android特有类型GLES11Ext.GL_TEXTURE_EXTERNAL_OES相关,要想使用这个类似的纹理,我们要先申明扩展“#extension~~”然后才能使用新的纹理类型samplerExternalOES。
然后我们再看对应的顶点模型
1.public class FrameRect {
2.public static final int SIZE_OF_FLOAT = 4;
3./**
4.* 一个“完整”的正方形,从两维延伸到-1到1。
5.* 当模型/视图/投影矩阵是都为单位矩阵的时候,这将完全覆盖视口。
6.* 纹理坐标相对于矩形是y反的。
7.* (This seems to work out right with external textures from SurfaceTexture.)
8.*/
9.private static final float FULL_RECTANGLE_COORDS[] = {
10.-1.0f, -1.0f, // 0 bottom left
11. 1.0f, -1.0f, // 1 bottom right
12.-1.0f, 1.0f, // 2 top left
13. 1.0f, 1.0f, // 3 top right
14.};
15.private static final float FULL_RECTANGLE_TEX_COORDS[] = {
16.0.0f, 0.0f, // 0 bottom left
17. 1.0f, 0.0f, // 1 bottom right
18.0.0f, 1.0f, // 2 top left
19. 1.0f, 1.0f // 3 top right
20.};
21.// 定义mVertexArray mT exCoordArray mCoordsPerVertex mVertexCount mVertexStride mTexCoordStride
22.... ... ...
23.public FrameRect() {
24.mVertexArray = createFloatBuffer(FULL_RECTANGLE_COORDS);
25.mTexCoordArray = createFloatBuffer(FULL_RECTANGLE_TEX_COORDS);
26.mCoordsPerVertex = 2;
27.mVertexCount = FULL_RECTANGLE_COORDS.length / mCoordsPerVertex; // 4
28.mTexCoordStride = 2 * SIZE_OF_FLOAT;
29.mVertexStride = 2 * SIZE_OF_FLOAT;
30.}
31.//... ... ...以上变量的get方法
32.private FrameRectSProgram mProgram;
33.public void setShaderProgram(FrameRectSProgram mProgram) {
34.this.mProgram = mProgram;
35.}
36.}
没啥疑问的吧,但请注意顶点坐标那句英文注释,那是我从Camera.SurfaceTexture源码中找出来的,是Android系统知道自己的特性所以就没有反转y了吧。
然后有同学疑问,为啥只有矩阵的4个点,不应该是两个三角形吗 or 用索引吗?额,并不是,接着下去吧。
FrameRect暂且是这样,我们回到测试页面
ContinuousRecordActivity,我们在onCreate创建FrameRect,在EGL环境下创建渲染管线程序FrameRectSProgram 并设置到FrameRect当中
1.@Override
2.protected void onCreate(Bundle savedInstanceState) {
3.super.onCreate(savedInstanceState);
4.... ...
5.frameRect = new FrameRect();
6.}
7.@Override
8.public void surfaceCreated(SurfaceHolder surfaceHolder) {
9.... ...
10.mDisplaySurface.makeCurrent();
11.frameRect.setShaderProgram(new FrameRectSProgram());
12.... ...
13.}
14.//为啥要这样分开,还记得OpenGL的指令需要在EGL 环境下执行的铁律了吗?
我们来个小总结,这次我们学会了:
1、自定义的EGL环境的使用。
2、Camera.SurfaceTexture对应的使用注意事项。
3、Android平台下特有的纹理类型GL_OES_EGL_image_external 和使用方法。
由于篇幅关系,我们先到这。
下一篇我们把预览图像画出来,额外加上个性签名的水印效果。