iOS - 直播系列一:視訊採集

LinXunFeng發表於2017-10-16

蘋果官方文件-AVFoundation

為了管理從相機或者麥克風等這樣的裝置捕獲到的資訊,我們需要輸入物件(input)和輸出物件(output),並且使用一個會話(AVCaptureSession)來管理 input 和 output 之前的資料流:

類名 簡介
AVCaptureDevice 輸入裝置,例如 攝像頭 麥克風
AVCaptureInput 輸入埠 [使用其子類]
AVCaptureOutput 裝置輸出 [使用其子類],輸出視訊檔案或者靜態影象
AVCaptureSession 管理輸入到輸出的資料流
AVCaptureVideoPreviewLayer 展示採集 預覽View

如圖,通過單個 session,也可以管理多個 input 和 output 物件之間的資料流,從而得到視訊、靜態影象和預覽檢視

多個輸入輸出裝置

如圖,input 可以有一個或多個輸入埠,output 也可以有一個或多個資料來源(如:一個 AVCaptureMovieFileOutput 物件可以接收視訊資料和音訊資料)

當新增 input 和 output 到 session 中時,session 會自動建立起一個連線(AVCaptureConnection)。我們可以使用這個 connection 來設定從 input 或者 從 output 得到的資料的有效性,也可以用來監控在音訊通道中功率的平均值和峰值。

AVCaptureConnection

使用 Session 來管理資料流

建立一個 session 用來管理捕獲到的資料,需要先將 inputs 和 outputs 新增到 session 中,當 session 執行 [startRunning] 方法後就會開始將資料流傳送至 session,通過執行[stopRunning] 方法來結束資料流的傳送。

AVCaptureSession *captureSession = [[AVCaptureSession alloc] init];

// 新增 inputs 和 outputs

[session startRunning];
複製程式碼

在 [session startRunning] 之前我們需要進行一些基本的配置 (如:裝置解析度,新增輸入輸出物件等)

設定解析度

// 設定解析度 720P 標清
if ([captureSession canSetSessionPreset:AVCaptureSessionPreset1280x720]) {
    captureSession.sessionPreset = AVCaptureSessionPreset1280x720;
}
複製程式碼

附蘋果官方文件中可供配置的解析度列表

解析度列表

其中高解析度(AVCaptureSessionPresetHigh) 為預設值,會根據當前裝置進行自適應,但是這樣之後匯出來的檔案就會很大,一般情況下設定為標清(AVCaptureSessionPreset1280x720) 就可以了

輸入物件

// 直接使用後置攝像頭
AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
複製程式碼
// 在這個方法中的 mediaType 有三個選項供我們使用
// AVMediaTypeVideo 視訊
// AVMediaTypeAudio 音訊
// AVMediaTypeMuxed 混合(視訊 + 音訊)
+ (nullable AVCaptureDevice *)defaultDeviceWithMediaType:(AVMediaType)mediaType;
複製程式碼

但是這種方式只能獲取到後置攝像頭,如果想要獲取前置攝像頭,可使用

AVCaptureDevice *videoDevice;
NSArray *devices = [AVCaptureDevice devices];
for (AVCaptureDevice *device in devices) {
   if(device.position == AVCaptureDevicePositionFront) {
        // 前置攝像頭
        videoDevice = device;
   }
}
複製程式碼
// 通過裝置獲取輸入物件
AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:nil];
// 給會話新增輸入
if([captureSession canAddInput:videoInput]) {
    [captureSession addInput:videoInput];
}
複製程式碼

輸出物件

// 視訊輸出:設定視訊原資料格式:YUV, RGB 
// 蘋果不支援YUV的渲染,只支援RGB渲染,這意味著: YUV => RGB
AVCaptureVideoDataOutput *videoOutput = [[AVCaptureVideoDataOutput alloc] init];

// videoSettings: 設定視訊原資料格式 YUV FULL
videoOutput.videoSettings = @{(NSString *)kCVPixelBufferPixelFormatTypeKey:@(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)};

