首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【Android 音视频开发打怪升级:FFmpeg音视频编解码篇】

【Android 音视频开发打怪升级:FFmpeg音视频编解码篇】

作者头像
开发的猫
发布于 2020-06-03 15:21:58
发布于 2020-06-03 15:21:58
2.6K0
举报
文章被收录于专栏:猫哥的专栏猫哥的专栏

本文你可以了解到

如何在 NDK 层调用 OpenGL ES ,以及使用 OpenGL ES 来渲染 FFmpeg 解码出来的视频数据。

一、渲染流程介绍

在 Java 层,Android 已经为我们提供了 GLSurfaceView 用于 OpenGL ES 的渲染,我们不必关心 OpenGL ES 中关于 EGL 部分的内容,也无需关注 OpenGL ES 的渲染流程。

在 NDK 层,就没有那么幸运了,Android 没有为我们提供封装好 OpenGL ES 工具,所以想要使用 OpenGL ES ,一切就只有从头做起了。

但是也不必担心,关于 EGL 的使用,在前面文章【深入了解OpenGL之EGL】中专门做了详细的介绍,在 NDK 层,也是一样的,不过是使用 C/C++ 实现一遍而已。

下图,是本文整个解码和渲染的流程图。

渲染流程

在【Android FFmpeg视频解码播放】中,我们建立了 FFMpeg 解码线程,并且将解码数据输出到本地窗口进行渲染,只用到了一个线程。

而使用 OpenGL ES 来渲染视频,则需要建立另外一个独立线程与 OpenGL ES 进行绑定。

因此,这里涉及到两个线程之间的数据同步问题,这里,我们将 FFmpeg 解码出来的数据送到 绘制器 中,等待 OpenGL ES 线程的调用。

特别说明一下 这里,OpenGL 线程渲染的过程中,不是直接调用绘制器去渲染,而是通过一个代理来间接调用,这样 OpenGL 线程就不需要关心有多少个绘制器需要调用,统统交给代理去管理就好了。

二、创建 OpenGL ES 渲染线程

与 Java 层一样,先对 EGL 相关的内容进行封装。

EGLCore 封装 EGL 底层操作,如

  • init 初始化
  • eglCreateWindowSurface/eglCreatePbufferSurface 创建渲染表面
  • MakeCurrent 绑定 OpenGL 线程
  • SwapBuffers 交换数据缓冲
  • ......

EGLSurface 对 EGLCore 进一步封装,主要是对 EGLCore 创建的 EGLSurface 进行管理,并对外提供更加简洁的调用方法。

EGL 原理请阅读《深入了解OpenGL之EGL》一文,这里将不再具体介绍。

封装 EGLCore

头文件 elg_core.h

代码语言:javascript
复制
// egl_core.h

extern "C" {
#include <EGL/egl.h>
#include <EGL/eglext.h>
};

class EglCore {
private:
    const char *TAG = "EglCore";

    // EGL显示窗口
    EGLDisplay m_egl_dsp = EGL_NO_DISPLAY;
    // EGL上线问
    EGLContext m_egl_cxt = EGL_NO_CONTEXT;
    // EGL配置
    EGLConfig m_egl_cfg;

    EGLConfig GetEGLConfig();

public:
    EglCore();
    ~EglCore();

    bool Init(EGLContext share_ctx);

    // 根据本地窗口创建显示表面
    EGLSurface CreateWindSurface(ANativeWindow *window);

    EGLSurface CreateOffScreenSurface(int width, int height);

    // 将OpenGL上下文和线程进行绑定
    void MakeCurrent(EGLSurface egl_surface);

    // 将缓存数据交换到前台进行显示
    void SwapBuffers(EGLSurface egl_surface);

    // 释放显示
    void DestroySurface(EGLSurface elg_surface);

    // 释放ELG
    void Release();
};

具体实现 egl_core.cpp

代码语言:javascript
复制
// egl_core.cpp

