前言
在直播和短視頻行業(yè)日益火熱的發(fā)展形勢下,音視頻開發(fā)(采集、編解碼、傳輸、播放、美顏)等技術(shù)也隨之成為開發(fā)者們關(guān)注的重點,本系列文章就音視頻開發(fā)過程中所運用到的技術(shù)和原理進(jìn)行梳理和總結(jié)。
認(rèn)識 AVCapture 系列
AVCapture 系列是AVFoundation框架為我們提供的用于管理輸入設(shè)備、采集、輸出、預(yù)覽等一系列接口,其工作原理如下:

1. AVCaptureDevice: 信號采集硬件設(shè)備(攝像頭、麥克風(fēng)、屏幕等)
AVCaptureDevice 代表硬件設(shè)備,并且為AVCaptureSession 提供 input,要想使用 AVCaptureDevice,應(yīng)該先將設(shè)備支持的 device枚舉出來, 根據(jù)攝像頭的位置( 前置或者后置攝像頭 )獲取需要用的那個攝像頭, 再使用;
如果想要對 AVCaptureDevice 對象的一些屬性進(jìn)行設(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)建時沒有任何連接,當(dā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 :在兼容的攝像機(jī)設(shè)備上記錄場景深度信息的捕獲輸出;
AVCaptureMetadataOutput :用于處理捕獲會話 AVCaptureSession產(chǎn)生的定時元數(shù)據(jù)的捕獲輸出;
AVCaptureStillImageOutput:在macOS中捕捉靜止照片的捕獲輸出。該類在 iOS 10.0 中被棄用,并且不支持新的相機(jī)捕獲功能,例如原始圖像輸出和實時照片,在 iOS 10.0 或更高版本中,使用 AVCapturePhotoOutput類代替;
AVCapturePhotoOutput :靜態(tài)照片、動態(tài)照片和其他攝影工作流的捕獲輸出;
AVCaptureVideoDataOutput :記錄視頻并提供對視頻幀進(jìn)行處理的捕獲輸出;
AVCaptureFileOutput:用于捕獲輸出的抽象超類,可將捕獲數(shù)據(jù)記錄到文件中;
AVCaptureMovieFileOutput :繼承自 AVCaptureFileOutput,將視頻和音頻記錄到 QuickTime 電影文件的捕獲輸出;
AVCaptureAudioFileOutput :繼承自AVCaptureFileOutput,記錄音頻并將錄制的音頻保存到文件的捕獲輸出。
4. AVCaptureSession:用來管理采集數(shù)據(jù)和輸出數(shù)據(jù),它負(fù)責(zé)協(xié)調(diào)從哪里采集數(shù)據(jù),輸出到哪里,它是整個Capture的核心,類似于RunLoop,它不斷的從輸入源獲取數(shù)據(jù),然后分發(fā)給各個輸出源
AVCaptureSession 繼承自NSObject,是AVFoundation的核心類,用于管理捕獲對象AVCaptureInput的視頻和音頻的輸入,協(xié)調(diào)捕獲的輸出AVCaptureOutput

5. AVCaptureConnection:用于AVCaptureSession來建立和維護(hù) AVCaptureInput和AVCaptureOutput之間的連接
AVCaptureConnection 是 Session 和 Output 中間的控制節(jié)點,每個 Output 與 Session 建立連接后,都會分配一個默認(rèn)的 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];
// --- 獲取當(dāng)前方向攝像頭 ---
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; // 攝像頭位置,默認(rèn)為前置攝像頭
_sessionPreset = AVCaptureSessionPreset1280x720; // 視頻分辨率 默認(rèn) AVCaptureSessionPreset1280x720
_frameRate = 15; // 幀 單位為 幀/秒,默認(rèn)為15幀/秒
_videoOrientation = AVCaptureVideoOrientationPortrait; // 攝像頭方向 默認(rèn)為當(dāng)前手機(jī)屏幕方向
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,把上面的輸入和輸出加進(jìn)來,在添加輸入和輸出到 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),當(dāng)開始采集、停止采集、出現(xiàn)錯誤等都會發(fā)起通知,我們可以監(jiān)聽通知來獲取 AVCaptureSession 的狀態(tài),也可以調(diào)用其屬性來獲取當(dāng)前 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ù)、攝像頭方向、幀率等進(jìn)行修改,具體的實現(xiàn)附上 Demo 地址:
小編這呢,給大家推薦一個優(yōu)秀的iOS學(xué)習(xí)平臺,平臺里的伙伴們都是非常優(yōu)秀的iOS開發(fā)人員,我們專注于技術(shù)的分享、學(xué)習(xí)和交流,大家可以在平臺上討論技術(shù),交流學(xué)習(xí)。歡迎大家的加入(想學(xué)習(xí)小伙伴的可加小編微信15673450590)。
轉(zhuǎn)載于作者:G-Jayson
鏈接:https://juejin.im/post/5d26b47e6fb9a07ea5681868