項目Demo,實現了實時濾鏡、拍照、錄像功能。html
最近玩了哈實時濾鏡,學到挺多東西的。筆者長得醜,看看有沒有機會沒那麼醜。只挑了幾種濾鏡,筆者是個鋼鐵直男,沒有美顏效果。git
設備獲取圖像輸入流後,通過對該幀處理造成新圖像,最後刷新UI。github
蘋果有簡單的 UIImagePickerController
,但擴展性差。因此筆者採用的是 AVFoundation
框架。其涉及到輸入流和輸出流,方便咱們對每一幀進行處理,顯示出來。bash
若是你對輸入和輸出相關的類不瞭解,應該也不影響你理解本文。但筆者仍是建議你先看哈蘇沫離的博客。好比,OC之輸入管理AVCaptureInput,OC之輸出管理 AVCaptureOutput。session
有一個類 AVCaptureMovieFile
,直接把音頻和圖像結合起來。但因爲要處理畫面,因此又得拆分開。因此筆者採用的是 AVCaptureAudioDataOutput
和 AVCaptureVideoDataOutput
,處理完圖像再拼。app
輸入流要經過相關設備初始化。框架
// 圖像
_device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
_cameraDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:self.device error:nil];
// 音頻
AVCaptureDevice *micDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
_microphoneDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:micDevice error:nil];
複製代碼
_queue = dispatch_queue_create("VideoDataOutputQueue", DISPATCH_QUEUE_SERIAL);
// 圖像
_videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
_videoDataOutput.videoSettings = @{(id)kCVPixelBufferPixelFormatTypeKey : [NSNumber numberWithInteger:kCVPixelFormatType_32BGRA]};
_videoDataOutput.alwaysDiscardsLateVideoFrames = YES;
[_videoDataOutput setSampleBufferDelegate:self queue:_queue];
// 音頻
_audioDataOutput = [[AVCaptureAudioDataOutput alloc] init];
[_audioDataOutput setSampleBufferDelegate:self queue:_queue];
複製代碼
建立了一個串行隊列,以確保每一幀按順序處理。輸出流的回調方法爲- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection;
。ide
AVCaptureSession
會話起到中間層的做用。性能
_session = [[AVCaptureSession alloc] init];
if ([_session canSetSessionPreset:AVCaptureSessionPreset1280x720]) {
[_session setSessionPreset:AVCaptureSessionPreset1280x720];
}
{ // 把輸入輸出結合起來
if ([_session canAddInput:_cameraDeviceInput]) {
[_session addInput:_cameraDeviceInput];
}
if ([_session canAddOutput:_videoDataOutput]) {
[_session addOutput:_videoDataOutput];
}
}
複製代碼
開啓輸入流,獲取數據到輸出流。優化
//開始啓動
[_session startRunning];
複製代碼
要注意的是,若是要修改輸入流或者輸出流,要在一次提交中完成。好比切換攝像頭(修改輸入流)。
//輸入流
AVCaptureDeviceInput *newInput = [AVCaptureDeviceInput deviceInputWithDevice:newCamera error:nil];
if (newInput != nil) {
[self.session beginConfiguration];
//先移除原來的input
[self.session removeInput:self.cameraDeviceInput];
if ([self.session canAddInput:newInput]) {
[self.session addInput:newInput];
self.cameraDeviceInput = newInput;
} else {
[self.session addInput:self.cameraDeviceInput];
}
[self.session commitConfiguration];
}
複製代碼
// 在這裏處理獲取的圖像,而且保存每一幀到self.outputImg
- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
@autoreleasepool {
if (output == _audioDataOutput && [_audioWriterInput isReadyForMoreMediaData]) {// 處理音頻
[_audioWriterInput appendSampleBuffer:sampleBuffer];
}
if (output == self.videoDataOutput) { // 處理視頻幀
// 處理圖片,保存到self.outputImg中
[self imageFromSampleBuffer:sampleBuffer];
}
}
}
複製代碼
在這一步,處理每一幀圖像,加上濾鏡。
這裏用到了CIFilter
,是蘋果自帶的CoreImage框架對圖片進行處理
的一個框架。其實現了上百種效果,筆者只選取了其中三種。感興趣的能夠去官方文檔查看。
//1.建立基於CPU的CIContext對象
self.context = [CIContext contextWithOptions:
[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES]
forKey:kCIContextUseSoftwareRenderer]];
//2.建立基於GPU的CIContext對象
self.context = [CIContext contextWithOptions: nil];
//3.建立基於OpenGL優化的CIContext對象,可得到實時性能
self.context = [CIContext contextWithEAGLContext:[[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]];
// 將UIImage轉換成CIImage
CIImage *ciImage = [[CIImage alloc] initWithImage:[UIImage imageNamed:@"WechatIMG1.jpeg"]];
// 建立濾鏡
CIFilter *filter = [CIFilter filterWithName:_dataSourse[indexPath.row]
keysAndValues:kCIInputImageKey, ciImage, nil];
[filter setDefaults];
// 獲取繪製上下文
CIContext *context = [CIContext contextWithOptions:nil];
// 渲染並輸出CIImage
CIImage *outputImage = [filter outputImage];
// 建立CGImage句柄
CGImageRef cgImage = [self.context createCGImage:outputImage
fromRect:[outputImage extent]];
imageview.image = [UIImage imageWithCGImage:cgImage];
// 釋放CGImage句柄
CGImageRelease(cgImage);
複製代碼
在回調方法裏,咱們加完濾鏡獲得每一幀。當點下拍照按鈕,把這一張圖片保存到相冊即完成了拍照功能。
對於錄像,咱們主要用到 AVAssetWriter
以及 AVAssetWriterInputPixelBufferAdaptor
。
獲取到圖像和音頻流後,咱們將其放到緩衝區內。最終判斷時間戳 ,AVAssetWriter將他們合成視頻。
遇到了一個問題,也記錄一下吧。
前置攝像頭鏡像問題。網上大多思路都是iOS 前置攝像頭鏡像問題,但不能處理。緣由多是由於筆者項目對幀進行了處理,數據不是原生的圖像。對圖片再進行一次鏡像處理便可。
if ([[self.cameraDeviceInput device] position] == AVCaptureDevicePositionFront) {// 前置要鏡像
result = [result imageByApplyingOrientation:UIImageOrientationUpMirrored];
}
複製代碼