iOS:音視頻開發——視頻採集

前言

在直播和短視頻行業日益火熱的發展形勢下,音視頻開發(採集、編解碼、傳輸、播放、美顏)等技術也隨之成爲開發者們關注的重點,本系列文章就音視頻開發過程當中所運用到的技術和原理進行梳理和總結。git

認識 AVCapture 系列

AVCapture 系列是 AVFoundation 框架爲咱們提供的用於管理輸入設備、採集、輸出、預覽等一系列接口,其工做原理以下: github

image.png

1. AVCaptureDevice: 信號採集硬件設備(攝像頭、麥克風、屏幕等)

AVCaptureDevice 表明硬件設備,而且爲 AVCaptureSession 提供 input,要想使用 AVCaptureDevice,應該先將設備支持的 device 枚舉出來, 根據攝像頭的位置( 前置或者後置攝像頭 )獲取須要用的那個攝像頭, 再使用; 若是想要對 AVCaptureDevice 對象的一些屬性進行設置,應該先調用 lockForConfiguration: 方法, 設置結束後,調用 unlockForConfiguration 方法;數組

[self.device lockForConfiguration:&error];
    // 設置 ***
    [self.device unlockForConfiguration];
複製代碼
2. AVCaptureInput: 輸入數據管理

AVCaptureInput 繼承自 NSObject,是向 AVCaptureSession 提供輸入數據的對象的抽象超類; 要將 AVCaptureInput 對象與會話 AVCaptureSession 關聯,須要 AVCaptureSession實例調用 -addInput: 方法。緩存

因爲 AVCaptureInput 是個抽象類,沒法直接使用,因此咱們通常使用它的子類類管理輸入數據。咱們經常使用的 AVCaptureInput 的子類有三個: bash

image.png
AVCaptureDeviceInput:用於從 AVCaptureDevice 對象捕獲數據; AVCaptureScreenInput:從 macOS 屏幕上錄製的一種捕獲輸入; AVCaptureMetadataInput:它爲 AVCaptureSession 提供 AVMetadataItems

3. AVCaptureOutput:輸出數據管理

AVCaptureOutput 繼承自 NSObject,是輸出數據管理,該對象將會被添加到會話AVCaptureSession中,用於接收會話AVCaptureSession各種輸出數據; AVCaptureOutput提供了一個抽象接口,用於將捕獲輸出數據(如文件和視頻預覽)鏈接到捕獲會話AVCaptureSession的實例,捕獲輸出能夠有多個由AVCaptureConnection對象表示的鏈接,一個鏈接對應於它從捕獲輸入(AVCaptureInput的實例)接收的每一個媒體流,捕獲輸出在首次建立時沒有任何鏈接,當向捕獲會話添加輸出時,將建立鏈接,將該會話的輸入的媒體數據映射到其輸出,調用AVCaptureSession-addOutput:方法將AVCaptureOutputAVCaptureSession關聯。session

AVCaptureOutput 是個抽象類,咱們必須使用它的子類,經常使用的 AVCaptureOutput的子類以下所示: app

image.png
AVCaptureAudioDataOutput:一種捕獲輸出,用於記錄音頻,並在錄製音頻時提供對音頻樣本緩衝區的訪問; AVCaptureAudioPreviewOutput :一種捕獲輸出,與一個核心音頻輸出設備相關聯、可用於播放由捕獲會話捕獲的音頻; AVCaptureDepthDataOutput :在兼容的攝像機設備上記錄場景深度信息的捕獲輸出; AVCaptureMetadataOutput :用於處理捕獲會話 AVCaptureSession 產生的定時元數據的捕獲輸出; AVCaptureStillImageOutput:在macOS中捕捉靜止照片的捕獲輸出。該類在 iOS 10.0 中被棄用,而且不支持新的相機捕獲功能,例如原始圖像輸出和實時照片,在 iOS 10.0 或更高版本中,使用 AVCapturePhotoOutput 類代替; AVCapturePhotoOutput :靜態照片、動態照片和其餘攝影工做流的捕獲輸出; AVCaptureVideoDataOutput :記錄視頻並提供對視頻幀進行處理的捕獲輸出; AVCaptureFileOutput:用於捕獲輸出的抽象超類,可將捕獲數據記錄到文件中; AVCaptureMovieFileOutput :繼承自 AVCaptureFileOutput,將視頻和音頻記錄到 QuickTime 電影文件的捕獲輸出; AVCaptureAudioFileOutput :繼承自 AVCaptureFileOutput,記錄音頻並將錄製的音頻保存到文件的捕獲輸出。

4. AVCaptureSession:用來管理採集數據和輸出數據,它負責協調從哪裏採集數據,輸出到哪裏,它是整個Capture的核心,相似於RunLoop,它不斷的從輸入源獲取數據,而後分發給各個輸出源

AVCaptureSession 繼承自NSObject,是AVFoundation的核心類,用於管理捕獲對象AVCaptureInput的視頻和音頻的輸入,協調捕獲的輸出AVCaptureOutput 框架

image.png

5. AVCaptureConnection:用於 AVCaptureSession 來創建和維護 AVCaptureInputAVCaptureOutput 之間的鏈接

AVCaptureConnectionSessionOutput 中間的控制節點,每一個 OutputSession 創建鏈接後,都會分配一個默認的 AVCpatureConnectionide

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

開始視頻採集

一、建立並初始化輸入AVCaptureInput: AVCaptureDeviceInput 和輸出AVCaptureOutput: AVCaptureVideoDataOutput; 二、建立並初始化 AVCaptureSession,把 AVCaptureInputAVCaptureOutput 添加到 AVCaptureSession 中; 三、調用 AVCaptureSessionstartRunning 開啓採集oop

初始化輸入

經過 AVCaptureDevicedevicesWithMediaType: 方法獲取攝像頭,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 先查詢一下 AVCaptureSession 是否支持添加該輸入或輸出端口;

二、設置視頻分辨率及圖像質量(AVCaptureSessionPreset),設置以前一樣須要先查詢一下 AVCaptureSession 是否支持這個分辨率;

三、若是在已經開啓採集的狀況下須要修改分辨率或輸入輸出,須要用 beginConfigurationcommitConfiguration 把修改的代碼包圍起來。在調用 beginConfiguration 後,能夠配置分辨率、輸入輸出等,直到調用 commitConfiguration 了纔會被應用;

四、AVCaptureSession 管理了採集過程當中的狀態,當開始採集、中止採集、出現錯誤等都會發起通知,咱們能夠監聽通知來獲取 AVCaptureSession 的狀態,也能夠調用其屬性來獲取當前 AVCaptureSession 的狀態, AVCaptureSession 相關的通知都是在主線程的。

前置攝像頭採集到的畫面是翻轉的,若要解決畫面翻轉問題,須要設置 AVCaptureConnectionvideoMirrored 爲 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 地址:

github.com/G-Jayson/JX…

相關文章
相關標籤/搜索