最近需要給直播項(xiàng)目中添加美顏的功能,調(diào)研了很多SDK和開(kāi)源代碼(視決,涂圖,七牛,金山云,videoCore等),綜合成本/效果/對(duì)項(xiàng)目侵入性,最后決定使用一款基于GPUImage實(shí)現(xiàn)的 BeautifyFaceDemo美顏濾鏡。
關(guān)于濾鏡代碼和實(shí)現(xiàn)思路可以到BeautifyFace Github和作者琨君的簡(jiǎn)書(shū)中查看。
集成GPUImageBeautifyFilter和GPUImage Framework
首先需要集成好GPUImage,通過(guò)觀(guān)察目前iOS平臺(tái),90%以上美顏方案都是基于這個(gè)框架來(lái)做的。
原來(lái)項(xiàng)目中的AVCaptureDevice需要替換成GPUImageVideoCamera,刪除諸如AVCaptureSession/AVCaptureDeviceInput/AVCaptureVideoDataOutput這種GPUImage實(shí)現(xiàn)了的部分。修改一些生命周期,攝像頭切換,橫豎屏旋轉(zhuǎn)等相關(guān)邏輯,保證前后行為統(tǒng)一。
聲明需要的屬性
@property (nonatomic, strong) GPUImageVideoCamera *videoCamera;
//屏幕上顯示的View
@property (nonatomic, strong) GPUImageView *filterView;
//BeautifyFace美顏濾鏡
@property (nonatomic, strong) GPUImageBeautifyFilter *beautifyFilter;
然后初始化
self.sessionPreset = AVCaptureSessionPreset1280x720;
self.videoCamera = [[GPUImageVideoCamera alloc] initWithSessionPreset:self.sessionPreset cameraPosition:AVCaptureDevicePositionBack];
self.filterView = [[GPUImageView alloc] init];
[self.view insertSubview:self.filterView atIndex:1]; //省略frame的相關(guān)設(shè)置
//這里我在GPUImageBeautifyFilter中增加個(gè)了初始化方法用來(lái)設(shè)置美顏程度intensity
self.beautifyFilter = [[GPUImageBeautifyFilter alloc] initWithIntensity:0.6];
為filterView增加美顏濾鏡
[self.videoCamera addTarget:self.beautifyFilter];
[self.beautifyFilter addTarget:self.filterView];
然后調(diào)用startCameraCapture方法就可以看到效果了
[self.videoCamera startCameraCapture];
到這里,僅僅是屏幕顯示的內(nèi)容帶有濾鏡效果,而作為直播應(yīng)用,還需要輸出帶有美顏效果的視頻流
輸出帶有美顏效果的視頻流
剛開(kāi)始集成的時(shí)候碰見(jiàn)一個(gè)坑,原本的邏輯是實(shí)現(xiàn)AVCaptureVideoDataOutputSampleBufferDelegate方法來(lái)獲得原始幀
- (void) captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection;
而GPUImageVideoCamera也實(shí)現(xiàn)了一個(gè)類(lèi)似的代理:
@protocol GPUImageVideoCameraDelegate <NSObject>
@optional
- (void)willOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer;
@end
而替換之后發(fā)現(xiàn)輸出的流依舊是未經(jīng)美顏的圖像,看了實(shí)現(xiàn)后發(fā)現(xiàn)果不其然,GPUImageVideoCameraDelegate還是通過(guò)AVCaptureVideoDataOutputSampleBufferDelegate直接返回的數(shù)據(jù),所以想輸出帶有濾鏡的流這里就得借助GPUImageRawDataOutput了
CGSize outputSize = {720, 1280};
GPUImageRawDataOutput *rawDataOutput = [[GPUImageRawDataOutput alloc] initWithImageSize:CGSizeMake(outputSize.width, outputSize.height) resultsInBGRAFormat:YES];
[self.beautifyFilter addTarget:rawDataOutput];
這個(gè)GPUImageRawDataOutput其實(shí)就是beautifyFilter的輸出工具,可在setNewFrameAvailableBlock方法的block中獲得帶有濾鏡效果的數(shù)據(jù)
__weak GPUImageRawDataOutput *weakOutput = rawDataOutput;
__weak typeof(self) weakSelf = self;
[rawDataOutput setNewFrameAvailableBlock:^{
__strong GPUImageRawDataOutput *strongOutput = weakOutput;
[strongOutput lockFramebufferForReading];
// 這里就可以獲取到添加濾鏡的數(shù)據(jù)了
GLubyte *outputBytes = [strongOutput rawBytesForImage];
NSInteger bytesPerRow = [strongOutput bytesPerRowInOutput];
CVPixelBufferRef pixelBuffer = NULL;
CVPixelBufferCreateWithBytes(kCFAllocatorDefault, outputSize.width, outputSize.height, kCVPixelFormatType_32BGRA, outputBytes, bytesPerRow, nil, nil, nil, &pixelBuffer);
// 之后可以利用VideoToolBox進(jìn)行硬編碼再結(jié)合rtmp協(xié)議傳輸視頻流了
[weakSelf encodeWithCVPixelBufferRef:pixelBuffer];
[strongOutput unlockFramebufferAfterReading];
CFRelease(pixelBuffer);
}];
目前依舊存在的問(wèn)題
經(jīng)過(guò)和其他產(chǎn)品對(duì)比,GPUImageBeautifyFilter磨皮效果和花椒最為類(lèi)似。這里采用雙邊濾波, 花椒應(yīng)該用了高斯模糊實(shí)現(xiàn)。同印客對(duì)比,美白效果一般。
還存在些關(guān)于性能的問(wèn)題:
1 調(diào)用setNewFrameAvailableBlock后很多機(jī)型只能跑到不多不少15fps
2 在6s這代機(jī)型上溫度很高,幀率可到30fps但不穩(wěn)定
Update(8-13)
關(guān)于性能問(wèn)題,最近把項(xiàng)目中集成的美顏濾鏡(BeautifyFace)里用到的 GPUImageCannyEdgeDetectionFilter 替換為 GPUImageSobelEdgeDetectionFilter 會(huì)有很大改善,而且效果幾乎一致,6s經(jīng)過(guò)長(zhǎng)時(shí)間測(cè)試沒(méi)有再次出現(xiàn)高溫警告了。(替換也十分簡(jiǎn)單,直接改倆處類(lèi)名/變量名就可以了)
分享一個(gè)BUG,最近發(fā)現(xiàn)當(dāng)開(kāi)啟美顏的時(shí)候,關(guān)閉直播內(nèi)存竟然沒(méi)有釋放。分析得出GPUImageRawDataOutput的setNewFrameAvailableBlock方法的block參數(shù)仍然保持著self,解決思路就是將GPUImageRawDataOutput移除。
先附上之前的相關(guān)release代碼:
[self.videoCamera stopCameraCapture];
[self.videoCamera removeInputsAndOutputs];
[self.videoCamera removeAllTargets];
開(kāi)始以為camera調(diào)用removeAllTargets會(huì)把camera上面的filter,以及filter的output一同釋放,但實(shí)際camera并不會(huì)'幫忙'移除filter的target,所以需要添加:
[self.beautifyFilter removeAllTargets]; //修復(fù)開(kāi)啟美顏內(nèi)存無(wú)法釋放的問(wèn)題
關(guān)閉美顏output是直接加在camera上,camera直接removeAllTargets就可以;
開(kāi)啟美顏output加在filter上,camera和filter都需要removeAllTargets。