笔者之前写了三篇Android中使用OpenGL ES入门级的文章,从OpenGL ES的相关概念出发,分析了利用OpenGL ES实现3D绘图的重要的两个步骤:定义形状和绘制形状,简单的绘制了一个三角形。
这里再简单回顾下:Android中使用OpenGL一共会涉及到四个类:

1)Activity——自不用说,Android界面展示的类;
2)GLSurfaceView——SurfaceView(API Level 1)的子类,View的孪生兄弟-SurfaceView专用于要求频繁刷新的界面中,比如相机预览界面;而GLSurfaceVIew(API Level 3)是继承于SurfaceView,且实现了SurfaceHolder.Callback2接口,专门用于OpenGL 中界面的展示;
3)GLSurfaceView.Renderer——这是一个接口,我们会自定义一个渲染器类来说实现这个接口。GLSurfaceView本身并不处理很多图像展示的相关任务,更多的实现是放在了该渲染器类中进行实现;该接口的实现会要求实现三个方法,每个方法会最终对应Camera的一种状态;
4)图像类——具体的类,具体实现看需求,比如笔者前面的文章中,该图像类就是Triangle,三角形类,关键的类,最终图像预览时呈现何种状态是在这个类中被定义的。

前期基础知识储备
SurfaceTexture了解
SurfaceTexture(API Level 11),掌握这个类是利用OpenGL实现相机预览的关键所在。等会看完代码,你就会发现,利用OpenGL画一个简单的形状和利用OpenGL实现相机预览两个看起来难度相差很大的任务实际上用到的OpenGL的代码却是十分相似的,唯一的区别就是在于相机预览时会用到SurfaceTexture进行纹理数据的处理。首先看下开发者文档中的描述:

Captures frames from an image stream as an OpenGL ES texture.
The image stream may come from either camera preview or video decode. A Surface created from a SurfaceTexture can be used as an output destination for the android.hardware.camera2, MediaCodec, MediaPlayer, and Allocation APIs. When updateTexImage() is called, the contents of the texture object specified when the SurfaceTexture was created are updated to contain the most recent image from the image stream. This may cause some frames of the stream to be skipped.

从文档中的描述,我们可以知道:SurfaceTexture的作用就是从Image Stream中捕获帧数据,用作OpenGL的纹理(纹理用于填充片元着色器,即“上色”),其中Image Stream来自相机预览或视频解码。 所以我们可以使用SurfaceTexture来和Camera进行连接(调用camera.setPreviewTexture(mSurfaceTexture)方法)从而获取相机传过来的预览图像流数据,而SurfaceTexture类获取到图像流数据之后并不会进行显示,而是对其处理后传给GLSurfaceView或者TextureView进行显示,因此SurfaceTexture和GLSurfaceView的搭配使用十分合适。

上代码,具体实现
1)自定义相机预览类,实现渲染器接口和SurfaceTexture的接口;

public class CameraGLSurfaceView extends GLSurfaceView implements Renderer, SurfaceTexture.OnFrameAvailableListener
1
2)写入详解预览类CameraGLSurfaceView的构造方法;

public CameraGLSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
mContext = context;
setEGLContextClientVersion(2);
setRenderer(this);
setRenderMode(RENDERMODE_WHEN_DIRTY);
}
看过笔者之前OpenGL文章的朋友理解这段代码是没有困难的,这里首先指定使用的OpenGL ES的版本为2.0,然后调用setRenderer()方法绑定适渲染器接口,最后调用setRendererMode()的方法指定模式为RENDERMODE_WHEN_DIRTY-即有新数据时进行刷新,否则等待。

