🎵

AVAudioEngineの基礎を理解する〜前編

2024/12/24に公開

はじめに

AVAudioEngineを扱うための基本的な概念や全体感を掴めるように記事にまとめました。
これまでAVAudioEngineを使ったことがない方の理解の助けとなれば幸いです。
分量の都合で、今回はコードベースの説明でなく、基本的な用語の説明を中心に行います。
一部厳密ではない点もありますが、後編にて補足させていただく予定です。

AVAudioEngineのイメージを掴む

AVAudioEngineでは、AduioNodeと呼ばれるものを鎖状に繋ぐことで音声再生ができます。
具体的には下記の図のように、PlayerNode -> MixerNode -> OutputNodeのように接続していきます。ひとまずは、このようにイメージしてもらえれば良いかと思います。(もう少し細かい話は後編に書く予定です。)


https://developer.apple.com/documentation/avfaudio/avaudioengine

AVAudioEngineの基本概念

先ほどはイメージを持っていただくために、ざっくりと3つに分けましたが、実際にはもっと細かく分類できます。AVAudioEngineの基礎として、理解しておきたい5つの登場人物を押さえていきましょう。

AVAudioEngine

AVAudioEngine は、さまざまなオーディオノードを作成し、接続し、音声の入力、処理、出力を一貫して管理するものです。

AVAudioPlayerNode

AVAudioPlayerNodeは、オーディオデータの再生に関する役割を担っています。
音声の再生、停止、スケジュール、音声やピッチの変更等を行うノードです。

AVAudioUnitEffect

AVAudioUnitEffectは、音声にエフェクトを適用するノード群を指し、サブクラスとして、 AVAudioUnitVarispeedAVAudioUnitTimePitchAVAudioUnitReverbなどがあり、これらのインスタンスを通じて、様々なEffectを適用できます。

AVAudioMixerNode

AVAudioMixerNodeは、オーディオのミキシングと音量調整を行うノードで、複数のオーディオノードからの音声を受け取り、それらを混ぜ合わせて出力することなどができます。

AVAudioOutputNode

AVAudioOutputNodeは、オーディオエンジンからの音声データを出力するためのノードです。

まとめると、PlayerNodeから複数のEffectNodeを繋ぎ、これら複数のEffectNodeをMixerがまとめて処理し、それがOutputNodeに渡され音声として出力されます。

ここまで言葉での説明のみになってしまったので、一応サンプルのコードも載せますが、あくまでこの記事ではざっくりイメージを掴むものとして使っていただければと思います。
次回後編で音声を再生するまでの流れと、AVAudioEngineを扱う上で出てくる概念を整理しながら
詳しく解説しようと思います。その中で、もう少し厳密な話を補足していくのでご了承ください。

サンプルコード

import AVFoundation

class AudioPlayerWithEffect {
    private let engine = AVAudioEngine()
    private let playerNode = AVAudioPlayerNode()
    private let reverb = AVAudioUnitReverb()

    init() {
        engine.attach(playerNode)
        engine.attach(reverb)

        // ここでAudioNodeを繋いでいる
        engine.connect(playerNode, to: reverb, format: nil)
        engine.connect(reverb, to: engine.mainMixerNode, format: nil)

        do {
            try engine.start()
        } catch {
            print("Error starting the audio engine: \(error)")
        }
    }

    func playAudio(fileURL: URL) {
        do {
            let audioFile = try AVAudioFile(forReading: fileURL)
            playerNode.scheduleFile(audioFile, at: nil, completionHandler: nil)
            playerNode.play()
        } catch {
            print("Error loading audio file: \(error)")
        }
    }
}

Discussion