在直播和短視頻行業日益火熱的發展形勢下,音視頻開發(採集、編解碼、傳輸、播放、美顏)等技術也隨之成爲開發者們關注的重點,本系列文章就音視頻開發過程當中所運用到的技術和原理進行梳理和總結。git
AVCapture
系列是 AVFoundation
框架爲咱們提供的用於管理輸入設備、採集、輸出、預覽等一系列接口,其工做原理以下: github
AVCaptureDevice
表明硬件設備,而且爲 AVCaptureSession
提供 input,要想使用 AVCaptureDevice
,應該先將設備支持的 device
枚舉出來, 根據攝像頭的位置( 前置或者後置攝像頭 )獲取須要用的那個攝像頭, 再使用; 若是想要對 AVCaptureDevice
對象的一些屬性進行設置,應該先調用 lockForConfiguration:
方法, 設置結束後,調用 unlockForConfiguration
方法;數組
[self.device lockForConfiguration:&error];
// 設置 ***
[self.device unlockForConfiguration];
複製代碼
AVCaptureInput 繼承自 NSObject
,是向 AVCaptureSession
提供輸入數據的對象的抽象超類; 要將 AVCaptureInput
對象與會話 AVCaptureSession
關聯,須要 AVCaptureSession
實例調用 -addInput:
方法。緩存
因爲 AVCaptureInput
是個抽象類,沒法直接使用,因此咱們通常使用它的子類類管理輸入數據。咱們經常使用的 AVCaptureInput
的子類有三個: bash
AVCaptureDeviceInput
:用於從
AVCaptureDevice
對象捕獲數據;
AVCaptureScreenInput
:從 macOS 屏幕上錄製的一種捕獲輸入;
AVCaptureMetadataInput
:它爲
AVCaptureSession
提供
AVMetadataItems
。
AVCaptureOutput 繼承自 NSObject
,是輸出數據管理,該對象將會被添加到會話AVCaptureSession
中,用於接收會話AVCaptureSession
各種輸出數據; AVCaptureOutput
提供了一個抽象接口,用於將捕獲輸出數據(如文件和視頻預覽)鏈接到捕獲會話AVCaptureSession
的實例,捕獲輸出能夠有多個由AVCaptureConnection
對象表示的鏈接,一個鏈接對應於它從捕獲輸入(AVCaptureInput
的實例)接收的每一個媒體流,捕獲輸出在首次建立時沒有任何鏈接,當向捕獲會話添加輸出時,將建立鏈接,將該會話的輸入的媒體數據映射到其輸出,調用AVCaptureSession
的-addOutput:
方法將AVCaptureOutput
與AVCaptureSession
關聯。session
AVCaptureOutput
是個抽象類,咱們必須使用它的子類,經常使用的 AVCaptureOutput
的子類以下所示: app
AVCaptureAudioDataOutput
:一種捕獲輸出,用於記錄音頻,並在錄製音頻時提供對音頻樣本緩衝區的訪問;
AVCaptureAudioPreviewOutput
:一種捕獲輸出,與一個核心音頻輸出設備相關聯、可用於播放由捕獲會話捕獲的音頻;
AVCaptureDepthDataOutput
:在兼容的攝像機設備上記錄場景深度信息的捕獲輸出;
AVCaptureMetadataOutput
:用於處理捕獲會話
AVCaptureSession
產生的定時元數據的捕獲輸出;
AVCaptureStillImageOutput
:在macOS中捕捉靜止照片的捕獲輸出。該類在 iOS 10.0 中被棄用,而且不支持新的相機捕獲功能,例如原始圖像輸出和實時照片,在 iOS 10.0 或更高版本中,使用
AVCapturePhotoOutput
類代替;
AVCapturePhotoOutput
:靜態照片、動態照片和其餘攝影工做流的捕獲輸出;
AVCaptureVideoDataOutput
:記錄視頻並提供對視頻幀進行處理的捕獲輸出;
AVCaptureFileOutput
:用於捕獲輸出的抽象超類,可將捕獲數據記錄到文件中;
AVCaptureMovieFileOutput
:繼承自
AVCaptureFileOutput
,將視頻和音頻記錄到 QuickTime 電影文件的捕獲輸出;
AVCaptureAudioFileOutput
:繼承自
AVCaptureFileOutput
,記錄音頻並將錄製的音頻保存到文件的捕獲輸出。
AVCaptureSession 繼承自NSObject
,是AVFoundation
的核心類,用於管理捕獲對象AVCaptureInput
的視頻和音頻的輸入,協調捕獲的輸出AVCaptureOutput
框架
AVCaptureSession
來創建和維護 AVCaptureInput
和 AVCaptureOutput
之間的鏈接AVCaptureConnection 是 Session
和 Output
中間的控制節點,每一個 Output
與 Session
創建鏈接後,都會分配一個默認的 AVCpatureConnection
。ide
AVCaptureSession
的一個屬性,繼承自 CALayer
,提供攝像頭的預覽功能,照片以及視頻就是經過把 AVCapturePreviewLayer
添加到 UIView
的 layer
上來顯示一、建立並初始化輸入AVCaptureInput
: AVCaptureDeviceInput
和輸出AVCaptureOutput
: AVCaptureVideoDataOutput
; 二、建立並初始化 AVCaptureSession
,把 AVCaptureInput
和 AVCaptureOutput
添加到 AVCaptureSession
中; 三、調用 AVCaptureSession
的 startRunning
開啓採集oop
經過 AVCaptureDevice
的 devicesWithMediaType:
方法獲取攝像頭,iPhone 都是有先後攝像頭的,這裏獲取到的是一個設備的數組,要從數組裏面拿到咱們想要的前攝像頭或後攝像頭,而後將 AVCaptureDevice
轉化爲 AVCaptureDeviceInput
,添加到 AVCaptureSession
中
/************************** 設置輸入設備 *************************/
// --- 獲取全部攝像頭 ---
NSArray *cameras = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
// --- 獲取當前方向攝像頭 ---
NSArray *captureDeviceArray = [cameras filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"position == %d", _capturerParam.devicePosition]];
if (captureDeviceArray.count == 0) {
return nil;
}
// --- 轉化爲輸入設備 ---
AVCaptureDevice *camera = captureDeviceArray.firstObject;
self.captureDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:camera
error:&error];
複製代碼
@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
,並設置視頻數據格式,設置採集數據回調線程,這裏視頻輸出格式選的是 kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,YUV 數據格式
/************************** 設置輸出設備 *************************/
// --- 設置視頻輸出 ---
self.captureVideoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
NSDictionary *videoSetting = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange], kCVPixelBufferPixelFormatTypeKey, nil]; // kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange 表示輸出的視頻格式爲NV12
[self.captureVideoDataOutput setVideoSettings:videoSetting];
// --- 設置輸出串行隊列和數據回調 ---
dispatch_queue_t outputQueue = dispatch_queue_create("VideoCaptureOutputQueue", DISPATCH_QUEUE_SERIAL);
// --- 設置代理 ---
[self.captureVideoDataOutput setSampleBufferDelegate:self queue:outputQueue];
// --- 丟棄延遲的幀 ---
self.captureVideoDataOutput.alwaysDiscardsLateVideoFrames = YES;
複製代碼
一、初始化 AVCaptureSession
,把上面的輸入和輸出加進來,在添加輸入和輸出到 AVCaptureSession
先查詢一下 AVCaptureSession
是否支持添加該輸入或輸出端口;
二、設置視頻分辨率及圖像質量(AVCaptureSessionPreset),設置以前一樣須要先查詢一下 AVCaptureSession
是否支持這個分辨率;
三、若是在已經開啓採集的狀況下須要修改分辨率或輸入輸出,須要用 beginConfiguration
和commitConfiguration
把修改的代碼包圍起來。在調用 beginConfiguration
後,能夠配置分辨率、輸入輸出等,直到調用 commitConfiguration
了纔會被應用;
四、AVCaptureSession
管理了採集過程當中的狀態,當開始採集、中止採集、出現錯誤等都會發起通知,咱們能夠監聽通知來獲取 AVCaptureSession
的狀態,也能夠調用其屬性來獲取當前 AVCaptureSession
的狀態, AVCaptureSession
相關的通知都是在主線程的。
前置攝像頭採集到的畫面是翻轉的,若要解決畫面翻轉問題,須要設置
AVCaptureConnection
的videoMirrored
爲 YES。
/************************** 初始化會話 *************************/
self.captureSession = [[AVCaptureSession alloc] init];
self.captureSession.usesApplicationAudioSession = NO;
// --- 添加輸入設備到會話 ---
if ([self.captureSession canAddInput:self.captureDeviceInput]) {
[self.captureSession addInput:self.captureDeviceInput];
}
else {
NSLog(@"VideoCapture:: Add captureVideoDataInput Faild!");
return nil;
}
// --- 添加輸出設備到會話 ---
if ([self.captureSession canAddOutput:self.captureVideoDataOutput]) {
[self.captureSession addOutput:self.captureVideoDataOutput];
}
else {
NSLog(@"VideoCapture:: Add captureVideoDataOutput Faild!");
return nil;
}
// --- 設置分辨率 ---
if ([self.captureSession canSetSessionPreset:self.capturerParam.sessionPreset]) {
self.captureSession.sessionPreset = self.capturerParam.sessionPreset;
}
/************************** 初始化鏈接 *************************/
self.captureConnection = [self.captureVideoDataOutput connectionWithMediaType:AVMediaTypeVideo];
// --- 設置攝像頭鏡像,不設置的話前置攝像頭採集出來的圖像是反轉的 ---
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;
複製代碼
/**
* 開始採集
*/
- (NSError *)startCpture {
if (self.isCapturing) {
return [NSError errorWithDomain:@"VideoCapture:: startCapture faild: is capturing" code:1 userInfo:nil];
}
// --- 攝像頭權限判斷 ---
AVAuthorizationStatus videoAuthStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if (videoAuthStatus != AVAuthorizationStatusAuthorized) {
return [NSError errorWithDomain:@"VideoCapture:: Camera Authorizate faild!" code:1 userInfo:nil];
}
[self.captureSession startRunning];
self.isCapturing = YES;
kLOGt(@"開始採集視頻");
return nil;
}
/**
* 中止採集
*/
- (NSError *)stopCapture {
if (!self.isCapturing) {
return [NSError errorWithDomain:@"VideoCapture:: stop capture faild! is not capturing!" code:1 userInfo:nil];
}
[self.captureSession stopRunning];
self.isCapturing = NO;
kLOGt(@"中止採集視頻");
return nil;
}
#pragma mark ————— AVCaptureVideoDataOutputSampleBufferDelegate —————
/**
* 攝像頭採集數據回調
@prama output 輸出設備
@prama sampleBuffer 幀緩存數據,描述當前幀信息
@prama connection 鏈接
*/
- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
if ([self.delagate respondsToSelector:@selector(videoCaptureOutputDataCallback:)]) {
[self.delagate videoCaptureOutputDataCallback:sampleBuffer];
}
}
複製代碼
調用很簡單,初始化視頻採集參數 VideoCapturerParam
和 視頻採集器 VideoVapturer
, 設置預覽圖層 videoPreviewLayer
, 調用 startCpture
就能夠開始採集了,而後實現數據採集回調的代理方法 videoCaptureOutputDataCallback
獲取數據
// --- 初始化視頻採集參數 ---
VideoCapturerParam *param = [[VideoCapturerParam alloc] init];
// --- 初始化視頻採集器 ---
self.videoCapture = [[VideoVapturer alloc] initWithCaptureParam:param error:nil];
self.videoCapture.delagate = self;
// --- 開始採集 ---
[self.videoCapture startCpture];
// --- 初始化預覽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 ————— 視頻採集回調
- (void)videoCaptureOutputDataCallback:(CMSampleBufferRef)sampleBuffer {
NSLog(@"%@ sampleBuffer : %@ ", kLOGt(@"視頻採集回調"), sampleBuffer);
}
複製代碼
至此,咱們就完成了視頻的採集,在採集前和過程當中,咱們可能會對採集參數、攝像頭方向、幀率等進行修改,具體的實現附上 Demo 地址: