相关文章推荐
重情义的蟠桃  ·  c# ...·  9 月前    · 
重感情的黄瓜  ·  vim 删除 行尾-掘金·  1 年前    · 
非常酷的大象  ·  vb.net textbox focus ...·  1 年前    · 
率性的大熊猫  ·  vba function compile ...·  1 年前    · 

在过去十年伴随着移动互联网的发展浪潮,音视频行业得以飞速发展,音视频技术方面有几个神器是音视频从业者的必备技能,其中OpenGL ES是移动端视频图像处理的核心技能,诸如抖音,美拍,直播中的美白、磨皮、大眼、瘦脸、贴纸等都是OpenGL ES佳作。

安卓上提供了一套基于Java的OpenGL ES的API,但最终还是通过调用Native的方法实现的。此外,音视频是属于一种通用技术,平台相关性不是很明显,普遍的做法是实现一套跨平台的SDK,这样节约的代价是相当可观。基于音视频技术的一些特点,跨平台语言选用c/c++已成为了默认的标准,但c++层面并没有像GLSurfaceView这样封装完整的环境供开发者使用,需要程序猿们手动来实现这个流程。

本篇文章是除了基本概念之外的另一个入门篇,基于c++的OpenGL ES开发环境搭建,具体的来说,是将GLSurfaceView的核心代码用c++来实现,并开放一些重要的api做多线程开发(代码以Android为例)。

二、EGL环境搭建

1、在CmakeLists.txt的加入这样的依赖

目的是在链接目标文件时将安卓中的libEGL.so和libOpenGLES_2.so这两个动态链接库加入开发者的代码中供使用,可以理解为没有这个#include相关文件会file not find。

