前言

系列文章:
《iOS视频开发(一):视频采集》
《iOS视频开发(二):视频H264硬编码》
《iOS视频开发(三):视频H264硬解码》
《iOS视频开发(四):通俗理解YUV数据》

作为iOS音视频开发之视频开发的第一篇,本文介绍iOS视频采集的相关概念及视频采集的工作原理,后续将对采集后的视频数据进行硬编码、硬解码、播放等流程进行分析讲解。


基本概念

AVCaptureSession

苹果为了管理从摄像头、麦克风等设备捕获到的信息,整了一个叫做AVCaptureSession的东西来对输入和输出数据流进行管理。AVFoundation官方文档

单个AVCaptureSession管理多个输入输出

AVCaptureSession对象是用来管理采集数据和输出数据的,它负责协调从哪里采集数据,输出到哪里。

AVCaptureDevice

一个AVCaptureDevice对象代表一个物理采集设备,我们可以通过该对象来设置物理设备的属性。

AVCaptureInput

AVCaptureInput是一个抽象类,AVCaptureSession的输入端必须是AVCaptureInput的实现类。
这里我们用到的是AVCaptureDeviceInput,作为采集设备输入端。

AVCaptureOutput

AVCaptureOutput是一个抽象类,AVCaptureSession的输出端必须是AVCaptureOutput的实现类。
这里我们用到的是AVCaptureVideoDataOutput,作为视频数据的输出端。

AVCaptureConnection

AVCaptureConnection是AVCaptureSession用来建立和维护AVCaptureInput和AVCaptureOutput之间的连接的。

AVCaptureVideoPreviewLayer

这是AVCaptureSession的一个属性,集成自CALayer,通过类名我们可以知道这个layer是用来预览采集到的视频图像的,直接把这个layer加到UIView上面就可以实现采集到的视频实时预览了。

AVCaptureConnection表示输入和输出之间的连接


视频采集的步骤

1、创建并初始化输入(AVCaptureInput)和输出(AVCaptureOutput)
2、创建并初始化AVCaptureSession,把AVCaptureInput和AVCaptureOutput添加到AVCaptureSession中
3、调用AVCaptureSession的startRunning开启采集

初始化输入(摄像头)

通过AVCaptureDevice的devicesWithMediaType:方法获取摄像头,iPhone都是有前后摄像头的,这里获取到的是一个设备的数组,要从数组里面拿到我们想要的前摄像头或后摄像头,然后将AVCaptureDevice转化为AVCaptureDeviceInput,一会儿添加到AVCaptureSession中。

// 获取所有摄像头
NSArray *cameras= [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
// 获取前置摄像头
NSArray *captureDeviceArray = [cameras filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"position == %d", AVCaptureDevicePositionFront]];
if (!captureDeviceArray.count)
{NSLog(@"获取前置摄像头失败");return;
}
// 转化为输入设备
AVCaptureDevice *camera = captureDeviceArray.firstObject;
NSError *errorMessage = nil;
self.captureDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:camera error:&errorMessage];
if (errorMessage)
{NSLog(@"AVCaptureDevice转AVCaptureDeviceInput失败");return;
}

初始化输出

初始化视频输出并设置视频数据格式,设置采集数据回调线程。
这里视频输出格式选的是kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,YUV数据格式,不理解YUV数据的概念的话可以先这么写,后面编解码再深入了解YUV数据格式

// 设置视频输出
self.captureVideoDataOutput = [[AVCaptureVideoDataOutput alloc] init];// 设置视频数据格式
NSDictionary *videoSetting = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange], kCVPixelBufferPixelFormatTypeKey, nil];
[self.captureVideoDataOutput setVideoSettings:videoSetting];// 设置输出代理、串行队列和数据回调
dispatch_queue_t outputQueue = dispatch_queue_create("ACVideoCaptureOutputQueue", DISPATCH_QUEUE_SERIAL);
[self.captureVideoDataOutput setSampleBufferDelegate:self queue:outputQueue];
// 丢弃延迟的帧
self.captureVideoDataOutput.alwaysDiscardsLateVideoFrames = YES;

初始化AVCaptureSession并设置输入输出

1、初始化AVCaptureSession,把上面的输入和输出加进来,在添加输入和输出到AVCaptureSession先查询一下AVCaptureSession是否支持添加该输入或输出端口。

