[watchOS 3] AVFoundation の AVAudioEngine と AVAudioPlayerNode を使用して音楽ファイルを再生する #wwdc

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

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

前回の記事「新クラス「WKCrownSequencer」を使用して Apple Watch‎ のデジタルクラウンの値を取得する」に引き続き、watchOS 3 の新機能を使用した実装を紹介します。

watchOS 3 の API の差分を解説している資料「watchOS 3.0 API Diffs」によると、watchOS 3 からは AVFoundation framework を使えるようになったようです。

本記事では、AVFoundation framework の AVAudioEngine と AVAudioPlayerNode を使用して音楽ファイルを再生する実装を紹介していきます。

本記事は Apple からベータ版として公開されているドキュメントを情報源としています。 そのため、正式版と異なる情報になる可能性があります。ご留意の上、お読みください。

iOS との比較

watchOS で AVAudioEngine と AVAudioPlayerNode を使用して音楽ファイルを再生する手順は iOS の場合と同様です。以下の記事もあわせてご覧ください。

実装内容

AVAudioEngine を使用した実装では Node と呼ばれる部品を繋ぎあわせて、音を鳴らしたり、エフェクトをかけたりすることができます。Node には AVAudioPlayerNodeAVAudioMixerNode などがあります。

AVAudioEngine は Node のグラフの管理、Node の接続のセットアップなどを担当します。

今回は「[iOS 8] AVFoundationのAVAudioPlayerNodeで音楽ファイルを再生してみる」と同様の実装を watchOS で試してみました。Node の構成は以下の通りです (Mixer Node や Output Node は AVAudioEngine が内部的に持つ Nodeです)

実装

それでは、音楽ファイルを再生する実装を紹介していきます。

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

import WatchKit
import Foundation
import AVFoundation

class InterfaceController: WKInterfaceController {
    let engine = AVAudioEngine()
    let audioPlayerNode = AVAudioPlayerNode()
    var audioFile:AVAudioFile!

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

        // [1] AVAudioFile を作成する
        do {
            let path = Bundle.main().pathForResource("beat", ofType: "aif")
            self.audioFile = try AVAudioFile.init(forReading: URL.init(fileURLWithPath: path!))
        } catch {
            fatalError("\(error)")
        }

        // [2] AVAudioPlayerNode を AVAudioEngine に装着する
        self.engine.attach(self.audioPlayerNode)

        // [3] Node 同士を繋ぐ
        self.engine.connect(self.audioPlayerNode, to: self.engine.mainMixerNode, format: self.audioFile!.processingFormat)

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

    @IBAction func buttonDidTap() {
        // [5] オーディオファイルの再生をスケジュールする
        self.audioPlayerNode.scheduleFile(self.audioFile!, at: nil, completionHandler: nil)

        // [6] 再生する
        self.audioPlayerNode.play()
    }
}

上記コードのそれぞれについて解説していきます。

[1] AVAudioFile を作成する

まずは、AVAudioFile を作成します。これはオーディオファイルを表すオブジェクトであり、ファイルのフォーマット情報などを保持しています。

[2] AVAudioPlayerNode を AVAudioEngine に装着する

attach(AVAudioNode) メソッドを使用して、AVAudioPlayerNode を、AVAudioEngine に装着します。AVAudioPlayerNode はファイルやバッファからのオーディオ再生を行うための Node です。

[3] Node 同士を繋ぐ

connect(AVAudioNode, to: AVAudioNode, format: AVAudioFormat?) メソッドを使用して、[2]で作成した AVAudioPlayerNodeAVAudioEngine が内部に持つ AVAudioMixerNode を接続します。

これでファイルを再生するための Node は全て接続完了になります。

[4] AVAudioEngine の処理を開始する

start() メソッドを使用して AVAudioEngine の処理を開始します。Node の接続等に問題がなければこれで準備完了です。

[5] オーディオファイルの再生をスケジュールする

AVAudioPlayerNodescheduleFile(AVAudioFile, at: AVAudioTime?, completionHandler: AVAudioNodeCompletionHandler? = nil) メソッドを使用してオーディオファイルの再生をスケジュールします。

今回は引数 atnil を指定しているので (また、他に再生がスケジュールされてもいないので)、「すぐに再生する」という処理がスケジュールされます。時間を指定することで指定時間後に再生することもできます。

[6] 再生する

AVAudioPlayerNodeplay() メソッドを使用して音楽を再生します。

[5]でスケジュールされた処理が実行されます。

実行結果

アプリにバンドルした、長さ 2 秒程度の音楽ファイル (AIFF-C audio ファイル) を再生できました!

さいごに

AVFoundation の AVAudioEngine と AVAudioPlayerNode を使用して音楽ファイルを再生する実装をご紹介しました。

今後も引き続き、watchOS 3 の新機能を試していきます。

参考資料