3)关键-实现渲染器接口的三个方法

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// TODO Auto-generated method stub
Log.i(TAG, "onSurfaceCreated...");
mTextureID = createTextureID();
mSurface = new SurfaceTexture(mTextureID);
mSurface.setOnFrameAvailableListener(this);
mDirectDrawer = new DirectDrawer(mTextureID);
CameraInterface.getInstance().doOpenCamera(null); //①在创建surface时打开相机
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
// TODO Auto-generated method stub
Log.i(TAG, "onSurfaceChanged...");
GLES20.glViewport(0, 0, width, height);
if(!CameraInterface.getInstance().isPreviewing()){
CameraInterface.getInstance().doStartPreview(mSurface, 1.33f); //②预览
}
}
@Override
public void onDrawFrame(GL10 gl) {
// TODO Auto-generated method stub
Log.i(TAG, "onDrawFrame...");
GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
mSurface.updateTexImage(); //SurfaceTexture的关键方法
float[] mtx = new float[16];
mSurface.getTransformMatrix(mtx); //SurfaceTexture的关键方法
mDirectDrawer.draw(mtx); //③调用图形类的draw()方法
}
分析:这三个方法里面的代码非常关键,(1)onSurfaceCreated()方法中实例化SurfaceTexture对象mSurface和图形类对象mDirectDrawer,并且在这里调用了相机的开启方法doOpenCamera(),这是使用OpenGL实现相机预览时OpenGL代码对相机的第一次控制;
(2)onSurfaceChanged()方法中调用相机的doStartPreview()方法,这个方法调用在相机界面发生变化时,会关闭相机预览然后重新打开相机预览,即doStartPreview()方法内部实现实际是连续调用了mCamera.stopPreview(); mCamera.setPreviewDisplay(mHolder); mCamera.startPreview();三个方法;
(3)onDrawFrame()方法最为关键,首先是两行OpenGL的常规设置代码(glClearColor()、glClear()),用于设置背景;接着是连续调用了SurfaceTexture的两个关键的方法:①updateTexImage(), 当updateTexImage()方法被调用时,SurfaceTexture对象所关联的OpenGLES中纹理对象的内容将被更新为相机预览传出的图像流中最新的图片;②getTransformMatrix(),该方法也是必须调用的,用以转换已经发生变换的纹理矩阵的坐标;最后是调用了图像类的draw()方法用以绘制预览的界面。

4)剩余的代码段,辅助作用

@Override
public void onPause() {
// TODO Auto-generated method stub
super.onPause();
CameraInterface.getInstance().doStopCamera(); //④暂停预览时调用的相机方法
}
private int createTextureID()
{
int[] texture = new int[1];

GLES20.glGenTextures(1, texture, 0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture[0]);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GL10.GL_TEXTURE_MIN_FILTER,GL10.GL_LINEAR);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);

return texture[0];
}

public SurfaceTexture _getSurfaceTexture(){
return mSurface;
}

//该方法是实现SurfaceTexture.OnFrameAvailableListener接口时实现的方法
//用于提示新的数据流的到来
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
// TODO Auto-generated method stub
this.requestRender();
}

以上的代码就是自定义的相机预览类的中的关键代码,接下来,我们再看看另外的一个关键类——图像类
1)自定义一个图像类 DirectDrawer

public class DirectDrawer
1
2)相关变量的声明:顶点着色器的声明,片元着色器的声明,缓冲区变量的声明,程式的声明,顶点坐标的声明,纹理坐标的声明

private final String vertexShaderCode =
"attribute vec4 vPosition;" +
"attribute vec2 inputTextureCoordinate;" +
"varying vec2 textureCoordinate;" + //传给片元着色器的变量
"void main()" +
"{"+
"gl_Position = vPosition;"+
"textureCoordinate = inputTextureCoordinate;" +
"}";

private final String fragmentShaderCode =
"#extension GL_OES_EGL_image_external : require\n"+ //固定指令
"precision mediump float;" + //固定指令
"varying vec2 textureCoordinate;\n" + //顶点着色器中传递过来的变量
"uniform samplerExternalOES s_texture;\n" + //固定指定
"void main() {" +
" gl_FragColor = texture2D( s_texture, textureCoordinate );\n" +
"}";

private FloatBuffer vertexBuffer, textureVerticesBuffer;
private ShortBuffer drawListBuffer;
private final int mProgram;
private int mPositionHandle;
private int mTextureCoordHandle;

private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; //绘制顶点的顺序

// number of coordinates per vertex in this array
private static final int COORDS_PER_VERTEX = 2;

private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

//顶点坐标-坐标值是固定的搭配,不同的顺序会出现不同的预览效果
static float squareCoords[] = {
-1.0f, 1.0f,
-1.0f, -1.0f,
1.0f, -1.0f,
1.0f, 1.0f,
};
//纹理坐标-相机预览时对片元着色器使用的是纹理texture而不是颜色color
static float textureVertices[] = {
0.0f, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f,
0.0f, 0.0f,
};

2)图像类的构造方法-相机预览的图像类和画图时的图像类的构造方法极为相似

private int texture;

