音视频之opengl绘制三角形

音视频之opengl渲染图片

音视频之渲染yuv图片

2018年用了一年业余时间学习了音视频,直播,解码,编码,倍速,跳转,滤镜,倒放等。,慢慢把这部分内容写到博客上,一步步来。

Android中使用的是android opengl es 2.0,可以使用它建立三维或者二维的图形。
在音视频的方向目前好像还没看到使用三维来干嘛,主要还是为了分担cpu的压力,因为编解码对cpu的压力已经很大了,再把渲染给cpu就有点忙不过来了。
Opengles在java层和c/c++层都可以调用。原理是相通的所以就先在java层学习就行了。

先opengl渲染一个三角形
音视频之opengl绘制三角形-LMLPHP

新建一个GLSurfaceView
我们就是在他基础上绘画

然后验证是否支持opengles2.0

ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
ConfigurationInfo cgi = am.getDeviceConfigurationInfo();
//验证是否支持opengles 2.0
if(cgi.reqGlEsVersion > 0x20000){
    glSurfaceView.setEGLContextClientVersion(2);
    glSurfaceView.setRenderer(new TrangleRender(this));
    /*
    GLSurfaceView.RENDERMODE_WHEN_DIRTY 自己请求刷新
     RENDERMODE_CONTINUOUSLY 一直不断刷新
    */
    glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY );

}

然后在对应的生命周期上调用对应函数:

protected void onPause() {
    super.onPause();
    glSurfaceView.onPause();
}

@Override
protected void onResume() {
    super.onResume();
    glSurfaceView.onResume();
}

注意还需要一个渲染的TrangleRender,渲染什么由他决定。

先讲讲这个的坐标,因为是三维的空间是需要x,y,z轴的,但是我们只是播放视频,所以只需要了解x,y轴就可以了。
Opengl的原点是在正中心,x轴正方向的屏幕边界是1,负方向的边界是-1,y轴同理。

如下图:
音视频之opengl绘制三角形-LMLPHP
所以我们要为三角形的三个顶点找好位置:

float[] trangle = {
        //x, y
        -1f, -1f,
        1f, 1f,
        -1f, 1f,
};

然后需要将这块三角形的数据放入本地内存。

private static final int BYTES_PER_FLOAT = 4;

trangleData = ByteBuffer.allocateDirect(trangle.length * BYTES_PER_FLOAT)
        .order(ByteOrder.nativeOrder())
        .asFloatBuffer();

先申请一块内存,内存的大小是这个数组的长度*每个元素占用字节数(float占用4个字节)
然后将数组的数据放入这块本地内存中,才好与opengl传递数据。

下面我们来了解下着色器相关的内容。

顶点着色器:
生成每个顶点的位置,针对每个顶点,它都会执行一次,一旦最终位置确定,opengl就可以把这些可见顶点的集合组装成点,直线,三角形。
我们先在res/raw下新建一个triangle_vs.glsl 文件用于存放顶点着色器代码。

attribute vec4 a_position ;
void main(){
      gl_Position =  a_position ;
}

变量gl_Position是一个预定义的内建4分量变量,它包含顶点着色器要求的一个输出。
Opengl中的变量类型有很多,我们这简单介绍下:
Vec2,vec3,vec4 -》 2分量,3分量,4分量的浮点向量
比如我新建一个变量

vec4 name;

可以通过以下的方式操作:

Name = vec4(1.0f , 1.0f,1.0f,1.0f);
//或者(xyzw)
Name.x = 1.0f;
Name.y= 1.0f;
//如何是操作颜色相关的还可以(rgba)
Name.r =1.0f;
Name.g= 1.0f;

当然还有其他的变量什么mat4矩阵相关的我们就不展开了,毕竟opengl实在太庞大了,我们只需要音视频相关的知识点够用就行了,我自己在学习有时候过度都忘记我最初学习opengl的目的了,学到后面三维空间什么法向量,然后一些线性代数有点吃力的时候才发现我只是要渲染一个视频的图形部分而已。

这其中还有三种变量类型需要我们了解下:
uniform变量
一个从客户端(android)传递过来的变量,在opengl中不能被改变。

attribute变量
attribute变量是只能在vertex shader中使用的变量,它只能在顶点着色器中使用,也能从客户端中传递过来赋值的变量。

varying变量
是顶点着色器和片段着色器中传递使用的,一般来说是顶点中传递给片段中比较多,在后面学习渲染纹理的时候使用比较多。

片段着色器
为组成点,直线或者三角形的每个片段生成最终的颜色,针对每个片段,它都会执行一次,一个片段是一个小的,单一颜色的长方形区域,类似于计算机上的一个像素。

