Pebble Coding

ソフトウェアエンジニアによるIT技術、数学の備忘録

swift3でCoreAudioを使う 再生編

swiftでCoreAudioを使ってみます。
ラ(440Hz)のサイン波の音を再生するサンプルです。

import Foundation
import AudioUnit
import AudioToolbox
import AVFoundation

class MyAudioPlayer
{
    var _audiounit: AudioUnit? = nil
    var _x: Float = 0
    let _sampleRate:Double = 44100
    init() {
#if os(iOS)
        let subtype = kAudioUnitSubType_RemoteIO
#else
        let subtype = kAudioUnitSubType_HALOutput 
#endif
        var acd = AudioComponentDescription(componentType: kAudioUnitType_Output, 
                                            componentSubType:subtype, 
                                            componentManufacturer: kAudioUnitManufacturer_Apple,
                                            componentFlags: 0, 
                                            componentFlagsMask: 0)
        
        let ac = AudioComponentFindNext(nil, &acd)
        AudioComponentInstanceNew(ac!, &_audiounit)
        AudioUnitInitialize(_audiounit!);
        // オーディオデータ供給を44100,ステレオ,標準フォーマット(Float32, Non-Interleave)に設定
        let audioformat:AVAudioFormat = AVAudioFormat(standardFormatWithSampleRate: _sampleRate, channels: 2)
        var asbd:AudioStreamBasicDescription = audioformat.streamDescription.pointee
        AudioUnitSetProperty(_audiounit!, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &asbd, UInt32(MemoryLayout.size(ofValue: asbd)))
    }
    let callback: AURenderCallback = {
        (inRefCon: UnsafeMutableRawPointer,
        ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>, 
        inTimeStamp: UnsafePointer<AudioTimeStamp>, 
        inBusNumber: UInt32, 
        inNumberFrames: UInt32, 
        ioData: UnsafeMutablePointer<AudioBufferList>?)
        in
        // ポインタからMyAudioPlayerインスタンスに変換
        let myAudioPlayer:MyAudioPlayer = Unmanaged<MyAudioPlayer>.fromOpaque(inRefCon).takeUnretainedValue()
        myAudioPlayer.render(inNumberFrames, ioData:ioData)
        return noErr
    }
    func render(_ inNumberFrames: UInt32, ioData: UnsafeMutablePointer<AudioBufferList>?) {
        let delta:Float = Float(440 * 2 * M_PI / _sampleRate)
        guard let abl = UnsafeMutableAudioBufferListPointer(ioData) else {
            return
        }
        var x:Float = 0
        for buffer:AudioBuffer in abl {
            x = _x
            let cap = Int(buffer.mDataByteSize)/MemoryLayout<Float>.size
            assert(cap == Int(inNumberFrames))
            if let buf:UnsafeMutablePointer<Float> = buffer.mData?.bindMemory(to: Float.self, capacity: cap) {
                for i:Int in 0 ..< Int(inNumberFrames) {
                    buf[i] = sin(x)
                    x += delta
                }
            }
        }
        if abl.count > 0 {
            _x = x
        }
    }
    func play() {
        // MyAudioPlayerインスタンスをポインタに変換
        let ref: UnsafeMutableRawPointer = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
        var callbackstruct:AURenderCallbackStruct = AURenderCallbackStruct(inputProc: callback, inputProcRefCon: ref)
        AudioUnitSetProperty(_audiounit!, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &callbackstruct, UInt32(MemoryLayout.size(ofValue: callbackstruct)))
        
        AudioOutputUnitStart(_audiounit!)
    }
}

init()では、まず、音を鳴らすためのRemoteIOというAudioUnitを生成します。
次にAVAudioFormatを使ってステレオ(2チャンネル)の44100Hzのサンプリングレートで標準のオーディオフォーマットを作成し、
そのAudioStreamBasicDescriptionを取得します。
音データとして1秒間に44100サンプルの-1.0から1.0の間のfloatの値のデジタルデータで表現されたものを使うよと宣言しています。
波形の形によって、音色が決まるわけですが、ここでは計算しやすいようにsin波を使います。

次にplay()を見てみます。
CoreAudioで音を鳴らす場合、OSから一定の間隔で設定したコールバック関数が呼ばれますので、
その関数内で、引数のポインタに再生する音データを設定していくことになります。
何サンプルのデータを設定すればよいかは引数で与えられますので、それに従わなければなりません。
コールバック関数の実装をしているのが

let callback: AURenderCallback

の部分です。
AURenderCallbackの宣言をみると分かりますが、C言語形式の関数ポインタです。
波形の形などの制御は元のクラスの情報を使ったりするため、実際のデータの設定処理はクラスのメソッドでやったほうが便利なので、
メソッド

func render(inNumberFrames: UInt32, ioData: UnsafeMutablePointer<AudioBufferList>?)

を呼び出すようにしています。

ここでMyAudioPlayerクラスのインスタンスのアドレスをvoidポインタに変換し、

let ref: UnsafeMutableRawPointer = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())

ここで、callbackstruct構造体にコールバック関数のアドレスと、voidポインタアドレスを設定し、

var callbackstruct:AURenderCallbackStruct = AURenderCallbackStruct(inputProc: callback, inputProcRefCon: ref)

kAudioUnitProperty_SetRenderCallbackのプロパティに設定することによって、AudioOutputUnitStart呼び出しで、
AudioUnitが開始されると、指定されたコールバック関数が呼ばれるようになります。

次に、

func render(inNumberFrames: UInt32, ioData: UnsafeMutablePointer<AudioBufferList>?)

の中をみていきます。

1秒間に44100サンプルですから、440Hzの波にするのに、1サンプルでどれだけ位相を回せばいいかをdeltaに設定しています。
次に、UnsafeMutableAudioBufferListPointerでAudioBufferListからチャネル数分のAudioBufferの参照を取り出します。
2チャンネルのAudioStreamBasicDescriptionを指定しましたので、ここでは2チャンネル分のAudioBufferのループになり、
最初の方がLeftチャンネル、2つ目がRightチャンネルで、ここでは両方に同じデータを設定します。
buffer.mDataに音データを設定していきますが、データ型はAudioStreamBasicDescriptionで指定したようにfloat型なので、
ここでもunsafeBitCastを使って先頭ポインタをfloatポインタに変換し、そこにsin関数で計算した音データを指定されたinNumberFramesサンプル分
詰めます。

CoreAudioは元々Objective-C用に作られたものなので、swiftで使う場合、若干面倒くさいところがありますが、pure swiftで書けるようになっただけでもだいぶ進歩したなあという感想です。
Audio周りはAppleが用意しているユーティリティライブラリがいまだにC++だったりとswiftとは非常に相性が悪いです。
Obj-CやswiftではARCでスピンロックが入るのでノイズが出やすくなるためです。http://postd.cc/four-common-mistakes-in-audio-development/,

AVAudioEngineもCoreAudioに比べて全然機能が足りませんし、まだまだ、先は長そうだなあという感じです。

[2017-02 update]
unsafeBitCastを使っていましたがswift3で別のキャスト方法が追加されたためそちらに置き換えました。
(ネイティブポインタ周りはいつまで構造の変更が続くんでしょうかね。。)

swift3対応したものをgithubにupしました。

github.com

Learning Core Audio: A Hands-On Guide to Audio Programming for Mac and iOS

Learning Core Audio: A Hands-On Guide to Audio Programming for Mac and iOS