上一篇文章咱们学习了音频的基础知识和音频的渲染以后,该篇咱们学习视频的知识,与上一篇学习方式同样,基础 + demo ,主打渲染,采集跟编码咱们后面学习播放器和录屏在来研究。html
作过 Camera 采集或者作过帧动画其实应该知道,视频是由一幅幅图像或者说一帧帧 YUV 数据组成,因此要学习视频还得从图像开始学习。java
咱们回顾一下,应该是初中的时候作过一个三棱镜实验,内容是如何利用三棱镜将太阳光分解成彩色的光带?第一个作这个实验者是 牛顿 ,各色光因其所造成的折射角不一样而彼此分离,就像彩虹同样,因此白光可以分解成多种色彩的光。后来人们经过实验证实,红绿蓝三种色光没法被分解,故称为三原色光,等量的三原色光相加会变为白光,即白光中含有等量的红光(R),绿光(G),蓝光(B)。android
在平常生活中,因为光的反射,咱们才能看到各种物体的轮廓和颜色。可是若是将这个理论应用到手机中,那么该结论还成立吗?答案是否认的,由于在黑暗中咱们也能够看到手机屏幕中的内容,实际上人眼能看到手机屏幕上的内容的原理以下。c++
假设一部手机屏幕的分辨率是 1920 * 1080 说明水平方向有 1080 个像素点,垂直方向有 1920 个像素点,因此整个屏幕就有 1920 * 1080 个像素点(这也是分辨率的含义)。每一个像素点都由三个子像素点组成,以下图所示,这些密密麻麻的子像素点在图像放大或者在显微镜下能够看得一清二楚。当要显示某篇文字或者某幅图像时,就会把这幅图像的每个像素点的 RGB 通道分别对应的屏幕位置上的子像素点绘制到屏幕上,从而显示整个图像。git
因此在黑暗的环境下也能看到手机屏幕上的内容,是由于手机屏幕是自发光的,而不是经过光的反射才被人们看到的。github
经过上一小节咱们清楚的知道任何一个图像都是由 RGB 组成,那么一个像素点的 RGB 该如何表示呢?音频里面的每个采样 (sample) 均使用 16 bit 来表示,那么像素里面的子像素又该如何表示呢?一般的表示方式有如下几种。算法
对于一幅图像,通常使用整数表示方法进行描述,好比计算一张 1920 * 1080 的 RGB_8888 的图像大小,可采用以下计算方式:编程
1920 * 1080 * 4 / 1024 / 1024 ≈ 7.910 MB
复制代码
这也是 Bitmap 在内存中所占用的大小,因此每一张图像的裸数据都是很大的。对于图像的裸数据来讲,直接来网络中进行传输也是不大可能的,因此就有了图像的压缩格式,好比我以前开源过一个基于 JPEG 压缩 :JPEG 是静态图像压缩标准,由 ISO 制定。 JPEG 图像压缩算法在提供良好的压缩性能的同时,具备较好的重建质量。这种算法被普遍应用于图像处理领域,固然它也是一种有损压缩。在不少网站如淘宝上使用的都是这种压缩以后的图像,可是,这种压缩不能直接应用于视频压缩,由于对于视频来说,还有一个时域上的因素须要考虑,也就是说不只仅要考虑帧内编码,还要考虑帧间编码。视频采用的是更加成熟的算法,关于视频压缩算法的相关内容咱们会在后面进行介绍。缓存
对于视频帧的裸数据表示,其实更多的是 YUV 数据格式的表示, YUV 主要应用于优化彩色视频信号的传输,使其向后兼容老式黑白电视。在 RGB 视频信号传输相比,它最大的优势在于只须要占用极少的频宽(RGB 要求三个独立的视频信号同时传输)。其中 Y 表示明亮度,而 “U”,"V" 表示的则是色度值,它们的做用是描述影像的色彩及饱和度,用于指定像素的颜色。“亮度” 是透过 RGB 输入信号来创建的,方法时将 RGB 信号的特定部分叠加到一块儿。“色度” 则定义了颜色的两个方面 - 色调与饱和度,分别用 Cr 和 Cb 来表示。其中,Cr 反应了 RGB 输入信号红色部分与 RGB 信号亮度值之间的差别,而 Cb 反映的则是 RGB 输入信号蓝色部分与 RGB 信号亮度值之间的差别。bash
之因此采用 YUV 色彩空间,是由于它的亮度信号 Y 和色度信号 U、V 是分离的。若是只有 Y 信号份量而没有 U 、V 份量,那么这样表示的图像就是黑白灰图像。彩色电视采用 YUV 空间正是为了用亮度信号 Y 解决彩色电视机与黑白电视机的兼容问题,使黑白电视机也能接收彩色电视信号,最经常使用的表示形式是 Y、U、V 都使用 8 字节来表示,因此取值范围是 0 ~ 255 。 在广播电视系统中不传输很低和很高的数值,其实是为了防止信号变更形成过载, Y 的取值范围都是 16 ~ 235 ,UV 的取值范围都是 16 ~ 240。
YUV 最经常使用的采样格式是 4:2:0 , 4:2:0 并不意味着只有 Y 、Cb 而没有 Cr 份量。它指的是对每行扫描线来讲,只有一种色度份量是以 2:1 的抽样率来存储的。相邻的扫描行存储着不一样的色度份量,也就是说,若是某一行是 4:2:0,那么下一行就是 4:0:2,在下一行是 4:2:0,以此类推。对于每一个色度份量来讲,水平方向和竖直方向的抽象率都是 2:1,因此能够说色度的抽样率是 4:1。对非压缩的 8 bit 量化的视频来讲,8*4 的一张图片须要占用 48 byte 内存。
相较于 RGB ,咱们能够计算一帧为 1920 * 1080 的视频帧,用 YUV420P 的格式来表示,其数据量的大小以下:
(1920 * 1080 * 1 + 1920 * 1080 * 0.5 ) / 1024 /1024 ≈ 2.966MB
复制代码
若是 fps(1 s 的视频帧数量)是 25 ,按照 5 分钟的一个短视频来计算,那么这个短视频用 YUV420P 的数据格式来表示的话,其数据量的大小就是 :
2.966MB * 25fps * 5min * 60s / 1024 ≈ 21GB
复制代码
能够看到仅仅 5 分钟的视频数据量就能达到 21 G, 像抖音,快手这样短视频领域的表明这样的话还不卡死,那么如何对短视频进行存储以及流媒体播放呢?答案确定是须要进行视频编码,后面会介绍视频编码的内容。
若是对 YUV 采样或者存储不明白的能够看这篇文章:音视频基础知识---像素格式YUV
前面已经讲过,凡是渲染到屏幕上的文字、图片、或者其它,都须要转为 RGB 的表示形式,那么 YUV 的表示形式和 RGB 的表示形式之间是如何进行转换的呢?能够参考该篇文章YUV <——> RGB 转换算法, 相互转换 C++ 代码能够参考地址
还记得上一篇文章咱们学习的音频编码方式吗?音频的编码主要是去除冗余信息,从而实现数据量的压缩。那么对于视频压缩,又该从哪几个方面来对数据进行压缩呢?其实与以前提到的音频编码相似,视频压缩也是经过去除冗余信息来进行压缩的。相较于音频数据,视频数据有极强的相关性,也就是说有大量的冗余信息,包括空间上的冗余信息和时间上的冗余信息,具体包括如下几个部分。
使用帧内编码技术能够去除空间上的冗余信息。
你们还记得以前提到的图像编码 JPEG 吗?对于视频, ISO 一样也制定了标准: Motion JPEG 即 MPEG ,MPEG 算法是适用于动态视频的压缩算法,它除了对单幅图像进行编码外,还利用图像序列中的相关原则去除冗余,这样能够大大提升视频的压缩比,截至目前,MPEG 的版本一直在不断更新中,主要包括这样几个版本: Mpeg1(用于 VCD)、Mpeg2(用于 DVD)、Mpeg4 AVC(如今流媒体使用最多的就是它了)。
想比较 ISO 指定的 MPEG 的视频压缩标准,ITU-T 指定的 H.26一、H.26二、H.26三、H.264 一系列视频编码标准是一套单独的体系。其中,H.264 集中了以往标准的全部优势,并吸收了以往标准的经验,采样的是简洁设计,这使得它比 Mpeg4 更容易推广。如今使用最多的就是 H.264 标准, H.264 创造了多参考帧、多块类型、整数变换、帧内预测等新的压缩技术,使用了更精准的分像素运动矢量(1/四、1/8) 和新一代的环路滤波器,这使得压缩性能获得大大提升,系统也变得更加完善。
视频编码中,每帧都表明着一幅静止的图像。而在进行实际压缩时,会采起各类算法以减小数据的容量,其中 IPB 帧就是最多见的一种。
在 H264 的概念中有一个帧称为 IDR 帧,那么 IDR 帧与 I 帧的区别是什么呢 ? 首先要看下 IDR 的英文全称 instantaneous decoding refresh picture , 由于 H264 采用了多帧预测,因此 I 帧以后的 P 帧有可能会参考 I 帧以前的帧,这就使得在随机访问的时候不能以找到 I 帧做为参考条件,由于即便找到 I 帧,I 帧以后的帧仍是有可能解析不出来,而 IDR 帧就是一种特殊的 I 帧,即这一帧以后的全部参考帧只会参考到这个 IDR 帧,而不会再参考前面的帧。在解码器中,一旦收到第一个 IDR 帧,就会当即清理参考帧缓冲区,并将 IDR 帧做为被参考的帧。
DTS 主要用视频的解码,全称为(Decoding Time Stamp), PTS 主要用于解码阶段进行视频的同步和输出, 全称为 (Presentation Time Stamp) 。在没有 B 帧的状况下, DTS 和 PTS 的输出顺序是同样的。由于 B 帧打乱了解码和显示的顺序,因此一旦存在 B 帧, PTS 与 DTS 势必就会不一样。在大多数编解码标准(H.264 或者 HEVC) 中,编码顺序和输入顺序并不一致,因而才会须要 PTS 和 DTS 这两种不一样的时间戳。
两个 I 帧之间造成的一组图片,就是 GOP (Group Of Picture) 的概念。一般在为编码器设置参数的时候,必需要设置 gop_size 的值,其表明的是两个 I 帧之间的帧数目。一个 GOP 中容量最大的帧就是 I 帧,因此相对来说,gop_size 设置得越大,整个画面的质量就会越好,可是在解码端必须从接收到的第一个 I 帧开始才能够正确的解码出原始图像,不然会没法正确解码,在提升视频质量的技巧中,还有个技巧是多使用 B 帧,通常来讲,I 的压缩率是 7 (与 JPG 差很少),P 是 20 ,B 能够达到 50 ,可见使用 B 帧能节省大量空间,节省出来的空间能够用来更多地保存 I 帧,这样就能在相同的码率下提供更好的画质,因此咱们要根据不一样的业务场景,适当地设置 gop_size 的大小,以获得更高质量的视频。
结合下图,但愿能够帮组你们更好的理解 DTS 和 PTS 的概念。
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 的上下文环境以及渲染视频数据。
使用 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;
}
复制代码
初始化 EGL
//初始化egl,后两个参数为主次版本号
if (EGL_TRUE != eglInitialize(display, 0, 0)) {
LOGD("eglInitialize failed");
showMessage(env, "eglInitialize failed", false);
return;
}
复制代码
肯定可用的渲染表面( 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;
}
复制代码
建立渲染表面 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;
}
复制代码
建立渲染上下文 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;
}
复制代码
指定某个 EGLContext 为当前上下文, 关联起来
//将egl和opengl关联
//两个surface一个读一个写。第二个通常用来离线渲染
if (EGL_TRUE != eglMakeCurrent(display, winSurface, winSurface, context)) {
LOGD("eglMakeCurrent failed");
showMessage(env, "eglMakeCurrent failed", false);
return;
}
复制代码
使用 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<GLuint>(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<GLuint>(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_TEXTURE一、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);
}
复制代码
交换 EGL 的 Surface 的内部缓冲和 EGL 建立的和平台无关的窗口 diaplay
//窗口显示,交换双缓冲区
eglSwapBuffers(display, winSurface);
复制代码
释放资源
/** * 销毁数据 */
void Gles_play::release() {
if (display || winSurface || context) {
//销毁显示设备
eglDestroySurface(display, winSurface);
//销毁上下文
eglDestroyContext(display, context);
//释放窗口
ANativeWindow_release(nativeWindow);
//释放线程
eglReleaseThread();
//中止
eglTerminate(display);
eglMakeCurrent(display, winSurface, EGL_NO_SURFACE, context);
context = EGL_NO_CONTEXT;
display = EGL_NO_SURFACE;
winSurface = nullptr;
winSurface = 0;
nativeWindow = 0;
isPlay = false;
}
}
复制代码
到这里整个 OpenGL ES 渲染工做都完成了,代码已上传到 GitHub 仓库,须要的能够自行查看 在提供一个 Java 端实现 OpenGL ES 实时渲染 YUV 的 DEMO,注意: 测试的时候须要把 raw/*.yuv 放入 sdcard/ 根目录中。
本章的概念比较多,不免会枯燥一些,可是了解这些概念是必须的。下一篇将带来 FFmpeg + LibRtmp 播放器开发练习,支持 rtmp 拉流、本地视频播放(该篇文章和上一篇文章都分别讲解了音频视频基础和渲染就是为了播放器开发作准备),能够先看一下效果(以下图)。是否是有那么一点小小的期待 😜 ,预计在 2 月下旬发布文章,在等一等。