マイクの入力レベルをdBで取得する

マイクの入力レベルを調べるのには、AVFoundationを使うのがお手軽です。下のコードは、マイクからの入力レベルをコンソール上に出力するごくごく簡単なサンプルです。

try!を使っていたりcanAddInput/Output()を使っていなかったりしていますので、その点は注意してください。

final class PowerLevel: NSObject {
    private let captureSession_ = AVCaptureSession()
    // オーディオデータを渡してくれるデリゲートメソッドが動くスレッド。
    /* ※データ順が狂ったりメインスレッドで処理落ちしないように
       「バックグラウンド」の「シリアルキュー」を必ず使うこと。*/
    private let serialQueue_ = dispatch_queue_create("PowerLevel.serialqueue.audio",
                                                    DISPATCH_QUEUE_SERIAL)
    init() {
        super.init()
        setupCaptureRoute()
    }

    func setupCaptureRoute() {
        // OSがデフォルトにしているマイクデバイスを選ぶ
        let audioDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeAudio)

        // マイクデバイスをキャプチャセッションにつなぐ入力クラスを用意
        let audioInput = try! AVCaptureDeviceInput(device: audioDevice)

        // マイク入力の出力先(今回はデータをデリゲートメソッドに渡す)を用意
        let audioDataOut = AVCaptureAudioDataOutput()
        // デリゲートオブジェクトを設定
        audioDataOut.setSampleBufferDelegate(self, queue: serialQueue_)

        // キャプチャセッションに入出力を接続。これでいつでもstartできる
        captureSession_.addInput(audioInput)
        captureSession_.addOutput(audioDataOut)
    }

    func start() { captureSession_.startRunning() /* 開始 */ }
    func stop() { captureSession_.stopRunning() /* 停止 */ }
}

// デリゲートメソッドの用意
extension PowerLevel: AVCaptureAudioDataOutputSampleBufferDelegate {
    func captureOutput(captureOutput: AVCaptureOutput!,
                             didOutputSampleBuffer sampleBuffer: CMSampleBuffer!,
                             fromConnection connection: AVCaptureConnection!) {

        // キャプチャコネクションからオーディオチャンネルを取得し、パワーレベル(dB)を入手
        let audioChannels = connection.audioChannels.filter{ $0 is AVCaptureAudioChannel }
        audioChannels.forEach {
            print("\($0.averagePowerLevel) / \($0.peakHoldLevel)")
        }
    }
}

このコードはこんなふうに使います。

let obj = PowerLevel()

obj.start() // コンソールにマイク入力のレベルをdB表示

obj.stop() // 処理停止

得られる値はdBで、デバイスの最大入力レベルを「0dB」とした値(つまり0に近いほど大きな値を表すマイナス値)になっています。

追記

オーディオチャンネルが複数あることも考慮すると、それぞれの平均をとったほうがよいかもです。つまり、デリゲートメソッドの中にある処理を、次のように変えます。

        let audioChannels = connection.audioChannels.filter{ $0 is AVCaptureAudioChannel }
        if !audioChannels.isEmpty {
            let averagePowerLevel = audioChannels.reduce(0.0){ $0 + $1.averagePowerLevel }
                / Float(audioChannels.count)
            let peakHoldLevel = audioChannels.reduce(0.0){ $0 + $1.peakHoldLevel }
                / Float(audioChannels.count)
            print("\(averagePowerLevel) / \(peakHoldLevel)")
        }

参考: stackoverflow.com