前言
在直播和短視頻行業(yè)日益火熱的發(fā)展形勢下礁凡,音視頻開發(fā)(采集妒牙、編解碼吐绵、傳輸迹淌、播放、美顏)等技術(shù)也隨之成為開發(fā)者們關(guān)注的重點己单,本系列文章就音視頻開發(fā)過程中所運用到的技術(shù)和原理進行梳理和總結(jié)唉窃。
認識 AVCapture 系列
AVCapture
系列是AVFoundation
框架為我們提供的用于管理輸入設(shè)備、采集纹笼、輸出句携、預(yù)覽等一系列接口,其工作原理如下:
1. AVCaptureDevice: 信號采集硬件設(shè)備(攝像頭允乐、麥克風矮嫉、屏幕等)
AVCaptureDevice
代表硬件設(shè)備削咆,并且為AVCaptureSession
提供 input,要想使用 AVCaptureDevice
蠢笋,應(yīng)該先將設(shè)備支持的 device
枚舉出來, 根據(jù)攝像頭的位置( 前置或者后置攝像頭 )獲取需要用的那個攝像頭, 再使用拨齐;
如果想要對 AVCaptureDevice
對象的一些屬性進行設(shè)置,應(yīng)該先調(diào)用 lockForConfiguration:
方法, 設(shè)置結(jié)束后昨寞,調(diào)用unlockForConfiguration
方法瞻惋;
[self.device lockForConfiguration:&error];
// 設(shè)置 ***
[self.device unlockForConfiguration];
2. AVCaptureInput: 輸入數(shù)據(jù)管理
AVCaptureInput 繼承自 NSObject
,是向 AVCaptureSession
提供輸入數(shù)據(jù)的對象的抽象超類; 要將 AVCaptureInput
對象與會話 AVCaptureSession
關(guān)聯(lián)援岩,需要 AVCaptureSession
實例調(diào)用 -addInput:
方法歼狼。
由于 AVCaptureInput
是個抽象類,無法直接使用享怀,所以我們一般使用它的子類類管理輸入數(shù)據(jù)羽峰。我們常用的AVCaptureInput
的子類有三個:
AVCaptureDeviceInput:
用于從 AVCaptureDevice
對象捕獲數(shù)據(jù);
AVCaptureScreenInput:
從 macOS 屏幕上錄制的一種捕獲輸入;
AVCaptureMetadataInput:
它為AVCaptureSession
提供AVMetadataItems
。
3. AVCaptureOutput:輸出數(shù)據(jù)管理
AVCaptureOutput 繼承自 NSObject
添瓷,是輸出數(shù)據(jù)管理梅屉,該對象將會被添加到會話AVCaptureSession
中,用于接收會話AVCaptureSession
各類輸出數(shù)據(jù); AVCaptureOutput
提供了一個抽象接口鳞贷,用于將捕獲輸出數(shù)據(jù)(如文件和視頻預(yù)覽)連接到捕獲會話AVCaptureSession
的實例坯汤,捕獲輸出可以有多個由AVCaptureConnection
對象表示的連接,一個連接對應(yīng)于它從捕獲輸入(AVCaptureInput
的實例)接收的每個媒體流搀愧,捕獲輸出在首次創(chuàng)建時沒有任何連接惰聂,當向捕獲會話添加輸出時,將創(chuàng)建連接咱筛,將該會話的輸入的媒體數(shù)據(jù)映射到其輸出庶近,調(diào)用AVCaptureSession
的-addOutput:
方法將AVCaptureOutput
與AVCaptureSession
關(guān)聯(lián)。
AVCaptureOutput
是個抽象類眷蚓,我們必須使用它的子類鼻种,常用的 AVCaptureOutput
的子類如下所示:
AVCaptureAudioDataOutput:
一種捕獲輸出,用于記錄音頻沙热,并在錄制音頻時提供對音頻樣本緩沖區(qū)的訪問叉钥;
AVCaptureAudioPreviewOutput :
一種捕獲輸出,與一個核心音頻輸出設(shè)備相關(guān)聯(lián)篙贸、可用于播放由捕獲會話捕獲的音頻投队;
AVCaptureDepthDataOutput :
在兼容的攝像機設(shè)備上記錄場景深度信息的捕獲輸出;
AVCaptureMetadataOutput :
用于處理捕獲會話 AVCaptureSession
產(chǎn)生的定時元數(shù)據(jù)的捕獲輸出爵川;
AVCaptureStillImageOutput:
在macOS中捕捉靜止照片的捕獲輸出敷鸦。該類在 iOS 10.0 中被棄用,并且不支持新的相機捕獲功能,例如原始圖像輸出和實時照片扒披,在 iOS 10.0 或更高版本中值依,使用 AVCapturePhotoOutput
類代替;
AVCapturePhotoOutput :
靜態(tài)照片碟案、動態(tài)照片和其他攝影工作流的捕獲輸出愿险;
AVCaptureVideoDataOutput :
記錄視頻并提供對視頻幀進行處理的捕獲輸出;
AVCaptureFileOutput:
用于捕獲輸出的抽象超類价说,可將捕獲數(shù)據(jù)記錄到文件中辆亏;
AVCaptureMovieFileOutput :
繼承自 AVCaptureFileOutput
,將視頻和音頻記錄到 QuickTime 電影文件的捕獲輸出鳖目;
AVCaptureAudioFileOutput :
繼承自AVCaptureFileOutput
扮叨,記錄音頻并將錄制的音頻保存到文件的捕獲輸出。
4. AVCaptureSession:用來管理采集數(shù)據(jù)和輸出數(shù)據(jù)领迈,它負責協(xié)調(diào)從哪里采集數(shù)據(jù)彻磁,輸出到哪里,它是整個Capture的核心惦费,類似于RunLoop兵迅,它不斷的從輸入源獲取數(shù)據(jù)抢韭,然后分發(fā)給各個輸出源
AVCaptureSession 繼承自NSObject
薪贫,是AVFoundation
的核心類,用于管理捕獲對象AVCaptureInput
的視頻和音頻的輸入刻恭,協(xié)調(diào)捕獲的輸出AVCaptureOutput
5. AVCaptureConnection:用于AVCaptureSession
來建立和維護 AVCaptureInput
和AVCaptureOutput
之間的連接
AVCaptureConnection 是 Session
和 Output
中間的控制節(jié)點瞧省,每個 Output
與 Session
建立連接后,都會分配一個默認的 AVCpatureConnection
鳍贾。
6. AVCapturePreviewLayer:預(yù)覽層鞍匾,AVCaptureSession
的一個屬性,繼承自 CALayer
骑科,提供攝像頭的預(yù)覽功能橡淑,照片以及視頻就是通過把 AVCapturePreviewLayer
添加到 UIView
的layer
上來顯示
開始視頻采集
1、創(chuàng)建并初始化輸入AVCaptureInput
: AVCaptureDeviceInput
和輸出AVCaptureOutput
: AVCaptureVideoDataOutput
;
2咆爽、創(chuàng)建并初始化 AVCaptureSession
梁棠,把 AVCaptureInput
和 AVCaptureOutput
添加到 AVCaptureSession
中;
3、調(diào)用 AVCaptureSession
的 startRunning
開啟采集
初始化輸入
通過AVCaptureDevice
的 devicesWithMediaType:
方法獲取攝像頭斗埂,iPhone 都是有前后攝像頭的符糊,這里獲取到的是一個設(shè)備的數(shù)組,要從數(shù)組里面拿到我們想要的前攝像頭或后攝像頭呛凶,然后將 AVCaptureDevice
轉(zhuǎn)化為AVCaptureDeviceInput
男娄,添加到 AVCaptureSession
中
/************************** 設(shè)置輸入設(shè)備 *************************/
// --- 獲取所有攝像頭 ---
NSArray *cameras = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
// --- 獲取當前方向攝像頭 ---
NSArray *captureDeviceArray = [cameras filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"position == %d", _capturerParam.devicePosition]];
if (captureDeviceArray.count == 0) {
return nil;
}
// --- 轉(zhuǎn)化為輸入設(shè)備 ---
AVCaptureDevice *camera = captureDeviceArray.firstObject;
self.captureDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:camera
error:&error];
設(shè)置視頻采集參數(shù)
@implementation VideoCapturerParam
- (instancetype)init {
self = [super init];
if (self) {
_devicePosition = AVCaptureDevicePositionFront; // 攝像頭位置,默認為前置攝像頭
_sessionPreset = AVCaptureSessionPreset1280x720; // 視頻分辨率 默認 AVCaptureSessionPreset1280x720
_frameRate = 15; // 幀 單位為 幀/秒,默認為15幀/秒
_videoOrientation = AVCaptureVideoOrientationPortrait; // 攝像頭方向 默認為當前手機屏幕方向
switch ([UIDevice currentDevice].orientation) {
case UIDeviceOrientationPortrait:
case UIDeviceOrientationPortraitUpsideDown:
_videoOrientation = AVCaptureVideoOrientationPortrait;
break;
case UIDeviceOrientationLandscapeRight:
_videoOrientation = AVCaptureVideoOrientationLandscapeRight;
break;
case UIDeviceOrientationLandscapeLeft:
_videoOrientation = AVCaptureVideoOrientationLandscapeLeft;
break;
default:
break;
}
}
return self;
}
初始化輸出
初始化視頻輸出 AVCaptureVideoDataOutput
模闲,并設(shè)置視頻數(shù)據(jù)格式建瘫,設(shè)置采集數(shù)據(jù)回調(diào)線程,這里視頻輸出格式選的是 kCVPixelFormatType_420YpCbCr8BiPlanarFullRange围橡,YUV 數(shù)據(jù)格式
/************************** 設(shè)置輸出設(shè)備 *************************/
// --- 設(shè)置視頻輸出 ---
self.captureVideoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
NSDictionary *videoSetting = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange], kCVPixelBufferPixelFormatTypeKey, nil]; // kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange 表示輸出的視頻格式為NV12
[self.captureVideoDataOutput setVideoSettings:videoSetting];
// --- 設(shè)置輸出串行隊列和數(shù)據(jù)回調(diào) ---
dispatch_queue_t outputQueue = dispatch_queue_create("VideoCaptureOutputQueue", DISPATCH_QUEUE_SERIAL);
// --- 設(shè)置代理 ---
[self.captureVideoDataOutput setSampleBufferDelegate:self queue:outputQueue];
// --- 丟棄延遲的幀 ---
self.captureVideoDataOutput.alwaysDiscardsLateVideoFrames = YES;
初始化 AVCaptureSession 并設(shè)置輸入輸出
1暖混、初始化 AVCaptureSession
,把上面的輸入和輸出加進來翁授,在添加輸入和輸出到 AVCaptureSession
先查詢一下 AVCaptureSession
是否支持添加該輸入或輸出端口;
2拣播、設(shè)置視頻分辨率及圖像質(zhì)量(AVCaptureSessionPreset),設(shè)置之前同樣需要先查詢一下 AVCaptureSession
是否支持這個分辨率;
3收擦、如果在已經(jīng)開啟采集的情況下需要修改分辨率或輸入輸出贮配,需要用 beginConfiguration
和commitConfiguration
把修改的代碼包圍起來。在調(diào)用 beginConfiguration
后塞赂,可以配置分辨率泪勒、輸入輸出等,直到調(diào)用 commitConfiguration
了才會被應(yīng)用;
4宴猾、AVCaptureSession
管理了采集過程中的狀態(tài)圆存,當開始采集、停止采集仇哆、出現(xiàn)錯誤等都會發(fā)起通知沦辙,我們可以監(jiān)聽通知來獲取 AVCaptureSession
的狀態(tài),也可以調(diào)用其屬性來獲取當前 AVCaptureSession
的狀態(tài)讹剔, AVCaptureSession
相關(guān)的通知都是在主線程的油讯。
前置攝像頭采集到的畫面是翻轉(zhuǎn)的,若要解決畫面翻轉(zhuǎn)問題延欠,需要設(shè)置
AVCaptureConnection
的videoMirrored
為 YES陌兑。
/************************** 初始化會話 *************************/
self.captureSession = [[AVCaptureSession alloc] init];
self.captureSession.usesApplicationAudioSession = NO;
// --- 添加輸入設(shè)備到會話 ---
if ([self.captureSession canAddInput:self.captureDeviceInput]) {
[self.captureSession addInput:self.captureDeviceInput];
}
else {
NSLog(@"VideoCapture:: Add captureVideoDataInput Faild!");
return nil;
}
// --- 添加輸出設(shè)備到會話 ---
if ([self.captureSession canAddOutput:self.captureVideoDataOutput]) {
[self.captureSession addOutput:self.captureVideoDataOutput];
}
else {
NSLog(@"VideoCapture:: Add captureVideoDataOutput Faild!");
return nil;
}
// --- 設(shè)置分辨率 ---
if ([self.captureSession canSetSessionPreset:self.capturerParam.sessionPreset]) {
self.captureSession.sessionPreset = self.capturerParam.sessionPreset;
}
/************************** 初始化連接 *************************/
self.captureConnection = [self.captureVideoDataOutput connectionWithMediaType:AVMediaTypeVideo];
// --- 設(shè)置攝像頭鏡像,不設(shè)置的話前置攝像頭采集出來的圖像是反轉(zhuǎn)的 ---
if (self.capturerParam.devicePosition == AVCaptureDevicePositionFront && self.captureConnection.supportsVideoMirroring) { // supportsVideoMirroring 視頻是否支持鏡像
self.captureConnection.videoMirrored = YES;
}
self.captureConnection.videoOrientation = self.capturerParam.videoOrientation;
self.videoPreviewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.captureSession];
self.videoPreviewLayer.connection.videoOrientation = self.capturerParam.videoOrientation;
self.videoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
調(diào)用 / 獲取數(shù)據(jù)
調(diào)用很簡單由捎,初始化視頻采集參數(shù)VideoCapturerParam
和 視頻采集器VideoVapturer
, 設(shè)置預(yù)覽圖層videoPreviewLayer
, 調(diào)用 startCpture
就可以開始采集了兔综,然后實現(xiàn)數(shù)據(jù)采集回調(diào)的代理方法videoCaptureOutputDataCallback
獲取數(shù)據(jù)
// --- 初始化視頻采集參數(shù) ---
VideoCapturerParam *param = [[VideoCapturerParam alloc] init];
// --- 初始化視頻采集器 ---
self.videoCapture = [[VideoVapturer alloc] initWithCaptureParam:param error:nil];
self.videoCapture.delagate = self;
// --- 開始采集 ---
[self.videoCapture startCpture];
// --- 初始化預(yù)覽View ---
self.recordLayer = self.videoCapture.videoPreviewLayer;
self.recordLayer.frame = CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), CGRectGetHeight(self.view.bounds));
[self.view.layer addSublayer:self.recordLayer];
#pragma mark ————— VideoCapturerDelegate ————— 視頻采集回調(diào)
- (void)videoCaptureOutputDataCallback:(CMSampleBufferRef)sampleBuffer {
NSLog(@"%@ sampleBuffer : %@ ", kLOGt(@"視頻采集回調(diào)"), sampleBuffer);
}
至此,我們就完成了視頻的采集狞玛,在采集前和過程中软驰,我們可能會對采集參數(shù)、攝像頭方向为居、幀率等進行修改碌宴,具體的實現(xiàn)附上 Demo 地址:
小編這呢,給大家推薦一個優(yōu)秀的iOS學習平臺蒙畴,平臺里的伙伴們都是非常優(yōu)秀的iOS開發(fā)人員贰镣,我們專注于技術(shù)的分享呜象、學習和交流,大家可以在平臺上討論技術(shù)碑隆,交流學習恭陡。歡迎大家的加入(想學習小伙伴的可加小編微信15673450590)。
轉(zhuǎn)載于作者:G-Jayson
鏈接:https://juejin.im/post/5d26b47e6fb9a07ea5681868