public DirectDrawer(int texture)
{
this.texture = texture;
// initialize vertex byte buffer for shape coordinates
ByteBuffer bb = ByteBuffer.allocateDirect(squareCoords.length * 4);
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
vertexBuffer.put(squareCoords); //第一个装载的数据是顶点坐标
vertexBuffer.position(0);

// initialize byte buffer for the draw list
ByteBuffer dlb = ByteBuffer.allocateDirect(drawOrder.length * 2);
dlb.order(ByteOrder.nativeOrder());
drawListBuffer = dlb.asShortBuffer();
drawListBuffer.put(drawOrder); //第二个装载的数据是绘制顶点的顺序
drawListBuffer.position(0);

ByteBuffer bb2 = ByteBuffer.allocateDirect(textureVertices.length * 4);
bb2.order(ByteOrder.nativeOrder());
textureVerticesBuffer = bb2.asFloatBuffer();
textureVerticesBuffer.put(textureVertices); //第三个装载的数据是纹理坐标
textureVerticesBuffer.position(0);

//解析之前变量中声明的两个着色器
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);

mProgram = GLES20.glCreateProgram(); // 创建空程式
GLES20.glAttachShader(mProgram, vertexShader); // 添加顶点着色器到程式中
GLES20.glAttachShader(mProgram, fragmentShader); // 添加片元着色器到程式中
GLES20.glLinkProgram(mProgram); // 链接程式
}
}

3)千呼万唤始出来-绘制方法draw()

public void draw(float[] mtx)
{
GLES20.glUseProgram(mProgram);

GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture);

// get handle to vertex shader's vPosition member
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

// Enable a handle to the triangle vertices
GLES20.glEnableVertexAttribArray(mPositionHandle);

// Prepare the <insert shape here> coordinate data
GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, vertexBuffer);

//使用一次glEnableVertexAttribArray方法就要使用一次glVertexAttribPointer方法
mTextureCoordHandle = GLES20.glGetAttribLocation(mProgram, "inputTextureCoordinate");
GLES20.glEnableVertexAttribArray(mTextureCoordHandle);

GLES20.glVertexAttribPointer(mTextureCoordHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, textureVerticesBuffer);

GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length, GLES20.GL_UNSIGNED_SHORT, drawListBuffer);

// 使用了glEnableVertexAttribArray方法就必须使用glDisableVertexAttribArray方法
GLES20.glDisableVertexAttribArray(mPositionHandle);
GLES20.glDisableVertexAttribArray(mTextureCoordHandle);
}
 
代码看上去有点复杂,但是结合笔者之前利用OpenGL实现画图的draw()方法的代码和整理使用OpenGL实现相机预览的draw()方法来看,就会发现这段代码看上去复杂,实际是几乎固定的代码,每个方法调用都是有规律可循。

程式相关方法
glUseProgram()
顶点着色器相关方法
glGetAttribLocation()
glEnableVertexAttribArray()
glVertexAttribPointer()
片元着色器相关方法
glGetUniformLocation()
glUniform4fv()
绘制相关方法
glDrawArrays-绘制方法
收尾相关方法
glDisableVertexAttribArray()

前面OpenGL相关文章的读者朋友会发现使用OpenGL画图和使用OpenGL实现相机预览时,图像类的变量声明、构造方法和绘制方法draw()都是十分的相似的,换言之,这里的使用OpenGL的代码几乎是可以看成是模板代码,而实现两种功能的不同就在于相机预览类中使用了SurfaceTexture进行相关数据传输和控制。