一旦颜色生成了,opengl就会把他们写到一个块称为帧缓冲区的内存块中,然后,android会把这个帧缓冲区显示到屏幕上。

precision  mediump float;
void main() {
    gl_FragColor=vec4(1.0 , 1.0 , 1.0 , 1.0);
}

第一句是设置浮点数的精度,就像我们java中的float,和double的感觉一样。
gl_FragColor也是内建变量直接用vec4(1.0,1.0,1.0,1.0,)赋值,也就是白色。

Raw中的代码需要先加载到内存中,使用以下代码即可:

public static String readTextFileFromResource(Context context , int resId){

    StringBuilder body = new StringBuilder();
    try{
        InputStream inputStream = context.getResources().openRawResource(resId);
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
        String nextLine ;
        while((nextLine = bufferedReader.readLine()) != null){
            body.append(nextLine);
            body.append("\n");
        }
    }catch (Exception e){
        Log.e("xhc" , "read source faild ");
    }
    return body.toString();
}

好了下面就涉及到opengl中的操作部分了,编译着色器:

//这个创建一个新的着色器对象
final int shaderObjuectId = glCreateShader(type);
//然后判断是否成功创建
if(shaderObjuectId == 0){
    Log.e("xhc" , " create new shader faild ");
    return 0;
}

//然后将我们raw中的代码传入进去,和shaderObjuectId 绑定
glShaderSource(shaderObjuectId , shaderCode);

//然后开始编译代码
glCompileShader(shaderObjuectId);

//但是我们写的opengl代码不一定是正确的,所以我们还是要获取下编译状态
final int[] compileStatus = new int[1];
glGetShaderiv(shaderObjuectId , GL_COMPILE_STATUS , compileStatus , 0);
Log.e("xhc" , "compile result :  "+shaderCode +" "+glGetShaderInfoLog(shaderObjuectId));
if(compileStatus[0] == 0){
    glDeleteShader(shaderObjuectId);
    Log.e("xhc" , " compile of shader faild ");
    return 0;
}

以上的编译操作,对两种着色器都管用。

两者相互编译了,但是顶点和片段着色器是要配合一起使用的,所以我们要连接起来。

//创建一个program程序对象
final int programObjectId = glCreateProgram();
//当然还是有可能创建失败,判断是否成功
if(programObjectId == 0){
    Log.e("xhc" , "create program faild ");
    return 0;
}
//然后将两个着色器对象附加到program中
glAttachShader(programObjectId , vertexShaderId);
glAttachShader(programObjectId , fragmentShaderId);
glLinkProgram(programObjectId);

//一样需要检测状态
final int[] linkStatus = new int[1];
glGetProgramiv(programObjectId , GL_LINK_STATUS , linkStatus , 0);
Log.e("xhc" , "\n\n result link progrom : \n"+glGetProgramInfoLog(programObjectId));
if(linkStatus[0] == 0){
    glDeleteProgram(programObjectId);
    Log.e("xhc" , " program link faild !");
    return 0;
}
//这个是告诉opengl绘制东西的时候要使用这个程序。
glUseProgram(program);

//获取顶点着色器中a_position中的变量的位置
positonLocation = glGetAttribLocation(program , "a_position");

//把这个buffer中的数据的偏移置为起点
trangleData.position(0);
//这个函数是告诉opengl是可以在trangledata中获取顶点的位置。
glVertexAttribPointer(positonLocation , POSITION_COMPONENT_COUNT ,   GL_FLOAT , false , 0 , trangleData);

我们来看各个参数的意思:
positonLocation :就是a_position 在opengl中的位置

POSITION_COMPONENT_COUNT:这个参数是告诉每个顶点使用了几个参数,我们这里只有x.y所以只是使用了两个参数

GL_FLOAT:这是数据类型。

Normalized(false):指定当被访问时,固定点数据值是否应该被归一化(GL_TRUE)或者直接转换为固定点值。

Stride(0):这个是数据的偏移量,比如如果这个数组中有种属性,有顶点,比如纹理的坐标,这个就是用来区分使用的。

trangleData:顶点数据。

//这个是使能这个顶点
glEnableVertexAttribArray(positonLocation);

//在这里调用设置opengl的窗口大小
@Override
public void onSurfaceChanged(GL10 gl10, int width, int height) {
    glViewport(0, 0, width, height);
}
//先清空所有窗口,
//然后画三角形。
//其实还可以画线(GL_LINE),还有点(GL_POINT)。
 @Override
public void onDrawFrame(GL10 gl10) {
    glClear(GL_COLOR_BUFFER_BIT);
    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
}

代码连接,没事star下还是很欢迎的

03-29 17:28