一句話(huà)概述:視頻的幀數(shù)據(jù),傳遞給OpenGL,處理后輸出給FBO,然后取得FBO里的color render buffer,然后通過(guò)CAEAGLLayer上呈現(xiàn)到屏幕
想多了解下音視頻開(kāi)發(fā),看了下kxmovie的代碼,kxmovie是一個(gè)基于FFmpeg的iOS上的開(kāi)源音視頻庫(kù),主體就3部分:
- FFmpeg對(duì)音視頻資源的解碼,輸出一幀幀的數(shù)據(jù)
- 對(duì)數(shù)據(jù)的管理,如緩沖區(qū)管理;播放操作的管理,如停止、開(kāi)始
- 把視頻數(shù)據(jù)呈現(xiàn)到屏幕上
現(xiàn)在要說(shuō)的就是第三段,而kxmovie就是用的OpenGL ES來(lái)處理的(確切的說(shuō)是優(yōu)先使用OpenGL ES來(lái)做的)。
上代碼:
- (CGFloat) presentVideoFrame: (KxVideoFrame *) frame
{
if (_glView) {
[_glView render:frame]; //代碼1
} else {
KxVideoFrameRGB *rgbFrame = (KxVideoFrameRGB *)frame;
_imageView.image = [rgbFrame asImage];
}
_moviePosition = frame.position;
return frame.duration;
}
入口就在代碼1位置,_glView就是用來(lái)呈現(xiàn)視頻畫(huà)面的View,而frame是一幀數(shù)據(jù)。整體就是不斷的在調(diào)用這個(gè)方法,不斷地顯示一幀幀的畫(huà)面。
然后進(jìn)入render方法:
- (void)render: (KxVideoFrame *) frame
{
static const GLfloat texCoords[] = {
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f,
};
[EAGLContext setCurrentContext:_context];
glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer); //代碼1
glViewport(0, 0, _backingWidth, _backingHeight);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(_program);
if (frame) {
[_renderer setFrame:frame]; //代碼2
}
if ([_renderer prepareRender]) { //代碼3
GLfloat modelviewProj[16];
mat4f_LoadOrtho(-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, modelviewProj);
glUniformMatrix4fv(_uniformMatrix, 1, GL_FALSE, modelviewProj);
glVertexAttribPointer(ATTRIBUTE_VERTEX, 2, GL_FLOAT, 0, 0, _vertices);
glEnableVertexAttribArray(ATTRIBUTE_VERTEX);
glVertexAttribPointer(ATTRIBUTE_TEXCOORD, 2, GL_FLOAT, 0, 0, texCoords);
glEnableVertexAttribArray(ATTRIBUTE_TEXCOORD);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
//代碼4
glBindRenderbuffer(GL_RENDERBUFFER, _renderbuffer);
[_context presentRenderbuffer:GL_RENDERBUFFER];
}
(1)代碼1位置,綁定_framebuffer,OpenGL里面有許多的類(lèi)似的Bind函數(shù),我理解的是,這樣做之后,之后所有對(duì)frameBuffer的操作就是針對(duì)這個(gè)frameBuffer的了。官方文檔里有句解釋?zhuān)?/p>
While a non-zero framebuffer object name is bound, GL operations on target GL_FRAMEBUFFER
affect the bound framebuffer object, and queries of target GL_FRAMEBUFFER
or of framebuffer details such as GL_DEPTH_BITS
return state from the bound framebuffer object.
(2)說(shuō)下frameBuffer Object(FBO),具體的使用流程可以參考這篇.簡(jiǎn)單說(shuō),就是OpenGL的繪制結(jié)果不是直接顯示到屏幕上,而是存起來(lái)了,這個(gè)存儲(chǔ)的東西就是FBO。所以FBO里面包含了color、depth、stencil等一些用于顯示的信息。
代碼1就是指定當(dāng)前使用的FBO是哪個(gè),然后執(zhí)行后數(shù)據(jù)就會(huì)輸入到這個(gè)FBO里了。
(3)指定好,數(shù)據(jù)的去向,那也要指定源頭,數(shù)據(jù)從哪來(lái)。我們現(xiàn)在只有一個(gè)frame,所以要把這個(gè)frame數(shù)據(jù)變成OpenGL的輸入。然后就是代碼2,函數(shù)體是:
- (void) setFrame: (KxVideoFrame *) frame
{
KxVideoFrameRGB *rgbFrame = (KxVideoFrameRGB *)frame;
assert(rgbFrame.rgb.length == rgbFrame.width * rgbFrame.height * 3);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
if (0 == _texture)
glGenTextures(1, &_texture); //代碼5
glBindTexture(GL_TEXTURE_2D, _texture);//代碼6
//代碼7
glTexImage2D(GL_TEXTURE_2D,
0,
GL_RGB,
frame.width,
frame.height,
0,
GL_RGB,
GL_UNSIGNED_BYTE,
rgbFrame.rgb.bytes);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
整體來(lái)說(shuō),這一段的作用就是使用傳入的frame,構(gòu)建了一個(gè)紋理_texture。代碼5生成一個(gè)紋理,代碼6綁定紋理,也就是指定下面要操作的是_texture這個(gè)紋理,代碼7應(yīng)該是賦值,最后一個(gè)參數(shù)就是數(shù)據(jù),具體各個(gè)參數(shù)意思看文檔。然后frame數(shù)據(jù)就轉(zhuǎn)換成了texture。
(4)有了數(shù)據(jù)紋理,把紋理傳到OpenGL的pipline里去,就是代碼3,函數(shù)體是:
- (BOOL) prepareRender
{
if (_texture == 0)
return NO;
glActiveTexture(GL_TEXTURE0); //代碼8
glBindTexture(GL_TEXTURE_2D, _texture);//代碼9
glUniform1i(_uniformSampler, 0);//代碼10
return YES;
}
代碼8 選擇一個(gè)紋理槽位,即GL_TEXTURE0;9綁定紋理為_(kāi)texture,這樣_texture和槽位GL_TEXTURE0聯(lián)系上了;代碼10是給shader里的變量賦值,第一個(gè)參數(shù)是指定被賦值的變量的位置,同一個(gè)shader里面會(huì)定義多個(gè)輸入對(duì)象,每個(gè)都有對(duì)應(yīng)的位置,可以這樣獲得:
_uniformSampler = glGetUniformLocation(program, "s_texture");
這就是取得uniform變量s_texture的位置,賦值給_uniformSampler。代碼10第二個(gè)參數(shù)就是指定哪個(gè)紋理,傳入n,就是GL_TEXTURE0+n槽位的紋理,這里傳入0,結(jié)合代碼3.1和3.2,其實(shí)就是把_texture。然后_uniformSampler是s_texture的位置,所以整體就是把_texture賦值給了shader里面的s_texture變量。
這樣,紋理數(shù)據(jù)就傳遞給了shader,進(jìn)入到OpenGL的pipline里了。
(5)OpenGL把數(shù)據(jù)輸入給我們綁定的FBO,F(xiàn)BO管理著各種buffer,其中就有color buffer。代碼4位置,_renderbuffer就是綁定在當(dāng)前FBO上的color buffer,存儲(chǔ)著顏色信息,第一句glBindRenderbuffer指定下面對(duì)GL_RENDERBUFFER的操作是使用GL_RENDERBUFFER,然后_context顯示render buffer。然后數(shù)據(jù)就被顯示到屏幕上了。
最后,render buffer的綁定代碼:
glGenFramebuffers(1, &_framebuffer);
glGenRenderbuffers(1, &_renderbuffer); //代碼11
glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _renderbuffer); //代碼12
[_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer]; //代碼13
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_backingWidth);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_backingHeight);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderbuffer); //代碼14
代碼11和12就是生成和綁定一個(gè)render buffer,但實(shí)際這是render buffer只是生成了一個(gè)名字,并沒(méi)有內(nèi)存空間,而關(guān)鍵的就是代碼13.這一句,_context從self.layer里獲取到一段內(nèi)存給新生成的render buffer,根據(jù)iOS文檔,render buffer和這個(gè)CAEAGLLayer對(duì)象是共享內(nèi)存的。如圖:

沒(méi)看到具體文檔,但我猜這就是為什么_context調(diào)用presentRenderbuffer,然后self.layer就會(huì)更新內(nèi)容的原因,這句代碼把_context、render buffer和self.layer關(guān)聯(lián)了起來(lái)。
代碼14就是把render buffer 綁定給FBO,注意第二個(gè)參數(shù)使用GL_COLOR_ATTACHMENT0,這個(gè)指定了這個(gè)render buffer使用來(lái)存儲(chǔ)顏色信息的,所以O(shè)penGL把數(shù)據(jù)渲染到FBO后,這個(gè)render buffer保存的是顏色信息。
參考文章:FBO的使用
OpenGL ES渲染到layeriOS官方文檔里面Rendering to a Core Animation Layer那一節(jié)
OpenGL ES2.0 – Iphone開(kāi)發(fā)指引
OpenGL ES入門(mén)系列