從kxmovie代碼看iOS上OpenGL ES的顯示流程

一句話(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)存的。如圖:

Core Animation shares the renderbuffer with OpenGL ES

沒(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)系列

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容