逆水行舟用力撑,一篙松劲退千寻。这篇文章主要讲述OpenGL播放yuv数据流(着色器SHADER)-android相关的知识,希望能为你提供帮助。
OpenGL播放yuv数据流(着色器SHADER)-android(一)
可以参考:
http://blog.csdn.net/ueryueryuery/article/details/17608185这篇文章很有帮助。
这个和windows还有ios略有不同,
下面将步骤整理一下以做记录:
1:
在avtivity_main.xml中添加用于显示的GLsurfaceView
<
android.opengl.GLSurfaceView
android:id=
"
@
+
id/lvsPlaySurfaceView"
android:layout_width=
"
match_parent"
android:layout_height=
"
400dp"
/>
2: 将GLsurfaceView传到里面
//得到opengal渲染用的surfaceView openglsurfaceView = (GLSurfaceView) findViewById(R.id.lvsPlaySurfaceView);
3: 需要添加权限在AndroidMainfest.xml中:
< !--为了能使用OpenGLES 2.0 API, 你必须在你的manifest中添加以下声明: --> < uses-feature android:glEsVersion= " 0x00020000" android:required= " true" /> < !-- 如果你的应用要使用纹理压缩功能, 你必须还要声明设备需要支持什么样的压缩格式--> < supports-gl-texture android:name= " GL_OES_compressed_ETC1_RGB8_texture" /> < supports-gl-texture android:name= " GL_OES_compressed_paletted_texture" />
4: 下面就是具体的实现代码
//.java
package com.example.zhuweigang.lvsandroidplay; import android.content.Context; import android.opengl.GLES20; import android.opengl.GLSurfaceView; import android.support.v4.app.NavUtils; import android.util.AttributeSet; import android.util.Log; import android.view.SurfaceHolder; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; /** * Created by zhuweigang on 2016/12/26. */public class Lvs_OpenGl_Interface_Android implements GLSurfaceView.Renderer { private static final String TAG = " lvs_OpenGL" ; //顶点数组(物体表面坐标取值范围是-1到1,数组坐标: 左下, 右下, 左上, 右上) private static float[] vertexVertices = { -1.0f, -1.0f, 1.0f, -1.0f, -1.0f,1.0f, 1.0f,1.0f, }; //像素, 纹理数组(纹理坐标取值范围是0-1, 坐标原点位于左下角,数组坐标: 左上, 右上, 左下, 右下,如果先左下, 图像会倒过来) private static float[] textureVertices = { 0.0f,1.0f, 1.0f,1.0f, 0.0f,0.0f, 1.0f,0.0f, }; //shader的vsh源码字符串 private static final String vertexShaderString = " attribute vec4 vertexIn; " + " attribute vec2 textureIn; " + " varying vec2 textureOut; " + " void main() {" + " gl_Position = vertexIn; " + " textureOut = textureIn; " + " }" ; //shader的fsh源码字符串 private static final String yuvFragmentShaderString = " precision mediump float; " + " uniform sampler2D tex_y; " + " uniform sampler2D tex_u; " + " uniform sampler2D tex_v; " + " varying vec2 textureOut; " + " void main() {" + " vec4 c = vec4((texture2D(tex_y, textureOut).r - 16./255.) * 1.164); " + " vec4 U = vec4(texture2D(tex_u, textureOut).r - 128./255.); " + " vec4 V = vec4(texture2D(tex_v, textureOut).r - 128./255.); " + " c + = V * vec4(1.596, -0.813, 0, 0); " + " c + = U * vec4(0, -0.392, 2.017, 0); " + " c.a = 1.0; " + " gl_FragColor = c; " + " }" ; //着色器用的顶点属性索引 position是由3个( x,y,z) 组成, public int ATTRIB_VERTEX = 0; //着色器用的像素, 纹理属性索引 而颜色是4个( r,g,b,a) public int ATTRIB_TEXTURE = 0; private GLSurfaceView mTargetSurface; //外部传入的GLSurfaceView public int p = 0; //Program着色器程序的id ByteBuffer vertexVertices_buffer = null; //定义顶点数组 ByteBuffer textureVertices_buffer = null; //定义像素纹理数组 public int m_IsInitShaders = 0; //是否已经InitShaders, onSurfaceCreated public Lvs_Play_Interface_Sdk_Android.OpenGl_DisplayCallBackInterface m_displaydatack = null; //用于显示回调函数, 参数数据及时间戳 public byte m_yuvbuf[] = new byte[640*480*3]; //存放yuv数据的buf指针, 申请buffer在外面 public ByteBuffer yuvplaner_y = null; //分用于渲染的变量 public ByteBuffer yuvplaner_u = null; //分用于渲染的变量 public ByteBuffer yuvplaner_v = null; //分用于渲染的变量; public int[] m_millis_realtime = new int[1]; //实时的时间戳, 每次回调会更新 public int m_yuvdata_width = 0; //数据宽 public int m_yuvdata_height = 0; //数据高 public int m_frameBuffer = 0; //framebuffer public int m_renderBuffer = 0; //renderbuffer public int m_textureid_y, m_textureid_u, m_textureid_v; //纹理的名称, 并且, 该纹理的名称在当前的应用中不能被再次使用。 public int m_textureUniformY, m_textureUniformU,m_textureUniformV; //用于纹理渲染的变量//构造方法 public Lvs_OpenGl_Interface_Android(GLSurfaceView paramGLSurfaceView) { //将surfaceview传进来用于显示数据时候刷新 mTargetSurface = paramGLSurfaceView; //应用GlsurfaceView版本号2.0 mTargetSurface.setEGLContextClientVersion(2); }@ Override public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) { Log.i(TAG, " onSurfaceCreated :" ); //这个必须在onSurfaceCreated中, 否则失败 //初始化着色器, 类似于告GPU当传进去数据的时候采用什么样的规则。 InitShaders(); m_IsInitShaders = 1; }@ Override public void onSurfaceChanged(GL10 gl10, int width, int height) { GLES20.glViewport(0, 0, width, height); }@ Override public void onDrawFrame(GL10 gl10) { //这里做具体的处理 //具体的显示 if (mTargetSurface != null) { DisplayImage(0); } }//接口初始化 int lvs_opengl_interface_init(GLSurfaceView surface,int yuvdata_width,int yuvdata_height, Lvs_Play_Interface_Sdk_Android.OpenGl_DisplayCallBackInterface displaydatack) { int ret= 0; //初始化 ret = initopengl(yuvdata_width,yuvdata_height,displaydatack); if (ret != 1) { return -1; }ret = 1; return ret ; }//接口渲染数据(定时器, 渲染时间,毫秒),数据及渲染定时时间在回调里面做处理 void lvs_opengl_interface_write(int value) { //这里如果有可能则调成类的成员函数, 以后处理, 暂时不知道怎么解决类成员函数递归 TimerFunc1(); }//渲染数据(定时器, 渲染时间,毫秒),数据及渲染定时时间在回调里面做处理 void TimerFunc1() { int ret = 0; //因为glut的定时器是调用一次才产生一次定时, 所以如果要持续产生定时的话, //在定时函数末尾再次调用glutTimerFunc//调用回调函数获取数据 if (m_displaydatack != null & & m_IsInitShaders = = 1) { ret= m_displaydatack.OpenGl_DisplayDataCallback(m_yuvbuf,m_millis_realtime); if (ret > 0) { //刷新让他能显示在onDrawFrame中处理 mTargetSurface.requestRender(); try { Thread.sleep(m_millis_realtime[0]); } catch (InterruptedException e) { e.printStackTrace(); } } else { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } //递归调用自身,java用递归调用本身有问题了, 外面for循环调用处理 //TimerFunc1(opengl_interface); } }//初始化 int initopengl(int yuvdata_width,int yuvdata_height, Lvs_Play_Interface_Sdk_Android.OpenGl_DisplayCallBackInterface displaydatack) { int ret = 0; m_yuvdata_width = yuvdata_width; m_yuvdata_height = yuvdata_height; m_displaydatack = displaydatack; //分配内存 if (yuvplaner_y = = null) { yuvplaner_y = ByteBuffer.allocate(m_yuvdata_width*m_yuvdata_height + 100); } if (yuvplaner_u = = null) { yuvplaner_u = ByteBuffer.allocate(m_yuvdata_width*m_yuvdata_height + 100); } if (yuvplaner_v = = null) { yuvplaner_v = ByteBuffer.allocate(m_yuvdata_width*m_yuvdata_height + 100); }ret = 1; return ret; } //初始化着色器, 类似于告GPU当传进去数据的时候采用什么样的规则。 void InitShaders() { int error = 0; createBuffers(vertexVertices, textureVertices); p = createProgram(vertexShaderString, yuvFragmentShaderString); ATTRIB_VERTEX = GLES20.glGetAttribLocation(p, " vertexIn" ); if (ATTRIB_VERTEX = = -1) { Log.i(TAG, " glGetAttribLocation : " + error); } ATTRIB_TEXTURE = GLES20.glGetAttribLocation(p, " textureIn" ); if (ATTRIB_TEXTURE = = -1) { Log.i(TAG, " glGetAttribLocation : " + error); }//Program:在链接了程序以后, 我们可以使用glUseProgram()函数来加载并使用链接好的程序 GLES20.glUseProgram(p); //获取片源着色器源码中的变量,用于纹理渲染 m_textureUniformY = GLES20.glGetUniformLocation(p, " tex_y" ); m_textureUniformU = GLES20.glGetUniformLocation(p, " tex_u" ); m_textureUniformV = GLES20.glGetUniformLocation(p, " tex_v" ); //初始化纹理 int[] textures_y = new int[1]; GLES20.glGenTextures(1, textures_y,0); m_textureid_y = textures_y[0]; textures_y = null; //绑定纹理 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, m_textureid_y); //设置该纹理的一些属性 GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_LINEAR); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); int[] textures_u = new int[1]; GLES20.glGenTextures(1, textures_u,0); m_textureid_u = textures_u[0]; textures_u = null; GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, m_textureid_u); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_LINEAR); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); int[] textures_v = new int[1]; GLES20.glGenTextures(1, textures_v,0); m_textureid_v = textures_v[0]; textures_v = null; GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, m_textureid_v); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_LINEAR); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); return; } //具体显示图像的函数(参数是指针) int DisplayImage(long parm) { int ret = 0; //关联到yuv数据的分量数组 if (yuvplaner_y != null) { yuvplaner_y.clear(); yuvplaner_y.put(m_yuvbuf,0,m_yuvdata_width*m_yuvdata_height); yuvplaner_y.position(0); } if (yuvplaner_u != null) { yuvplaner_u.clear(); yuvplaner_u.put(m_yuvbuf,m_yuvdata_width*m_yuvdata_height,m_yuvdata_width*m_yuvdata_height/4); yuvplaner_u.position(0); } if (yuvplaner_v != null) { yuvplaner_v.clear(); yuvplaner_v.put(m_yuvbuf,m_yuvdata_width*m_yuvdata_height + m_yuvdata_width*m_yuvdata_height/4,m_yuvdata_width*m_yuvdata_height/4); yuvplaner_v.position(0); }//Clear //清除颜色设为黑色, 把整个窗口清除为当前的清除颜色, glClear( ) 的唯一参数表示需要被清除的缓冲区。 GLES20.glClearColor(0.0f,0.0f,0.0f,1.0f); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); //定义顶点数组,android平台要在这里做其他平台在initshader中, 否则显示不出来图像 GLES20.glVertexAttribPointer(ATTRIB_VERTEX, 2, GLES20.GL_FLOAT, false, 0, vertexVertices_buffer); //启用属性数组, android平台要在这里做其他平台在initshader中, 否则显示不出来图像 GLES20.glEnableVertexAttribArray(ATTRIB_VERTEX); //定义像素纹理数组, android平台要在这里做其他平台在initshader中, 否则显示不出来图像 GLES20. glVertexAttribPointer(ATTRIB_TEXTURE, 2, GLES20.GL_FLOAT, false, 0, textureVertices_buffer); //启用属性数组, android平台要在这里做其他平台在initshader中, 否则显示不出来图像 GLES20.glEnableVertexAttribArray(ATTRIB_TEXTURE); //显卡中有N个纹理单元( 具体数目依赖你的显卡能力) , 每个纹理单元( GL_TEXTURE0、GL_TEXTURE1等) 都有GL_TEXTURE_1D、GL_TEXTURE_2D等 //Y //选择当前活跃的纹理单元 GLES20.glActiveTexture(GLES20.GL_TEXTURE0); //允许建立一个绑定到目标纹理的有名称的纹理 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, m_textureid_y); //根据指定的参数, 生成一个2D纹理( Texture) 。相似的函数还有glTexImage1D、glTexImage3D。 GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, m_yuvdata_width, m_yuvdata_height, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, yuvplaner_y); GLES20.glUniform1i(m_textureUniformY, 0); //设置纹理, 按照前面设置的规则怎样将图像或纹理贴上( 参数和选择的活跃纹理单元对应, GL_TEXTURE0) //U GLES20.glActiveTexture(GLES20.GL_TEXTURE1); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, m_textureid_u); GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, m_yuvdata_width/2, m_yuvdata_height/2, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, yuvplaner_u); GLES20.glUniform1i(m_textureUniformU, 1); //V GLES20.glActiveTexture(GLES20.GL_TEXTURE2); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, m_textureid_v); GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, m_yuvdata_width/2, m_yuvdata_height/2, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, yuvplaner_v); GLES20.glUniform1i(m_textureUniformV, 2); // Draw // 绘制 GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); //单缓冲显示 GLES20.glFlush(); GLES20.glDisableVertexAttribArray(ATTRIB_VERTEX); GLES20.glDisableVertexAttribArray(ATTRIB_TEXTURE); return 1; }/** * create program and load shaders, fragment shader is very important. */ public int createProgram(String vertexSource, String fragmentSource) { // create shaders int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); // just checkint program = GLES20.glCreateProgram(); if (program != 0) { GLES20.glAttachShader(program, vertexShader); GLES20.glAttachShader(program, pixelShader); GLES20.glLinkProgram(program); int[] linkStatus = new int[1]; GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); if (linkStatus[0] != GLES20.GL_TRUE) { GLES20.glDeleteProgram(program); program = 0; } } return program; } /** * create shader with given source. */ private int loadShader(int shaderType, String source) { int shader = GLES20.glCreateShader(shaderType); if (shader != 0) { GLES20.glShaderSource(shader, source); GLES20.glCompileShader(shader); int[] compiled = new int[1]; GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); if (compiled[0] = = 0) { GLES20.glDeleteShader(shader); shader = 0; } } return shader; }/** * these two buffers are used for holding vertices, screen vertices and texture vertices. */ private void createBuffers(float[] vert, float[] coord) { vertexVertices_buffer = ByteBuffer.allocateDirect(vert.length * 4); vertexVertices_buffer.order(ByteOrder.nativeOrder()); vertexVertices_buffer.asFloatBuffer().put(vert); vertexVertices_buffer.position(0); if (textureVertices_buffer = = null) { textureVertices_buffer = ByteBuffer.allocateDirect(coord.length * 4); textureVertices_buffer.order(ByteOrder.nativeOrder()); textureVertices_buffer.asFloatBuffer().put(coord); textureVertices_buffer.position(0); } } }
5: 调用代码
//GLSurfaceView m_glsurfaceview = nsurfaceView; //opengl的view类 pinterfaceOpenGL = new Lvs_OpenGl_Interface_Android(m_glsurfaceview); m_glsurfaceview.setRenderer(pinterfaceOpenGL); // 只有在绘制数据改变时才绘制view m_glsurfaceview.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
//Opengl初始化 ret = pinterfaceOpenGL.lvs_opengl_interface_init(m_glsurfaceview,m_opengl_width,m_opengl_height, opengl_displaycallback); if (ret < 0) { return; }for (; ; ) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }//渲染, 带定时器,数据回调, 及渲染时间回调, 第一帧timer 40以后根据时间戳做调整 pinterfaceOpenGL.lvs_opengl_interface_write(40); }
6: 数据传入在回掉函数ret= m_displaydatack.OpenGl_DisplayDataCallback(m_yuvbuf,m_millis_realtime); 中
7: 实现效果
文章图片
本demo还需完善。
如有错误请指正:
交流请加QQ群: 62054820
QQ: 379969650.
【OpenGL播放yuv数据流(着色器SHADER)-android】
推荐阅读
- android 解决小米手机上选择照片路径为null情况
- Android逆向从未如此简单
- 在ANDROID STUDIO环境下使用JUNIT框架进行单元测试
- 随风而行(Android运动提示)
- Android性能检测工具
- Android热修复学习之旅——HotFix完全解析
- 利用Android Camera2 的照相机api 实现 实时的图像采集与预览
- 密码学的发展介绍
- 使用python的密码学GUI项目示例