SwiftでAudio Queue Recorderを書きました。

ちょっと思い立って、Audio Queueを使ったAudio録音ができるコードをかいてみました。

github.com

基本的にはAppleのだしているhttps://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/Introduction/Introduction.htmlを参考にしたんですが、このドキュメントは最終更新日が2013年で、サンプルコードは当然 Objective-Cで書かれています。

Audio QueueはiOS2.0から使われているとても古いフレームワークですし、いろいろとつらみも多く、思ったより手間取ってしまいました。

使い方

録音用のクラスを生成し、prepareなどをよんでバッファー作成などをしておきます。

        audioRecorder = AudioRecorder()
        audioRecorder?.prepare()
        audioRecorder?.prepareQueue()
        audioRecorder?.setupBuffer()

録音を開始する時にはこちらをよんでください。

       audioRecorder?.startRecord()

終了する時にはこちら。

        audioRecorder?.stopRecord()

録音データ

Documentフォルダにファイルを保存します。

録音フォーマットは aiff ですが、このコードの中のこのあたりで変更できます。

        let documentDirectories = FileManager.default.urls(
            for: FileManager.SearchPathDirectory.documentDirectory,
            in: FileManager.SearchPathDomainMask.userDomainMask)
        let docDirectory = (documentDirectories.first)!
        
        var audioFilePathURL = docDirectory.appendingPathComponent("audiotest")
        audioFilePathURL.appendPathExtension("aiff")

また、サンプリングレートなどはこのあたりで変えてみてください。

    var currentMusicDataFormat = AudioStreamBasicDescription(
        mSampleRate: 44100,
        mFormatID: kAudioFormatLinearPCM,
        mFormatFlags: AudioFormatFlags(kLinearPCMFormatFlagIsBigEndian|kLinearPCMFormatFlagIsSignedInteger|kLinearPCMFormatFlagIsPacked),
        mBytesPerPacket: 4,
        mFramesPerPacket: 1,
        mBytesPerFrame: 4,
        mChannelsPerFrame: 2,
        mBitsPerChannel: 16,
        mReserved: 0)

iOSむけにかきましたが、おそらくMacでも動くんじゃないかと思います。

いろいろ

つまったポイントとしては、 AudioQueueInputCallback にわたされるinUserData :Optional<UnsafeMutableRawPointer>を、bindMemoryでもとのクラスにcastしようとしたら、うまくいかなかったことでした。

 let userData1: UnsafeMutablePointer<RecorderState>? = inUserData?.bindMemory(to: RecorderState.self, capacity: MemoryLayout<RecorderState>.size)

念のため、

 let userData1: UnsafeMutablePointer<RecorderState>? = inUserData?.bindMemory(to: RecorderState.self, capacity: MemoryLayout<RecorderState>.stride)

にしてみてもうまく動かず。

最終的には、こんな形でcastしています。

        let unManagedUserData = Unmanaged<RecorderState>.fromOpaque(inUserData!)
        let receivedUserData = unManagedUserData.takeUnretainedValue()

castには手間取りましたが、AudioQueueは予想していたより楽に使えますね。 簡単に録音ができるとアプリの機能もいろいろと豪華にできそうです。