检测用户向麦克吹气

系统 1576 0

转自http://blog.chinaunix.net/u1/49717/showart_2180166.html

 

如果几年前你告诉我人们可以通过晃动手机或向麦克吹气使手机有所动作,我一定会大笑不止。但现在这已经是事实了。

 

检查晃动动作是很直接的,所有这些在3.0“motion event”(动作事件)中都有介绍。

 

检测向麦克吹气困难一点。本教程将建立一个简单的单视图程序,它将在用户向麦克吹气时向控制台写入记录信息。

 

 

源代码/Github

 

教程源代码 可从GitHub获得。 你可以克隆软件仓库或直接下载 zip文件

 

概述

 

检测向麦克吹气的工作可分为两部分:(1) 获取麦克输入 (2) “听”吹气的声音。

 

我们将使用3.0中新的AVAudioRecorder类来捕获麦克输入。使用AVAudioRecorder可以让我们使用Objective-C,而不需像其他方法一样使用C。

 

向麦克吹气的噪声/声音是由低频声音组成的。我们将使用 low pass filter(低频滤波) 来降低来自麦克的高频声音;当滤波信号的电平等级突然增大时,我们就知道有人向麦克吹气了。

 

创建项目

 

启动Xcode创建一个View-Based iPhone程序,叫MicBlow:

  1. 使用Xcode菜单 File > New Project… 创建一个新项目
  2. iPhone OS > Application 选择 View-based Application s然后按 Choose…
  3. 将项目命名为 MicBlow, Save

 

添加AVFoundation Framework

 

为使用AVAudioRecorder类,我们需要向项目添加AVFoundation framework:

  1. 在项目 Groups & Files 面板上展开 Targets
  2. 按Control-点击或右击 MicBlow
  3. 选择 Add > Existing Frameworks…
  4. 按下 Linked Libraries 左下角的 + 按钮
  5. 选择 AVFoundation.framework 并按下 Add
  6. AVFoundation.framework 出现在 Linked Libraries 下。关闭窗口

 

然后,我们在view controller接口中引入AVFoundation头文件并设置AVAudioRecorder实例变量:

  1. 展开项目 Groups & Files 面板下的 MicBlow
  2. 展开 Classes 文件夹
  3. 选择 MicBlowViewController.h 进行编辑
  4. 更新文件。修改见如下2,3,7行:
1
2
3
4
5
6
7
8
9
#import <UIKit/UIKit.h>  
#import <AVFoundation/AVFoundation.h>  
#import <CoreAudio/CoreAudioTypes.h>

 

@interface MicBlowViewController : UIViewController
{  
    AVAudioRecorder * recorder;  
}    
@end

 

引入CoreAudioTypes头文件实际上是下一步需要的工作。我们还需要在设置AVAudioRecorder定义更多的常量。

 

获取麦克输入

 

我们在ViewDidLoad进行设置并开始“听“取麦克:

  1. 解除样本 ViewDidLoad 方法注释
  2. 更新如下。见4-18行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- ( void ) viewDidLoad
{  
    [ super viewDidLoad ] ;
    NSURL * url = [ NSURL fileURLWithPath : @ "/dev/null" ] ;         
    NSDictionary * settings = [ NSDictionary dictionaryWithObjectsAndKeys :
        [ NSNumber numberWithFloat : 44100.0 ] , AVSampleRateKey,
        [ NSNumber numberWithInt : kAudioFormatAppleLossless ] ,
        AVFormatIDKey, [ NSNumber numberWithInt : 1 ] , AVNumberOfChannelsKey,
        [ NSNumber numberWithInt : AVAudioQualityMax ] , AVEncoderAudioQualityKey, nil ] ;       
       
    NSError * error;        
    recorder = [ [ AVAudioRecorder alloc ] initWithURL : url settings : settings error :& amp;error ] ;       
    if ( recorder ) {        
        [ recorder prepareToRecord ] ;        
        recorder.meteringEnabled = YES ;        
        [ recorder record ] ;     
    } else         
        NSLog ( [ error description ] ) ;    
}

 

AVAudioRecorder的主要功能就像前名字暗示的那样进行音频录制。其第二个功能是提供音频电平等级信息。所以,这里我们只是将音频输入指向 /dev/null 位 – 我没有找到任何文档支持我的观点,但一致意见是就像在任何Unix下一样,/dev/null将打开音频计量表。

 

注意: 如果你准备采用上述代码,记住在设置meteringEnabled属性或音频计量开始工作前,要调用prepareToRecord (或者record)。

 

记住在dealloc中释放recorder。 见第三行:

1
2
3
4
5
  - ( void ) dealloc
  {    
      [ recorder release ] ;
      [ super dealloc ] ;  
  }

 

音频采样

 

我们将使用定时器每秒30次检查一次音频电平等级。NSTimer实例变量以及其回调函数在MicBlowViewController.h中定义。修改见7,10行:

1
2
3
4
5
6
7
8
9
10
11
12
#import <UIKit/UIKit.h>  
#import <AVFoundation/AVFoundation.h>  
#import <CoreAudio/CoreAudioTypes.h>    

 

@interface MicBlowViewController : UIViewController {  
    AVAudioRecorder * recorder;     
    NSTimer * levelTimer;  
}

- ( void ) levelTimerCallback : ( NSTimer * ) timer;

@end

 

