Android-音视频学习系列-(六)掌握视频基础知识并使用-OpenGL-ES-2-0-渲染-YUV-数据

结合下图,希望可以帮组大家更好的理解 DTS 和 PTS 的概念。

视频渲染

OpenGL ES

实现效果

介绍

OpenGL (Open Graphics Lib) 定义了一个跨编程语言、跨平台编程的专业图形程序接口。可用于二维或三维图像的处理与渲染,它是一个功能强大、调用方便的底层图形库。对于嵌入式的设备,其提供了 OpenGL ES(OpenGL for Embedded System) 版本,该版本是针对手机、Pad 等嵌入式设备而设计的,是 OpenGL 的一个子集。到目前为止,OpenGL ES 已经经历过很多版本的迭代与更新,到目前为止运用最广泛的还是 OpenGL ES 2.0 版本。我们接下来所实现的 Demo 就是基于 OpenGL ES 2.0 接口进行编程并实现图像的渲染。

由于 OpenGL ES 是基于跨平台的设计,所以在每个平台上都要有它的具体实现,既要提供 OpenGL ES 的上下文环境以及窗口的管理。在 OpenGL 的设计中,OpenGL 是不负责管理窗口的。那么在 Android 平台上其实是使用 EGL 提供本地平台对 OpenGL ES 的实现。

使用

