【Android 音視頻開發打怪升級:FFmpeg音視頻編解碼篇】5、Android FFmpeg+OpenGL ES播放視頻

聲 明

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

碼字不易,轉載請註明出處!git

教程代碼:【Github傳送門

目錄

1、Android音視頻硬解碼篇:
2、使用OpenGL渲染視頻畫面篇
3、Android FFmpeg音視頻解碼篇

本文你能夠了解到

如何在 NDK 層調用 OpenGL ES ,以及使用 OpenGL ES 來渲染 FFmpeg 解碼出來的視頻數據。github

1、渲染流程介紹

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 線程就不須要關心有多少個繪製器須要調用,通通交給代理去管理就行了。

2、建立 OpenGL ES 渲染線程

Java 層同樣,先對 EGL 相關的內容進行封裝。

EGLCore 封裝 EGL 底層操做,如

  • init 初始化
  • eglCreateWindowSurface/eglCreatePbufferSurface 建立渲染表面
  • MakeCurrent 綁定 OpenGL 線程
  • SwapBuffers 交換數據緩衝
  • ......

EGLSurfaceEGLCore 進一步封裝,主要是對 EGLCore 建立的 EGLSurface 進行管理,並對外提供更加簡潔的調用方法。

EGL 原理請閱讀《深刻了解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);   // 根據本地窗口建立顯示錶面  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; } 複製代碼

建立 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 渲染線程

定義成員變量

// 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); } 複製代碼

能夠看到,ANativeWindow 窗口的初始化和《Android FFmpeg視頻解碼播放》中直接使用本地窗口顯示視頻畫面時同樣的。

接着在 CreateSurface 中將窗口綁定給了 EGL

  • 渲染

渲染就很簡單了,直接調用渲染代理繪製,再調用 EGLSwapBuffers 交換緩衝數據顯示。

// 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;  } } 複製代碼

3、建立 OpenGL ES 繪製器

NDK 層的 OpenGL 繪製過程和 Java 層是如出一轍的,因此將再也不贅述這個過程了,具體請見《初步瞭解OpenGL ES》和《使用OpenGL渲染視頻畫面》。代碼也儘可能從簡,主要介紹總體流程,具體代碼可查看【Demo 源碼的 draw 】。

基礎繪製器 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 函數,相似 Javaabstract 函數,須要子類實現。

具體實現 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

// 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 方法,映射到 OpenGL2D 紋理中。

繪製代理

前文講到過,爲了兼容多個視頻解碼渲染的狀況,須要定義個代理繪製器,把 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 ,實現真正的繪製。

4、整合播放

以上,完成了

  • 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")  }  } } 複製代碼
相關文章
相關標籤/搜索