1.基本概念
要了解如何通过AVFoundation扫描二维码,首先要了解下面几个类
<code>AVCaptureSession</code>
关于AVCaptureSession,我在前面一个文章里已经有所描述,可以去前面文章看下,其实它是一个连通输入和输出设备的桥梁AVCaptureSession概述
<code>AVCaptureDevice</code>
AVCaptureDevice,获取手机的相机设备,通常使用下面方法进行初始化
<code>+ (AVCaptureDevice *)defaultDeviceWithMediaType:(NSString *)mediaType;</code>
可以调节以下的功能:1. 前置和后置摄像头2. 闪光灯开关3. 手电筒开关4. 焦距调整5. 曝光量调节6. 白平衡
<code>AVCaptureVideoPreviewLayer</code>
视频抓取的预览层,基于CALayer,显示捕获到的相机输出流。
<code>AVCaptureDeviceInput</code>
是AVCaptureInput的子类,可以作为输入捕获会话,用AVCaptureDevice实例初始化。
<code>AVCaptureMetadataOutput</code>
是AVCaptureOutput的子类,处理输出捕获会话。捕获的对象传递给一个委托实现AVCaptureMetadataOutputObjectsDelegate协议。协议方法在指定的派发队列(dispatch queue)上执行。
2 初始化
定义成员变量
@property (strong, nonatomic)AVCaptureVideoPreviewLayer *preview;
@property (nonatomic, strong) AVCaptureDeviceInput *activeVideoInput;
@property (nonatomic, strong) AVCaptureMetadataOutput *metadataOutPut;
@property (nonatomic, strong) AVCaptureSession *captureSeesion;
初始化方法
- (BOOL)setUpSession{
NSError *error;
// 初始化会话
self.captureSeesion = [[AVCaptureSession alloc] init];
self.captureSeesion.sessionPreset = AVCaptureSessionPresetHigh;
// 初始化默认输入设备
AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
// 默认的视频捕捉设备
self.activeVideoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error];
if (error) {
// 处理错误信息
return NO;
}
// 初始化输出设备
if (self.activeVideoInput) {
if ([self.captureSeesion canAddInput: self.activeVideoInput]) {
[self.captureSeesion addInput: self.activeVideoInput];
}
}else{
// 处理错误信息
return NO;
}
// 初始化输出设备
self.metadataOutPut = [[AVCaptureMetadataOutput alloc] init];
if ([self.captureSeesion canAddOutput:self.metadataOutPut]) {
[self.captureSeesion addOutput:self.metadataOutPut];
dispatch_queue_t mainQueue = dispatch_get_main_queue();
[self.metadataOutPut setMetadataObjectsDelegate:self queue:mainQueue];
// 支持的扫描样式
NSArray *types = @[AVMetadataObjectTypeQRCode,AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode128Code];
self.metadataOutPut.metadataObjectTypes = types;
}else{
// 处理错误信息
return NO;
}
return YES;
}
添加AVCaptureVideoPreviewLayer到视图中
BOOL isSuccess= [self setUpSession];
if (isSuccess) {
self.preview = [AVCaptureVideoPreviewLayer layerWithSession:self.captureSeesion];
self.preview.videoGravity = AVLayerVideoGravityResizeAspectFill;
//把拍摄的layer添加到主视图的layer中
[self.view.layer insertSublayer:self.preview atIndex:0];
self.preview.frame = self.view.bounds;
[self.captureSeesion startRunning];
}
实现代理方法
//扫描完成的时候就会调用
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
//终止会话
[self.captureSeesion stopRunning];
//把扫描的layer从主视图的layer中移除
[self.preview removeFromSuperlayer];
NSString *val = nil;
if (metadataObjects.count > 0)
{
//取出最后扫描到的对象
AVMetadataMachineReadableCodeObject *obj = metadataObjects[0];
//获得扫描的结果
val = obj.stringValue;
NSLog(@"%@",val);
// 可以执行回调函数
}
}
3.other
类似微信二维码扫描
可以在AVCaptureVideoPreviewLayer上添加一个View,然后讲扫描范围缩小到一个指定的区域,可以提升扫描速度和性能的.
通过设置<code>AVCaptureMetadataOutput</code>的<code>rectOfInterest</code>属性来约束扫描范围
/*!
@property rectOfInterest
@abstract
Specifies a rectangle of interest for limiting the search area for visual metadata.
@discussion
The value of this property is a CGRect that determines the receiver's rectangle of interest for each frame of video.
The rectangle's origin is top left and is relative to the coordinate space of the device providing the metadata. Specifying
a rectOfInterest may improve detection performance for certain types of metadata. The default value of this property is the
value CGRectMake(0, 0, 1, 1). Metadata objects whose bounds do not intersect with the rectOfInterest will not be returned.
*/
@property(nonatomic) CGRect rectOfInterest NS_AVAILABLE_IOS(7_0);
<code>rectOfInterest</code>是一个基于X,Y,Width和Height的比例,是基于图像的大小裁剪的。
可以通过对下面的代码进行修改,裁剪
CGSize size = self.view.bounds.size;
CGRect cropRect = CGRectMake(40, 100, 240, 240);
CGFloat p1 = size.height/size.width;
CGFloat p2 = 1920./1080.; //使用了1080p的图像输出
if (p1 < p2) {
CGFloat fixHeight = bounds.size.width * 1920. / 1080.;
CGFloat fixPadding = (fixHeight - size.height)/2;
captureOutput.rectOfInterest = CGRectMake((cropRect.origin.y + fixPadding)/fixHeight,
cropRect.origin.x/size.width,
cropRect.size.height/fixHeight,
cropRect.size.width/size.width);
} else {
CGFloat fixWidth = bounds.size.height * 1080. / 1920.;
CGFloat fixPadding = (fixWidth - size.width)/2;
captureOutput.rectOfInterest = CGRectMake(cropRect.origin.y/size.height,
(cropRect.origin.x + fixPadding)/fixWidth,
cropRect.size.height/size.height,
cropRect.size.width/fixWidth);
}
4.补充
通常情况下,我们还要对手机是否支持摄像头进行判断,或者是否开启对摄像头的权限.
判断摄像头是否可用(在模拟器上返回no)
-(BOOL)isCameraValid {
return [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera] &&
[UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceRear];
}
判断摄像头的权限是否开启
- (BOOL)isCameraAllowed {
NSString *mediaType = AVMediaTypeVideo;
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:mediaType];
if(authStatus == AVAuthorizationStatusRestricted || authStatus == AVAuthorizationStatusDenied){
return NO;
}
return YES;
}
可以通过进行这两个判断,然后选择是否弹出扫描的控制器
可以通过下载github上的例子查看
https://github.com/Raul7777/XHScanTool