target_link_libraries(
        target
        GLESv2

2、创建渲染表面

通过调用这个eglGetDisplay() 这个Api来创建渲染表面

3、初始化

通过eglInitialize()来初始化渲染表面

4、获取配置

通过eglChooseConfig来配置OpenGL 版本,RGBA每个通道的精度等

5、创建上下文

通过eglCreateContext来获取EGL环境的上下文

至此5步,EGL环境已经搭建完毕,在使用过程中要根据当前线程的环境适时调 eglMakeCurrent切换线程,每一帧渲染之后调用eglSwapBuffers交换前后台内容。

6、一个简单的EGL换将帮助类

这个类的原型是谷歌提供的一个非官方demo,我将其中的Java代码转换成c++实现,实测有效

#include <GLES2/gl2.h>
#include <EGL/egl.h>
#include <android/native_window.h>
class EglCore {
public:
    EglCore();
    EglCore(const EglCore &) = delete;
    EglCore(EglCore &&) = delete;
    bool init();
    void makeCurrent(EGLSurface surface);
    void swapBuffer(EGLSurface surface);
    EGLSurface createEGLSurface(ANativeWindow *window);
    void destroyEGLSurface(EGLSurface surface);
    void destroy();
private:
    EGLDisplay _display{nullptr};
    EGLConfig _config{nullptr};
    EGLContext _context{nullptr};
bool EglCore::init() {
    if (_display != EGL_NO_DISPLAY) {
        return false;
    _display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    if (EGL_NO_DISPLAY == _display) {
        LOGI("egl get no display");
        return false;
    GLint majorVersion, minorVersion;
    if (!eglInitialize(_display, &majorVersion, &minorVersion)) {
        LOGI("egl initialize fail");
        return false;
    EGLint config_attrs[] = {
            EGL_BUFFER_SIZE, 32,
            EGL_ALPHA_SIZE, 8,
            EGL_BLUE_SIZE, 8,
            EGL_GREEN_SIZE, 8,
            EGL_RED_SIZE, 8,
            EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
            EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
            EGL_NONE
    int num_configs = 0;
    EGLConfig config;
    if (!eglChooseConfig(_display, config_attrs, &config, 1, &num_configs)) {
        LOGI("egl choose config error");
        return false;
    if (config) {
        EGLint context_attrs[] = {
                EGL_CONTEXT_CLIENT_VERSION, 2,
                EGL_NONE
        EGLContext context = eglCreateContext(_display, config, share_context, context_attrs);
    return EGL_NO_CONTEXT != _context;
EGLSurface EglCore::createEGLSurface(ANativeWindow *window) {
    EGLint width, height;
    EGLSurface surface = eglCreateWindowSurface(_display, _config, window, nullptr);
    if (EGL_NO_SURFACE == surface) {
        LOGI("egl create surface error");
        return nullptr;
    return surface;
void EglCore::destroyEGLSurface(EGLSurface surface) {
    eglDestroySurface(_display, surface);
void EglCore::makeCurrent(EGLSurface surface) {
    if (!eglMakeCurrent(_display, surface, surface, _context)) {
        LOGI("egl make current error");
void EglCore::swapBuffer(EGLSurface surface) {
    if (!eglSwapBuffers(_display, surface)) {
        LOGI("egl swap error");
void EglCore::destroy() {
    eglMakeCurrent(_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    eglDestroyContext(_display, _context);
    eglTerminate(_display);
    _display = EGL_NO_DISPLAY;
    _context = EGL_NO_CONTEXT;

三、用c++实现GLSurfaceView

本文提供一个精简版的OpenGL开发环境,能满足相机渲染等基本需求,更多的内容请查看GLSurfaceView源码获取帮助

#include "glview.h" GlView::GlView() { LOGI("GLView Constructor"); void GlView::createSurface(jobject surface, int w, int h) { window = ANativeWindow_fromSurface(env, surface); this->width = w; this->height = h; is_exit = false; future = std::async(std::launch::async, &GlView::guardedRun, this); void GlView::guardedRun() { if (window != nullptr) { eglCore = new EglCore(); eglCore->init(); eglSurface = eglCore->createEGLSurface(window); eglCore->makeCurrent(eglSurface); if (render && window) { render->onSurfaceCreate(nullptr, width, height); render->onDraw(); while (!is_exit) { cond.wait(lock); if (render) { render->onDraw(); if (eglCore) { eglCore->swapBuffer(eglSurface); if (render) { render->onSurfaceDestroy(); if (eglCore) { eglCore->destroyEGLSurface(eglSurface); eglCore->destroy(); delete eglCore; LOGI("GL thread quit"); void GlView::destroySurface() { is_exit = true; cond.notify_all(); if (future.isValid()) { future.wait(); void GlView::setRender(IRender *render_) { this->render = render_; void GlView::requestRender() { cond.notify_all(); GlView::~GlView() { LOGI("~ GlView");

有了一个类似Java层的GLSurfaceView,那么后续的开发就变得容易,只需要将重心放在滤镜的实现上即可。

四、多线程开发

要在多个线程中使用OpenGL ES开发,需先了解一些状态机的概念,其实很简单。前面在创建EGL环境的时候有个eglContext的概念,这个EGL环境的上下文,可在不同线程共享,切换线程的时候通过这个context调用eglMakeCurrent即可使用当前拥有的program一次包装好的滤镜对象。

Broadcast Receiver: 广播接收器,Android的广播要么来自系统,要么来自普通程序。 可以在AndroidMenifest.xml注册广播接收器或在代码注册。 4.   service启动默认是什么线程 答:默认启动的是主线程 5.  使用过哪些设计模式进行开发 答:单例模式、工厂模式、观察者模式 6.   线程 池有什么优点缺点 答:优点就是减少创建线程的消耗,每次创建线程都从线程池拿。 缺点是一直占用内存。 7. 多种布局方式的特
设定一个场景,要读取一个opengl渲染线程一帧的画面到内存,可以使用glReadPixel函数,但是这个函数存在很糟糕的性能问题,为了解决这个问题可以使用如下步骤: 1. 两个opengl渲染线程: A线程负责前台窗口的渲染,B线程在pbuffer上渲染,并且把A...
为了在Java线程进行OpenGL调用,需要为java线程初始化OpenGL环境,initOpenGL函数展示了初始化OpenGL环境的过程。在setupOpenGL方法,在线程上先执行该调用即可。Java代码示例如下: 1 package com.thornbirds.unity; 3 public class PluginTexture {
开发基于OpenGL ES的实时滤镜相机,您需要以下开发环境: 1. 一台计算机:您需要一台配有OpenGL ES 2.0或更高版本的计算机,以便能够编写和测试您的代码。 2. 编辑器:您需要一个能够编写OpenGL ES代码的编辑器。一些常用的编辑器包括Visual Studio Code、Sublime Text和Atom。 3. OpenGL ES SDK:您需要安装适用于您的目标平台的OpenGL ES SDK。例如,如果您的目标平台是Android设备,则需要安装Android NDK。 4. 相机API:您需要使用相机API来访问设备的摄像头。对于Android设备,您可以使用Camera2 API。 5. 滤镜库:您需要选择并集成一个滤镜库,以便能够添加各种滤镜效果。一些常用的滤镜库包括GPUImage和OpenCV。 6. 设备测试:最后,您需要在目标设备上测试您的应用程序。您可以使用Android模拟器进行测试,但最好在实际设备上进行测试,以确保您的应用程序能够正常工作并获得所需的性能。