AudioQueueを使って m4a (AAC) で録音する Swift 3.0 のサンプルコード

現在の仕事で作ってるiOSアプリの中に、マイクからの音をm4a (AAC) で録音する機能が必要なので、 適当なサンプルコードはネット上に在るだろうと思い探したのですが、案外みつかりませんでした。
なぜ見つからないかというと

  • 今回はいろいろな理由から、安易な AVFoundation ではなく AudioQueueを使う必要があった
  • Objective.c のサンプルは見つかるが、Swiftは少ない、さらに Swift 3.0 のコードは絶望的
  • また録音フォーマットが PCMのものばかり
  • オーディオ周りが好きなiOS(mac OS)プログラマーは少ないの?
  • 私もオーディオ周りの知識は極めて低い

f:id:yuum3:20170914161050p:plain

Google先生が教えてくれたのは以下でした

絶望の中、GitHubを検索するとAudioQueueTutorial という探していたものを見つけました。 Swift 3.0 です!
しかし、録音フォーマットは PCM ・・・・

そこで、いろいろ調べて少しずつ試しながら、遂に m4a(AAC) で録音できるサンプルが出来ました。完全なコードは AudioQueueTutorialのfork に置きました (m4aブランチ)。

変更か所は

録音フォーマットの設定

        info.mDataFormat.mFormatID = kAudioFormatMPEG4AAC
        info.mDataFormat.mSampleRate = 44100.0
        info.mDataFormat.mChannelsPerFrame = 2
        info.mDataFormat.mBitsPerChannel = 0
        info.mDataFormat.mFramesPerPacket = 1024
        info.mDataFormat.mBytesPerFrame = 0
        info.mDataFormat.mBytesPerPacket = 0
        info.mDataFormat.mFormatFlags = 0

バッファローサイズ計算の追加

    func DeriveBufferSize(seconds: Double) -> UInt32 {
        let maxBufferSize: UInt32 = 0x50000 // 320 KB

        var maxPacketSize: UInt32 = info.mDataFormat.mBytesPerPacket
        var outBufferSize: UInt32 = 0
        
        if maxPacketSize == 0 {
            var maxVBRPacketSize = UInt32(MemoryLayout<UInt32>.size)
            AudioQueueGetProperty (
                info.mQueue!,
                kAudioQueueProperty_MaximumOutputPacketSize,
                &maxPacketSize,
                &maxVBRPacketSize
            )
        }
        let numBytesForTime = info.mDataFormat.mSampleRate * Double(maxPacketSize) * seconds
        outBufferSize = numBytesForTime < Double(maxBufferSize) ? UInt32(numBytesForTime) : maxBufferSize
        return outBufferSize
    }

MagicCookie設定の追加

    func SetMagicCookieForFile() {
        var cookieSize = UInt32(MemoryLayout<UInt32>.size)
        
        var st = AudioQueueGetPropertySize(info.mQueue!,
                                          kAudioQueueProperty_MagicCookie,
                                          &cookieSize)
        if st == noErr {
            let magicCookie: UnsafeMutablePointer<CChar> = UnsafeMutablePointer<CChar>.allocate(capacity: Int(cookieSize))
            st = AudioQueueGetProperty(info.mQueue!,
                                 kAudioQueueProperty_MagicCookie,
                                 magicCookie,
                                 &cookieSize)
            if st != noErr {
                print("---- AudioQueueGetProperty error  \(st)")
                return
            }
            st = AudioFileSetProperty(info.mAudioFile!,
                                  kAudioFilePropertyMagicCookieData,
                                  cookieSize,
                                  magicCookie)
            magicCookie.deallocate(capacity: Int(cookieSize))
            if st != noErr {
                print("---- AudioFileSetProperty error  \(st)")
                return
            }
        } else {
            print("----  AudioQueueGetPropertySize error  \(st)")
        }
    }

ファイル作成、バッファー確保コードで上のメソッドを呼び出すようにした

        // create file
        let path = (NSTemporaryDirectory() as NSString).appendingPathComponent("audio.m4a")
        outputUrl = URL(fileURLWithPath: path)
        st = AudioFileCreateWithURL(outputUrl as CFURL, kAudioFileM4AType, &info.mDataFormat, .eraseFile, &info.mAudioFile)
        if st != noErr {
            print("---- AudioFileCreateWithURL error  \(st)")
            return nil
        }
        SetMagicCookieForFile()

        
        // allocate buffer
        info.bufferByteSize = DeriveBufferSize(seconds: 10.0)
        for _ in 0..<kNumberBuffers {
            var buffer: AudioQueueBufferRef?
            st = AudioQueueAllocateBuffer(queue!, info.bufferByteSize, &buffer)
            if st != noErr {
                print("---- AudioQueueAllocateBuffer error  \(st)")
                return nil
            }
            info.mBuffers.append(buffer!)
            AudioQueueEnqueueBuffer(queue!, buffer!, 0, nil)
        }

以上です、

tomisacat (tomisacat) · GitHub さんありがとうございます! それから参考にさえて頂いた情報を書いた人ありがとうございます。