koogawa blog

iOS、Android、foursquareに関する話題

【Tips】iOSでマイクの音を検知する(Swift編)

数年前に書いた下記記事が古くなってきたので、Swift編 として書き直しました。

f:id:koogawa:20160417130548p:plain


Core Audioを使用して、マイクから音を検知する方法をメモしておきます。

実行環境

  • Xcode 7.3
  • Swift 2.2
  • Deployment Target iOS 8

実装方法

ヘッダをインポートします。

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
}

通常、mPeakPowermAveragePower には負の値がセットされており、音量に比例して値も大きくなっていきます。最大値は 0 になります。

最後に忘れずにキューを空にして停止します。

AudioQueueFlush(self.queue)
AudioQueueStop(self.queue, false)
AudioQueueDispose(self.queue, true)

サンプルコード

注意点

  • ユーザがマイクの使用を許可していない場合、音の検知はできません

応用できそうなアプリ

  • 声や拍手などに反応するゲームアプリなど
  • iPhoneに息を吹きかけると何かがめくれるアプリ(実際にありましたねw)

参考にさせて頂いたリンク