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

前言


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

AVCaptureConnectionSessionOutput 中間的控制節(jié)點瞧省,每個 OutputSession 建立連接后,都會分配一個默認的 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];
        // ---  獲取當前方向攝像頭  ---
        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)開啟采集的情況下需要修改分辨率或輸入輸出贮配,需要用 beginConfigurationcommitConfiguration 把修改的代碼包圍起來。在調(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è)置 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ù)、攝像頭方向为居、幀率等進行修改碌宴,具體的實現(xiàn)附上 Demo 地址:

github.com/G-Jayson/JX…

小編這呢,給大家推薦一個優(yōu)秀的iOS學習平臺蒙畴,平臺里的伙伴們都是非常優(yōu)秀的iOS開發(fā)人員贰镣,我們專注于技術(shù)的分享呜象、學習和交流,大家可以在平臺上討論技術(shù)碑隆,交流學習恭陡。歡迎大家的加入(想學習小伙伴的可加小編微信15673450590)。

轉(zhuǎn)載于作者:G-Jayson
鏈接:https://juejin.im/post/5d26b47e6fb9a07ea5681868

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末上煤,一起剝皮案震驚了整個濱河市休玩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌劫狠,老刑警劉巖拴疤,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異独泞,居然都是意外死亡呐矾,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門懦砂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜒犯,“玉大人,你說我怎么就攤上這事荞膘》K妫” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵羽资,是天一觀的道長淘菩。 經(jīng)常有香客問我,道長削罩,這世上最難降的妖魔是什么瞄勾? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任费奸,我火速辦了婚禮弥激,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘愿阐。我一直安慰自己微服,他們只是感情好,可當我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布缨历。 她就那樣靜靜地躺著以蕴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪辛孵。 梳的紋絲不亂的頭發(fā)上丛肮,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機與錄音魄缚,去河邊找鬼宝与。 笑死焚廊,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的习劫。 我是一名探鬼主播咆瘟,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼诽里!你這毒婦竟也來了袒餐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤谤狡,失蹤者是張志新(化名)和其女友劉穎灸眼,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體墓懂,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡幢炸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了拒贱。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宛徊。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖逻澳,靈堂內(nèi)的尸體忽然破棺而出闸天,到底是詐尸還是另有隱情,我是刑警寧澤斜做,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布苞氮,位于F島的核電站,受9級特大地震影響瓤逼,放射性物質(zhì)發(fā)生泄漏笼吟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一霸旗、第九天 我趴在偏房一處隱蔽的房頂上張望贷帮。 院中可真熱鬧,春花似錦诱告、人聲如沸撵枢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锄禽。三九已至,卻和暖如春靴姿,著一層夾襖步出監(jiān)牢的瞬間沃但,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工佛吓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留宵晚,地道東北人恨旱。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像坝疼,于是被迫代替她去往敵國和親搜贤。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,446評論 2 348