一句話概述:視頻的幀數(shù)據(jù)芳室,傳遞給OpenGL,處理后輸出給FBO北救,然后取得FBO里的color render buffer门怪,然后通過CAEAGLLayer上呈現(xiàn)到屏幕
想多了解下音視頻開發(fā),看了下kxmovie的代碼票髓,kxmovie是一個(gè)基于FFmpeg的iOS上的開源音視頻庫攀涵,主體就3部分:
- FFmpeg對音視頻資源的解碼,輸出一幀幀的數(shù)據(jù)
- 對數(shù)據(jù)的管理洽沟,如緩沖區(qū)管理以故;播放操作的管理,如停止裆操、開始
- 把視頻數(shù)據(jù)呈現(xiàn)到屏幕上
現(xiàn)在要說的就是第三段怒详,而kxmovie就是用的OpenGL ES來處理的(確切的說是優(yōu)先使用OpenGL ES來做的)。
上代碼:
- (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就是用來呈現(xiàn)視頻畫面的View昆烁,而frame是一幀數(shù)據(jù)。整體就是不斷的在調(diào)用這個(gè)方法缎岗,不斷地顯示一幀幀的畫面静尼。
然后進(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里面有許多的類似的Bind函數(shù)鼠渺,我理解的是,這樣做之后眷细,之后所有對frameBuffer的操作就是針對這個(gè)frameBuffer的了拦盹。官方文檔里有句解釋:
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)說下frameBuffer Object(FBO),具體的使用流程可以參考這篇.簡單說,就是OpenGL的繪制結(jié)果不是直接顯示到屏幕上薪鹦,而是存起來了掌敬,這個(gè)存儲的東西就是FBO惯豆。所以FBO里面包含了color、depth奔害、stencil等一些用于顯示的信息楷兽。
代碼1就是指定當(dāng)前使用的FBO是哪個(gè),然后執(zhí)行后數(shù)據(jù)就會輸入到這個(gè)FBO里了华临。
(3)指定好芯杀,數(shù)據(jù)的去向,那也要指定源頭雅潭,數(shù)據(jù)從哪來揭厚。我們現(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);
}
整體來說,這一段的作用就是使用傳入的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綁定紋理為_texture,這樣_texture和槽位GL_TEXTURE0聯(lián)系上了澎怒;代碼10是給shader里的變量賦值褒搔,第一個(gè)參數(shù)是指定被賦值的變量的位置,同一個(gè)shader里面會定義多個(gè)輸入對象喷面,每個(gè)都有對應(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趣倾,存儲著顏色信息聘惦,第一句glBindRenderbuffer指定下面對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è)名字,并沒有內(nèi)存空間瘟仿,而關(guān)鍵的就是代碼13.這一句箱锐,_context從self.layer里獲取到一段內(nèi)存給新生成的render buffer,根據(jù)iOS文檔劳较,render buffer和這個(gè)CAEAGLLayer對象是共享內(nèi)存的驹止。如圖:
沒看到具體文檔,但我猜這就是為什么_context調(diào)用presentRenderbuffer观蜗,然后self.layer就會更新內(nèi)容的原因臊恋,這句代碼把_context、render buffer和self.layer關(guān)聯(lián)了起來墓捻。
代碼14就是把render buffer 綁定給FBO,注意第二個(gè)參數(shù)使用GL_COLOR_ATTACHMENT0抖仅,這個(gè)指定了這個(gè)render buffer使用來存儲顏色信息的,所以O(shè)penGL把數(shù)據(jù)渲染到FBO后砖第,這個(gè)render buffer保存的是顏色信息撤卢。
參考文章:FBO的使用
OpenGL ES渲染到layeriOS官方文檔里面Rendering to a Core Animation Layer那一節(jié)
OpenGL ES2.0 – Iphone開發(fā)指引
OpenGL ES入門系列