美文网首页AVCaptureDevice
初识AV Foundation-视频、图像采集

初识AV Foundation-视频、图像采集

作者: Charlie_超仔 | 来源:发表于2018-09-13 15:26 被阅读0次
image

最近上手一个蓝牙控制手机视频录制的app项目,蓝牙这一块改天再做分享,今天先说说如何调用AV Foundation去控制视频录制的。AV Foundation 视频和图像采集主要应用到以上几个接口类。

AVCaptureDeviceInput 【音视频输入口】

作为视频和音频的输入口,而输入源则是镜头(前置和后置)、麦克风(手机自带麦克风和耳麦),它的初始化如下:

初始化镜头输入


AVCaptureDevice * device =[self getCameraDeviceWithPosition:position];

NSError *initError =nil;

_mVideoInput =[[AVCaptureDeviceInput alloc] initWithDevice:device error:&initError];

if (initError) {

NSLog(@"初始化摄像头输入错误:%@",initError.localizedDescription);

}

初始化音频输入


NSArray *devices =[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio];

if (!devices) {

NSLog(@"无法获取音频设备");

return;

}

AVCaptureDevice *mdevice =devices.firstObject;

NSError *initError =nil;

_mAudioInput =[[AVCaptureDeviceInput alloc] initWithDevice:mdevice error:&initError];

if (initError) {

NSLog(@"初始化音频输入错误:%@",initError.localizedDescription);

}

补充说明,获取前置或后置摄像头相关设备的代码如下:


-(AVCaptureDevice *)getCameraDeviceWithPosition:(AVCaptureDevicePosition )position{

NSArray *cameras= [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];

for (AVCaptureDevice *camera in cameras) {

     if ([camera position]==position) {

             [[camera formats] enumerateObjectsUsingBlock:^(AVCaptureDeviceFormat* obj,    NSUInteger idx, BOOL * _Nonnull stop) {

        }];

             return camera;

     }

}

return nil;

}

前置摄像头对应的AVCaptureDevicePosition的值为AVCaptureDevicePositionFront,而后置对应的为AVCaptureDevicePositionBack。

AVCaptureVideoDataOutput【音视频流输出口】

该类主要作为音视频的数据输出口,可以对采样得到的音频流和视频流进行裁剪,拼接等操作。比如app想达到暂停录制的话,这个是一个很好地切入点。那么他的初始化及应用代码如下:

初始化视频输出


//先初始化输出队列

if (!mRecordOutputQueue) {

mRecordOutputQueue =dispatch_queue_create(OutputQueueKey, NULL);

}

_mVideoOutput =[[AVCaptureVideoDataOutput alloc] init];

//设置视频

_mVideoConnection =[_mVideoOutput connectionWithMediaType:AVMediaTypeVideo];

//设置视频拍摄方向为正方向

_mVideoConnection.videoOrientation =AVCaptureVideoOrientationPortrait;

//设置采样输出回调代理

[_mVideoOutput setSampleBufferDelegate:self queue:mRecordOutputQueue];

初始化音频输出


if (!mRecordOutputQueue) {

mRecordOutputQueue =dispatch_queue_create(OutputQueueKey, NULL);

}

_mAudioOutput =[[AVCaptureAudioDataOutput alloc] init];

_mAudioConnection =[_mAudioOutput connectionWithMediaType:AVMediaTypeAudio];

//设置采样输出回调代理

[_mAudioOutput setSampleBufferDelegate:self queue:mRecordOutputQueue];

而采样得到的数据将在**- (void)captureOutput:(AVCaptureOutput )captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection )connection代理函数中得以获取。下面是一段简单的视频数据保存的过程对应的代码:


- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection{

BOOL isVideo =YES;

if (captureOutput!=_mVideoOutput) {

isVideo =NO;//音频

}

//初步建立音频

if (!mVideoEncodeOp&&!isVideo) {

//获取音频相关信息

CMFormatDescriptionRef fmt = CMSampleBufferGetFormatDescription(sampleBuffer);

const AudioStreamBasicDescription *asbd = CMAudioFormatDescriptionGetStreamBasicDescription(fmt);

audio_sample_rate = asbd->mSampleRate;

audio_channel = asbd->mChannelsPerFrame;

//创建文件路径

NSString *fileFullPath =[[mFileManger getVideoCachePath] stringByAppendingString:[mFileManger createDefaultVideoFileName:@"mp4"]];

//创建编码器

mVideoEncodeOp =[[VideoEncodeOperation alloc] initWithPath:fileFullPath audioChannel:audio_channel audioSampleRate:audio_sample_rate videoDimensionWidth:video_dimension_width videoHeight:video_dimension_height];

}

// 计算当前视频流的时长

CMTime dur = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); 

