iOS:音視頻開發(fā)——視頻采集

前言


在直播和短視頻行業(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:方法將AVCaptureOutputAVCaptureSession關(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ù) AVCaptureInputAVCaptureOutput之間的連接

AVCaptureConnectionSessionOutput 中間的控制節(jié)點,每個 OutputSession 建立連接后,都會分配一個默認(rèn)的 AVCpatureConnection。

6. AVCapturePreviewLayer:預(yù)覽層,AVCaptureSession的一個屬性,繼承自 CALayer,提供攝像頭的預(yù)覽功能,照片以及視頻就是通過把 AVCapturePreviewLayer添加到 UIViewlayer上來顯示

開始視頻采集

1、創(chuàng)建并初始化輸入AVCaptureInput: AVCaptureDeviceInput 和輸出AVCaptureOutput: AVCaptureVideoDataOutput;
2、創(chuàng)建并初始化 AVCaptureSession,把 AVCaptureInputAVCaptureOutput添加到 AVCaptureSession中;
3、調(diào)用 AVCaptureSessionstartRunning開啟采集

初始化輸入

通過AVCaptureDevicedevicesWithMediaType: 方法獲取攝像頭,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)開啟采集的情況下需要修改分辨率或輸入輸出,需要用 beginConfigurationcommitConfiguration 把修改的代碼包圍起來。在調(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è)置 AVCaptureConnectionvideoMirrored為 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 地址:

github.com/G-Jayson/JX…

小編這呢,給大家推薦一個優(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容