// 設定代理:獲取幀資料
// 佇列:序列/並行,這裡使用序列,保證資料順序 
dispatch_queue_t queue = dispatch_queue_create("LinXunFengSerialQueue", DISPATCH_QUEUE_SERIAL);
[videoOutput setSampleBufferDelegate:self queue:queue];

// 給會話新增輸出物件
if([captureSession canAddOutput:videoOutput]) {
    // 給會話新增輸入輸出就會自動建立起連線
    [captureSession addOutput:videoOutput];
}
複製程式碼

在這裡,輸出物件可以設定幀率

// 幀率:1秒10幀就差不多比較流暢了
videoOutput.minFrameDuration = CMTimeMake(1, 10);
複製程式碼

輸出物件在設定視訊原資料格式時使用 videoSettings 屬性,需要賦值的型別是字典 格式有兩種,一種是YUV,另一種是RGB(一般我們都使用YUV,因為體積比RGB小)

// key
kCVPixelBufferPixelFormatTypeKey 指定解碼後的影象格式

// value
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange  : YUV420 用於標清視訊[420v]
kCVPixelFormatType_420YpCbCr8BiPlanarFullRange   : YUV422 用於高清視訊[420f] 
kCVPixelFormatType_32BGRA : 輸出的是BGRA的格式,適用於OpenGL和CoreImage

區別:
1、前兩種是相機輸出YUV格式,然後轉成RGBA,最後一種是直接輸出BGRA,然後轉成RGBA;
2、420v 輸出的視訊格式為NV12;範圍: (luma=[16,235] chroma=[16,240])
3、420f 輸出的視訊格式為NV12;範圍: (luma=[0,255] chroma=[1,255])
複製程式碼

預覽圖層

AVCaptureVideoPreviewLayer *previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:captureSession];
previewLayer.frame = self.view.bounds;
[self.view.layer  addSublayer:previewLayer];
複製程式碼

實時顯示攝像頭捕獲到的影象,但不適用於濾鏡渲染

代理方法

#pragma mark - AVCaptureVideoDataOutputSampleBufferDelegate
/*
 CMSampleBufferRef: 幀快取資料,描述當前幀資訊
 CMSampleBufferGetXXX : 獲取幀快取資訊
 CMSampleBufferGetDuration : 獲取當前幀播放時間
 CMSampleBufferGetImageBuffer : 獲取當前幀圖片資訊
 */
// 獲取幀資料
- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
    // captureSession 會話如果沒有強引用,這裡不會得到執行
    
    NSLog(@"----- sampleBuffer ----- %@", sampleBuffer);
}
複製程式碼
// 獲取幀播放時間
CMTime duration = CMSampleBufferGetDuration(sampleBuffer);
複製程式碼

在代理方法中,可以把 sampleBuffer 資料渲染出來去顯示畫面。適用於濾鏡渲染

// 獲取圖片幀資料
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CIImage *ciImage = [CIImage imageWithCVImageBuffer:imageBuffer];
UIImage *image = [UIImage imageWithCIImage:ciImage];

dispatch_async(dispatch_get_main_queue(), ^{
    self.imageView.image = image;
});
複製程式碼

需要注意的是:代理方法中的所有動作所在佇列都是在非同步序列佇列中,所以更新UI的操作需要回到主佇列中進行!!

但是此時會發現,畫面是向左旋轉了90度,因為預設採集的視訊是橫屏的,需要我們進一步做調整。以下步驟新增在[session startRunning];之前即可,但是一定要在新增了 input 和 output之後~

// 獲取輸入與輸出之間的連線
AVCaptureConnection *connection = [videoOutput connectionWithMediaType:AVMediaTypeVideo];
// 設定採集資料的方向
connection.videoOrientation = AVCaptureVideoOrientationPortrait;
// 設定映象效果映象
connection.videoMirrored = YES;
複製程式碼

Demo

LXFAudioVideo

相關文章