前言
在看這篇之前,如果您還不瞭解直播原理,請查看這篇文章如何快速的開發一個完整的iOS直播app(原理篇)
開發一款直播app,首先需要採集主播的視頻和音頻,然後傳入流媒體服務器,本篇主要講解如何採集主播的視頻和音頻,當前可以切換前置後置攝像頭和焦點光標,但是美顏功能還沒做,可以看見素顏的你,後續還會有直播的其他功能文章陸續發佈。
如果喜歡我的文章,可以關注我微博:袁崢Seemygo,也可以來小碼哥,瞭解下我們的iOS培訓課程。後續還會更新更多內容,有任何問題,歡迎簡書留言袁崢Seemygo。。。
效果
爲了採集效果圖,我也是豁出去了,請忽略人物,關注技術。
基本知識介紹
-
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問題
- pod install就能解決
- 下載jkplayer庫,點擊下載
- 把jkplayer直接拖入到與Classes同一級目錄下,直接運行程序,就能成功了
- 注意不需要打開工程,把jkplayer拖入到工程中,而是直接把jkplayer庫拷貝到與Classes同一級目錄下就可以了。
- 錯誤示範:不要向下面這樣操作