本文你可以了解到
如何在 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
线程中做对应的操作。
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
- 启动线程
// 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();
}
- 线程状态切换
// 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 ,启动渲染
// 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
交换缓冲数据显示。
// opengl_render.cpp
void OpenGLRender::Render() {
if (RENDERING == m_state) {
m_drawer_proxy->Draw();
m_egl_surface->SwapBuffers();
}
}
- 释放资源
当外部调用 Stop()
方法以后,状态变为 STOP
,将会调用 ReleaseRender()
,释放相关资源。
// 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()
方法,详细请看【源码】
// drawer.cpp
void Drawer::Draw() {
if (IsReadyToDraw()) {
CreateTextureId();
CreateProgram();
BindTexture();
PrepareDraw();
DoDraw();
DoneDraw();
}
}
绘制流程和 Java
层的 OpenGL
绘制流程是一样的:
- 创建纹理ID
- 创建GL程序
- 激活、绑定纹理ID
- 绘制
最后,看下子类的具体实现。
视频绘制器 VideoDrawer
在前面的系列文章中,为了程序的拓展性,定义了渲染器接口 VideoRender
。在视频解码器 VideoDecoder
中,会在完成解码后调用渲染器中的 Render()
方法。
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
// 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
// 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")
}
}
}