本文你可以了解到
如何在 NDK 层调用 OpenGL ES ,以及使用 OpenGL ES 来渲染 FFmpeg 解码出来的视频数据。
一、渲染流程介绍
在 Java
层,Android
已经为我们提供了 GLSurfaceView
用于 OpenGL ES
的渲染,我们不必关心 OpenGL ES
中关于 EGL
部分的内容,也无需关注 OpenGL ES
的渲染流程。
在 NDK
层,就没有那么幸运了,Android
没有为我们提供封装好 OpenGL ES
工具,所以想要使用 OpenGL ES
,一切就只有从头做起了。
下图,是本文整个解码和渲染的流程图。
渲染流程
我们建立了 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
进行管理,并对外提供更加简洁的调用方法。
本文福利, 免费领取C++音视频学习资料包、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
封装 EGLCore
头文件 elg_core.h
// 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
// 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
可以既可以创建前台渲染表面,也可以创建离屏渲染表面,离屏渲染主要用于后面合成视频的时候使用。
也就是这两个方法:
EGLSurface CreateWindSurface(ANativeWindow *window);
EGLSurface CreateOffScreenSurface(int width, int height);
创建 EglSurface
头文件 egl_surface.h
// 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
// 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 渲染线程
本文福利, 免费领取C++音视频学习资料包、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
定义成员变量
// 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
,主要考虑到可能会同时解码多个视频,如果只包含一个绘制器的话,就无法处理了,所以这里将渲染通过代理交给代理者去处理。下一节再详细介绍。
定义成员方法
// 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);
}
接着在 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
层是一模一样的。代码也尽量从简,主要介绍整体流程
基础绘制器 Drawer
首先将基础操作封装到基类中,这里我们不再详细贴出代码,只看绘制的“骨架”:函数。
头文件 drawer.h
// 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
// 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
// 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
// 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
的调用交给它来实现,下面就来看看如何实现。
定义绘制器代理
// 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
// 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
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 接口
// 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();
}
}
在页面中启动播放
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")
}
}
}
本文福利, 免费领取C++音视频学习资料包、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