if (startTime.value == 0) {

    startTime = dur;

}

CMTime sub = CMTimeSubtract(dur, startTime);

currentRecordTime = CMTimeGetSeconds(sub);

// 进行数据编码

[mVideoEncodeOp encodeFrame:sampleBuffer isVideo:isVideo];

//录制进程时间回调

if ([self.delegate respondsToSelector:@selector(recordProgress:)]) {

      dispatch_async(dispatch_get_main_queue(), ^{

           [self.delegate recordProgress:currentRecordTime];

     });      

}

CFRelease(sampleBuffer);//释放内存

}

除了使用AVCaptureVideoDataOutput取输出数据流以外,还可以直接使用AVCaptureMovieFileOutput直接输出到文件,但很明显的是,你无法使用AVCaptureMovieFileOutput对数据流进行诸如裁剪,拼接等操作。根据需求不同可以自主选择合适的输出方式。

AVCaptureStillImageOutput【静态图像输出】

如果项目需要进行拍摄等操作的话,就要考虑如何接入AVCaptureStillImageOutput来进行操作了。静态图像捕捉过程可以沿用当前摄像头的分辨率,也可以采用最高质量的分辨率,具体过程请参考AVCaptureStillImageOutput的highResolutionStillImageOutputEnabled属性。以下是初始化和使用的代码过程:

初始化


_mStillImageOuput =[[AVCaptureStillImageOutput alloc] init];

NSDictionary *outputSettings = [[NSDictionary alloc] initWithObjectsAndKeys:AVVideoCodecJPEG, AVVideoCodecKey, nil];

_mStillImageOuput.outputSettings =outputSettings;

拍照


for (AVCaptureConnection *connection in _mStillImageOuput.connections) {

      for (AVCaptureInputPort *port in [connection inputPorts]) {

             if ([[port mediaType] isEqual:AVMediaTypeVideo]) {

                   _mStillImageConnection = connection;

                  break;

             }

       }

       if (_mStillImageConnection) {

             break;

       }

}

if (!_mStillImageConnection) {

       NSLog(@"无法获取图像输出connection");

       return;

}

//设置高质量的采样图像分辨率

if (_mStillImageOuput.isHighResolutionStillImageOutputEnabled) {

      _mStillImageOuput.highResolutionStillImageOutputEnabled =YES;

}

[_mStillImageOuput captureStillImageAsynchronouslyFromConnection:_mStillImageConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {

if (imageDataSampleBuffer == NULL) {

      return;

}

NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];

UIImage *image0 = [UIImage imageWithData:imageData];

}];

AVCaptureConnection【输入输出的桥接】

输入、输出都已经设置好了,但尚未不清楚他们之间的联系,所以我们得给显式或隐式给他构造一个桥接。代码如下:


//视频输出与视频输入之间的连接[显式构造]

_mVideoConnection =[_mVideoOutput connectionWithMediaType:AVMediaTypeVideo];

//音频输出和音频输入之间的连接[显式构造]

_mAudioConnection =[_mAudioOutput connectionWithMediaType:AVMediaTypeAudio];

//静态捕捉图像的连接[隐实构造]

for (AVCaptureConnection *connection in _mStillImageOuput.connections) {

          for (AVCaptureInputPort *port in [connection inputPorts]) {

               if ([[port mediaType] isEqual:AVMediaTypeVideo]) {

                       _mStillImageConnection = connection;

                       break;

              }

          }

}

AVCaptureSession【音视频捕捉总控制】

无论是输入输出,都需要受到管理调度,而AVCaptureSession就很好扮演了该角色。你只需要将AVCaptureDeviceInput、AVCaptureVideoDataOutput、AVCaptureAudioDataOutput、AVCaptureStillImageOutput等往里放,即可将从捕捉到输出的过程疏通,AVCaptureSession仅仅负责开始捕捉和停止捕捉。执行代码如下:


_mRecorderSession =[[AVCaptureSession alloc] init];

if ([_mRecorderSession canAddInput:_mVideoInput]) {

       [_mRecorderSession addInput:_mVideoInput];

}

if ([_mRecorderSession canAddInput:_mAudioInput]) {

       [_mRecorderSession addInput:_mAudioInput];

}

if ([_mRecorderSession canAddOutput:_mVideoOutput]) {

       [_mRecorderSession addOutput:_mVideoOutput];

}

