最近上手一个蓝牙控制手机视频录制的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];//获取所有设备格式
那么如何找到你想要的分辨率对应的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
}
具体打印出来的信息如下:
拿到想要的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.有什么问题也可留言提问,工作繁忙,繁文絮节就多说了。
网友评论