【如何快速的開發一個完整的iOS直播app】(采集篇)

前言

在看這篇之前,如果您還不了解直播原理,請查看這篇文章如何快速的開發一個完整的iOS直播app(原理篇)

開發一款直播app,首先需要采集主播的視頻和音頻,然后傳入流媒體服務器,本篇主要講解如何采集主播的視頻和音頻,當前可以切換前置后置攝像頭和焦點光標,但是美顏功能還沒做,可以看見素顏的你,后續還會有直播的其他功能文章陸續發布。

如果喜歡我的文章,可以關注我微博:袁崢Seemygo

效果

為了采集效果圖,我也是豁出去了,請忽略人物,關注技術。

忽略本人.png

基本知識介紹

  • AVFoundation: 音視頻數據采集需要用AVFoundation框架.

  • AVCaptureDevice:硬件設備,包括麥克風、攝像頭,通過該對象可以設置物理設備的一些屬性(例如相機聚焦、白平衡等)

  • AVCaptureDeviceInput:硬件輸入對象,可以根據AVCaptureDevice創建對應的AVCaptureDeviceInput對象,用于管理硬件輸入數據。

  • AVCaptureOutput:硬件輸出對象,用于接收各類輸出數據,通常使用對應的子類AVCaptureAudioDataOutput(聲音數據輸出對象)、AVCaptureVideoDataOutput(視頻數據輸出對象)

  • AVCaptionConnection:當把一個輸入和輸出添加到AVCaptureSession之后,AVCaptureSession就會在輸入、輸出設備之間建立連接,而且通過AVCaptureOutput可以獲取這個連接對象。

  • AVCaptureVideoPreviewLayer:相機拍攝預覽圖層,能實時查看拍照或視頻錄制效果,創建該對象需要指定對應的AVCaptureSession對象,因為AVCaptureSession包含視頻輸入數據,有視頻數據才能展示。

  • AVCaptureSession: 協調輸入與輸出之間傳輸數據

    • 系統作用:可以操作硬件設備
    • 工作原理:讓App與系統之間產生一個捕獲會話,相當于App與硬件設備有聯系了, 我們只需要把硬件輸入對象和輸出對象添加到會話中,會話就會自動把硬件輸入對象和輸出產生連接,這樣硬件輸入與輸出設備就能傳輸音視頻數據。
    • 現實生活場景:租客(輸入錢),中介(會話),房東(輸出房),租客和房東都在中介登記,中介就會讓租客與房東之間產生聯系,以后租客就能直接和房東聯系了。

捕獲音視頻步驟:官方文檔

  • 1.創建AVCaptureSession對象
  • 2.獲取AVCaptureDevicel錄像設備(攝像頭),錄音設備(麥克風),注意不具備輸入數據功能,只是用來調節硬件設備的配置。
  • 3.根據音頻/視頻硬件設備(AVCaptureDevice)創建音頻/視頻硬件輸入數據對象(AVCaptureDeviceInput),專門管理數據輸入。
  • 4.創建視頻輸出數據管理對象(AVCaptureVideoDataOutput),并且設置樣品緩存代理(setSampleBufferDelegate)就可以通過它拿到采集到的視頻數據
  • 5.創建音頻輸出數據管理對象(AVCaptureAudioDataOutput),并且設置樣品緩存代理(setSampleBufferDelegate)就可以通過它拿到采集到的音頻數據
  • 6.將數據輸入對象AVCaptureDeviceInput、數據輸出對象AVCaptureOutput添加到媒體會話管理對象AVCaptureSession中,就會自動讓音頻輸入與輸出和視頻輸入與輸出產生連接.
  • 7.創建視頻預覽圖層AVCaptureVideoPreviewLayer并指定媒體會話,添加圖層到顯示容器layer中
  • 8.啟動AVCaptureSession,只有開啟,才會開始輸入到輸出數據流傳輸。
// 捕獲音視頻
- (void)setupCaputureVideo
{
    // 1.創建捕獲會話,必須要強引用,否則會被釋放
    AVCaptureSession *captureSession = [[AVCaptureSession alloc] init];
    _captureSession = captureSession;
    
    // 2.獲取攝像頭設備,默認是后置攝像頭
    AVCaptureDevice *videoDevice = [self getVideoDevice:AVCaptureDevicePositionFront];
    
    // 3.獲取聲音設備
    AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
    
    // 4.創建對應視頻設備輸入對象
    AVCaptureDeviceInput *videoDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:nil];
    _currentVideoDeviceInput = videoDeviceInput;
    
    // 5.創建對應音頻設備輸入對象
    AVCaptureDeviceInput *audioDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:nil];
    
    // 6.添加到會話中
    // 注意“最好要判斷是否能添加輸入,會話不能添加空的
    // 6.1 添加視頻
    if ([captureSession canAddInput:videoDeviceInput]) {
        [captureSession addInput:videoDeviceInput];
    }
    // 6.2 添加音頻
    if ([captureSession canAddInput:audioDeviceInput]) {
        [captureSession addInput:audioDeviceInput];
    }
    
    // 7.獲取視頻數據輸出設備
    AVCaptureVideoDataOutput *videoOutput = [[AVCaptureVideoDataOutput alloc] init];
    // 7.1 設置代理,捕獲視頻樣品數據
    // 注意:隊列必須是串行隊列,才能獲取到數據,而且不能為空
    dispatch_queue_t videoQueue = dispatch_queue_create("Video Capture Queue", DISPATCH_QUEUE_SERIAL);
    [videoOutput setSampleBufferDelegate:self queue:videoQueue];
    if ([captureSession canAddOutput:videoOutput]) {
        [captureSession addOutput:videoOutput];
    }
    
    // 8.獲取音頻數據輸出設備
    AVCaptureAudioDataOutput *audioOutput = [[AVCaptureAudioDataOutput alloc] init];
    // 8.2 設置代理,捕獲視頻樣品數據
    // 注意:隊列必須是串行隊列,才能獲取到數據,而且不能為空
    dispatch_queue_t audioQueue = dispatch_queue_create("Audio Capture Queue", DISPATCH_QUEUE_SERIAL);
    [audioOutput setSampleBufferDelegate:self queue:audioQueue];
    if ([captureSession canAddOutput:audioOutput]) {
        [captureSession addOutput:audioOutput];
    }
    
    // 9.獲取視頻輸入與輸出連接,用于分辨音視頻數據
    _videoConnection = [videoOutput connectionWithMediaType:AVMediaTypeVideo];
    
    // 10.添加視頻預覽圖層
    AVCaptureVideoPreviewLayer *previedLayer = [AVCaptureVideoPreviewLayer layerWithSession:captureSession];
    previedLayer.frame = [UIScreen mainScreen].bounds;
    [self.view.layer insertSublayer:previedLayer atIndex:0];
    _previedLayer = previedLayer;
    
    // 11.啟動會話
    [captureSession startRunning];
}

// 指定攝像頭方向獲取攝像頭
- (AVCaptureDevice *)getVideoDevice:(AVCaptureDevicePosition)position
{
    NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
    for (AVCaptureDevice *device in devices) {
        if (device.position == position) {
            return device;
        }
    }
    return nil;
}

#pragma mark - AVCaptureVideoDataOutputSampleBufferDelegate
// 獲取輸入設備數據,有可能是音頻有可能是視頻
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
    if (_videoConnection == connection) {
        NSLog(@"采集到視頻數據");
    } else {
        NSLog(@"采集到音頻數據");
    }
}

視頻采集額外功能一(切換攝像頭)

  • 切換攝像頭步驟
    • 1.獲取當前視頻設備輸入對象
    • 2.判斷當前視頻設備是前置還是后置
    • 3.確定切換攝像頭的方向
    • 4.根據攝像頭方向獲取對應的攝像頭設備
    • 5.創建對應的攝像頭輸入對象
    • 6.從會話中移除之前的視頻輸入對象
    • 7.添加新的視頻輸入對象到會話中
// 切換攝像頭
- (IBAction)toggleCapture:(id)sender {
    
    // 獲取當前設備方向
    AVCaptureDevicePosition curPosition = _currentVideoDeviceInput.device.position;
    
    // 獲取需要改變的方向
    AVCaptureDevicePosition togglePosition = curPosition == AVCaptureDevicePositionFront?AVCaptureDevicePositionBack:AVCaptureDevicePositionFront;
    
    // 獲取改變的攝像頭設備
    AVCaptureDevice *toggleDevice = [self getVideoDevice:togglePosition];
    
    // 獲取改變的攝像頭輸入設備
    AVCaptureDeviceInput *toggleDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:toggleDevice error:nil];
    
    // 移除之前攝像頭輸入設備
    [_captureSession removeInput:_currentVideoDeviceInput];
    
    // 添加新的攝像頭輸入設備
    [_captureSession addInput:toggleDeviceInput];
    
    // 記錄當前攝像頭輸入設備
    _currentVideoDeviceInput = toggleDeviceInput;
    
}

