OpenGL ES實(shí)踐教程(九)OpenGL與視頻混合

前言

前面的實(shí)踐教程:
OpenGL ES實(shí)踐教程1-Demo01-AVPlayer
OpenGL ES實(shí)踐教程2-Demo02-攝像頭采集數(shù)據(jù)和渲染
OpenGL ES實(shí)踐教程3-Demo03-Mirror
OpenGL ES實(shí)踐教程4-Demo04-VR全景視頻播放
OpenGL ES實(shí)踐教程5-Demo05-多重紋理實(shí)現(xiàn)圖像混合
OpenGL ES實(shí)踐教程6-Demo06-全景視頻獲取焦點(diǎn)
OpenGL ES實(shí)踐教程7-Demo07-多濾鏡疊加處理
OpenGL ES實(shí)踐教程8-Demo08-blend混合與shader混合
其他教程請(qǐng)移步OpenGL ES文集。

在前面的文章《AVFoundation詳細(xì)解析(一)視頻合并與混音》介紹如何用AVFoundation的指令進(jìn)行視頻合并與混音,GPUImage文集中也介紹了第三方擴(kuò)展的GPUImage視頻混合和基于AVFoundation指令的GPUImage視頻混合。
最近在幫一個(gè)群友解決貼圖問題的時(shí)候,我突然想起可以用AVFoundation的接口抽象優(yōu)勢,輔以O(shè)penGL ES對(duì)圖像處理的優(yōu)點(diǎn),進(jìn)行比較容易的視頻混合。

核心思路

用AVFoundation處理視頻合并的時(shí)間軸關(guān)系(混合規(guī)則),用OpenGL ES處理兩個(gè)視頻圖像混合。
1、用AVURLAsset加載視頻,取得視頻相關(guān)的軌道信息;
2、用AVMutableComposition承載視頻的合并信息,主要是添加音頻和視頻軌道,同時(shí)記錄一個(gè)時(shí)間軸,表明一個(gè)時(shí)間點(diǎn),應(yīng)該有哪些音頻軌道和視頻軌道;
3、新建AVMutableVideoComposition類,并且設(shè)定自定義的視頻合并類;
4、用AVMutableComposition新建AVPlayerItem類,并設(shè)定videoComposition為第三步創(chuàng)建的AVMutableVideoComposition類;
5、用AVPlayerItem創(chuàng)建AVPlayer;
6、開始播放后,如果有視頻需要顯示,會(huì)通過AVVideoCompositing協(xié)議的startVideoCompositionRequest:方法進(jìn)行回調(diào),request參數(shù)包括有當(dāng)前時(shí)間節(jié)點(diǎn)的視頻軌道信息,可以通過request的sourceFrameByTrackID:方法取得視頻幀信息,用OpenGL進(jìn)行圖像處理,最后把渲染的結(jié)果通過finishWithComposedVideoFrame:回傳。

上面的1~5步驟主要是AVFoundation相關(guān)的操作,在之前有所介紹,本文重點(diǎn)介紹第6步的OpenGL ES處理。

效果

正常播放視頻的時(shí)候:


正常播放視頻

視頻進(jìn)行混合的時(shí)候:


視頻混合

代碼解析

demo的地址在這里

1、從視頻軌道中取出視頻幀的圖像

AVAsynchronousVideoCompositionRequest的sourceTrackIDs屬性存在當(dāng)前可獲取的視頻軌道信息,再通過sourceFrameByTrackID:方法可以取得CVPixelBufferRef格式的視頻信息。
需要注意的是,在AVVideoCompositing協(xié)議的sourcePixelBufferAttributesrequiredPixelBufferAttributesForRenderContext的方法中,需要指定對(duì)應(yīng)像素的格式。

2、配置OpenGL ES渲染到紋理

通過CVOpenGLESTextureCacheCreateTextureFromImage方法,創(chuàng)建目標(biāo)紋理destTextureRef,并且用glFramebufferTexture2D綁定到幀緩存上。

3、配置GLKBaseEffect

同樣用CVOpenGLESTextureCacheCreateTextureFromImage方法,創(chuàng)建視頻幀信息相關(guān)的紋理,賦值給GLKBaseEffect的texture2d0屬性,并配置好頂點(diǎn)、紋理相關(guān)數(shù)據(jù),最后使用glDrawArrays繪制圖像。

4、返回CVPixelBufferRef數(shù)據(jù)

如果是處于合并狀態(tài)下,可能需要多次GLKBaseEffect的繪制,最終再返回CVPixelBufferRef。(這里比較推薦使用多重紋理的合并圖像方式)

OpenGL ES相關(guān)的核心代碼:

- (void)prepareToDraw:(CVPixelBufferRef)videoPixelBuffer andDestination:(CVPixelBufferRef)destPixelBuffer {
    // 準(zhǔn)備繪制相關(guān)
    if ([EAGLContext currentContext] != context) {
        [EAGLContext setCurrentContext:context];
    }
    glBindFramebuffer(GL_FRAMEBUFFER, frameBufferId);
    [self cleanUpTextures];
    glViewport(0, 0, (GLsizei)CVPixelBufferGetWidth(destPixelBuffer), (GLsizei)CVPixelBufferGetHeight(destPixelBuffer));
    
    BOOL success = NO;
    do {
        CVReturn ret;
        
        // 配置渲染目標(biāo)
        ret = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
                                                           videoTextureCache,
                                                           destPixelBuffer,
                                                           NULL,
                                                           GL_TEXTURE_2D,
                                                           GL_RGBA,
                                                           (int)CVPixelBufferGetWidth(destPixelBuffer),
                                                           (int)CVPixelBufferGetHeight(destPixelBuffer),
                                                           GL_BGRA,
                                                           GL_UNSIGNED_BYTE,
                                                           0,
                                                           &destTextureRef);
        if (ret) {
            NSLog(@"Error at CVOpenGLESTextureCacheCreateTextureFromImage %d", ret);
            break;
        }
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, CVOpenGLESTextureGetTarget(destTextureRef), CVOpenGLESTextureGetName(destTextureRef), 0);
        if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
            NSLog(@"Error at glFramebufferTexture2D");
            break;
        }
        
        // 上傳圖像
        ret = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
                                                           videoTextureCache,
                                                           videoPixelBuffer,
                                                           NULL,
                                                           GL_TEXTURE_2D,
                                                           GL_RGBA,
                                                           (int)CVPixelBufferGetWidth(videoPixelBuffer),
                                                           (int)CVPixelBufferGetHeight(videoPixelBuffer),
                                                           GL_BGRA,
                                                           GL_UNSIGNED_BYTE,
                                                           0,
                                                           &videoTextureRef);
        
        if (ret) {
            NSLog(@"Error at CVOpenGLESTextureCacheCreateTextureFromImage %d", ret);
            break;
        }
        glBindTexture(CVOpenGLESTextureGetTarget(videoTextureRef), CVOpenGLESTextureGetName(videoTextureRef));
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        baseEffect.texture2d0.name = CVOpenGLESTextureGetName(videoTextureRef);
        baseEffect.texture2d0.target = CVOpenGLESTextureGetTarget(videoTextureRef);
        glEnableVertexAttribArray(GLKVertexAttribPosition);
        glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, baseVertices);
        glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
        glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, baseVertices + 3);
        [baseEffect prepareToDraw];
        
        glClearColor(0.0, 0.0, 0.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        glDrawArrays(GL_TRIANGLES, 0, 6); // 6個(gè)頂點(diǎn),2個(gè)三角形

        success = YES;
    } while (NO);
    
    if (!success) {
        NSLog(@"render is %@", success ? @"success":@"fail");
    }
}

遇到的問題

1、運(yùn)行中出現(xiàn)GLError 0x0506

通過查錯(cuò)誤碼,知道錯(cuò)誤是GL_INVALID_FRAMEBUFFER_OPERATION。
即是在framebuffer未初始化完成時(shí)候,對(duì)framebuffer進(jìn)行read/write/render的操作。
自信查看代碼,發(fā)現(xiàn)是glClearColor提前,放在glBindFramebuffer之后,glFramebufferTexture2D之前。
解決方案是把glClearColor放在glDrawArray之前。

2、CVOpenGLESTextureCacheCreateTextureFromImage返回錯(cuò)誤

CVOpenGLESTextureCacheCreateTextureFromImage的參數(shù)信息需要與AVVideoCompositing的sourcePixelBufferAttributesrequiredPixelBufferAttributesForRenderContext對(duì)齊。

總結(jié)

這里的視頻混合,為了節(jié)省開發(fā)時(shí)間,OpenGL ES的上下文管理用的GLKit,視頻混合部分用的頂點(diǎn)來簡單區(qū)分,如果要深入開發(fā),需要用自己的shader來處理視頻混合的規(guī)則。
OpenGL ES的文集有將近半年沒更新,一部分原因是新的工作比較忙碌,一部分原因是覺得需要沉淀一段時(shí)間。
如今恢復(fù)更新,維持一定的精力在圖形學(xué)上,歡迎簡友一起探討有關(guān)OpenGL ES的問題,私信郵箱均可。

PS:如果demo代碼寫的精簡,架構(gòu)清晰,既有利于自己開發(fā)維護(hù),也會(huì)節(jié)省其他人很多時(shí)間。

最后編輯于
?著作權(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)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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