基於Surface的視頻編解碼與OpenGL ES渲染

http://blog.csdn.net/gh_home/article/details/52399959


1. 概述

這篇文章所做的事情是這樣的: 
1. 從一個.mp4文件中解碼視頻流到surface上 
2. 利用OpenGL ES渲染改變視頻流中每一幀的內容 
3. 將改變後的視頻流重新編碼輸出到一個新的.mp4文件

所有代碼可在此處下載:https://github.com/GH-HOME/DecodeEncodeMP4

2. 數據流

圖像的數據流按照以下方式傳遞。 
這裏寫圖片描述

3. 工作流

1. 初始化MediaEncoder(視頻編碼)、MediaDecoder(視頻解碼)、MediaMux(生成MP4文件合成音頻)、MediaExtractor(分割視頻與音頻)。在這一步中,encoder和decoder需要分別綁定一個surface。

核心代碼如下: 
1)初始化編碼器與MediaMux

     MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, WIDTH, HEIGHT);
     format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
             MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
     format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
     format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
     format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
     encoder = null;
     try {
         encoder = MediaCodec.createEncoderByType(MIME_TYPE);
         encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
         encodesurface=encoder.createInputSurface();
         encoder.start();
         mMuxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
     }catch (IOException ioe) {
         throw new RuntimeException("failed init encoder", ioe);
     }
     mTrackIndex = -1;
     mMuxerStarted = false;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

2)初始化解碼器與MediaExtractor

try {
     File inputFile = new File(FILES_DIR, INPUT_FILE);   // must be an absolute path
     if (!inputFile.canRead()) {
          throw new FileNotFoundException("Unable to read " + inputFile);
      }
      extractor = new MediaExtractor();
      extractor.setDataSource(inputFile.toString());
      DecodetrackIndex = selectTrack(extractor);
      if (DecodetrackIndex < 0) {
          throw new RuntimeException("No video track found in " + inputFile);
      }
      extractor.selectTrack(DecodetrackIndex);

      MediaFormat format = extractor.getTrackFormat(DecodetrackIndex);
      if (VERBOSE) {
          Log.d(TAG, "Video size is " + format.getInteger(MediaFormat.KEY_WIDTH) + "x" +
                  format.getInteger(MediaFormat.KEY_HEIGHT));
      }

      outputSurface = new CodecOutputSurface(saveWidth, saveHeight,encodersurface);
      String mime = format.getString(MediaFormat.KEY_MIME);
      decoder = MediaCodec.createDecoderByType(mime);
      decoder.configure(format, outputSurface.getSurface(), null, 0);
      decoder.start();
  }catch (IOException e)
  {
      e.printStackTrace();
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

2. 初始化EGL,配置OpenGL的環境

這一步的相關概念可以參照 http://www.cnitblog.com/zouzheng/archive/2011/05/30/74326.html 
主要是配置以下幾個內容

數據類型 取值
EGLDisplay (系統顯示 ID 或句柄)
EGLConfig (Surface 的 EGL 配置)
EGLSurface (系統窗口或 frame buffer 句柄)
EGLContext (OpenGL ES 圖形上下文)

在這裏我們需要建立兩個EGLContext ,一個用於控制接收從mp4文件中傳過來的數據,一個用於控制將EGLSurface上的數據經過OPENGL ES渲染傳遞到encoder中的surface。在初始化第二個EGLContext 的時候需要將其與第一個EGLContext 綁定,這樣兩者可以共享一個Texture ID(也就是實際的圖像數據)。

這部分代碼較多:主要是CodecOutputSurface類中的eglsetup函數 
關鍵代碼是這一句

mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT,
                attrib_list, 0);

        mEGLContextEncoder = EGL14.eglCreateContext(mEGLDisplay, configEncoder, mEGLContext,
                attrib_list, 0);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

保證了兩個EGLContext共享Texture id

3. OPENGL渲染的準備工作

這一步主要是分以下步驟: 
1. 寫shader language的程序,包含VERTEX_SHADER和FRAGMENT_SHADER 
2. 編譯鏈接以及加載上述程序 
3. 獲取VERTEX_SHADER以及FRAGMENT_SHADER中的變量的句柄,創建Texture ID

在參考的Big Flake示例代碼中發現在shader language中可以直接去渲染YUV420編碼的數據,需要加以下標誌聲明

"#extension GL_OES_EGL_image_external : require\n"
  • 1
  • 1

這一部分的程序主要是在CodecOutputSurface類中的setup函數以及STextureRender類中的一些成員函數

4. 繪圖程序的運行過程

在初始化編解碼器後,將解碼器對應的surface和一個SurfaceTexture綁定起來,同時SurfaceTexture的另外一邊與OPRNGL ES中初始化建立的一個Texture ID綁定。這樣就建立了一條由解碼的mp4數據到OPENGL ES的Texture的數據流。 其中SurfaceTexture充當中介,在上述工作準備好後,開啓SurfaceTexture內容偵聽,即回調函數onFrameAvailable。 一旦SurfaceTexture內容發生變化(有新的編碼數據流流入),系統會自動調用onFrameAvailable表明SurfaceTexture中有可用數據,之後我們調用SurfaceTexture的成員函數updateTexImage將當前的圖像流傳遞到OPENGL ES中的texture。

在掉用GLES20繪圖函數之前需要先調用

EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)
  • 1
  • 1

這個使得我們通知GPU以及OPENGL ES在執行繪圖指令的時候,是在當前mEGLContext這個上下文繪製在mEGLSurface上的。所以我們在最後繪圖的時候需要makecurrent到與Encoder綁定的surface對應的那個EGLSurface上:之前我們需要這樣綁定這兩個量

EGLSurface mEGLSurfaceEncoder = EGL14.eglCreateWindowSurface(mEGLDisplay, configEncoder, surface,
                surfaceAttribs2, 0);   //creates an EGL window surface and returns its handle
  • 1
  • 2
  • 1
  • 2

4. 總結

對於OpenGL ES與編解碼的結合主要可以參考以下兩個網站 
http://bigflake.com/mediacodec/ 
https://github.com/google/grafika

個人覺得grafika的模塊化寫的更好一點。總體來說,這個流程就是Encoder和Decoder把數據流弄到surface上,然後與OpenGL中的Texture id綁定,之後調用OPENGL ES的繪圖指令渲染的過程,其中EGL就是給OPENGL ES方便移植做中間層的,它在程序中的作用就是爲OPENGL ES做了初始化的工作,同時它也與mediacodec留有交互接口,因此說它是一箇中間層


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章