要在 Android 平台下使用 OpenGL ES , 第一种方式是直接使用 GLSurfaceView ,通过这种方式使用 OpenGL ES 比较简单,因为不需要开发者搭建 OpenGL ES 的上下文环境,以及创建 OpenGL ES 的显示设备。但是凡事都有两面,有好处也有坏处,使用 GLSurfaceView 不够灵活,很多真正的 OpenGL ES 的核心用法(比如共享上下文来达到多线程使用 EGL 的 API 来搭建的,并且是基于 C++ 的环境搭建的。因为如果仅仅在 Java 层编写 ,那么对于普通的应用也许可行,但是对于要进行解码或者使用第三方库的场景(比如人脸识别),则需要到 C++ 层来实施。处于效率和性能的考虑,这里的架构将直接使用 Native 层的 EGL 搭建一个 OpenGL ES 的开发环境。要想在 Native 层使用 EGL ,那么就必须在 CmakeLists.txt 中添加 EGL 库(可以参考如下提供的 CMakeLists 文件配置),并在使用该库的 C++ 文件中引入对应的头文件,需要引如的头文件地址如下:

//1. 在开发中如果要使用 EGL 需要在 CMakeLists.txt 中添加 EGL 库,并指定头文件

//使用 EGL 需要添加的头文件
#include <EGL/egl.h>
#include <EGL/eglext.h>

//2. 使用 OpenGL ES 2.0 也需要在 CMakeLists.txt 中添加 GLESv2 库,并指定头文件

//使用 OpenGL ES 2.0 需要添加的头文件
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>

CMakeLists 文件配置:

cmake_minimum_required(VERSION 3.4.1)

#音频渲染
set(OpenSL ${CMAKE_SOURCE_DIR}/opensl)
#视频渲染
set(OpenGL ${CMAKE_SOURCE_DIR}/gles)

#批量添加自己编写的 cpp 文件,不要把 .h 加入进来了
file(GLOB ALL_CPP ${OpenSL}/
.cpp ${OpenGL}/*.cpp)

#添加自己编写 cpp 源文件生成动态库
add_library(audiovideo SHARED ${ALL_CPP})

#找系统中 NDK log库
find_library(log_lib
log)

#最后才开始链接库
target_link_libraries(
#最后生成的 so 库名称
audiovideo
#音频渲染
OpenSLES

OpenGL 与 NativeWindow 连接本地窗口的中间者

EGL
#视频渲染
GLESv2
#添加本地库
android

${log_lib}
)

至此,对于 OpenGL 的开发需要用到的头文件以及库文件就引入完毕了,下面再来看看如何使用 EGL 搭建出 OpenGL 的上下文环境以及渲染视频数据。

    1. 使用 EGL 首先必须创建,建立本地窗口系统和 OpenGL ES 的连接

//1.获取原始窗口
nativeWindow = ANativeWindow_fromSurface(env, surface);
//获取Display
display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (display == EGL_NO_DISPLAY) {
LOGD(“egl display failed”);
showMessage(env, “egl display failed”, false);
return;
}

    1. 初始化 EGL

//初始化egl,后两个参数为主次版本号
if (EGL_TRUE != eglInitialize(display, 0, 0)) {
LOGD(“eglInitialize failed”);
showMessage(env, “eglInitialize failed”, false);
return;
}

    1. 确定可用的渲染表面( Surface )的配置。

//surface 配置,可以理解为窗口
EGLConfig eglConfig;
EGLint configNum;
EGLint configSpec[] = {
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_NONE
};

if (EGL_TRUE != eglChooseConfig(display, configSpec, &eglConfig, 1, &configNum)) {
LOGD(“eglChooseConfig failed”);
showMessage(env, “eglChooseConfig failed”, false);
return;
}

    1. 创建渲染表面 surface(4/5步骤可互换)

//创建surface(egl和NativeWindow进行关联。最后一个参数为属性信息,0表示默认版本)
winSurface = eglCreateWindowSurface(display, eglConfig, nativeWindow, 0);
if (winSurface == EGL_NO_SURFACE) {
LOGD(“eglCreateWindowSurface failed”);
showMessage(env, “eglCreateWindowSurface failed”, false);
return;
}

    1. 创建渲染上下文 Context

//4 创建关联上下文
const EGLint ctxAttr[] = {
EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE
};
//EGL_NO_CONTEXT表示不需要多个设备共享上下文
context = eglCreateContext(display, eglConfig, EGL_NO_CONTEXT, ctxAttr);
if (context == EGL_NO_CONTEXT) {
LOGD(“eglCreateContext failed”);
showMessage(env, “eglCreateContext failed”, false);
return;
}

    1. 指定某个 EGLContext 为当前上下文, 关联起来

//将egl和opengl关联
//两个surface一个读一个写。第二个一般用来离线渲染
if (EGL_TRUE != eglMakeCurrent(display, winSurface, winSurface, context)) {
LOGD(“eglMakeCurrent failed”);
showMessage(env, “eglMakeCurrent failed”, false);
return;
}

    1. 使用 OpenGL 相关的 API 进行绘制操作

GLint vsh = initShader(vertexShader, GL_VERTEX_SHADER);
GLint fsh = initShader(fragYUV420P, GL_FRAGMENT_SHADER);

//创建渲染程序
GLint program = glCreateProgram();
if (program == 0) {
LOGD(“glCreateProgram failed”);
showMessage(env, “glCreateProgram failed”, false);
return;
}

//向渲染程序中加入着色器
glAttachShader(program, vsh);
glAttachShader(program, fsh);

//链接程序
glLinkProgram(program);
GLint status = 0;
glGetProgramiv(program, GL_LINK_STATUS, &status);
if (status == 0) {
LOGD(“glLinkProgram failed”);
showMessage(env, “glLinkProgram failed”, false);
return;
}
LOGD(“glLinkProgram success”);
//激活渲染程序
glUseProgram(program);

//加入三维顶点数据
static float ver[] = {
1.0f, -1.0f, 0.0f,
-1.0f, -1.0f, 0.0f,
1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f
};

GLuint apos = static_cast(glGetAttribLocation(program, “aPosition”));
glEnableVertexAttribArray(apos);
glVertexAttribPointer(apos, 3, GL_FLOAT, GL_FALSE, 0, ver);

//加入纹理坐标数据
static float fragment[] = {
1.0f, 0.0f,
0.0f, 0.0f,
1.0f, 1.0f,
0.0f, 1.0f
};
GLuint aTex = static_cast(glGetAttribLocation(program, “aTextCoord”));
glEnableVertexAttribArray(aTex);
glVertexAttribPointer(aTex, 2, GL_FLOAT, GL_FALSE, 0, fragment);

//纹理初始化
//设置纹理层对应的对应采样器?

/**

  • //获取一致变量的存储位置
    GLint textureUniformY = glGetUniformLocation(program, “SamplerY”);
    GLint textureUniformU = glGetUniformLocation(program, “SamplerU”);
    GLint textureUniformV = glGetUniformLocation(program, “SamplerV”);
    //对几个纹理采样器变量进行设置
    glUniform1i(textureUniformY, 0);
    glUniform1i(textureUniformU, 1);
    glUniform1i(textureUniformV, 2);
    */
    //对sampler变量,使用函数glUniform1i和glUniform1iv进行设置
    glUniform1i(glGetUniformLocation(program, “yTexture”), 0);
    glUniform1i(glGetUniformLocation(program, “uTexture”), 1);
    glUniform1i(glGetUniformLocation(program, “vTexture”), 2);
    //纹理ID
    GLuint texts[3] = {0};
    //创建若干个纹理对象,并且得到纹理ID
    glGenTextures(3, texts);

//绑定纹理。后面的的设置和加载全部作用于当前绑定的纹理对象
//GL_TEXTURE0、GL_TEXTURE1、GL_TEXTURE2 的就是纹理单元,GL_TEXTURE_1D、GL_TEXTURE_2D、CUBE_MAP为纹理目标
//通过 glBindTexture 函数将纹理目标和纹理绑定后,对纹理目标所进行的操作都反映到对纹理上
glBindTexture(GL_TEXTURE_2D, texts[0]);
//缩小的过滤器
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
//放大的过滤器
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//设置纹理的格式和大小
// 加载纹理到 OpenGL,读入 buffer 定义的位图数据,并把它复制到当前绑定的纹理对象
// 当前绑定的纹理对象就会被附加上纹理图像。
//width,height表示每几个像素公用一个yuv元素?比如width / 2表示横向每两个像素使用一个元素?
glTexImage2D(GL_TEXTURE_2D,
0,//细节基本 默认0
GL_LUMINANCE,//gpu内部格式 亮度,灰度图(这里就是只取一个亮度的颜色通道的意思)
width,//加载的纹理宽度。最好为2的次幂(这里对y分量数据当做指定尺寸算,但显示尺寸会拉伸到全屏?)
height,//加载的纹理高度。最好为2的次幂
0,//纹理边框
GL_LUMINANCE,//数据的像素格式 亮度,灰度图
GL_UNSIGNED_BYTE,//像素点存储的数据类型
NULL //纹理的数据(先不传)
);

//绑定纹理
glBindTexture(GL_TEXTURE_2D, texts[1]);
//缩小的过滤器
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//设置纹理的格式和大小
glTexImage2D(GL_TEXTURE_2D,
0,//细节基本 默认0
GL_LUMINANCE,//gpu内部格式 亮度,灰度图(这里就是只取一个颜色通道的意思)
width / 2,//u数据数量为屏幕的4分之1
height / 2,
0,//边框
GL_LUMINANCE,//数据的像素格式 亮度,灰度图
GL_UNSIGNED_BYTE,//像素点存储的数据类型
NULL //纹理的数据(先不传)
);

//绑定纹理
glBindTexture(GL_TEXTURE_2D, texts[2]);
//缩小的过滤器
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//设置纹理的格式和大小
glTexImage2D(GL_TEXTURE_2D,
0,//细节基本 默认0
GL_LUMINANCE,//gpu内部格式 亮度,灰度图(这里就是只取一个颜色通道的意思)
width / 2,
height / 2,//v数据数量为屏幕的4分之1
0,//边框
GL_LUMINANCE,//数据的像素格式 亮度,灰度图
GL_UNSIGNED_BYTE,//像素点存储的数据类型
NULL //纹理的数据(先不传)
);

unsigned char *buf[3] = {0};
buf[0] = new unsigned char[width * height];//y
buf[1] = new unsigned char[width * height / 4];//u
buf[2] = new unsigned char[width * height / 4];//v

showMessage(env, “onSucceed”, true);

FILE *fp = fopen(data_source, “rb”);
if (!fp) {
LOGD(“oepn file %s fail”, data_source);
return;
}

while (!feof(fp)) {
//解决异常退出,终止读取数据
if (!isPlay)
return;
fread(buf[0], 1, width * height, fp);
fread(buf[1], 1, width * height / 4, fp);
fread(buf[2], 1, width * height / 4, fp);

//激活第一层纹理,绑定到创建的纹理
//下面的width,height主要是显示尺寸?
glActiveTexture(GL_TEXTURE0);
//绑定y对应的纹理
glBindTexture(GL_TEXTURE_2D, texts[0]);
//替换纹理,比重新使用glTexImage2D性能高多
glTexSubImage2D(GL_TEXTURE_2D, 0,
0, 0,//相对原来的纹理的offset
width, height,//加载的纹理宽度、高度。最好为2的次幂
GL_LUMINANCE, GL_UNSIGNED_BYTE,
buf[0]);

//激活第二层纹理,绑定到创建的纹理
glActiveTexture(GL_TEXTURE1);
//绑定u对应的纹理
glBindTexture(GL_TEXTURE_2D, texts[1]);
//替换纹理,比重新使用glTexImage2D性能高
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width / 2, height / 2, GL_LUMINANCE,
GL_UNSIGNED_BYTE,
buf[1]);

//激活第三层纹理,绑定到创建的纹理
glActiveTexture(GL_TEXTURE2);
//绑定v对应的纹理
glBindTexture(GL_TEXTURE_2D, texts[2]);
//替换纹理,比重新使用glTexImage2D性能高
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width / 2, height / 2, GL_LUMINANCE,
GL_UNSIGNED_BYTE,
buf[2]);

glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
//8. 窗口显示,交换双缓冲区
eglSwapBuffers(display, winSurface);
}

    1. 交换 EGL 的 Surface 的内部缓冲和 EGL 创建的和平台无关的窗口 diaplay

//窗口显示,交换双缓冲区
eglSwapBuffers(display, winSurfac

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享

e);
复制代码

    1. 释放资源

/**

  • 销毁数据
    */
    ]);

glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
//8. 窗口显示,交换双缓冲区
eglSwapBuffers(display, winSurface);
}

    1. 交换 EGL 的 Surface 的内部缓冲和 EGL 创建的和平台无关的窗口 diaplay

//窗口显示,交换双缓冲区
eglSwapBuffers(display, winSurfac

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享

e);
复制代码

    1. 释放资源

/**

  • 销毁数据
    */
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值