首先,這一系列文章均基於本身的理解和實踐,可能有不對的地方,歡迎你們指正。
其次,這是一個入門系列,涉及的知識也僅限於夠用,深刻的知識網上也有許許多多的博文供你們學習了。
最後,寫文章過程當中,會借鑑參考其餘人分享的文章,會在文章最後列出,感謝這些做者的分享。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") } } } 複製代碼