更新.m文件中ViewDidLoad启用定时器。修改见16,17行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- ( void ) viewDidLoad {  
    [ super viewDidLoad ] ;       
    NSURL * url = [ NSURL fileURLWithPath : @ "/dev/null" ] ;         
    NSDictionary * settings = [ NSDictionary dictionaryWithObjectsAndKeys :           
        [ NSNumber numberWithFloat : 44100.0 ] ,AVSampleRateKey,
        [ NSNumber numberWithInt : kAudioFormatAppleLossless ] , AVFormatIDKey, [ NSNumber numberWithInt : 1 ] ,
        AVNumberOfChannelsKey, [ NSNumber numberWithInt : AVAudioQualityMax ] ,
        AVEncoderAudioQualityKey, nil ] ;        
   
    NSError * error;        
    recorder = [ [ AVAudioRecorder alloc ] initWithURL : url settings : settings error :& amp;error ] ;       
    if ( recorder ) {        
        [ recorder prepareToRecord ] ;        
        recorder.meteringEnabled = YES ;        
        [ recorder record ] ;         
        levelTimer = [ NSTimer scheduledTimerWithTimeInterval : 0.03 target : self
            selector : @selector ( levelTimerCallback : ) userInfo : nil repeats : YES ] ;      
    } else
        NSLog ( [ error description ] ) ;    
  }

 

现在,我们只是直接进行音频采样而未使用滤波。在.m文件中添加levelTimerCallback:

1
2
3
4
5
- ( void ) levelTimerCallback : ( NSTimer * ) timer {  
    [ recorder updateMeters ] ;   
    NSLog ( @ "Average input: %f Peak input: %f" ,
        [ recorder averagePowerForChannel : 0 ] , [ recorder peakPowerForChannel : 0 ] ) ;  
}

 

发送updateMeters消息来刷新平均和峰值功率。此计数是以对数刻度计量的,-160表示完全安静,0表示最大输入值。

 

不要忘记在dealloc中释放定时器。修改见第三行:

1
2
3
4
5
6
- ( void ) dealloc
{  
    <strong> [ levelTimer release ] ;< / strong>
    [ recorder release ] ;     
    [ super dealloc ] ;  
}

 

”聆听“吹气声

 

正如概述中提到的那样,我们要使用低通滤波来消除高频声音对电平带来的影响。该算法建立了一系列将过去的每个采样输入合成而得到的结果。我们需要一个实例变量来保存此结果。更新.h文件。修改见第八行:

1
2
3
4
5
6
7
8
9
#import <UIKit/UIKit.h>  
#import <AVFoundation/AVFoundation.h>  
#import <CoreAudio/CoreAudioTypes.h>    

 

@interface MicBlowViewController : UIViewController {  
    AVAudioRecorder * recorder;     
    NSTimer * levelTimer;   
    double lowPassResults;  
}

 

替换levelTimerCallback:方法来实现此算法:

1
2
3
4
5
6
7
8
9
- ( void ) levelTimerCallback : ( NSTimer * ) timer {  
    [ recorder updateMeters ] ;       
    const double ALPHA = 0.05 ;     
    double peakPowerForChannel = pow ( 10, ( 0.05 * [ recorder peakPowerForChannel : 0 ] ) ) ;   
    lowPassResults = ALPHA * peakPowerForChannel + ( 1.0 - ALPHA ) * lowPassResults;         
    NSLog ( @ "Average input: %f Peak input: %f Low pass results: %f" ,
        [ recorder averagePowerForChannel : 0 ] ,
        [ recorder peakPowerForChannel : 0 ] , lowPassResults ) ;  
}

 

我们在每次定时器回调时重新计算一次lowPassResults变量。为方便,我们将其转换为0-1,0代表完全安静,1代表最大音量。

 

但低通滤波值超过一定门槛范围时,我们就可以判断有人向麦克吹了气。门槛范围值的设定是一种技巧。它设定太小,则太容易被触发,如果设定太高,则必须长时间用尽力气吹气才会有效果。在我们的程序中,我将其设为0.95。我们要改变一下log的条件,见第6,7行:

1
2
3
4
5
6
7
8
- ( void ) listenForBlow : ( NSTimer * ) timer {   
    [ recorder updateMeters ] ;       
    const double ALPHA = 0.05 ;     
    double peakPowerForChannel = pow ( 10, ( 0.05 * [ recorder peakPowerForChannel : 0 ] ) ) ;   
    lowPassResults = ALPHA * peakPowerForChannel + ( 1.0 - ALPHA ) * lowPassResults;     
    if ( lowPassResults > 0.95 )         
        NSLog ( @ "Mic blow detected" ) ;
}

 

好了!你可以检测是否有人吹了麦克了。

 

鸣谢及说明

 

此方法在大部分情况下工作良好,但并非任何情况都正确。我是在飞行中写的这篇文章,飞机的引擎声经常触发我的算法。类似地,在一个噪声很大的房间内足够多的低频声也会触发我的算法。

 

算法节选自 this Stack Overflow post 。上面帖子使用的是 SCListener 库来进行音频电平检测。SCListener比AVAudioRecorder更早出现;它是用来隐藏C语言细节的获取音频电平代码。而无疑AVAudioRecorder更容易使用。

 

最后,此方法确实可以在模拟器中正常工作。但你要找到Mac上的麦克。出乎我的意料,第一代Macbook上的麦克处于摄像头左方的小孔中。

检测用户向麦克吹气


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论