转自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:
- 使用Xcode菜单 File > New Project… 创建一个新项目
- 从 iPhone OS > Application 选择 View-based Application s然后按 Choose…
- 将项目命名为 MicBlow, 按 Save
添加AVFoundation Framework
为使用AVAudioRecorder类,我们需要向项目添加AVFoundation framework:
- 在项目 Groups & Files 面板上展开 Targets
- 按Control-点击或右击 MicBlow
- 选择 Add > Existing Frameworks…
- 按下 Linked Libraries 左下角的 + 按钮
- 选择 AVFoundation.framework 并按下 Add
- AVFoundation.framework 出现在 Linked Libraries 下。关闭窗口
然后,我们在view controller接口中引入AVFoundation头文件并设置AVAudioRecorder实例变量:
- 展开项目 Groups & Files 面板下的 MicBlow
- 展开 Classes 文件夹
- 选择 MicBlowViewController.h 进行编辑
- 更新文件。修改见如下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
|
引入CoreAudioTypes头文件实际上是下一步需要的工作。我们还需要在设置AVAudioRecorder定义更多的常量。
获取麦克输入
我们在ViewDidLoad进行设置并开始“听“取麦克:
- 解除样本 ViewDidLoad 方法注释
- 更新如下。见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 |
更新.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
{
|
替换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上的麦克处于摄像头左方的小孔中。