OpenGL ES总结(四)OpenGL 渲染视频画面

版权声明:我已委托“维权骑士”(rightknights.com)为我的文章进行维权行动.转载务必转载所有,且须注明出处。否则保留追究法律责任 https://blog.csdn.net/hejjunlin/article/details/62976457

转载请把头部出处链接和尾部二维码一起转载,本文出自:http://blog.csdn.net/hejjunlin/article/details/62976457

前一篇介绍是渲染一张图片,今天是在MediaPlayer播放过程中,渲染视频,看下Agenda:

  • 与渲染图片的区别
  • 创建SurfaceTexture
  • 设置shader(着色器)
  • 建立纹理坐标
    • UV坐标介绍
    • UV纹理坐标设定与贴图规则是什么?
  • 视频播放

与渲染图片的区别

渲染视频画面和渲染图片不同,视频需要不断地刷新,每当有新的一帧来时,我们都应该更新纹理,然后重新绘制。我们使用SurfaceTexture来设置MediaPlayer的setSurface.

创建一个纹理时,视频的每一帧都可以看成图片,也就是要不断的更新纹理

主要的原因是,MediaPlayer的输出往往不是RGB格式(一般是YUV),而GLSurfaceView需要RGB格式才能正常显示,另外,获取每一帧的数据并没有那么方便。
所以,我们创建的纹理应该稍有不同,SurfaceTexture在《Android Multimedia框架总结(三)MediaPlayer中创建到setDataSource过程》就曾详细介绍过,这里贴出来:

SurfaceTexture: SurfaceTexture是从Android3.0(API 11)加入的一个新类。这个类跟SurfaceView很像,可以从video decode里面获取图像流(image stream)。但是,和SurfaceView不同的是,SurfaceTexture在接收图像流之后,不需要显示出来。SurfaceTexture不需要显示到屏幕上,因此我们可以用SurfaceTexture接收来自decode出来的图像流,然后从SurfaceTexture中取得图像帧的拷贝进行处理,处理完毕后再送给另一个SurfaceView用于显示即可。

创建SurfaceTexture

public VideoTexture(Context context, int textureId) {
       mContext = context;
       mTexureId = textureId;
       mSurfaceTexture = new SurfaceTexture(textureId);
       mSurface = new Surface(mSurfaceTexture);
       // 初始化顶点坐标与着色数据
       initVertexData();
       // 初始化着色器
       initShader();
}

在onDrawFrame中

mSurfaceTexture.updateTexImage();
checkGlError("onDrawFrame start");
mSurfaceTexture.getTransformMatrix(mSTMatrix);
GLES30.glUseProgram(mProgram);
checkGlError("glUseProgram");

GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
GLES30.glBindTexture(mTarget, mTexId);

其中mTarget是 GLES11Ext.GL_TEXTURE_EXTERNAL_OES,GLES11Ext.GL_TEXTURE_EXTERNAL_OES的到底是做什么的?
我们知道视频解码的输出格式是YUV的(YUV420sp),那么这个扩展纹理的作用就是实现YUV格式到RGB的自动转化,我们就不需要再为此写YUV转RGB的代码。

我们用上面的textureId是构造时传入的,

// 生成纹理ID
int[] textures = new int[2];
GLES20.glGenTextures(//
    2, // 产生的纹理id的数量
    textures, // 纹理id的数组
    0 // 偏移量
);

mVideoTexture = new VideoTexture(mContext, textures[0]);

这个textureId是用于去创建一个SurfaceTexture,然后用surfaceTexture去创建一个Surface ,再把surface送给mediaPlayer进行输出。
updateTexImage,就是来更新纹理,其中getTransformMatrix的目的,是让新的纹理和纹理坐标系能够正确的对应,mSTMatrix的定义是和mMVPMatrix完全一样的。

    private float[] mMVPMatrix = new float[16];
    private float[] mSTMatrix = new float[16];

设置shader(着色器):

    public void initShader() {
        // 加载顶点着色器的脚本内容
        String mVertexShader = ShaderUtil.readRawTextFile(mContext, R.raw.vertex_oes);
        // 加载片元着色器的脚本内容
        String mFragmentShader = ShaderUtil.readRawTextFile(mContext, R.raw.frag_oes);
        // 基于顶点着色器与片元着色器创建程序
        mProgram = ShaderUtil.createProgram(mVertexShader, mFragmentShader);
        // 获取程序中顶点位置属性引用id
        maPositionHandle = GLES30.glGetAttribLocation(mProgram, "aPosition");
        // 获取程序中顶点纹理坐标属性引用id
        maTextureCoordHandle = GLES30.glGetAttribLocation(mProgram,
                "aTextureCoord");
        // 获取程序中总变换矩阵引用id
        muMVPMatrixHandle = GLES30.glGetUniformLocation(mProgram, "uMVPMatrix");
        muSTMatrixHandle = GLES30.glGetUniformLocation(mProgram, "uSTMatrix");

    }

其中vextex_oes.glsl

uniform mat4 uMVPMatrix; //总变换矩阵
uniform mat4 uSTMatrix;
attribute vec4 aPosition;  //顶点位置
attribute vec4 aTextureCoord;    //顶点纹理坐标
varying vec2 vTextureCoord;  //用于传递给片元着色器的变量
void main()
{
   gl_Position = uMVPMatrix * aPosition; //根据总变换矩阵计算此次绘制此顶点位置
   vTextureCoord = (uSTMatrix * aTextureCoord).xy;//将接收的纹理坐标传递给片元着色器
}                      

