iPhoneのマイクで一定以上の大きさの音を検知する

Hack for Japan 7/30 ハッカソン仙台会場で「堪忍袋」というアプリの根幹を実装しました。
アイディアとして、いびきなどの音に反応して爆発音を鳴らすという単純な仕掛けですが、
肝はマイクで一定以上の音の大きさを検知することが必須となります。

今回は、AudioToolboxのAudioQueueServicesを使い、AudioQueueLevelMeterStateのpeakPowerをスレッドで監視させて実装しました。


SoundPickerViewController.h

#import <UIKit/UIKit.h>
#import <AudioToolbox/AudioToolbox.h>

@interface SoundPickerViewController : UIViewController {
    AudioQueueRef queue;  
}
@end

SoundPickerViewController.m

@interface SoundPickerViewController ()
- (void)start;
- (void)fire;
@end

@implementation SoundPickerViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    [self start];
}

static void AudioInputCallback(  
                               void* inUserData,  
                               AudioQueueRef inAQ,  
                               AudioQueueBufferRef inBuffer,  
                               const AudioTimeStamp *inStartTime,  
                               UInt32 inNumberPacketDescriptions,  
                               const AudioStreamPacketDescription *inPacketDescs) {  
}

- (void)start {
    AudioStreamBasicDescription dataFormat;  
    dataFormat.mSampleRate = 44100.0f;  
    dataFormat.mFormatID = kAudioFormatLinearPCM;  
    dataFormat.mFormatFlags = kLinearPCMFormatFlagIsBigEndian | kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
    dataFormat.mBytesPerPacket = 2;  
    dataFormat.mFramesPerPacket = 1;  
    dataFormat.mBytesPerFrame = 2;  
    dataFormat.mChannelsPerFrame = 1;  
    dataFormat.mBitsPerChannel = 16;  
    dataFormat.mReserved = 0;  
    
    AudioQueueNewInput(&dataFormat,AudioInputCallback,self,CFRunLoopGetCurrent(),kCFRunLoopCommonModes,0,&queue);  
    AudioQueueStart(queue, NULL);
    
    UInt32 enabledLevelMeter = true;  
    AudioQueueSetProperty(queue,kAudioQueueProperty_EnableLevelMetering,&enabledLevelMeter,sizeof(UInt32));
    
    [NSTimer scheduledTimerWithTimeInterval:0.2 
                                     target:self 
                                   selector:@selector(updateVolume:) 
                                   userInfo:nil 
                                    repeats:YES];
}

- (void)updateVolume:(NSTimer *)timer {
    AudioQueueLevelMeterState levelMeter;  
    UInt32 levelMeterSize = sizeof(AudioQueueLevelMeterState);  
    AudioQueueGetProperty(queue,kAudioQueueProperty_CurrentLevelMeterDB,&levelMeter,&levelMeterSize);

    NSLog(@"mPeakPower=%0.9f", levelMeter.mPeakPower);
    NSLog(@"mAveragePower=%0.9f", levelMeter.mAveragePower);
    
    if (levelMeter.mPeakPower >= -1.0f) {
        [self fire];
    }
}

- (void)fire {
    NSLog(@"fire!");
}
@end


音を拾うためにAudioQueueをスタートさせますが、その際にAudioQueueSetPropertyでkAudioQueueProperty_EnableLevelMeteringをONにします。
後は、0.2秒毎にAudioQueueのproperty、kAudioQueueProperty_CurrentLevelMeterDBの値をAudioQueueLevelMeterStateの構造体で受け取ります。構造体の中はmPeakPowerとmAveragePowerがあり、今回はPeakPowerが-1.0以上を検知するようにしました。
尚、powerはFloat32ですが最大値は0で通常は負の値となります。


録音するなどの必要はないので、今回AudioInputCallbackは実装していません。

単に音をハンドリングとしたいだけなのに、ちょっと野暮ったい気もします。