首先,这一系列文章均基于本身的理解和实践,可能有不对的地方,欢迎你们指正。
其次,这是一个入门系列,涉及的知识也仅限于够用,深刻的知识网上也有许许多多的博文供你们学习了。
最后,写文章过程当中,会借鉴参考其余人分享的文章,会在文章最后列出,感谢这些做者的分享。android
码字不易,转载请注明出处!git
教程代码:【Github传送门】 |
---|
如何在 NDK 层调用 OpenGL ES ,以及使用 OpenGL ES 来渲染 FFmpeg 解码出来的视频数据。github
在 Java
层,Android
已经为咱们提供了 GLSurfaceView
用于 OpenGL ES
的渲染,咱们没必要关心 OpenGL ES
中关于 EGL
部分的内容,也无需关注 OpenGL ES
的渲染流程。web
在 NDK
层,就没有那么幸运了,Android
没有为咱们提供封装好 OpenGL ES
工具,因此想要使用 OpenGL ES
,一切就只有从头作起了。缓存
可是也没必要担忧,关于 EGL
的使用,在前面文章【深刻了解OpenGL之EGL】中专门作了详细的介绍,在 NDK
层,也是同样的,不过是使用 C/C++
实现一遍而已。app
下图,是本文整个解码和渲染的流程图。框架
在【Android FFmpeg视频解码播放】中,咱们创建了 FFMpeg
解码线程,而且将解码数据输出到本地窗口进行渲染,只用到了一个线程。jvm
而使用 OpenGL ES
来渲染视频,则须要创建另一个独立线程与 OpenGL ES
进行绑定。编辑器
所以,这里涉及到两个线程之间的数据同步问题,这里,咱们将 FFmpeg
解码出来的数据送到 绘制器
中,等待 OpenGL ES
线程的调用。ide
特别说明一下
这里,OpenGL 线程渲染的过程当中,不是直接调用绘制器去渲染,而是经过一个代理来间接调用,这样 OpenGL 线程就不须要关心有多少个绘制器须要调用,通通交给代理去管理就行了。
与 Java
层同样,先对 EGL
相关的内容进行封装。
EGLCore
封装 EGL
底层操做,如
init
初始化
eglCreateWindowSurface/eglCreatePbufferSurface
建立渲染表面
MakeCurrent
绑定 OpenGL 线程
SwapBuffers
交换数据缓冲
EGLSurface
对 EGLCore
进一步封装,主要是对 EGLCore
建立的 EGLSurface
进行管理,并对外提供更加简洁的调用方法。
EGL
原理请阅读《深刻了解OpenGL之EGL》一文,这里将再也不具体介绍。
头文件 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; } 复制代码
// egl_core.cpp
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 }; 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; } 复制代码
说明一下,EGL
能够既能够建立前台渲染表面,也能够建立离屏渲染表面,离屏渲染主要用于后面合成视频的时候使用。
// egl_core.cpp
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; } 复制代码
// egl_core.cpp
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); } 复制代码
// egl_core.cpp
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_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_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
状态,则会释放资源,并退出线程。
// 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; } } 复制代码
NDK
层的 OpenGL
绘制过程和 Java
层是如出一辙的,因此将再也不赘述这个过程了,具体请见《初步了解OpenGL ES》和《使用OpenGL渲染视频画面》。代码也尽可能从简,主要介绍总体流程,具体代码可查看【Demo 源码的 draw 】。
首先将基础操做封装到基类中,这里咱们再也不详细贴出代码,只看绘制的“骨架”:函数。
头文件 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
主要看 Draw()
方法,详细请看【源码】
// drawer.cpp
void Drawer::Draw() { if (IsReadyToDraw()) { CreateTextureId(); CreateProgram(); BindTexture(); PrepareDraw(); DoDraw(); DoneDraw(); } } 复制代码
绘制流程和 Java
层的 OpenGL
绘制流程是同样的:
最后,看下子类的具体实现。
在前面的系列文章中,为了程序的拓展性,定义了渲染器接口 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
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
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
的定义和实现
最后就差将它们组合到一块儿,实现整个流程的闭环。
头文件 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(); } 复制代码
// 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") } } } 复制代码