前言
前面的實(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é)議的sourcePixelBufferAttributes和requiredPixelBufferAttributesForRenderContext的方法中,需要指定對(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的sourcePixelBufferAttributes和requiredPixelBufferAttributesForRenderContext對(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í)間。