[watchOS 3] AVAudioMixerNode を使用して複数の音源をミックスする

2016.11.14

はじめに

こんにちは。モバイルアプリサービス部の平屋です。

前回の記事「[watchOS 3] AVSpeechSynthesizer による合成音声再生について」に引き続き、watchOS 3 で新規追加された AVFoundation framework を使用した実装を紹介します。

本記事では、2 つの AVAudioPlayerNodeAVAudioMixerNode に接続して 2 つの音源をミックスする実装を紹介します。

watchos3-audiomixernode-1

AVAudioEngine, AVAudioPlayerNode, AVAudioMixerNode などの基本事項については以下の記事で紹介していますので参考にしてみてください!

検証環境

  • Xcode Version 8.1
  • iPhone 7, iOS 10.1
  • Apple Watch, watchOS 3.1

実装

さっそく実装を紹介していきます。

「iOS App with WatchKit App」テンプレートからプロジェクトを作成し、WatchKit Extension の InterfaceController にコードを追加していきました。InterfaceController のコードは以下のようになりました。

class InterfaceController: WKInterfaceController {

    // ...

    override func awake(withContext context: Any?) {
        super.awake(withContext: context)

        // [1] AVAudioFile を準備する
        do {
            let leftAudioFilePath = Bundle.main.path(forResource: "left",
                                        ofType: "mp3")
            self.leftAudioFile = try AVAudioFile.init(forReading: URL.init(fileURLWithPath: leftAudioFilePath!))
            let rightAudioFilePath = Bundle.main.path(forResource: "right",
                                                      ofType: "mp3")
            self.rightAudioFile = try AVAudioFile.init(forReading: URL.init(fileURLWithPath: rightAudioFilePath!))
        } catch {
            fatalError("\(error)")
        }

        // [2] AVAudioPlayerNode の設定を行う
        // AVAudioEngine に装着する
        self.engine.attach(self.leftAudioPlayerNode)
        self.engine.attach(self.rightAudioPlayerNode)
        // pan (音像定位) を設定する
        self.leftAudioPlayerNode.pan = -0.6
        self.rightAudioPlayerNode.pan = 0.6

        // [3] AVAudioPlayerNode を AVAudioMixerNode に繋ぐ
        // leftAudioPlayerNode(Bus:0) -> mainMixerNode (InputBus:0) という順で繋ぐ
        self.engine.connect(self.leftAudioPlayerNode,
                            to: self.engine.mainMixerNode,
                            fromBus: 0,
                            toBus: 0,
                            format: self.leftAudioFile!.processingFormat)
        // rightAudioPlayerNode(Bus:0) -> mainMixerNode (InputBus:1) という順で繋ぐ
        self.engine.connect(self.rightAudioPlayerNode,
                            to: self.engine.mainMixerNode,
                            fromBus: 0,
                            toBus: 1,
                            format: self.rightAudioFile!.processingFormat)

        // AVAudioEngine の処理を開始する
        do {
            try self.engine.start()
        } catch {
            fatalError("\(error)")
        }
    }

    @IBAction func playButtonDidTap() {
        // [4] オーディオファイルを再生する
        self.leftAudioPlayerNode.scheduleFile(self.leftAudioFile!,
                                              at: nil,
                                              completionHandler: nil)
        self.rightAudioPlayerNode.scheduleFile(self.rightAudioFile!,
                                               at: nil,
                                               completionHandler: nil)
        self.leftAudioPlayerNode.play()
        self.rightAudioPlayerNode.play()
    }
}

[1] AVAudioFile を準備する

今回は再生するファイルが 2 つあるので AVAudioFile を 2 つ準備します。

[2] AVAudioPlayerNode の設定を行う

AVAudioFile と同様に AVAudioPlayerNode を 2 つ準備し、AVAudioEngine に装着します。

また、各チャンネルのボリュームや pan (音像定位) の設定は AVAudioMixerNode ではなく AVAudioMixerNode に繋ぐ Node 側で行います。ここでは pan の設定だけ行なっています。

[3] AVAudioPlayerNode を AVAudioMixerNode に繋ぐ

2 つの AVAudioPlayerNodeAVAudioEngine が内部に持つ AVAudioMixerNode に繋ぎます。

ここでは、片方の AVAudioPlayerNodeAVAudioMixerNodeInputBus 0 に、もう片方の AVAudioPlayerNodeInputBus 1 に繋いでいます。

Bus の数

AVAudioPlayerNodeAVAudioMixerNode (AVAudioNode のサブクラス) の Bus の数は numberOfInputs または numberOfOutputs プロパティを使用して取得できます。

print("self.leftAudioPlayerNode", "numberOfOutputs:", self.leftAudioPlayerNode.numberOfOutputs)
print("self.rightAudioPlayerNode", "numberOfOutputs:", self.rightAudioPlayerNode.numberOfOutputs)
print("self.engine.mainMixerNode", "numberOfInputs:", self.engine.mainMixerNode.numberOfInputs)

AVAudioPlayerNode の OutputBus の数は 1AVAudioMixerNode の InputBus の数は 8 のようです。

self.leftAudioPlayerNode  numberOfOutputs: 1
self.rightAudioPlayerNode  numberOfOutputs: 1
self.engine.mainMixerNode  numberOfInputs: 8

[4] オーディオファイルを再生する

2 つの AVAudioFile の再生をスケジュールし、再生を実行します。

実行結果

サンプルアプリを Apple Watch で実行し、2 つの音源のミックス結果を確認することができました。Watch に Bluetooth イヤホンを繋げば、pan の設定の効果がわかりやすくなります。

さいごに

本記事では、2 つの AVAudioPlayerNodeAVAudioMixerNode に接続して 2 つの音源をミックスする実装を紹介しました。

今回のサンプルに AVAudioUnitEffect のサブクラス (リバーブやイコライザーなど) を混ぜようと思ったのですが、watchOS では使用できないようです。次のメジャーバージョンで使うことができるようになるのでしょうか。

参考資料