1、视频采集流程
iOS采集器的基本结构图如下:
iOS采集相关架构图
从图里可以看到,我们可以通过AVCapture Device Input创建输入资源,通过Session搭配AVCaptureMovieFileOutput(或者AVCaptureStillImageOutput)来进行资源的输出,也可以通过AVCaptureVideoPreviewLayer来进行预览。本章,我们就简要的介绍下这全流程。
创建Session
// 管理输入和输出映射的采集器
AVCaptureSession* session = [[AVCaptureSession alloc] init];
获取系统设备指针
// 获取系统设备信息
AVCaptureDeviceDiscoverySession* deviceDiscoverySession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInWideAngleCamera] mediaType:AVMediaTypeVideo position:self.config.position];
NSArray* devices = deviceDiscoverySession.devices;
for (AVCaptureDevice* device in devices) {
if (device.position == self.config.position) {
self.device = device;
break;
}
}
相关函数原型介绍:
/*!
* @brief 创建相关的采集设备
* @param deviceTypes 设备的类型,可参考AVCaptureDeviceType相关变量,后续在做详细的解释。
* @param mediaType 需要采集的视频格式,音频或者视频。
* @param position 采集摄像头的方位,前置或者后置。
* @return 成功则返回相关的采集设备。
*/
+ (instancetype)discoverySessionWithDeviceTypes:(NSArray<AVCaptureDeviceType> *)deviceTypes mediaType:(nullable AVMediaType)mediaType position:(AVCaptureDevicePosition)position;
@end
到此,可以获取到相关的采集设备指针,该指针可用于创建创建输入。
配置Session
之后,我们需要配置Session,以至于其能够很好的对接从device过来的输入,然后转换为我们需要的输出。
[self.session beginConfiguration];
// 从设备中创建输入,之后需要设置到session
NSError* error = nil;
self.videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:self.device error:&error];
if (error) {
NSLog(@"%s:%d init input error!!!", __func__, __LINE__);
return;
}
// 设置session的输入
if ([self.session canAddInput:self.videoInput]) {
[self.session addInput:self.videoInput];
}
// 配置session的输出
self.videoOutput = [[AVCaptureVideoDataOutput alloc] init];
// 禁止丢帧
self.videoOutput.alwaysDiscardsLateVideoFrames = NO;
// 设置输出的PixelBuffer的类型,这里可以设置为:
// kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange
// kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
// kCVPixelFormatType_32BGRA
[self.videoOutput setVideoSettings:@{(__bridge NSString*)kCVPixelBufferPixelFormatTypeKey:@(self.config.pixelBufferType)}];
// 设置output的数据回调,需要为AVCaptureVideoDataOutputSampleBufferDelegate协议的实现者。
dispatch_queue_t captureQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
[self.videoOutput setSampleBufferDelegate:self queue:captureQueue];
if ([self.session canAddOutput:self.videoOutput]) {
[self.session addOutput:self.videoOutput];
}
// 设置连接器
AVCaptureConnection* connect = [self.videoOutput connectionWithMediaType:AVMediaTypeVideo];
// 设置图源的显示方位,具体可以参考AVCaptureVideoOrientation枚举。
connect.videoOrientation = self.config.orientation;
if ([connect isVideoStabilizationSupported]) {
connect.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
}
// 设置图片的缩放程度,实际上的效果不如设置Layer的顶点位置。
connect.videoScaleAndCropFactor = connect.videoMaxScaleAndCropFactor;
[self.session commitConfiguration];
开始采集
- (void)startCapture {
if (self.session) {
[self.session startRunning];
}
}
停止采集
- (void)stopCapture {
if (self.session) {
[self.session stopRunning];
}
}
配置数据回调
#pragma mark AVCaptureVideoDataOutputSampleBufferDelegate
- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
if (self.delegate && [self.delegate respondsToSelector:@selector(onVideoWithSampleBuffer:)]) {
[self.delegate onVideoWithSampleBuffer:sampleBuffer];
}
}
结果展示图
结果展示图
2、美颜
音视频使用美颜滤镜,我们会选择GPUImage来获取视频数据。
GPUImage是一个可以为录制视频添加实时滤镜的一个著名第三方库。
该框架大概原理是,使用OpenGL着色器对视频图像进行颜色处理,然后存到frameBuffer,之后可以对此数据再次处理。重复上述过程,即可达到多重滤镜效果。
具体实现不细说,这里简要介绍一下GPUImage的使用,如何美颜,如何获取音视频数据。
使用GPUImage
GPUImage的主要代码在 AWGPUImageAVCapture 这个类中。
初始化AWAVCaptureManager对象时将captureType设为AWAVCaptureTypeGPUImage,就会自动调用AWGPUImageAVCapture类来捕获视频数据。
代码在 onInit 方法中:
-(void)onInit{
//摄像头初始化
// AWGPUImageVideoCamera 继承自 GPUImageVideoCamera。继承是为了获取音频数据,原代码中,默认情况下音频数据发送给了 audioEncodingTarget。
// 这个东西一看类型是GPUImageMovieWriter,应该是文件写入功能。果断覆盖掉processAudioSampleBuffer方法,拿到音频数据后自己处理。
// 音频就这样可以了,GPUImage主要工作还是在视频处理这里。
// 设置预览分辨率 self.captureSessionPreset是根据AWVideoConfig的设置,获取的分辨率。设置前置、后置摄像头。
_videoCamera = [[AWGPUImageVideoCamera alloc] initWithSessionPreset:self.captureSessionPreset cameraPosition:AVCaptureDevicePositionFront];
//开启捕获声音
[_videoCamera addAudioInputsAndOutputs];
//设置输出图像方向,可用于横屏推流。
_videoCamera.outputImageOrientation = UIInterfaceOrientationPortrait;
//镜像策略,这里这样设置是最自然的。跟系统相机默认一样。
_videoCamera.horizontallyMirrorRearFacingCamera = NO;
_videoCamera.horizontallyMirrorFrontFacingCamera = YES;
//设置预览view
_gpuImageView = [[GPUImageView alloc] initWithFrame:self.preview.bounds];
[self.preview addSubview:_gpuImageView];
//初始化美颜滤镜
_beautifyFilter = [[GPUImageBeautifyFilter alloc] init];
//相机获取视频数据输出至美颜滤镜
[_videoCamera addTarget:_beautifyFilter];
//美颜后输出至预览
[_beautifyFilter addTarget:_gpuImageView];
// 到这里我们已经能够打开相机并预览了。
// 因为要推流,除了预览之外,我们还要截取到视频数据。这就需要使用GPUImage中的GPUImageRawDataOutput,它能将美颜后的数据输出,便于我们处理后发送出去。
// AWGPUImageAVCaptureDataHandler继承自GPUImageRawDataOutput,从 newFrameReadyAtTime 方法中就可以获取到美颜后输出的数据。
// 输出的图片格式为BGRA。
_dataHandler = [[AWGPUImageAVCaptureDataHandler alloc] initWithImageSize:CGSizeMake(self.videoConfig.width, self.videoConfig.height) resultsInBGRAFormat:YES capture:self];
[_beautifyFilter addTarget:_dataHandler];
// 令AWGPUImageAVCaptureDataHandler实现AWGPUImageVideoCameraDelegate协议,并且让camera的awAudioDelegate指向_dataHandler对象。
// 将音频数据转到_dataHandler中处理。然后音视频数据就可以都在_dataHandler中处理了。
_videoCamera.awAudioDelegate = _dataHandler;