在使用原生的 AVFoundation
框架实现二维码扫描的时候, 需要注意一下两个方面:
- 启动相机的卡顿问题;
- 有效扫描区域的问题; 本文主要针对这两个问题进行讲解.
1. 启动扫描卡顿
在Push
到二维码扫描页时, 一般在初始化扫描视图的时候就开始启动session
:
[self.session startRunning]
但是这样会有一个问题, 就是点击扫描按钮的时候, 按钮会1秒多的卡顿, 然后才会Push
到扫描页;
针对这个问题, 有的人是在Push
到扫描页的时候, 才去启动 session
, 但是这样会有1 – 2秒的等待时间, 才会出现正常的扫描画面;
针对此问题, 解决的方式很简单, 依然在初始化的时候启动 session
, 但是, 需要异步去启动:
[self.activity startAnimating];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
if (self.session.isRunning == NO) {
NSLog(@"startRunning");
[self.session startRunning];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.activity stopAnimating];
});
});
这样, 在push
的过程中就在准备session
, 可以很大程度上缩短等待时间, 甚至, 在push
到扫描页的时候, 几乎看不到等待时间.
需要注意
如果使用了 属性观察者模式, 其方法是异步执行的, 需要返回到主线程, 例如这里我监听了属性 running
:
[self.session addObserver:self forKeyPath:@"running" options:NSKeyValueObservingOptionNew context:nil];
根据这个来开始/停止扫描线的动画, 这时候, 就需要回到主线程执行:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context{
dispatch_async(dispatch_get_main_queue(), ^{
if ([object isKindOfClass:[AVCaptureSession class]]) {
if ([keyPath isEqualToString:@"running"]) {
BOOL isRunning = ((AVCaptureSession *)object).isRunning;
if (isRunning) {
[self startAnimate];
}else{
[self stopAnimate];
}
}
}
});
}
2. AVCaptureMetadataOutput 有效扫描区域 rectOfInterest
首先, 需要知道 rectOfInterest
所在的坐标系, 其坐标原点是图像的左上角, 注意, 这里是图像的左上角, 不是设备的左上角, 其值介于 0 – 1, 如果设置为 CGRectMake(0, 0, 1, 1)
将是全屏幕扫描; 所以需要将我们的扫描框的坐标(相对于设备), 转换为 0–1 之间的值(相对于图像).
2.1. 影响因素
要想准确计算其区域, 就需要知道影响因素, rectOfInterest
的影响因素有以下两个:
AVCaptureSession
的sessionPreset
属性
AVCaptureVideoPreviewLayer
的videoGravity
属性
前者为图像的分辨率, 不同的值生成不同分辨率的图像;
后者为预览时的图像填充模式, 类似于 UIView
的 contentMode
属性;
sessionPreset
// 完整的图像分辨率输出,不支持音频
NSString *const AVCaptureSessionPresetPhoto;
// 最高分辨率,根据设备系统自动选择最高分辨率
NSString *const AVCaptureSessionPresetHigh;
// 中等分辨率,根据设备系统自动选择中等分辨率
NSString *const AVCaptureSessionPresetMedium;
// 最低分辨率,根据设备系统自动选择最低分辨率
NSString *const AVCaptureSessionPresetLow;
// 以352x288分辨率输出
NSString *const AVCaptureSessionPreset352x288;
// 以640x480分辨率输出
NSString *const AVCaptureSessionPreset640x480;
// 以1280x720分辨率输出
NSString *const AVCaptureSessionPreset1280x720;
// 以1920x1080分辨率输出
NSString *const AVCaptureSessionPreset1920x1080;
// 以960x540分辨率输出
NSString *const AVCaptureSessionPresetiFrame960x540;
// 以1280x720分辨率输出
NSString *const AVCaptureSessionPresetiFrame1280x720;
// 不去控制音频与视频输出设置,而是通过已连接的捕获设备的 activeFormat 来反过来控制 capture session 的输出质量等级
NSString *const AVCaptureSessionPresetInputPriority;
videoGravity
// 保持原始比例,自适应最小的bounds,不足的会有留白;类似于UIView的contentMode属性的UIViewContentModeScaleAspectFit.
AVLayerVideoGravityResizeAspect;
// 保持原始比例,填充整个bounds,多余的会被剪掉,类似于UIView的contentMode属性的UIViewContentModeScaleAspectFill.
AVLayerVideoGravityResizeAspectFill;
// 拉伸直到填充整个bounds,类似于UIView的contentMode属性的UIViewContentModeSca