視頻采集額外功能二(聚焦光標)

  • 聚焦光標步驟
    • 1.監聽屏幕的點擊
    • 2.獲取點擊的點位置,轉換為攝像頭上的點,必須通過視頻預覽圖層(AVCaptureVideoPreviewLayer)轉
    • 3.設置聚焦光標圖片的位置,并做動畫
    • 4.設置攝像頭設備聚焦模式和曝光模式(注意:這里設置一定要鎖定配置lockForConfiguration,否則報錯)
// 點擊屏幕,出現聚焦視圖
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    // 獲取點擊位置
    UITouch *touch = [touches anyObject];
    CGPoint point = [touch locationInView:self.view];
    
    // 把當前位置轉換為攝像頭點上的位置
    CGPoint cameraPoint = [_previedLayer captureDevicePointOfInterestForPoint:point];
    
    // 設置聚焦點光標位置
    [self setFocusCursorWithPoint:point];
    
    // 設置聚焦
    [self focusWithMode:AVCaptureFocusModeAutoFocus exposureMode:AVCaptureExposureModeAutoExpose atPoint:cameraPoint];
}

/**
 *  設置聚焦光標位置
 *
 *  @param point 光標位置
 */
-(void)setFocusCursorWithPoint:(CGPoint)point{
    self.focusCursorImageView.center=point;
    self.focusCursorImageView.transform=CGAffineTransformMakeScale(1.5, 1.5);
    self.focusCursorImageView.alpha=1.0;
    [UIView animateWithDuration:1.0 animations:^{
        self.focusCursorImageView.transform=CGAffineTransformIdentity;
    } completion:^(BOOL finished) {
        self.focusCursorImageView.alpha=0;
        
    }];
}

/**
 *  設置聚焦
 */
-(void)focusWithMode:(AVCaptureFocusMode)focusMode exposureMode:(AVCaptureExposureMode)exposureMode atPoint:(CGPoint)point{

    AVCaptureDevice *captureDevice = _currentVideoDeviceInput.device;
    // 鎖定配置
    [captureDevice lockForConfiguration:nil];
    
    // 設置聚焦
    if ([captureDevice isFocusModeSupported:AVCaptureFocusModeAutoFocus]) {
        [captureDevice setFocusMode:AVCaptureFocusModeAutoFocus];
    }
    if ([captureDevice isFocusPointOfInterestSupported]) {
        [captureDevice setFocusPointOfInterest:point];
    }
    
    // 設置曝光
    if ([captureDevice isExposureModeSupported:AVCaptureExposureModeAutoExpose]) {
        [captureDevice setExposureMode:AVCaptureExposureModeAutoExpose];
    }
    if ([captureDevice isExposurePointOfInterestSupported]) {
        [captureDevice setExposurePointOfInterest:point];
    }
    
    // 解鎖配置
    [captureDevice unlockForConfiguration];
}

結束語

后續還會更新更多有關直播的資料,希望做到教會每一個朋友從零開始做一款直播app,并且Demo也會慢慢完善.
Demo點擊下載

  • 由于FFMPEG庫比較大,大概100M。
  • 本來想自己上傳所有代碼了,上傳了1個小時,還沒成功,就放棄了。
  • 提供另外一種方案,需要你們自己導入IJKPlayer庫

具體步驟:

  • 下載Demo后,打開YZLiveApp.xcworkspace問題
打開YZLiveApp.xcworkspace問題
  • pod install就能解決
Snip20160830_12.png
  • 下載jkplayer庫,點擊下載
  • 把jkplayer直接拖入到與Classes同一級目錄下,直接運行程序,就能成功了
拖入ijkplayer到與Classes同一級目錄下.png
  • 注意不需要打開工程,把jkplayer拖入到工程中,而是直接把jkplayer庫拷貝到與Classes同一級目錄下就可以了。
  • 錯誤示范:不要向下面這樣操作
Snip20160830_14.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容