if ([_mRecorderSession canAddOutput:_mAudioOutput]) {

      [_mRecorderSession addOutput:_mAudioOutput];

}

if ([_mRecorderSession canAddOutput:_mStillImageOuput]) {

      [_mRecorderSession addOutput:_mStillImageOuput];

}

//开始捕捉

[_mRecorderSession startRunning];

//结束捕捉

[_mRecorderSession stopRunning];

到此你就可以进行录制视频、拍照等操作了。但还有一个问题,如何显示实时镜头画面?这个就更简单了,看下面。

AVCaptureVideoPreviewLayer【实时捕捉画面】

很显然这是一个layer,你需要做的是初始化AVCaptureVideoPreviewLayer,然后将其添加到某个view的layer上,即可看到当前的画面了,代码如下:


_mRecoderPreviewLayer =[[AVCaptureVideoPreviewLayer alloc] initWithSession:_mRecorderSession];

//设置比例为铺满全屏

_mRecoderPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;

CGRect rect =self.view.frame;

_mRecoderPreviewLayer.frame =CGRectMake(0, 0, rect.size.width, rect.size.height);

[self.view.layer insertSublayer:_mRecoderPreviewLayer atIndex:0];

至此,简单的捕捉过程已经实现。但这都不是重点,重点还在下面:

不知道你常使用iPhone设备内置相机是否考虑里面的镜头拉近拉远、慢动作录制等等功能是怎么实现的,又或者专业的录像app FiLMic Pro是如何实现调整录制的分辨率和采样帧率的?当然上面所介绍的内容是无法解决的,你要是急着去翻文档的话,那先看看下面这篇介绍吧!

New AV Foundation Camera Features for the iPhone 6 and iPhone 6 Plus

或者看看翻译

iPhone 6和iPhone 6 plus的AV Foundation框架特性

不知道你是否注意到文中常提及的AVCaptureDeviceFormat?这就是今天基本的重点了。。。

首先说说很早之前苹果在镜头使用过程中常用到的一个方法:封装好几个合适的Preset,然后让你去挑。当然这种方法至今还是保留着的,它的好处在于简便高效。镜头本身有很多可调节的相关参数,诸如分辨率、采样帧率、曝光率、白平衡等等。假设每次使用镜头都需要你设置各种参数,而你作为普通的开发者,只是希望能够简单的调用一下镜头,录制和拍照,并不对图像质量有太多额外追求,那么设置各种镜头参数过程对来说是相当崩溃的,那么苹果采用一种策略,提供几种可行的参数设置方案,并将其封装起来,你只需要使用这些参数方案即可将镜头自动设置到相应的参数,这工作量至少成倍减少。那么它的peset有几种呢?看看苹果官网提供的:Video Input Preset。很显然默认情况下苹果给AVCaptureSession设置的preset为AVCaptureSessionPresetHigh,而AVCaptureSessionPresetHigh下的iPhone5s后置摄像头对应的采样视频分辨率为1080p,既不是最高,也不是最低,而采样频率则达到了最高的30FPS,要注意的是不同设备不同分辨率的最高采样帧率是不同的。而采用preset的代码如下:


[_mRecorderSession beginConfiguration];

if (![_mRecorderSession canSetSessionPreset:AVCaptureSessionPresetHigh]) {

[_mRecorderSession commitConfiguration];

return NO;

}

_mRecorderSession.sessionPreset =AVCaptureSessionPresetHigh;

[_mRecorderSession commitConfiguration];

那么现在我们想自己手动调节相应的参数,该如何去设置?此时AVCaptureDeviceFormat就派上用场了。

设置分辨率

New AV Foundation Camera Features for the iPhone 6 and iPhone 6 Plus文中可以看出iPhone6镜头分辨率能达到4k,也就是3264X2448。那么如何获取当前设备支持的所有的分辨率?代码如下:

AVCaptureDevice * device =[self getCameraDeviceWithPosition:position];//获取镜头设备

_mVideoDeviceFormats =[NSArray arrayWithArray:device.formats];//获取所有设备格式

image

那么如何找到你想要的分辨率对应的AVCaptureDeviceFormat?遍历formats,然后解释分辨率,代码如下:

CMVideoDimensions dims = CMVideoFormatDescriptionGetDimensions(format.formatDescription);

然后比对dims.width或dims.height即可知道那个AVCaptureDeviceFormat是你想要的了,下面下网上一个朋友写的format信息打印的函数,看代码:


