見出し画像

REALITYの配信にBGM機能を加えてみた話 - REALITY Advent Calendar 2022 #9

こんにちは、2022年8月からREALITYでiOSエンジニアをやっている、@chuymasterです。本記事はREALITY Advent Calendar 2022の9日目の記事です!

今回は、私が入社前からあったらいいなぁと思った、BGM機能の開発を、開発合宿で挑戦しました。その成果物と実装方法の一部を紹介していきます。

背景

REALITYアプリの配信は、フェイストラッキング、可愛らしいアバター、コラボ配信、ギフト機能、ワールド、ゲーム機能などがあり、視聴者と一緒に楽しめる環境が揃っています。

「東京ドームワールド」がリリースされていろんな仕掛けで盛り上がります! https://reality.inc/news/000454/

一方、「音声」に関しては、基本的に配信者のトーク中心で配信を進めることになります。そうすると、ずっと喋り続ける必要があったり、無音になってぎこちなくなる場面がありますね。

そこで、BGMを選んで流すことができたら、そういった緊張感を和らげて、配信のハードルが下がるではないかと思います。また、自前の音楽を流して雰囲気や世界観を作る方もいて、BGMは配信を盛り上げる重要な要素でもあります。なので、今回の開発合宿で実装に挑戦しました。

成果物

では、2日間でできたプロトタイプをお見せします。ちなみに「Blessing」というのはREALITYの仲間たちが作った曲です。よかったらチェックしてください!

開発中のBGM再生画面(動画埋め込みできないので…脳内で再生してください!)

/bgm 配信コマンドでBGM選択画面を立ち上げ、そこから再生したい音楽を選んで再生できるようにしました。(※このコマンドの実装を確約するものではありません)再生状況が分かるように、シークバーを追加し、音量を調整できるようにスライダーも用意しています。

実装について

ここからiOSアプリの実装について一部紹介します。その前に、前提知識として、REALITYアプリはiOSネイティブで開発した部分とUnityで開発した部分で構成されています。住み分けについては下記の記事をご覧ください。

配信画面では、目で見えるものほぼ全てはUnityで実装されていますが、「音声」の配信についてはiOSネイティブで実装されています。なので、 AVAudioEngine の知識が必要です。

AVAudioEngineとは

iOSアプリで精密なオーディオの制御をするには AVAudioEngine クラスを使います。複数の音源から一つにミックスしたり、エフェクトをかけたりすることができます。概念と実装例は、下記の記事が大変参考になりました。

また、実際にアプリに組み込んでみる例として、下記の記事が大変参考になったので、実装する方は一度やってみるといいと思います。

BGM再生機能の実装紹介

今回の合宿で実装した機能は、大きく分けて2つになります。一つ目は、BGM画面を立ち上げて、BGM一覧から再生したいBGMを選ぶ「BGM選択機能」です。そして、BGMをボイスとミックスして配信する「BGM再生機能」です。BGM選択については、SwiftUIで画面を作って、いい感じのプレイヤーUIを配置するだけなので詳細を割愛します。

ここからは、BGM再生機能の肝である「Nodeの構成」を紹介します。

AVAudioNode構成

まず、現状のREALITY iOSアプリのNode構成を簡単に説明します。一人で配信した場合の構成で、一部省略している部分があるのでご了承ください。

配信時のNode構成
  1.  AVAudioInputNode でマイク入力を受け取る

  2. 入力音量調整のAVAudioMixerNode(以降、Mixer)を経由

  3. 配信に適したフォーマットに変換して、音声配信サーバーに送信するリアルタイムバッファ出力用のMixerを経由

  4. 自分の声がスピーカーに出ないように、ミュートMixerを経由

  5. MainMixerに流してOutputNode(スピーカー)へ出力。※ただしミュート処理がされたので、何も聞こえない

③の配信サーバーに送信する処理は、installTap(onBus:bufferSize:format:block:) を使って、音声を指定したバッファサイズに分割して継続的に送信しています。そうすることでリアルタイム送信を実現しています。

BGMを加えたNode構成

REALITYアプリの既存の音声配信構成から分かるように、上記のリアルタイムバッファ出力Nodeに対して、BGMを加えれば視聴者に届けることができます。今回の合宿で実装したNode構成がこちらです。

BGMを加えた配信時のNode構成

注意すべき点は、AVAudioPlayerNode をそのままリアルタイムバッファ出力Nodeにつなげた場合、配信者はBGMが聞こえないので、何が再生されているのかが分かりません。なので、2つのMixerに分割させて、配信サーバーに送信するのと同時に、自分のスピーカーにもBGMを出力させています。

一つのMixerから複数のMixerに音声を分割したい場合、 AVAudioEngine の connect(_:to:fromBus:format:) 関数を使います。下記がNodeをつなげる実装例です。

private func connectNodes() {
    // ~省略~
    engine.connect(playerNode, to: [
        AVAudioConnectionPoint(node: bgmStreamingMixerNode, bus: 0),
        AVAudioConnectionPoint(node: bgmMainMixerNode, bus: 0)
    ], fromBus: 0, format: format)

    engine.connect(bgmStreamingMixerNode, to: streamingMixerNode, format: format)
    engine.connect(bgmMainMixerNode, to: engine.mainMixerNode, format: format)
}

bgmStreamingMixerNode と bgmMainMixerNode は音量を調整するためのMixerで、視聴者に配信する音量自分が聞こえる音量を分けて設定できるように設けました。これで図で描いたNode構成を構築することができ、BGMとボイスをミックスして配信することができました。

今後の展望

配信で使えるBGM機能は多くのユーザー様からご要望をいただいています。今回の開発合宿では、技術的に実装可能であることを検証できました。コラボ配信で使えない、イヤホンの抜き差しで音声が止まるなど、まだまだ課題がたくさんあっていろいろ調整中ですが、本格的に開発する方針です!

最後に

私自身、こういったハッカソン的な開発合宿の参加は人生で初めてです。普段の開発業務は、基本的に事業の優先度に従ってPdMが決めた案件を開発していますが、開発合宿では「自分が欲しかったもの」に全力で取り組めました。とても有意義な時間を過ごすことができたなぁと改めて思いました。

合宿後は、合宿地の近くの銚子に行って海を見ながらビールを飲みましたw

明日は

明日のアドベントカレンダーは、ますずみさんのミュート・寝落ち配信を検出してみた話です。お楽しみに!