2、设置视频分辨率及图像质量(AVCaptureSessionPreset),设置之前同样需要先查询一下AVCaptureSession是否支持这个分辨率。

3、如果在已经开启采集的情况下需要修改分辨率或输入输出,需要用beginConfiguration和commitConfiguration把修改的代码包围起来。在调用beginConfiguration后,可以配置分辨率、输入输出等,直到调用commitConfiguration了才会被应用。

4、AVCaptureSession管理了采集过程中的状态,当开始采集、停止采集、出现错误等都会发起通知,我们可以监听通知来获取AVCaptureSession的状态,也可以调用其属性来获取当前AVCaptureSession的状态。AVCaptureSession相关的通知都是在主线程的。

前置摄像头采集到的画面是翻转的,若要解决画面翻转问题,需要设置AVCaptureConnection的videoMirrored为YES。

self.captureSession = [[AVCaptureSession alloc] init];
// 不使用应用的实例,避免被异常挂断
self.captureSession.usesApplicationAudioSession = NO;
// 添加输入设备到会话
if ([self.captureSession canAddInput:self.captureDeviceInput])
{[self.captureSession addInput:self.captureDeviceInput];
}
// 添加输出设备到会话
if ([self.captureSession canAddOutput:self.captureVideoDataOutput])
{[self.captureSession addOutput:self.captureVideoDataOutput];
}
// 设置分辨率
if ([self.captureSession canSetSessionPreset:AVCaptureSessionPreset1280x720])
{self.captureSession.sessionPreset = AVCaptureSessionPreset1280x720;
}
// 获取连接并设置视频方向为竖屏方向
self.captureConnection = [self.captureVideoDataOutput connectionWithMediaType:AVMediaTypeVideo];
self.captureConnection.videoOrientation = AVCaptureVideoOrientationPortrait;
// 设置是否为镜像,前置摄像头采集到的数据本来就是翻转的,这里设置为镜像把画面转回来
if (camera.position == AVCaptureDevicePositionFront && self.captureConnection.supportsVideoMirroring)
{self.captureConnection.videoMirrored = YES;
}
// 获取预览Layer并设置视频方向,注意self.videoPreviewLayer.connection跟self.captureConnection不是同一个对象,要分开设置
self.videoPreviewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.captureSession];
self.videoPreviewLayer.connection.videoOrientation = self.captureParam.videoOrientation;
self.videoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;

开始视频数据采集

伺候好AVCaptureSession之后,直接调用startRunning就可以开始采集了,开启采集前最好判断一下摄像头权限有没有开启。采集到的数据会通过上面设置的代理方法captureOutput:didOutputSampleBuffer:fromConnection回调出来。若要停止采集,只需调用stopRunning即可。

AVCaptureSession的startRunning方法是个耗时操作,如果在主线程调用的话会卡UI。

/** 开始采集 */
- (BOOL)startCapture
{if (self.isCapturing){return NO;}// 摄像头权限判断AVAuthorizationStatus videoAuthStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];if (videoAuthStatus != AVAuthorizationStatusAuthorized){return NO;}[self.captureSession startRunning];self.isCapturing = YES;return YES;
}
/**摄像头采集的数据回调@param output 输出设备@param sampleBuffer 帧缓存数据,描述当前帧信息@param connection 连接*/
- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{if ([self.delegate respondsToSelector:@selector(videoCaptureDataCallback:)]){[self.delegate videoCaptureDataCallback:sampleBuffer];}
}

切换前后摄像头

采集视频数据的过程中,我们可能需要切换前后摄像头,这时候我们只需要把AVCaptureSession中的输入端改成前置摄像头就可以了。这里是修改了输入端口,也就是需要调用beginConfiguration和commitConfiguration包起来。当然也可以选择先调用stopRunning方法停止采集,然后重新配置好输入和输出,再调用startRunning开启采集。

