• iOS使用AVFoundation实现二维码扫描


    原文:http://strivingboy.github.io/blog/2014/11/08/scan-qrcode/

    关于二维码扫描有不少优秀第三方库如:

    最近项目需要,看了下使用ios7自带的 AVFoundation Framework 来实现二维码扫描,Demo 见:scan_qrcode_demo

    关于AVFoundation

    AVFoundation 是一个很大基础库,用来创建基于时间的视听媒体,可以使用它来检查,创建、编辑或媒体文件。也可以输入流从设备和操作视频实时捕捉和回放。详细框架介绍见官网:About AV Foundation,本文只是介绍如果使用AVFoundation获取二维码。

    首先获取流媒体信息我们需要AVCaptureSession对象来管理输入流和输出流,AVCaptureVideoPreviewLayer对象来显示信息,基本流程如下图所示:

    技术分享

    注:

    • AVCaptureSession 管理输入(AVCaptureInput)和输出(AVCaptureOutput)流,包含开启和停止会话方法。
    • AVCaptureDeviceInput 是AVCaptureInput的子类,可以作为输入捕获会话,用AVCaptureDevice实例初始化。
    • AVCaptureDevice 代表了物理捕获设备如:摄像机。用于配置等底层硬件设置相机的自动对焦模式。
    • AVCaptureMetadataOutput 是AVCaptureOutput的子类,处理输出捕获会话。捕获的对象传递给一个委托实现AVCaptureMetadataOutputObjectsDelegate协议。协议方法在指定的派发队列(dispatch queue)上执行。
    • AVCaptureVideoPreviewLayerCALayer的一个子类,显示捕获到的相机输出流。

    下面看下实现过程如下:

    Step1:需要导入:AVFoundation Framework 包含头文件:

    #import <AVFoundation/AVFoundation.h>
    

    Step2:设置捕获会话

    设置 AVCaptureSession 和 AVCaptureVideoPreviewLayer 成员

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
        #import <AVFoundation/AVFoundation.h>
    
        static const char *kScanQRCodeQueueName = "ScanQRCodeQueue";
    
        @interface ViewController () <AVCaptureMetadataOutputObjectsDelegate>
        .....
        @property (nonatomic) AVCaptureSession *captureSession;
        @property (nonatomic) AVCaptureVideoPreviewLayer *videoPreviewLayer;
        @property (nonatomic) BOOL lastResult;
        @end
    

    Step3:创建会话,读取输入流

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    
        - (BOOL)startReading
        {
            // 获取 AVCaptureDevice 实例
            NSError * error;
            AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
            // 初始化输入流
            AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error];
            if (!input) {
                NSLog(@"%@", [error localizedDescription]);
                return NO;
            }
            // 创建会话
            _captureSession = [[AVCaptureSession alloc] init];
            // 添加输入流
            [_captureSession addInput:input];
            // 初始化输出流
            AVCaptureMetadataOutput *captureMetadataOutput = [[AVCaptureMetadataOutput alloc] init];
            // 添加输出流
            [_captureSession addOutput:captureMetadataOutput];
    
            // 创建dispatch queue.
            dispatch_queue_t dispatchQueue;
            dispatchQueue = dispatch_queue_create(kScanQRCodeQueueName, NULL);
            [captureMetadataOutput setMetadataObjectsDelegate:self queue:dispatchQueue];
            // 设置元数据类型 AVMetadataObjectTypeQRCode
            [captureMetadataOutput setMetadataObjectTypes:[NSArray arrayWithObject:AVMetadataObjectTypeQRCode]];
    
            // 创建输出对象
            _videoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:_captureSession];
            [_videoPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
            [_videoPreviewLayer setFrame:_sanFrameView.layer.bounds];
            [_sanFrameView.layer addSublayer:_videoPreviewLayer];
            // 开始会话
            [_captureSession startRunning];
    
            return YES;
        }
    

    Step4:停止读取

    1
    2
    3
    4
    5
    6
    7
    
        - (void)stopReading
        {
            // 停止会话
            [_captureSession stopRunning];
            _captureSession = nil;
        }
    

    Step5:获取捕获数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    -(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects
      fromConnection:(AVCaptureConnection *)connection
    {
        if (metadataObjects != nil && [metadataObjects count] > 0) {
            AVMetadataMachineReadableCodeObject *metadataObj = [metadataObjects objectAtIndex:0];
            NSString *result;
            if ([[metadataObj type] isEqualToString:AVMetadataObjectTypeQRCode]) {
                result = metadataObj.stringValue;
            } else {
                NSLog(@"不是二维码");
            }
            [self performSelectorOnMainThread:@selector(reportScanResult:) withObject:result waitUntilDone:NO];
        }
    }
    

    Step6:处理结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
      - (void)reportScanResult:(NSString *)result
      {
          [self stopReading];
          if (!_lastResult) {
              return;
          }
          _lastResut = NO;
          UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"二维码扫描"
                                                          message:result
                                                         delegate:nil
                                                cancelButtonTitle:@"取消"
                                                otherButtonTitles: nil];
          [alert show];
          // 以下处理了结果,继续下次扫描
          _lastResult = YES;
      }
    

    以上基本就是二维码的获取流程,和扫一扫二维码伴随的就是开启系统照明,这个比较简单,也是利用 AVCaptureDevice,请看如下实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
      - (void)systemLightSwitch:(BOOL)open
      {
          AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
          if ([device hasTorch]) {
              [device lockForConfiguration:nil];
              if (open) {
                  [device setTorchMode:AVCaptureTorchModeOn];
              } else {
                  [device setTorchMode:AVCaptureTorchModeOff];
              }
              [device unlockForConfiguration];
          }
      }
    

    以上就是本文介绍的大部分内容,详细代码请看demo scan_qrcode_deomo

    实现过程中遇到一下两个问题:

    1、扫描一个二维码,输出流回重复调用,代理方法头文件介绍:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
       /*!
      @method captureOutput:didOutputMetadataObjects:fromConnection:
      .....  
      @discussion
         Delegates receive this message whenever the output captures and emits new objects, as specified by
         its metadataObjectTypes property. Delegates can use the provided objects in conjunction with other APIs
         for further processing. This method will be called on the dispatch queue specified by the output‘s
         metadataObjectsCallbackQueue property. **This method may be called frequently** so it must be efficient to 
         prevent capture performance problems, including dropped metadata objects.
     
         Clients that need to reference metadata objects outside of the scope of this method must retain them and
         then release them when they are finished with them.
     */
    

    代理方法会频繁调用,我暂且用一个标记(@property (nonatomic) BOOL lastResult)表示是否是第一次扫描成功,来处理。

    2、AVFoundation 该库不能扫描相册中的二维码图片,不知为啥苹果没有支持,有知道实现的麻烦告诉我哈。

    参考链接

  • 相关阅读:
    软件产品案例分析 ——华为软件开发云
    软件工程实践2017第一次作业
    软件工程实践2017结对第二次作业
    SDN第一次作业
    路由器工作原理
    Spring框架之springweb web源码完全解析
    Spring框架之springweb http源码完全解析
    Spring框架之jms源码完全解析
    Spring框架之AOP源码完全解析
    比特币里的计算机知识
  • 原文地址:https://www.cnblogs.com/lzjsky/p/5057134.html
Copyright © 2020-2023  润新知