直播SDK加入GPU自定義美顏
直播SDK提供了預設美顏,效果一般,不支援Mac。專案要求能自定義美顏,並支援Mac,iOS雙端。找了好多的文章,試了都不行,谷歌了幾篇文章,加上自己摸索,湊起來總算搞通了。寫下來給要接直播美顏SDK的夥伴捋一捋,PS:玩音視訊的各路大神別見笑。
瞭解情況
查了SDK暴露出來的介面,跟系統AVFoundation的回撥方法一致,直播SDK提供了Block回撥,返回封裝好的CMSampleBufferRef資料,然後在Block裡把CMSampleBufferRef資料傳輸到SDK內部方法推流。
- (void)processVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer { // do something [[SDK sharedSDK].netCallManager sendVideoSampleBuffer:sampleBuffer]; } 複製程式碼
大概思路:
- 取出影象資料CVPixelBufferRef,時間戳CMSampleTimingInfo
- 對CVPixelBufferRef進行美顏處理
- 重新封裝CMSampleBufferRef傳送到SDK方法內推流
採取的美顏濾鏡(非第三方SDK,支援Mac,iOS):
GPUImage用法 input -》do something -》output
網上很多關於GPUImage的用法是對視訊檔案,或者直接用Camera作為輸入源。缺少直接對一幀影象進行處理,所以問題在於如何對CVPixelBufferRef進行濾鏡處理,然後得到處理完的CVPixelBufferRef資料。對為此特意查閱很多資料(參考的網址在最後),並翻了翻GPUImage的程式碼。
-
GPUImage Framework的檔案很清晰,Sources輸入源,Filters是各種濾鏡,Outputs輸出源。
用法基本可以列為管道式,addTarget 相當於 -》: [Sources初始化物件 addTarget:Filter]; // 輸入源 -》濾鏡 [filter addTarget:output輸出源物件];// 濾鏡 -》輸出源 複製程式碼
-
輸入源採用GPUImageMovie,因為裡面有一個- (void)processMovieFrame:(CMSampleBufferRef)movieSampleBuffer方法,支援輸入一幀進行處理。(注意初始化Movie一定要用initWithAsset的方法,傳入nil,否則內部的GPUImageContext不會初始化)
-
濾鏡採取已經組合成美顏濾鏡的GPUImageBeautifyFilter,它是一個FilterGroup。
-
輸出的時候,使用GPUImageRawDataOutput,newFrameAvailableBlock裡取出影象資料,螢幕一片灰色 。如果用GPUImageView直接addSubview顯示,能成功顯示用濾鏡處理過的畫面。
證明資料已經成功處理,在GPUImageMovie的processMovieFrame方法裡也找到newFrameAvailableBlock的回撥,回撥確實有執行。
問題則是我在lockFramebufferForReading和unlockFramebufferAfterReading中間讀取幀資料的操作不對,想到這邊讀資料這麼複雜,就想著這個渲染完的資料到底放在哪裡了,有沒有別的辦法取出?
在前面說過GPUImageBeautifyFilter是一個GPUImageFilterGroup,GPUImageFilterGroup的父類是GPUImageOutput,這就意味著,即使我不新增任何output的target,資料也是可以拿到的。
再翻了下資料,GPUImageOutput的標頭檔案,發現一個frameProcessingCompletionBlock,這個跟我原來的processMovieFrame好像很對應。網上也有說怎麼從GPUImageOutput取出幀資料。那就行了,直接從GPUImageBeautifyFilter裡取,不用設定output的Target,大功告成。
程式碼程式碼程式碼
**說了一堆廢話,不好意思。獻上程式碼:**GPUImageBeautifyFilter程式碼在參考連結的最後一個
// 初始化Filter和GPUImageMovie _beautifyFilter = [[GPUImageBeautifyFilter alloc] init]; _gpumovie = [[GPUImageMovie alloc] initWithAsset:nil]; // 初始化內部資料結構 [_gpumovie addTarget:_beautifyFilter]; //連線過濾器 複製程式碼
// 重新封裝CMSampleBufferRef,並交給SDK推流 // 如果是rtmp協議傳輸視訊流,自己用VideoToolBox封裝。 - (void)sendVideoSampleBuffer:(CVPixelBufferRef)bufferRef time:(CMSampleTimingInfo)timingInfo{ CMSampleBufferRef newSampleBuffer = NULL; CMFormatDescriptionRef outputFormatDescription = NULL; CMVideoFormatDescriptionCreateForImageBuffer( kCFAllocatorDefault, bufferRef, &outputFormatDescription ); OSStatus err = CMSampleBufferCreateForImageBuffer( kCFAllocatorDefault, bufferRef, true, NULL, NULL, outputFormatDescription, &timingInfo, &newSampleBuffer ); if(newSampleBuffer) { [[SDK sharedSDK].netCallManager sendVideoSampleBuffer:newSampleBuffer]; }else { NSString *exceptionReason = [NSString stringWithFormat:@"sample buffer create failed (%i)", (int)err]; @throw [NSException exceptionWithName:NSInvalidArgumentException reason:exceptionReason userInfo:nil]; } } 複製程式碼
最重要的處理部分:
// 此方法由直播SDK負責回撥或者代理執行 // 自定義美顏時最好把直播SDK預設美顏關閉 - (void)processVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer { CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); //取出幀資料/時間戳 -》處理幀資料美顏 -》根據時間戳與畫素資料重新封裝包: [_gpumovie processMovieFrame:sampleBuffer]; // kCVPixelFormatType_420YpCbCr8BiPlanarFullRange 可能需要檢查 CMSampleTimingInfo timingInfo = { .duration= CMSampleBufferGetDuration(sampleBuffer), .presentationTimeStamp=CMSampleBufferGetPresentationTimeStamp(sampleBuffer), .decodeTimeStamp= CMSampleBufferGetDecodeTimeStamp(sampleBuffer) }; __weak typeof(self) weakSelf = self; [_beautifyFilter setFrameProcessingCompletionBlock:^(GPUImageOutput *output, CMTime time) { GPUImageFramebuffer *imageFramebuffer = output.framebufferForOutput; glFinish(); [weakSelf sendVideoSampleBuffer: [imageFramebuffer getRenderTarget] time:timingInfo]; }]; } 複製程式碼
參考:
- ofollow,noindex">stackoverflow.com/questions/2…
- www.jianshu.com/p/dde412cab…
- www.jianshu.com/p/a20995e1a…
- blog.csdn.net/weixin_4243… (GPUImageBeautifyFilter程式碼 )