数年前に書いた下記記事が古くなってきたので、Swift編 として書き直しました。
Core Audioを使用して、マイクから音を検知する方法をメモしておきます。
実行環境
実装方法
ヘッダをインポートします。
import AudioToolbox
音声入力用のキューと監視タイマーを準備しておきます。
var queue: AudioQueueRef! var timer: NSTimer!
記録するデータフォーマットを決めます。
var dataFormat = AudioStreamBasicDescription( mSampleRate: 44100.0, mFormatID: kAudioFormatLinearPCM, mFormatFlags: AudioFormatFlags(kLinearPCMFormatFlagIsBigEndian | kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked), mBytesPerPacket: 2, mFramesPerPacket: 1, mBytesPerFrame: 2, mChannelsPerFrame: 1, mBitsPerChannel: 16, mReserved: 0)
▼各項目の意味
データ型 | メンバ名 | 意味 |
---|---|---|
Float64 | mSampleRate | サンプリング周波数(1秒間のフレーム数) |
UInt32 | mFormatID | フォーマットID(リニアPCM、MP3、AACなど) |
UInt32 | mFormatFlags | フォーマットフラグ(エンディアン、整数or浮動小数点数) |
UInt32 | mBytesPerPacket | 1パケット(データを読み書きする単位)のバイト数 |
UInt32 | mFramesPerPacket | 1パケットのフレーム数 |
UInt32 | mBytesPerFrame | 1フレームのバイト数 |
UInt32 | mChannelsPerFrame | 1フレームのチャンネル数 |
UInt32 | mBitsPerChannel | 1チャンネルのビット数 |
UInt32 | mReserved | ? |
音の監視を開始します。
var audioQueue: AudioQueueRef = nil var error = noErr error = AudioQueueNewInput( &dataFormat, AudioQueueInputCallback, UnsafeMutablePointer(unsafeAddressOf(self)), .None, .None, 0, &audioQueue) if error == noErr { self.queue = audioQueue } AudioQueueStart(self.queue, nil)
今回は音を検知するだけで、録音の必要はないので AudioInputCallback
は空にしておきます。
private func AudioQueueInputCallback( inUserData: UnsafeMutablePointer<Void>, inAQ: AudioQueueRef, inBuffer: AudioQueueBufferRef, inStartTime: UnsafePointer<AudioTimeStamp>, inNumberPacketDescriptions: UInt32, inPacketDescs: UnsafePointer<AudioStreamPacketDescription>) { // Do nothing, because not recoding. }
マイクのレベルを取得できるように、レベルメータを有効化しておきます。
var enabledLevelMeter: UInt32 = 1 AudioQueueSetProperty(self.queue, kAudioQueueProperty_EnableLevelMetering, &enabledLevelMeter, UInt32(sizeof(UInt32)))
一定時間ごとにマイクレベルを監視します。ここでは NSTimer
を使用しました。
self.timer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: #selector(AudioViewController.detectVolume(_:)), userInfo: nil, repeats: true) self.timer.fire()
一定時間ごとにレベルを取得して、画面に表示します。
func detectVolume(timer: NSTimer) { // Get level var levelMeter = AudioQueueLevelMeterState() var propertySize = UInt32(sizeof(AudioQueueLevelMeterState)) AudioQueueGetProperty( self.queue, kAudioQueueProperty_CurrentLevelMeterDB, &levelMeter, &propertySize) // Show the audio channel's peak and average RMS power. self.peakTextField.text = "".stringByAppendingFormat("%.2f", levelMeter.mPeakPower) self.averageTextField.text = "".stringByAppendingFormat("%.2f", levelMeter.mAveragePower) // Show "LOUD!!" if mPeakPower is larger than -1.0 self.loudLabel.hidden = (levelMeter.mPeakPower >= -1.0) ? false : true }
通常、mPeakPower
、mAveragePower
には負の値がセットされており、音量に比例して値も大きくなっていきます。最大値は 0 になります。
最後に忘れずにキューを空にして停止します。
AudioQueueFlush(self.queue) AudioQueueStop(self.queue, false) AudioQueueDispose(self.queue, true)
サンプルコード
注意点
- ユーザがマイクの使用を許可していない場合、音の検知はできません
応用できそうなアプリ
- 声や拍手などに反応するゲームアプリなど
- iPhoneに息を吹きかけると何かがめくれるアプリ(実際にありましたねw)