bool EglCore::Init(EGLContext share_ctx) {
    if (m_egl_dsp != EGL_NO_DISPLAY) {
        LOGE(TAG, "EGL already set up")
        return true;
    }

    if (share_ctx == NULL) {
        share_ctx = EGL_NO_CONTEXT;
    }

    m_egl_dsp = eglGetDisplay(EGL_DEFAULT_DISPLAY);

    if (m_egl_dsp == EGL_NO_DISPLAY || eglGetError() != EGL_SUCCESS) {
        LOGE(TAG, "EGL init display fail")
        return false;
    }

    EGLint major_ver, minor_ver;
    EGLBoolean success = eglInitialize(m_egl_dsp, &major_ver, &minor_ver);
    if (success != EGL_TRUE || eglGetError() != EGL_SUCCESS) {
        LOGE(TAG, "EGL init fail")
        return false;
    }

    LOGI(TAG, "EGL version: %d.%d", major_ver, minor_ver)

    m_egl_cfg = GetEGLConfig();

    const EGLint attr[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
    m_egl_cxt = eglCreateContext(m_egl_dsp, m_egl_cfg, share_ctx, attr);
    if (m_egl_cxt == EGL_NO_CONTEXT) {
        LOGE(TAG, "EGL create fail, error is %x", eglGetError());
        return false;
    }

    EGLint egl_format;
    success = eglGetConfigAttrib(m_egl_dsp, m_egl_cfg, EGL_NATIVE_VISUAL_ID, &egl_format);
    if (success != EGL_TRUE || eglGetError() != EGL_SUCCESS) {
        LOGE(TAG, "EGL get config fail")
        return false;
    }

    LOGI(TAG, "EGL init success")
    return true;
}

EGLConfig EglCore::GetEGLConfig() {
    EGLint numConfigs;
    EGLConfig config;
    
    static const EGLint CONFIG_ATTRIBS[] = {
          EGL_BUFFER_SIZE, EGL_DONT_CARE,
          EGL_RED_SIZE, 8,
          EGL_GREEN_SIZE, 8,
          EGL_BLUE_SIZE, 8,
          EGL_ALPHA_SIZE, 8,
          EGL_DEPTH_SIZE, 16,
          EGL_STENCIL_SIZE, EGL_DONT_CARE,
          EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
          EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
          EGL_NONE // the end 结束标志
    };

    EGLBoolean success = eglChooseConfig(m_egl_dsp, CONFIG_ATTRIBS, &config, 1, &numConfigs);
    if (!success || eglGetError() != EGL_SUCCESS) {
        LOGE(TAG, "EGL config fail")
        return NULL;
    }
    return config;
}

EGLSurface EglCore::CreateWindSurface(ANativeWindow *window) {
    EGLSurface surface = eglCreateWindowSurface(m_egl_dsp, m_egl_cfg, window, 0);
    if (eglGetError() != EGL_SUCCESS) {
        LOGI(TAG, "EGL create window surface fail")
        return NULL;
    }
    return surface;
}

EGLSurface EglCore::CreateOffScreenSurface(int width, int height) {
    int CONFIG_ATTRIBS[] = {
            EGL_WIDTH, width,
            EGL_HEIGHT, height,
            EGL_NONE
    };

    EGLSurface surface = eglCreatePbufferSurface(m_egl_dsp, m_egl_cfg, CONFIG_ATTRIBS);
    if (eglGetError() != EGL_SUCCESS) {
        LOGI(TAG, "EGL create off screen surface fail")
        return NULL;
    }
    return surface;
}

void EglCore::MakeCurrent(EGLSurface egl_surface) {
    if (!eglMakeCurrent(m_egl_dsp, egl_surface, egl_surface, m_egl_cxt)) {
        LOGE(TAG, "EGL make current fail");
    }
}

void EglCore::SwapBuffers(EGLSurface egl_surface) {
    eglSwapBuffers(m_egl_dsp, egl_surface);
}

void EglCore::DestroySurface(EGLSurface elg_surface) {
    eglMakeCurrent(m_egl_dsp, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    eglDestroySurface(m_egl_dsp, elg_surface);
}

void EglCore::Release() {
    if (m_egl_dsp != EGL_NO_DISPLAY) {
        eglMakeCurrent(m_egl_dsp, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
        eglDestroyContext(m_egl_dsp, m_egl_cxt);
        eglReleaseThread();
        eglTerminate(m_egl_dsp);
    }
    m_egl_dsp = EGL_NO_DISPLAY;
    m_egl_cxt = EGL_NO_CONTEXT;
    m_egl_cfg = NULL;
}

说明一下,EGL 可以既可以创建前台渲染表面,也可以创建离屏渲染表面,离屏渲染主要用于后面合成视频的时候使用。

也就是这两个方法:

代码语言:javascript
复制
EGLSurface CreateWindSurface(ANativeWindow *window);

EGLSurface CreateOffScreenSurface(int width, int height);
创建 EglSurface

头文件 egl_surface.h

代码语言:javascript
复制
// egl_surface.h

#include <android/native_window.h>
#include "egl_core.h"

class EglSurface {
private:

    const char *TAG = "EglSurface";

    ANativeWindow *m_native_window = NULL;

    EglCore *m_core;

    EGLSurface m_surface;

public:
    EglSurface();
    ~EglSurface();
    
    bool Init();
    void CreateEglSurface(ANativeWindow *native_window, int width, int height);
    void MakeCurrent();
    void SwapBuffers();
    void DestroyEglSurface();
    void Release();
};

具体实现 egl_surface.cpp

代码语言:javascript
复制
// egl_surface.cpp

EglSurface::EglSurface() {
    m_core = new EglCore();
}

EglSurface::~EglSurface() {
    delete m_core;
}

bool EglSurface::Init() {
    return m_core->Init(NULL);
}

void EglSurface::CreateEglSurface(ANativeWindow *native_window,
                                  int width, int height) {
    if (native_window != NULL) {
        this->m_native_window = native_window;
        m_surface = m_core->CreateWindSurface(m_native_window);
    } else {
        m_surface = m_core->CreateOffScreenSurface(width, height);
    }
    if (m_surface == NULL) {
        LOGE(TAG, "EGL create window surface fail")
        Release();
    }
    MakeCurrent();
}

void EglSurface::SwapBuffers() {
    m_core->SwapBuffers(m_surface);
}

void EglSurface::MakeCurrent() {
    m_core->MakeCurrent(m_surface);
}

void EglSurface::DestroyEglSurface() {
    if (m_surface != NULL) {
        if (m_core != NULL) {
            m_core->DestroySurface(m_surface);
        }
        m_surface = NULL;
    }
}

void EglSurface::Release() {
    DestroyEglSurface();
    if (m_core != NULL) {
        m_core->Release();
    }
}
创建 OpenGL ES 渲染线程

定义成员变量

代码语言:javascript
复制
// opengl_render.h

class OpenGLRender {
private:

    const char *TAG = "OpenGLRender";

    // OpenGL 渲染状态
    enum STATE {
        NO_SURFACE, //没有有效的surface
        FRESH_SURFACE, //持有一个为初始化的新的surface
        RENDERING, //初始化完毕,可以开始渲染
        SURFACE_DESTROY, //surface销毁
        STOP //停止绘制
    };

    JNIEnv *m_env = NULL;

    // 线程依附的JVM环境
    JavaVM *m_jvm_for_thread = NULL;

    // Surface引用,必须使用引用,否则无法在线程中操作
    jobject m_surface_ref = NULL;

    // 本地屏幕
    ANativeWindow *m_native_window = NULL;

    // EGL显示表面
    EglSurface *m_egl_surface = NULL;

    // 绘制代理器
    DrawerProxy *m_drawer_proxy = NULL;

    int m_window_width = 0;
    int m_window_height = 0;

    STATE m_state = NO_SURFACE;
    
    // 省略其他...
}

除了定义 EGL 相关的成员变量,两个地方说明一下:

一是,定义了渲染线程的状态,我们将根据这几个状态在 OpenGL 线程中做对应的操作。

代码语言:javascript
复制
enum STATE {
    NO_SURFACE, //没有有效的surface
    FRESH_SURFACE, //持有一个未初始化的新的surface
    RENDERING, //初始化完毕,可以开始渲染
    SURFACE_DESTROY, //surface销毁
    STOP //停止绘制
};

二是,这里包含了一个渲染器代理 DrawerProxy ,主要考虑到可能会同时解码多个视频,如果只包含一个绘制器的话,就无法处理了,所以这里将渲染通过代理交给代理者去处理。下一节再详细介绍。

定义成员方法

代码语言:javascript
复制
// opengl_render.h

class OpenGLRender {
private:

    // 省略成员变量...
    
    // 初始化相关的方法
    void InitRenderThread();
    bool InitEGL();
    void InitDspWindow(JNIEnv *env);
    
    // 创建/销毁 Surface
    void CreateSurface();
    void DestroySurface();
    
    // 渲染方法
    void Render();
    
    // 释放资源相关方法
    void ReleaseRender();
    void ReleaseDrawers();
    void ReleaseSurface();
    void ReleaseWindow();
    
    // 渲染线程回调方法
    static void sRenderThread(std::shared_ptr<OpenGLRender> that);

public:
    OpenGLRender(JNIEnv *env, DrawerProxy *drawer_proxy);
    ~OpenGLRender();

    void SetSurface(jobject surface);
    void SetOffScreenSize(int width, int height);
    void Stop();
}

具体实现 opengl_rend.cpp

  • 启动线程
代码语言:javascript
复制
// opengl_render.cpp

OpenGLRender::OpenGLRender(JNIEnv *env, DrawerProxy *drawer_proxy):
m_drawer_proxy(drawer_proxy) {
    this->m_env = env;
    //获取JVM虚拟机,为创建线程作准备
    env->GetJavaVM(&m_jvm_for_thread);
    InitRenderThread();
}

OpenGLRender::~OpenGLRender() {
    delete m_egl_surface;
}

void OpenGLRender::InitRenderThread() {
    // 使用智能指针,线程结束时,自动删除本类指针
    std::shared_ptr<OpenGLRender> that(this);
    std::thread t(sRenderThread, that);
    t.detach();
}
  • 线程状态切换
代码语言:javascript
复制
// opengl_render.cpp

void OpenGLRender::sRenderThread(std::shared_ptr<OpenGLRender> that) {
    JNIEnv * env;

    //将线程附加到虚拟机,并获取env
    if (that->m_jvm_for_thread->AttachCurrentThread(&env, NULL) != JNI_OK) {
        LOGE(that->TAG, "线程初始化异常");
        return;
    }

    // 初始化 EGL
    if(!that->InitEGL()) {
        //解除线程和jvm关联
        that->m_jvm_for_thread->DetachCurrentThread();
        return;
    }

    while (true) {
        switch (that->m_state) {
            case FRESH_SURFACE:
                LOGI(that->TAG, "Loop Render FRESH_SURFACE")
                that->InitDspWindow(env);
                that->CreateSurface();
                that->m_state = RENDERING;
                break;
            case RENDERING:
                that->Render();
                break;
            case SURFACE_DESTROY:
                LOGI(that->TAG, "Loop Render SURFACE_DESTROY")
                that->DestroySurface();
                that->m_state = NO_SURFACE;
                break;
            case STOP:
                LOGI(that->TAG, "Loop Render STOP")
                //解除线程和jvm关联
                that->ReleaseRender();
                that->m_jvm_for_thread->DetachCurrentThread();
                return;
            case NO_SURFACE:
            default:
                break;
        }
        usleep(20000);
    }
}

bool OpenGLRender::InitEGL() {
    m_egl_surface = new EglSurface();
    return m_egl_surface->Init();
}

在进入 while(true) 渲染循环之前,创建了 EglSurface(既上边封装的 EGL 工具), 并调用了它的 Init 方法进行初始化。

进入 while 循环后:

i. 当接收到外部的 SurfaceView 时,将进入 FRESH_SURFACE 状态,这时将对窗口进行初始化,并把窗口绑定给 EGL 。

ii. 接着,自动进入 RENDERING 状态,开始渲染。

iii. 同时,如果检测到播放退出,进入 STOP 状态,则会释放资源,并退出线程。

  • 设置 SurfaceView ,启动渲染
代码语言:javascript
复制
// opengl_render.cpp

void OpenGLRender::SetSurface(jobject surface) {
    if (NULL != surface) {
        m_surface_ref = m_env->NewGlobalRef(surface);
        m_state = FRESH_SURFACE;
    } else {
        m_env->DeleteGlobalRef(m_surface_ref);
        m_state = SURFACE_DESTROY;
    }
}

void OpenGLRender::InitDspWindow(JNIEnv *env) {
    if (m_surface_ref != NULL) {
        // 初始化窗口
        m_native_window = ANativeWindow_fromSurface(env, m_surface_ref);

        // 绘制区域的宽高
        m_window_width = ANativeWindow_getWidth(m_native_window);
        m_window_height = ANativeWindow_getHeight(m_native_window);

        //设置宽高限制缓冲区中的像素数量
        ANativeWindow_setBuffersGeometry(m_native_window, m_window_width,
                                         m_window_height, WINDOW_FORMAT_RGBA_8888);

        LOGD(TAG, "View Port width: %d, height: %d", m_window_width, m_window_height)
    }
}

void OpenGLRender::CreateSurface() {
    m_egl_surface->CreateEglSurface(m_native_window, m_window_width, m_window_height);
    glViewport(0, 0, m_window_width, m_window_height);
}

可以看到,ANativeWindow 窗口的初始化和《Android FFmpeg视频解码播放》中直接使用本地窗口显示视频画面时一样的。

接着在 CreateSurface 中将窗口绑定给了 EGL 。

  • 渲染

渲染就很简单了,直接调用渲染代理绘制,再调用 EGL 的 SwapBuffers 交换缓冲数据显示。

代码语言:javascript
复制
// opengl_render.cpp

void OpenGLRender::Render() {
    if (RENDERING == m_state) {
        m_drawer_proxy->Draw();
        m_egl_surface->SwapBuffers();
    }
}
  • 释放资源

当外部调用 Stop() 方法以后,状态变为 STOP,将会调用 ReleaseRender() ,释放相关资源。

代码语言:javascript
复制
// opengl_render.cpp

void OpenGLRender::Stop() {
    m_state = STOP;
}

void OpenGLRender::ReleaseRender() {
    ReleaseDrawers();
    ReleaseSurface();
    ReleaseWindow();
}

void OpenGLRender::ReleaseSurface() {
    if (m_egl_surface != NULL) {
        m_egl_surface->Release();
        delete m_egl_surface;
        m_egl_surface = NULL;
    }
}

void OpenGLRender::ReleaseWindow() {
    if (m_native_window != NULL) {
        ANativeWindow_release(m_native_window);
        m_native_window = NULL;
    }
}

void OpenGLRender::ReleaseDrawers() {
    if (m_drawer_proxy != NULL) {
        m_drawer_proxy->Release();
        delete m_drawer_proxy;
        m_drawer_proxy = NULL;
    }
}

三、创建 OpenGL ES 绘制器

NDK 层的 OpenGL 绘制过程和 Java 层是一模一样的,所以将不再赘述这个过程了,具体请见《初步了解OpenGL ES》和《使用OpenGL渲染视频画面》。代码也尽量从简,主要介绍整体流程,具体代码可查看【Demo 源码的 draw 】。

基础绘制器 Drawer

首先将基础操作封装到基类中,这里我们不再详细贴出代码,只看绘制的“骨架”:函数。

头文件 drawer.h

代码语言:javascript
复制
// drawer.h
class Drawer {
private:
    // 省略成员变量...
    
    void CreateTextureId();
    void CreateProgram();
    GLuint LoadShader(GLenum type, const GLchar *shader_code);
    void DoDraw();
    
public:

    void Draw();

    bool IsReadyToDraw();
    
    void Release();

protected:
    // 自定义用户数据,可用于存放画面数据
    void *cst_data = NULL;

    void SetSize(int width, int height);
    void ActivateTexture(GLenum type = GL_TEXTURE_2D, GLuint texture = m_texture_id,
                         GLenum index = 0, int texture_handler = m_texture_handler);
    
    // 纯虚函数,子类实现
    virtual const char* GetVertexShader() = 0;
    virtual const char* GetFragmentShader() = 0;
    virtual void InitCstShaderHandler() = 0;
    virtual void BindTexture() = 0;
    virtual void PrepareDraw() = 0;
    virtual void DoneDraw() = 0;
  }

这里有两个地方重点说明一下,

i. void *cst_data :这个变量用于存放将要绘制的数据,它的类型是 void * ,可以存放任意类型的数据指针,用来存放 FFmpeg 解码好的画面数据。

ii. 最后的几个 virtual 函数,类似 Java 的 abstract 函数,需要子类实现。

具体实现 drawer.cpp

主要看 Draw() 方法,详细请看【源码】

代码语言:javascript
复制
// drawer.cpp
void Drawer::Draw() {
    if (IsReadyToDraw()) {
        CreateTextureId();
        CreateProgram();
        BindTexture();
        PrepareDraw();
        DoDraw();
        DoneDraw();
    }
}

绘制流程和 Java 层的 OpenGL 绘制流程是一样的:

  • 创建纹理ID
  • 创建GL程序
  • 激活、绑定纹理ID
  • 绘制

最后,看下子类的具体实现。

视频绘制器 VideoDrawer

在前面的系列文章中,为了程序的拓展性,定义了渲染器接口 VideoRender 。在视频解码器 VideoDecoder 中,会在完成解码后调用渲染器中的 Render() 方法。

代码语言:javascript
复制
class VideoRender {
public:
    virtual void InitRender(JNIEnv *env, int video_width, int video_height, int *dst_size) = 0;
    virtual void Render(OneFrame *one_frame) = 0;
    virtual void ReleaseRender() = 0;
};

在上文中,虽然我们已经定义了 OpenGLRender 来渲染 OpenGL,但是并没有继承自 VideoRender , 同时前面说过,OpenGLRender 会调用代理渲染器来实现真正的绘制。

因此,这里子类 视频绘制器 VideoDrawer 除了继承 Drawer 以外,还要继承 VideoRender 。具体来看看:

头文件 video_render.h

代码语言:javascript
复制
// video_render.h

class VideoDrawer: public Drawer, public VideoRender {
public:

    VideoDrawer();
    ~VideoDrawer();

    // 实现 VideoRender 定义的方法
    void InitRender(JNIEnv *env, int video_width, int video_height, int *dst_size) override ;
    void Render(OneFrame *one_frame) override ;
    void ReleaseRender() override ;

    // 实现几类定义的方法
    const char* GetVertexShader() override;
    const char* GetFragmentShader() override;
    void InitCstShaderHandler() override;
    void BindTexture() override;
    void PrepareDraw() override;
    void DoneDraw() override;
};

具体实现 video_render.cpp

代码语言:javascript
复制
// video_render.cpp

VideoDrawer::VideoDrawer(): Drawer(0, 0) {
}

VideoDrawer::~VideoDrawer() {

}

void VideoDrawer::InitRender(JNIEnv *env, int video_width, int video_height, int *dst_size) {
    SetSize(video_width, video_height);
    dst_size[0] = video_width;
    dst_size[1] = video_height;
}

void VideoDrawer::Render(OneFrame *one_frame) {
    cst_data = one_frame->data;
}

void VideoDrawer::BindTexture() {
    ActivateTexture();
}

void VideoDrawer::PrepareDraw() {
    if (cst_data != NULL) {
        glTexImage2D(GL_TEXTURE_2D, 0, // level一般为0
                     GL_RGBA, //纹理内部格式
                     origin_width(), origin_height(), // 画面宽高
                     0, // 必须为0
                     GL_RGBA, // 数据格式,必须和上面的纹理格式保持一直
                     GL_UNSIGNED_BYTE, // RGBA每位数据的字节数,这里是BYTE: 1 byte
                     cst_data);// 画面数据
    }
}

const char* VideoDrawer::GetVertexShader() {
    const GLbyte shader[] = "attribute vec4 aPosition;\n"
                            "attribute vec2 aCoordinate;\n"
                            "varying vec2 vCoordinate;\n"
                            "void main() {\n"
                            "  gl_Position = aPosition;\n"
                            "  vCoordinate = aCoordinate;\n"
                            "}";
    return (char *)shader;
}

const char* VideoDrawer::GetFragmentShader() {
    const GLbyte shader[] = "precision mediump float;\n"
                            "uniform sampler2D uTexture;\n"
                            "varying vec2 vCoordinate;\n"
                            "void main() {\n"
                            "  vec4 color = texture2D(uTexture, vCoordinate);\n"
                            "  gl_FragColor = color;\n"
                            "}";
    return (char *)shader;
}

void VideoDrawer::ReleaseRender() {
}

void VideoDrawer::InitCstShaderHandler() {

}

void VideoDrawer::DoneDraw() {
}

这里最主要的两个方法是:

Render(OneFrame *one_frame) : 将解码好的画面数据并保存到 cst_data 中。

PrepareDraw() : 在绘制前,将 cst_data 中的数据通过 glTexImage2D 方法,映射到 OpenGL 的 2D 纹理中。

绘制代理

前文讲到过,为了兼容多个视频解码渲染的情况,需要定义个代理绘制器,把 Drawer 的调用交给它来实现,下面就来看看如何实现。

定义绘制器代理

代码语言:javascript
复制
// drawer_proxy.h

class DrawerProxy {
public:
    virtual void Draw() = 0;
    virtual void Release() = 0;
    virtual ~DrawerProxy() {}
};

很简单,只有绘制和释放两个外部方法。

实现默认的代理器 DefDrawerProxyImpl

  • 头文件 def_drawer_proxy_impl.h
代码语言:javascript
复制
// def_drawer_proxy_impl.h

class DefDrawerProxyImpl: public DrawerProxy {

private:
    std::vector<Drawer *> m_drawers;

public:
    void AddDrawer(Drawer *drawer);
    void Draw() override;
    void Release() override;
};

这里通过一个容器来维护多个绘制器 Drawer 。

  • 具体实现 def_drawer_proxy_impl.cpp
代码语言:javascript
复制
// def_drawer_proxy_impl.cpp

void DefDrawerProxyImpl::AddDrawer(Drawer *drawer) {
    m_drawers.push_back(drawer);
}

void DefDrawerProxyImpl::Draw() {
    for (int i = 0; i < m_drawers.size(); ++i) {
        m_drawers[i]->Draw();为初始化
    }
}

void DefDrawerProxyImpl::Release() {
    for (int i = 0; i < m_drawers.size(); ++i) {
        m_drawers[i]->Release();
        delete m_drawers[i];
    }

    m_drawers.clear();
}

实现也很简单,将需要绘制的 Drawer 添加到容器中,在 OpenGLRender 调用

Draw() 方法的时候,遍历所有 Drawer ,实现真正的绘制。

四、整合播放

以上,完成了

  • OpenGL 线程的建立
  • EGL 的初始化
  • Drawer 绘制器的定义,VideoDrawer 的建立
  • DrawerProxy 以及 DefDrawerProxyImpl 的定义和实现

最后就差将它们组合到一起,实现整个流程的闭环。

定义 GLPlayer

头文件 gl_player.h

代码语言:javascript
复制
// gl_player.h

class GLPlayer {

private:
    VideoDecoder *m_v_decoder;
    OpenGLRender *m_gl_render;

    DrawerProxy *m_v_drawer_proxy;
    VideoDrawer *m_v_drawer;

    AudioDecoder *m_a_decoder;
    AudioRender *m_a_render;

public:
    GLPlayer(JNIEnv *jniEnv, jstring path);
    ~GLPlayer();

    void SetSurface(jobject surface);
    void PlayOrPause();
    void Release();
};

实现 gl_player.cpp

代码语言:javascript
复制
GLPlayer::GLPlayer(JNIEnv *jniEnv, jstring path) {
    m_v_decoder = new VideoDecoder(jniEnv, path);

    // OpenGL 渲染
    m_v_drawer = new VideoDrawer();
    m_v_decoder->SetRender(m_v_drawer);

    // 创建绘制代理
    DefDrawerProxyImpl *proxyImpl =  new DefDrawerProxyImpl();
    // 将video drawer 注入绘制代理中
    proxyImpl->AddDrawer(m_v_drawer);

    m_v_drawer_proxy = proxyImpl;
    
    // 创建OpenGL绘制器
    m_gl_render = new OpenGLRender(jniEnv, m_v_drawer_proxy);

    // 音频解码
    m_a_decoder = new AudioDecoder(jniEnv, path, false);
    m_a_render = new OpenSLRender();
    m_a_decoder->SetRender(m_a_render);
}

GLPlayer::~GLPlayer() {
    // 此处不需要 delete 成员指针
    // 在BaseDecoder 和 OpenGLRender 中的线程已经使用智能指针,会自动释放相关指针
}

void GLPlayer::SetSurface(jobject surface) {
    m_gl_render->SetSurface(surface);
}

void GLPlayer::PlayOrPause() {
    if (!m_v_decoder->IsRunning()) {
        m_v_decoder->GoOn();
    } else {
        m_v_decoder->Pause();
    }
    if (!m_a_decoder->IsRunning()) {
        m_a_decoder->GoOn();
    } else {
        m_a_decoder->Pause();
    }
}

void GLPlayer::Release() {
    m_gl_render->Stop();
    m_v_decoder->Stop();
    m_a_decoder->Stop();
}
定义 JNI 接口
代码语言:javascript
复制
// native-lib.cpp

extern "C" {
    JNIEXPORT jint JNICALL
    Java_com_cxp_learningvideo_FFmpegGLPlayerActivity_createGLPlayer(
        JNIEnv *env,
        jobject  /* this */,
        jstring path,
        jobject surface) {
        
        GLPlayer *player = new GLPlayer(env, path);
        player->SetSurface(surface);
        return (jint) player;
    }

    JNIEXPORT void JNICALL
    Java_com_cxp_learningvideo_FFmpegGLPlayerActivity_playOrPause(
        JNIEnv *env,
        jobject  /* this */,
        jint player) {
        
        GLPlayer *p = (GLPlayer *) player;
        p->PlayOrPause();
    }


    JNIEXPORT void JNICALL
    Java_com_cxp_learningvideo_FFmpegGLPlayerActivity_stop(
        JNIEnv *env,
        jobject  /* this */,
        jint player) {
        
        GLPlayer *p = (GLPlayer *) player;
        p->Release();
    }
}
在页面中启动播放
代码语言:javascript
复制
class FFmpegGLPlayerActivity: AppCompatActivity() {

    val path = Environment.getExternalStorageDirectory().absolutePath + "/mvtest.mp4"

    private var player: Int? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_ff_gl_player)
        initSfv()
    }

    private fun initSfv() {
        if (File(path).exists()) {
            sfv.holder.addCallback(object : SurfaceHolder.Callback {
                override fun surfaceChanged(holder: SurfaceHolder, format: Int,
                                            width: Int, height: Int) {}
                override fun surfaceDestroyed(holder: SurfaceHolder) {
                    stop(player!!)
                }

                override fun surfaceCreated(holder: SurfaceHolder) {
                    if (player == null) {
                        player = createGLPlayer(path, holder.surface)
                        playOrPause(player!!)
                    }
                }
            })
        } else {
            Toast.makeText(this, "视频文件不存在,请在手机根目录下放置 mvtest.mp4", Toast.LENGTH_SHORT).show()
        }
    }

    private external fun createGLPlayer(path: String, surface: Surface): Int
    private external fun playOrPause(player: Int)
    private external fun stop(player: Int)

    companion object {
        init {
            System.loadLibrary("native-lib")
        }
    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除
目录
  • 本文你可以了解到
  • 一、渲染流程介绍
  • 二、创建 OpenGL ES 渲染线程
    • 封装 EGLCore
    • 创建 EglSurface
    • 创建 OpenGL ES 渲染线程
  • 三、创建 OpenGL ES 绘制器
    • 基础绘制器 Drawer
    • 视频绘制器 VideoDrawer
    • 绘制代理
  • 四、整合播放
    • 定义 GLPlayer
    • 定义 JNI 接口
    • 在页面中启动播放
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档