frag_oes.glsl

#extension GL_OES_EGL_image_external : require
precision mediump float;
varying vec2 vTextureCoord; //接收从顶点着色器过来的参数
uniform samplerExternalOES sTexture;//纹理内容数据
void main()
{
   //给此片元从纹理中采样出颜色值
   gl_FragColor = texture2D(sTexture, vTextureCoord); 
}              

建立纹理坐标:

private final float[] mTriangleVerticesData = {
     // X, Y, Z, U, V
     -3.2f, -1.5f, 0, 0.f, 0.f, //
     3.2f, -1.5f, 0, 1.f, 0.f, //
     -3.2f, 1.5f, 0, 0.f, 1.f, //
     3.2f, 1.5f, 0, 1.f, 1.f, //
};

其中有X,Y,Z,还有U,V,那么什么是UV坐标?

对于三维模型,有两个最重要的坐标系统,一是顶点的位置(X,Y,Z)坐标,另一个就是UV坐标。什么是UV?简单的说,就是贴图影射到模型表面的依据。 完整的说,其实应该是UVW(因为XYZ已经用过了,所以另选三个字母表示)。U和V分别是图片在显示器水平、垂直方向上的坐标,取值一般都是0~1,也就是(水平方向的第U个像素/图片宽度,垂直方向的第V个像素/图片高度)。那W呢?贴图是二维的,何来三个坐标?嗯嗯,W的方向垂直于显示器表面,一般 用于程序贴图或者某些3D贴图技术(记住,确实有三维贴图这种概念!),对于游戏而言不常用到,所以一般我们就简称UV了。

所有的图象文件都是二维的一个平面。水平方向是U,垂直方向是V,通过这个平面的,二维的UV坐标系。我们可以定位图象上的任意一个象素。但是一个问题是如何把这个二维的平面贴到三维的NURBS表面和多边形表面呢? 对于NURBS表面。由于他本身具有UV参数,尽管这个UV值是用来定位表面上的点的参数,但由于它也是二维的,所以很容易通过换算把表面上的点和平面图象上的象素对应起来。所以把图象贴带NURBS是很直接的一件事。但是对于多变形模型来讲,贴图就变成一件麻烦的事了。所以多边形为了贴图就额外引进了一个UV坐标,以便把多边形的顶点和图象文件上的象素对应起来,这样才能在多边形表面上定位纹理贴图。所以说多边形的顶点除了具有三维的空间坐标外。还具有二维的UV坐标。

UV” 这里是指u,v纹理贴图坐标的简称(它和空间模型的X, Y, Z轴是类似的). 它定义了图片上每个点的位置的信息. 这些点与3D模型是相互联系的, 以决定表面纹理贴图的位置. UV就是将图像上每一个点精确对应到模型物体的表面. 在点与点之间的间隙位置由软件进行图像光滑插值处理. 这就是所谓的UV贴图.
那为什么用UV坐标而不是标准的投影坐标呢? 通常给物体纹理贴图最标准的方法就是以planar(平面),cylindrical(圆柱), spherical(球形),cubic(方盒)坐标方式投影贴图.
Planar projection(平面投影方式)是将图像沿x,y或z轴直接投影到物体. 这种方法使用于纸张, 布告, 书的封面等 - 也就是表面平整的物体.平面投影的缺点是如果表面不平整, 或者物体边缘弯曲, 就会产生如图A的不理想接缝和变形. 避免这种情况需要创建带有alpha通道的图像, 来掩盖临近的平面投影接缝, 而这会是非常烦琐的工作. 所以不要对有较大厚度的物体和不平整的表面运用平面投影方式. 对于立方体可以在x, y方向分别进行平面投影, 但是要注意边缘接缝的融合. 或者采用无缝连续的纹理, 并使用cubic投影方式. 多数软件有图片自动缩放功能, 使图像与表面吻合. 显然, 如果你的图像与表面形状不同, 自动缩放就会改变图像的比例以吻合表面. 这通常会产生不理想的效果, 所以制作贴图前先测量你的物体尺寸.

uv纹理坐标设定与贴图规则是什么?

当opengl对一个四方形进行贴图时,会定义纹理贴图坐标,一串数组。

当纹理映射启动后绘图时,你必须为OpenGL ES提供其他数据,即顶点数组中各顶点的纹理坐标。纹理坐标定义了图像的哪一部分将被映射到多边形。

视频播放

try {
    mMediaPlayer = new MediaPlayer();
    mMediaPlayer.setSurface(mSurface);
    mMediaPlayer.setLooping(true);

    String url = Environment.getExternalStorageDirectory() + "/节目.mp4";
    Log.w("videoTexture", " datasource file url " + url);
    mMediaPlayer.setDataSource(mContext, Uri.parse(url));
    mMediaPlayer.prepare();
    mMediaPlayer.start();
} catch (Exception e) {
      e.printStackTrace();
}

最后效果图:

这里写图片描述

第一时间获得博客更新提醒,以及更多android干货,源码分析,欢迎关注我的微信公众号,扫一扫下方二维码或者长按识别二维码,即可关注。

这里写图片描述
如果你觉得好,随手点赞,也是对笔者的肯定,也可以分享此公众号给你更多的人,原创不易

猜你喜欢

转载自blog.csdn.net/hejjunlin/article/details/62976457