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留有交互接口,因此說它是一箇中間層