// 获取所有摄像头
NSArray *cameras= [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
// 获取当前摄像头方向
AVCaptureDevicePosition currentPosition = self.captureDeviceInput.device.position;
AVCaptureDevicePosition toPosition = AVCaptureDevicePositionUnspecified;
if (currentPosition == AVCaptureDevicePositionBack || currentPosition == AVCaptureDevicePositionUnspecified)
{toPosition = AVCaptureDevicePositionFront;
}
else
{toPosition = AVCaptureDevicePositionBack;
}
NSArray *captureDeviceArray = [cameras filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"position == %d", toPosition]];
if (captureDeviceArray.count == 0)
{return;
}
NSError *error = nil;
AVCaptureDevice *camera = captureDeviceArray.firstObject;
// 开始配置
[self.captureSession beginConfiguration];
AVCaptureDeviceInput *newInput = [AVCaptureDeviceInput deviceInputWithDevice:camera error:&error];
[self.captureSession removeInput:self.captureDeviceInput];
if ([_captureSession canAddInput:newInput])
{[_captureSession addInput:newInput];self.captureDeviceInput = newInput;
}
// 提交配置
[self.captureSession commitConfiguration];// 重新获取连接并设置视频的方向、是否镜像
self.captureConnection = [self.captureVideoDataOutput connectionWithMediaType:AVMediaTypeVideo];
self.captureConnection.videoOrientation = AVCaptureVideoOrientationPortrait;
if (camera.position == AVCaptureDevicePositionFront && self.captureConnection.supportsVideoMirroring)
{self.captureConnection.videoMirrored = YES;
}

设置视频的帧率

iOS默认输出的视频帧率为30帧/秒,在某些应用场景下我们可能用不到30帧,我们也可以设置或修改视频的输出帧率

// 获取设置支持设置的帧率范围
NSInteger frameRate = 15;
AVFrameRateRange *frameRateRange = [self.captureDeviceInput.device.activeFormat.videoSupportedFrameRateRanges objectAtIndex:0];if (frameRate > frameRateRange.maxFrameRate || frameRate < frameRateRange.minFrameRate)
{return;
}
// 设置输入的帧率
self.captureDeviceInput.device.activeVideoMinFrameDuration = CMTimeMake(1, (int)frameRate);
self.captureDeviceInput.device.activeVideoMaxFrameDuration = CMTimeMake(1, (int)frameRate);

踩坑及总结

用的时候我们是有设置AVCaptureVideoPreviewLayer的connection的videoOrientation属性来确定画面的方向的。刚开始的时候出现了很奇怪的现象,AVCaptureVideoPreviewLayer的画面是正常的竖屏方向,但拿采集出来的视频流去播放的时候发现画面是横屏方向。后来发现,这里AVCaptureVideoPreviewLayer的connection属性跟我们通过connectionWithMediaType方法获取到的connection不是同一个对象,需要分别设置。像我们上面分析看到,connection是维护一个输入设备和一个输出的,也就是说AVCaptureSession有多个connection。找到对应需要的connection设置videoOrientation,问题解决。

iOS视频采集这一块内容不多,难度也比较低,应该还是比较好上手的。本文限于篇幅没有详细说明设置摄像头的相关内容,例如闪光灯、对焦等。建议阅读AVFoundation官方文档,少走弯路。

下一篇将介绍H264硬编码的实现,最后附上Demo地址:https://github.com/GenoChen/MediaService