+ (void) dumpCaptureDeviceFormat:(AVCaptureDeviceFormat*)format

{

#if DEBUG

CMVideoDimensions dims = CMVideoFormatDescriptionGetDimensions(format.formatDescription);

FourCharCode fourCC = CMFormatDescriptionGetMediaSubType(format.formatDescription);

unichar c[4];

c[0] = (fourCC >> 24) & 0xFF;

c[1] = (fourCC >> 16) & 0xFF;

c[2] = (fourCC >> 8) & 0xFF;

c[3] = (fourCC >> 0) & 0xFF;

NSString* fourCCStr = [NSString stringWithCharacters:c length:4];

NSLog(@"分辨率(%d x %d) %@", dims.width, dims.height, fourCCStr);

NSLog(@"曝光范围: %f - %f", CMTimeGetSeconds(format.minExposureDuration), CMTimeGetSeconds(format.maxExposureDuration));

NSLog(@"ISO范围: %f - %f", format.minISO, format.maxISO);

NSString* ratesStr = @"";

NSArray* supportedFrameRateRanges = [format videoSupportedFrameRateRanges];

int count = 0;

for (AVFrameRateRange* range in supportedFrameRateRanges)

{

if (count > 0)

{

ratesStr = [ratesStr stringByAppendingString:@", "];

}

NSString* rate = [NSString stringWithFormat:@"%f - %f", range.minFrameRate, range.maxFrameRate];

ratesStr = [ratesStr stringByAppendingString:rate];

count++;

}

NSLog(@"采样帧率范围: %@", ratesStr);

#endif

}

具体打印出来的信息如下:

image

拿到想要的format之后,你可以将该format直接传给镜头device的activeFormat,即可设置当前镜头的指定采样分辨率了,代码如下:


if ([_mVideoInput.device lockForConfiguration:NULL]) {

[_mVideoInput.device setActiveFormat:format];

[_mVideoInput.device unlockForConfiguration];

}

[_mRecorderSession commitConfiguration];

设置采样帧率

翻看api文档,没有发现直接设置镜头的采样频率,但发现activeVideoMinFrameDuration,activeVideoMaxFrameDuration,然后文档说可以这么处理来设置采样的帧率:


-(void)setCaptureVideFrameRate:(NSInteger)rate{

AVCaptureDeviceFormat *format =_mVideoInput.device.activeFormat;

if (!format) {

return ;

}

if ([_mVideoInput.device lockForConfiguration:NULL]) {

        //获取format允许的最大采样帧率

       float maxrate=((AVFrameRateRange*)[format.videoSupportedFrameRateRanges objectAtIndex:0]).maxFrameRate;

      //将最小,最大的采样帧率设为相同的值,即可达到指定采样帧率的效果

     if (rate<=maxrate) {

           [_mVideoInput.device setActiveVideoMinFrameDuration:CMTimeMake(10, 10*rate)];

           [_mVideoInput.device setActiveVideoMaxFrameDuration:CMTimeMake(10, 10*rate)];

     }

      [_mVideoInput.device unlockForConfiguration];

}

}

设置镜头伸缩

网上有文章说通过设置AVCaptureConnection的videoScaleAndCropFactor来达到镜头伸缩效果,但我尝试过,无法成功,后来还是翻了文档找到了,使用到了AVCaptureDevice的rampToVideoZoomFactor函数来实现,代码如下:


-(BOOL)captureZoomIn_Out:(CGFloat)zoomRate velocity:(CGFloat)velocity{

[_mVideoInput.device lockForConfiguration:nil];

AVCaptureDeviceFormat *format =_mVideoInput.device.activeFormat;

//最大的放大因子,最小为1.0

CGFloat maxZoom =format.videoMaxZoomFactor;

CGFloat curZoom =_mVideoInput.device.videoZoomFactor;

curZoom +=(maxZoom-1.0)*zoomRate;

if (curZoom<1.0) {

curZoom =1.0;

}

if (curZoom>maxZoom) {

curZoom =maxZoom;

}

[_mVideoInput.device rampToVideoZoomFactor:curZoom withRate:velocity];

[_mVideoInput.device unlockForConfiguration];

return YES;

}

至此文章基本介绍完毕,希望对你有帮助,代码暂时无法网上公开,需要代码的朋友可以发邮件到我邮箱1475134837@qq.com.有什么问题也可留言提问,工作繁忙,繁文絮节就多说了。

相关文章

网友评论

    本文标题:初识AV Foundation-视频、图像采集

    本文链接:https://www.haomeiwen.com/subject/chwwgftx.html