聲 明
首先异雁,這一系列文章均基于自己的理解和實(shí)踐,可能有不對(duì)的地方僧须,歡迎大家指正纲刀。
其次,這是一個(gè)入門系列担平,涉及的知識(shí)也僅限于夠用示绊,深入的知識(shí)網(wǎng)上也有許許多多的博文供大家學(xué)習(xí)了锭部。
最后,寫文章過程中面褐,會(huì)借鑒參考其他人分享的文章拌禾,會(huì)在文章最后列出,感謝這些作者的分享展哭。
碼字不易湃窍,轉(zhuǎn)載請(qǐng)注明出處!
教程代碼:【Github傳送門】 |
---|
目錄
一匪傍、Android音視頻硬解碼篇:
二秧饮、使用OpenGL渲染視頻畫面篇
- 1映挂,初步了解OpenGL ES
- 2,使用OpenGL渲染視頻畫面
- 3盗尸,OpenGL渲染多視頻柑船,實(shí)現(xiàn)畫中畫
- 4,深入了解OpenGL之EGL
- 5泼各,OpenGL FBO數(shù)據(jù)緩沖區(qū)
- 6鞍时,Android音視頻硬編碼:生成一個(gè)MP4
三、Android FFmpeg音視頻解碼篇
- 1扣蜻,F(xiàn)Fmpeg so庫(kù)編譯
- 2逆巍,Android 引入FFmpeg
- 3,Android FFmpeg視頻解碼播放
- 4莽使,Android FFmpeg+OpenSL ES音頻解碼播放
- 5锐极,Android FFmpeg+OpenGL ES播放視頻
- 6,Android FFmpeg簡(jiǎn)單合成MP4:視屏解封與重新封裝
- 7芳肌,Android FFmpeg視頻編碼
本文你可以了解到
如何在 NDK 層調(diào)用 OpenGL ES 灵再,以及使用 OpenGL ES 來渲染 FFmpeg 解碼出來的視頻數(shù)據(jù)。
一亿笤、渲染流程介紹
在 Java
層翎迁,Android
已經(jīng)為我們提供了 GLSurfaceView
用于 OpenGL ES
的渲染,我們不必關(guān)心 OpenGL ES
中關(guān)于 EGL
部分的內(nèi)容净薛,也無需關(guān)注 OpenGL ES
的渲染流程汪榔。
在 NDK
層,就沒有那么幸運(yùn)了肃拜,Android
沒有為我們提供封裝好 OpenGL ES
工具揍异,所以想要使用 OpenGL ES
全陨,一切就只有從頭做起了。
但是也不必?fù)?dān)心衷掷,關(guān)于 EGL
的使用辱姨,在前面文章【深入了解OpenGL之EGL】中專門做了詳細(xì)的介紹,在 NDK
層戚嗅,也是一樣的雨涛,不過是使用 C/C++
實(shí)現(xiàn)一遍而已。
下圖懦胞,是本文整個(gè)解碼和渲染的流程圖替久。
在【Android FFmpeg視頻解碼播放】中,我們建立了 FFMpeg
解碼線程躏尉,并且將解碼數(shù)據(jù)輸出到本地窗口進(jìn)行渲染蚯根,只用到了一個(gè)線程。
而使用 OpenGL ES
來渲染視頻胀糜,則需要建立另外一個(gè)獨(dú)立線程與 OpenGL ES
進(jìn)行綁定颅拦。
因此,這里涉及到兩個(gè)線程之間的數(shù)據(jù)同步問題教藻,這里距帅,我們將 FFmpeg
解碼出來的數(shù)據(jù)送到 繪制器
中,等待 OpenGL ES
線程的調(diào)用括堤。
特別說明一下
這里碌秸,OpenGL 線程渲染的過程中,不是直接調(diào)用繪制器去渲染悄窃,而是通過一個(gè)代理來間接調(diào)用讥电,這樣 OpenGL 線程就不需要關(guān)心有多少個(gè)繪制器需要調(diào)用,統(tǒng)統(tǒng)交給代理去管理就好了轧抗。
二允趟、創(chuàng)建 OpenGL ES 渲染線程
與 Java
層一樣,先對(duì) EGL
相關(guān)的內(nèi)容進(jìn)行封裝鸦致。
EGLCore
封裝 EGL
底層操作潮剪,如
-
init
初始化 -
eglCreateWindowSurface/eglCreatePbufferSurface
創(chuàng)建渲染表面 -
MakeCurrent
綁定 OpenGL 線程 -
SwapBuffers
交換數(shù)據(jù)緩沖 - ......
EGLSurface
對(duì) EGLCore
進(jìn)一步封裝,主要是對(duì) EGLCore
創(chuàng)建的 EGLSurface
進(jìn)行管理分唾,并對(duì)外提供更加簡(jiǎn)潔的調(diào)用方法抗碰。
EGL
原理請(qǐng)閱讀《深入了解OpenGL之EGL》一文,這里將不再具體介紹绽乔。
封裝 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);
// 根據(jù)本地窗口創(chuàng)建顯示表面
EGLSurface CreateWindSurface(ANativeWindow *window);
EGLSurface CreateOffScreenSurface(int width, int height);
// 將OpenGL上下文和線程進(jìn)行綁定
void MakeCurrent(EGLSurface egl_surface);
// 將緩存數(shù)據(jù)交換到前臺(tái)進(jìn)行顯示
void SwapBuffers(EGLSurface egl_surface);
// 釋放顯示
void DestroySurface(EGLSurface elg_surface);
// 釋放ELG
void Release();
};
具體實(shí)現(xiàn) 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 結(jié)束標(biāo)志
};
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
可以既可以創(chuàng)建前臺(tái)渲染表面,也可以創(chuàng)建離屏渲染表面,離屏渲染主要用于后面合成視頻的時(shí)候使用看疗。
也就是這兩個(gè)方法:
EGLSurface CreateWindSurface(ANativeWindow *window);
EGLSurface CreateOffScreenSurface(int width, int height);
創(chuàng)建 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();
};
具體實(shí)現(xiàn) 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();
}
}
創(chuàng)建 OpenGL ES 渲染線程
定義成員變量
// opengl_render.h
class OpenGLRender {
private:
const char *TAG = "OpenGLRender";
// OpenGL 渲染狀態(tài)
enum STATE {
NO_SURFACE, //沒有有效的surface
FRESH_SURFACE, //持有一個(gè)為初始化的新的surface
RENDERING, //初始化完畢沙峻,可以開始渲染
SURFACE_DESTROY, //surface銷毀
STOP //停止繪制
};
JNIEnv *m_env = NULL;
// 線程依附的JVM環(huán)境
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
相關(guān)的成員變量摔寨,兩個(gè)地方說明一下:
一是,定義了渲染線程的狀態(tài)怖辆,我們將根據(jù)這幾個(gè)狀態(tài)在 OpenGL
線程中做對(duì)應(yīng)的操作是复。
enum STATE {
NO_SURFACE, //沒有有效的surface
FRESH_SURFACE, //持有一個(gè)未初始化的新的surface
RENDERING, //初始化完畢,可以開始渲染
SURFACE_DESTROY, //surface銷毀
STOP //停止繪制
};
二是竖螃,這里包含了一個(gè)渲染器代理 DrawerProxy
淑廊,主要考慮到可能會(huì)同時(shí)解碼多個(gè)視頻,如果只包含一個(gè)繪制器的話特咆,就無法處理了季惩,所以這里將渲染通過代理交給代理者去處理。下一節(jié)再詳細(xì)介紹腻格。
定義成員方法
// opengl_render.h
class OpenGLRender {
private:
// 省略成員變量...
// 初始化相關(guān)的方法
void InitRenderThread();
bool InitEGL();
void InitDspWindow(JNIEnv *env);
// 創(chuàng)建/銷毀 Surface
void CreateSurface();
void DestroySurface();
// 渲染方法
void Render();
// 釋放資源相關(guān)方法
void ReleaseRender();
void ReleaseDrawers();
void ReleaseSurface();
void ReleaseWindow();
// 渲染線程回調(diào)方法
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();
}
具體實(shí)現(xiàn) opengl_rend.cpp
- 啟動(dòng)線程
// opengl_render.cpp
OpenGLRender::OpenGLRender(JNIEnv *env, DrawerProxy *drawer_proxy):
m_drawer_proxy(drawer_proxy) {
this->m_env = env;
//獲取JVM虛擬機(jī)画拾,為創(chuàng)建線程作準(zhǔn)備
env->GetJavaVM(&m_jvm_for_thread);
InitRenderThread();
}
OpenGLRender::~OpenGLRender() {
delete m_egl_surface;
}
void OpenGLRender::InitRenderThread() {
// 使用智能指針,線程結(jié)束時(shí)荒叶,自動(dòng)刪除本類指針
std::shared_ptr<OpenGLRender> that(this);
std::thread t(sRenderThread, that);
t.detach();
}
- 線程狀態(tài)切換
// opengl_render.cpp
void OpenGLRender::sRenderThread(std::shared_ptr<OpenGLRender> that) {
JNIEnv * env;
//將線程附加到虛擬機(jī)碾阁,并獲取env
if (that->m_jvm_for_thread->AttachCurrentThread(&env, NULL) != JNI_OK) {
LOGE(that->TAG, "線程初始化異常");
return;
}
// 初始化 EGL
if(!that->InitEGL()) {
//解除線程和jvm關(guān)聯(lián)
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關(guān)聯(lián)
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();
}
在進(jìn)入 while(true)
渲染循環(huán)之前输虱,創(chuàng)建了 EglSurface
(既上邊封裝的 EGL 工具)些楣, 并調(diào)用了它的 Init
方法進(jìn)行初始化。
進(jìn)入 while
循環(huán)后:
i. 當(dāng)接收到外部的 SurfaceView
時(shí)宪睹,將進(jìn)入 FRESH_SURFACE
狀態(tài)愁茁,這時(shí)將對(duì)窗口進(jìn)行初始化,并把窗口綁定給 EGL
亭病。
ii. 接著鹅很,自動(dòng)進(jìn)入 RENDERING
狀態(tài),開始渲染罪帖。
iii. 同時(shí)促煮,如果檢測(cè)到播放退出,進(jìn)入 STOP
狀態(tài)整袁,則會(huì)釋放資源菠齿,并退出線程。
- 設(shè)置 SurfaceView 坐昙,啟動(dòng)渲染
// 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);
// 繪制區(qū)域的寬高
m_window_width = ANativeWindow_getWidth(m_native_window);
m_window_height = ANativeWindow_getHeight(m_native_window);
//設(shè)置寬高限制緩沖區(qū)中的像素?cái)?shù)量
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視頻解碼播放》中直接使用本地窗口顯示視頻畫面時(shí)一樣的。
接著在 CreateSurface
中將窗口綁定給了 EGL
。
- 渲染
渲染就很簡(jiǎn)單了疾棵,直接調(diào)用渲染代理繪制戈钢,再調(diào)用 EGL
的 SwapBuffers
交換緩沖數(shù)據(jù)顯示。
// opengl_render.cpp
void OpenGLRender::Render() {
if (RENDERING == m_state) {
m_drawer_proxy->Draw();
m_egl_surface->SwapBuffers();
}
}
- 釋放資源
當(dāng)外部調(diào)用 Stop()
方法以后是尔,狀態(tài)變?yōu)?STOP
殉了,將會(huì)調(diào)用 ReleaseRender()
,釋放相關(guān)資源嗜历。
// 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;
}
}
三宣渗、創(chuàng)建 OpenGL ES 繪制器
NDK
層的 OpenGL
繪制過程和 Java
層是一模一樣的,所以將不再贅述這個(gè)過程了梨州,具體請(qǐng)見《初步了解OpenGL ES》和《使用OpenGL渲染視頻畫面》痕囱。代碼也盡量從簡(jiǎn),主要介紹整體流程暴匠,具體代碼可查看【Demo 源碼的 draw 】鞍恢。
基礎(chǔ)繪制器 Drawer
首先將基礎(chǔ)操作封裝到基類中,這里我們不再詳細(xì)貼出代碼每窖,只看繪制的“骨架”:函數(shù)帮掉。
頭文件 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:
// 自定義用戶數(shù)據(jù),可用于存放畫面數(shù)據(jù)
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);
// 純虛函數(shù)窒典,子類實(shí)現(xiàn)
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;
}
這里有兩個(gè)地方重點(diǎn)說明一下蟆炊,
i. void *cst_data
:這個(gè)變量用于存放將要繪制的數(shù)據(jù),它的類型是 void *
瀑志,可以存放任意類型的數(shù)據(jù)指針涩搓,用來存放 FFmpeg
解碼好的畫面數(shù)據(jù)。
ii. 最后的幾個(gè) virtual
函數(shù)劈猪,類似 Java
的 abstract
函數(shù)昧甘,需要子類實(shí)現(xiàn)。
具體實(shí)現(xiàn) drawer.cpp
主要看 Draw()
方法战得,詳細(xì)請(qǐng)看【源碼】
// drawer.cpp
void Drawer::Draw() {
if (IsReadyToDraw()) {
CreateTextureId();
CreateProgram();
BindTexture();
PrepareDraw();
DoDraw();
DoneDraw();
}
}
繪制流程和 Java
層的 OpenGL
繪制流程是一樣的:
- 創(chuàng)建紋理ID
- 創(chuàng)建GL程序
- 激活充边、綁定紋理ID
- 繪制
最后,看下子類的具體實(shí)現(xiàn)常侦。
視頻繪制器 VideoDrawer
在前面的系列文章中浇冰,為了程序的拓展性,定義了渲染器接口 VideoRender
聋亡。在視頻解碼器 VideoDecoder
中肘习,會(huì)在完成解碼后調(diào)用渲染器中的 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;
};
在上文中杀捻,雖然我們已經(jīng)定義了 OpenGLRender
來渲染 OpenGL
井厌,但是并沒有繼承自 VideoRender
蚓庭, 同時(shí)前面說過,OpenGLRender
會(huì)調(diào)用代理渲染器來實(shí)現(xiàn)真正的繪制仅仆。
因此器赞,這里子類 視頻繪制器 VideoDrawer
除了繼承 Drawer
以外,還要繼承 VideoRender
墓拜。具體來看看:
頭文件 video_render.h
// video_render.h
class VideoDrawer: public Drawer, public VideoRender {
public:
VideoDrawer();
~VideoDrawer();
// 實(shí)現(xiàn) VideoRender 定義的方法
void InitRender(JNIEnv *env, int video_width, int video_height, int *dst_size) override ;
void Render(OneFrame *one_frame) override ;
void ReleaseRender() override ;
// 實(shí)現(xiàn)幾類定義的方法
const char* GetVertexShader() override;
const char* GetFragmentShader() override;
void InitCstShaderHandler() override;
void BindTexture() override;
void PrepareDraw() override;
void DoneDraw() override;
};
具體實(shí)現(xiàn) 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, //紋理內(nèi)部格式
origin_width(), origin_height(), // 畫面寬高
0, // 必須為0
GL_RGBA, // 數(shù)據(jù)格式港柜,必須和上面的紋理格式保持一直
GL_UNSIGNED_BYTE, // RGBA每位數(shù)據(jù)的字節(jié)數(shù),這里是BYTE: 1 byte
cst_data);// 畫面數(shù)據(jù)
}
}
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() {
}
這里最主要的兩個(gè)方法是:
Render(OneFrame *one_frame)
: 將解碼好的畫面數(shù)據(jù)并保存到 cst_data
中咳榜。
PrepareDraw()
: 在繪制前夏醉,將 cst_data
中的數(shù)據(jù)通過 glTexImage2D
方法,映射到 OpenGL
的 2D
紋理中涌韩。
繪制代理
前文講到過畔柔,為了兼容多個(gè)視頻解碼渲染的情況,需要定義個(gè)代理繪制器臣樱,把 Drawer
的調(diào)用交給它來實(shí)現(xiàn)靶擦,下面就來看看如何實(shí)現(xiàn)。
定義繪制器代理
// drawer_proxy.h
class DrawerProxy {
public:
virtual void Draw() = 0;
virtual void Release() = 0;
virtual ~DrawerProxy() {}
};
很簡(jiǎn)單雇毫,只有繪制和釋放兩個(gè)外部方法衷旅。
實(shí)現(xiàn)默認(rèn)的代理器 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;
};
這里通過一個(gè)容器來維護(hù)多個(gè)繪制器 Drawer
苗胀。
- 具體實(shí)現(xiàn) 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();
}
實(shí)現(xiàn)也很簡(jiǎn)單,將需要繪制的 Drawer
添加到容器中宇整,在 OpenGLRender
調(diào)用
Draw()
方法的時(shí)候蝗羊,遍歷所有 Drawer
落剪,實(shí)現(xiàn)真正的繪制耸采。
四艳吠、整合播放
以上,完成了
-
OpenGL
線程的建立 -
EGL
的初始化 -
Drawer
繪制器的定義孝冒,VideoDrawer
的建立 -
DrawerProxy
以及DefDrawerProxyImpl
的定義和實(shí)現(xiàn)
最后就差將它們組合到一起柬姚,實(shí)現(xiàn)整個(gè)流程的閉環(huán)拟杉。
定義 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();
};
實(shí)現(xiàn) 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);
// 創(chuàng)建繪制代理
DefDrawerProxyImpl *proxyImpl = new DefDrawerProxyImpl();
// 將video drawer 注入繪制代理中
proxyImpl->AddDrawer(m_v_drawer);
m_v_drawer_proxy = proxyImpl;
// 創(chuàng)建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 中的線程已經(jīng)使用智能指針庄涡,會(huì)自動(dòng)釋放相關(guān)指針
}
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();
}
}
在頁面中啟動(dòng)播放
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, "視頻文件不存在,請(qǐng)?jiān)谑謾C(jī)根目錄下放置 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")
}
}
}