iOS视频开发(一):视频采集相关推荐

  1. 【Android音视频开发】- 实时采集视频

    前言 通过我的上一篇文章,可以知道直播大致有几个步骤:音视频采集 -> 美颜/滤镜/特效处理 -> 编码 -> 封包 -> 推流 -> 分发 -> 解码/渲染/播放 ...

  2. 音视频开发-音视频面试必问的直播延迟分析丨FFmpeg|SRS流媒体服务器|webrtc|Android NDK开发|HTTP-FLV|RTSP

    音视频面试必问的直播延迟分析 1.直播延迟500ms是极限吗,加上WebRTC能做到多少 2.推流的延迟分析 3.流媒体服务器延迟分析 4.拉流的延迟分析 视频讲解如下,点击观看: 音视频开发-音视频 ...

  3. 【Android 音视频开发-音视频硬解码篇】1.音视频基础知识

    这是一个入门系列,涉及的知识也仅限于够用. 最后,写文章过程中,会借鉴参考其他人分享的文章,会在文章最后列出,感谢这些作者的分享. 本文你可以了解到 作为开篇的文章,我们先来看看音视频由什么构成的,以 ...

  4. 音视频开发--音视频基础

    音视频基础 一.音视频录制原理 视频录制流程 1.准备摄像头 2.图像帧阶段 从摄像头采集视频数据(图像帧),采集数据格式:YUV或者RGB,YUV和RGB细分的话还包括YUV 4:4:4.YUV 4 ...

  5. Moviepy音视频开发:视频转gif动画或jpg图片exe图形化工具开发案例

    ☞ ░ 前往老猿Python博文目录 ░ 一.引言 老猿之所以学习和研究Moviepy的使用,是因为需要一个将视频转成动画的工具,当时在网上到处搜索查找免费使用工具,结果找了很多自称免费的工具,但转完 ...

  6. 音视频开发——音视频学习资料

    目录 1.为什么要学习音视频? 2.如何学习系统性音视频? 3.音视频相关的资料 最近有朋友问想学习音视频,应该怎么学,有什么资料吗? 这个问题也困扰我很久,几年前就想开始音视频相关的学习,但是一直找 ...

  7. 音视频开发---音视频同步算法

    目录 ffplay简介 为什么要做音视频同步 音视频同步算法 参考 本文是对音视频同步算法的总结,以阅读ffplay.c源码为基础,结合各位博主的分析, 逐渐深入理解同步算法原理, 并根据自身理解, ...

  8. 【Anychat音视频开发】视频直播系统的开发技术点

    视频直播是利用视频压缩.直播等流媒体技术,在装有电视卡或视频采集卡的电脑上安装一套视频直播服务软件,把采集到的视频信号进行一系列实时编码.处理,然后再广播出去,起到同步直播的效果.视频直播被广泛的应用 ...

  9. Qt/C++音视频开发45-音视频类结构体参数的设计

    一.前言 视频监控内核组件重构和完善花了一年多时间,整个组件个人认为设计的最好的部分就是各种结构体参数的设计,而且分门别类,有枚举值,也有窗体相关的结构体参数,解码相关的结构体参数,同时将部分常用的结 ...

  10. Qt音视频开发27-ffmpeg视频旋转显示

    一.前言 用手机或者平板拍摄的视频文件,很可能是旋转的,比如分辨率是1280x720,确是垂直的,相当于分辨率变成了720x1280,如果不做旋转处理的话,那脑袋必须歪着看才行,这样看起来太难受,所以 ...

最新文章

  1. 经典 | 吴恩达《机器学习落地应用指南》(30页ppt)
  2. web05-CounterServlet
  3. P1351 联合权值
  4. [python] 常用正则表达式爬取网页信息及分析HTML标签总结
  5. Python 文件读和写
  6. 什么是响应式设计?为什么要做响应式设计?响应式设计的基本原理是什么?...
  7. 手把手教你写一个微信小程序日历组件
  8. OSPF定义的5种区域类型:标准区域、主干区域、存根区域、完全存根区域
  9. IIS7.5使用web.config设置伪静态的二种方法(转)
  10. 编程语言(C语言,JAVA),程序设计,APP开发,算法
  11. java 获取中文拼音首字母(缩写) 含pinyin4j maven包
  12. 正确区分LJMP、AJMP、SJMP、JMP跳转指令
  13. 图像特征提取现成的方法
  14. android 解析json 日期格式,处理Json数据中的日期类型.如/Date(1415169703000)/格式
  15. 谨以此写下本人安装riscv的全过程 简单易懂!!(本人环境是在ubuntu18.04中)
  16. JWT、JWS与JWE
  17. mobaxterm配置Tunneling隧道连接服务器
  18. cdn perl_用perl对CDN节点日志进行统计
  19. 安卓开发— —仿微信界面(二)
  20. 一个初级运维工程师对于运维工作的一些浅显认知

热门文章

  1. python在excel中的应用:freeze_panes冻结不是第一行的问题,其实可能是一个小细节。
  2. Django - 安装wagtail
  3. 周受资从小米跳槽字节跳动任CFO、拜腾创始人戴雷将加盟恒大汽车 | 高管变动2021年3月22日-28日...
  4. 华为自研OS操作系统,今秋是否真的会面市?
  5. box-sizing与盒模型
  6. JMeter-Ramp-up Period解释
  7. PS如何调整图片像素大小
  8. arcgis里面怎么截图_怎么利用ARCGIS裁剪图像
  9. android q mix3,Android Q+5G 小米MIX3流畅播放8K视频
  10. word复制或粘贴等操作使应用未响应