視訊採集詳解
為了管理從相機或者麥克風等這樣的裝置捕獲到的資訊,我們需要輸入物件(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 得到的資料的有效性,也可以用來監控在音訊通道中功率的平均值和峰值。
使用 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;