在Android中使用OpenGL ES开发第(四)节:相机预览的更多相关文章

  1. 在Android中使用OpenGL ES开发第(五)节:GLSL基础语法

    一.前期基础储备笔者之前的四篇文综述了Android中使用OpenGL ES绘制基本图形和实现了简单的相机预览,初次接触OpenGL ES开发的读者可能对其中新的概念比较迷惑,尤其是其中的顶点着色器( ...

  2. 在Android中使用OpenGL ES进行开发第(三)节:绘制图形

    一.前期基础知识储备笔者计划写三篇文章来详细分析OpenGL ES基础的同时也是入门关键的三个点: ①OpenGL ES是什么?与OpenGL的关系是什么?——概念部分 ②使用OpenGLES绘制2D ...

  3. 在Android中使用OpenGL ES进行开发第(二)节:定义图形

    一.前期基础知识储备笔者计划写三篇文章来详细分析OpenGL ES基础的同时也是入门关键的三个点: ①OpenGL ES是什么?与OpenGL的关系是什么?——概念部分 ②使用OpenGLES绘制2D ...

  4. 在Android中使用OpenGL ES进行开发第(一)节:概念先行

    一.前期基础是知识储备笔者计划写三篇文章来详细分析OpenGL ES基础的同时也是入门关键的三个点: ①OpenGL ES是什么?与OpenGL的关系是什么?——概念部分 ②使用OpenGL ES绘制 ...

  5. 如何使用Android中的OpenGL ES媒体效果

    引自:http://www.2cto.com/kf/201506/404366.html Android的媒体效果框架允许开发者可以很容易的应用多种令人印象深刻的视觉效果到照片或视频之上.作为这个媒体 ...

  6. Android OpenGL ES 开发(四): OpenGL ES 绘制形状

    在上文中,我们使用OpenGL定义了能够被绘制出来的形状了,现在我们想绘制出来它们.使用OpenGLES 2.0来绘制形状会比你想象的需要更多的代码.因为OpenGL的API提供了大量的对渲染管线的控 ...

  7. Android OpenGL ES 开发(三): OpenGL ES 定义形状

    在上篇文章,我们能够配置好基本的Android OpenGL 使用的环境.但是如果我们不了解OpenGL ES如何定义图像的一些基本知识就使用OpenGL ES进行绘图还是有点棘手的.所以能够在Ope ...

  8. Android OpenGL ES 开发

    OpenGL(Open Graphics Library) 是开放图形库,是一个跨平台的图形 API.OpenGL ES(OpenGL for Embedded System)是专为移动端提供的一个子 ...

  9. Android OpenGL ES 开发教程 从入门到精通

    感谢,摘自:http://blog.csdn.net/mapdigit/article/details/7526556 Android OpenGL ES 简明开发教程 Android OpenGL ...

随机推荐

  1. 阿里云Centos7 搭建laravel

    最近在考虑学习laravel框架,唔 现在服务器搭建一下. laravel是依赖composer的,首先在linux服务器下先安装composer.运行composer需要 php 5.3以上版本, ...

  2. C# DataTable和List转换操作类

    using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.R ...

  3. hdu 2647 还是逆向拓扑

    Problem Description Dandelion's uncle is a boss of a factory. As the spring festival is coming , he ...

  4. bin文件夹下的某个dll总是自动刷新为不同版本的dll的解决方法

    如上图所示,一般这种问题都是dll版本和配置文件中的dll版本对应不上才引起的,可以通过替换对应版本的dll或者修改配置文件中的版本号即可. 然而我的情况是:修复后,还是不定时出现这样的问题,我以为是 ...

  5. C#通过地址获取省市区(基于百度地图API)

    最近公司有个需求,想通过地址获取对应的省市区,本来想直接通过对地址的截取,对于完整的地址还可以,不完整的就没法用了 所以本篇通过百度地图API来获取地址 第一步:申请ak密钥 登录百度地图开放平台,按 ...

  6. h5 移动端开发自适应 meta name="viewport"的使用总结

    本文系个人理解,可能有误差,仅供参考,谨慎采纳! 布局视口: 系统自带 一般大于屏幕宽度 理想宽度:  设置页面的viewport 的一个宽度,使不同的手机的布局视口宽度尽量接近可视窗口的值: 可视视 ...

  7. rabbitMQ安装 [linux]

    转载 https://blog.csdn.net/qq_22075041/article/details/78855708 安装Erlang 由于RabbitMQ依赖Erlang, 所以需要先安装Er ...

  8. 做一个函数 返回当前日期、当前时间 格式为“XXXX年XX月XX日”

    import time import datetime def time_strf(now_date):#传入0,1,2返回 当前日期.当前时间.当前日期与时间 today=datetime.date ...

  9. 从c到c++<二>

    用于对与局部变量同名的全局变量进行访问下面通过程序来进行说明:运行看一下: 用于表示类的成员,这将在关于类的一节中详细说明 对于学过java的人来说,应该对于new运算符很容易理解,它实际上相当于c语 ...

  10. 接口自动化平台搭建(二),搭建django项目与接口自动化平台的由来与功能特征

    1.创建django项目 a.使用命令创建,安装完django之后就有django-admin命令了,执行命令创建即可,命令如下: django-admin startproject my_djang ...