2025/2/11(火)

ついにストリーミング再生の実装に成功した。リサンプリングを別途まだ入れる必要があるが、とにかくバッファの消化を検出してそれに従ってロードするという大目標は達成できた。

API自体にも少し悩んでいたが、結局以下のようなAPIになった。

audio_player player;

audio se{"se.wav"};
player.play(se);

streaming_audio bgm{"bgm.ogg"};
player.play(bgm);

実体の大きく違う全読み込み済みオーディオデータとストリーミングオーディオは型から変えたいという思いがあった。一方で、以下のようにオーディオプレイヤークラスまでそれぞれに分けて作ると煩雑が過ぎる。

audio_player player;

audio se{"se.wav"};
player.play(se);

streaming_audio bgm{"bgm.ogg"};
streaming_audio_player stream_player{bgm};
stream_player.play(bgm);

ということで妥協点として上のような形になった。内部実装としてはプレイヤーの実装が複数あり抽象化されていて、プログラマ側からは上のように統一的に扱えるという形になっている。初学者から見た分かりやすさと実体の適切な反映をなんとか両立できたラインだと思っている。

具体的な実装としては、メインスレッドとオーディオスレッドがあったのとさらに別にストリーミングスレッドを立てて、オーディオスレッドがバッファを消費したことを検出して読み込みをする形にしている。ストリーミングスレッド自体の管理と読み込み処理の管理の責務は分けたかったので、ストリーミングスレッドの管理クラスにstd::functionとかを渡す感じにしている。std::functionは遅いと言われるが、せいぜいストリーミング再生1個につき1秒に1回呼び出せればいい訳なので、速度よりも設計を優先した。

シーク処理が未知数だったので、一旦ストリーミング再生の場合はループ・シーク不可としている。が、すくなくともminimp3はシークを提供しているようなので案外行けそう。

ところで散歩しながらストリーミングについて考えていたのだが、人が求めているのは必ずしもストリーミングではなく「非同期読み込み」とかだったりしないのかという考えがよぎった。ストリーミングは読み込み時間とメモリの節約に主眼がある。しかし、Webサイトのローディングとかをイメージすると「読み込んだ分から利用できるようになる」ことに主眼がある動作をする。既に読んだ分はメモリに溜まっている、また停止中でも読み込み続けてはいる、という動作もありではないか。かなり実装が面倒くさそうだが。

それからもう一つ、今回実装したストリーミングはバッファサイズを1秒くらいに取っている。つまりあくまで時間とメモリの節約だけに主眼があって、例えばボイスチャットのように切れ目なく流れてくるリアルタイムな音声ストリームとかをターゲットにしている訳ではない。今ある仕組みでやれなくはないかもしれないが、リアルタイム性は損なわれる。

ということで、

  • フルロード済みオーディオ(audio)
  • ストリーミングオーディオ(streaming_audio)
  • 非同期読み込みオーディオ(asyncload_audio)
  • リアルタイムオーディオ(realtime_audio)

という4種構成にしたいという野望が新たに立った。非同期読み込みはちょっと難易度の見積もりからというレベルなので実現は先になりそうだが、リアルタイムオーディオはライブラリ的には実装は楽そうだし必要性も高そうなので間違いなくやる。


今週の胎界主を読んだ。サタナキアめっちゃ覗いとる。世界中の鏡を覗けるメッフィーすらチートだが、こいつも大概だよな。

Categories: