https://adventar.org/calendars/3353 の2日目エントリーです。
オーディオI/OのAPIは、プラットフォームに固有のものです。これは、オーディオデバイスへのアクセスの仕方、デバイスをオープンする時に求められる設定値などが、OS固有の情報なども含み、OSによってバラバラであるためでしょう。標準的なファイルI/Oほどには単純化できないということは理由として挙げられそうです。歴史的にも、オーディオデバイスとひとことで言っても入力(マイク)は含まないものも多かったり、出力先のチャンネル数(スピーカーの数など)が増えてきたりなど、ファイルI/OのAPIなどと比べると、割と最近でも進化している部分が多い領域です。
そして、どのOS環境に行っても、オーディオデバイスの接続設定を調整してデバイスをオープンしてオーディオデータを書き込んで再生するステップは、実際にやりたいことに比べたら長大なものになります。5行程度のオーディオデータ生成/書き込み処理を行うのに数十行のコードを書かないといけないのが、プラットフォーム固有オーディオ処理の現状です。
一方で、サウンドを発生させたい、という場合は、ファイルI/Oのような感じで単純なオーディオストリームの読み書きと最低限の設定調整(録音・再生周波数の調整など)さえ出来ればよい、という状況が大半でしょう。このような状況で、プラットフォームごとにサウンド部分を実装するのは労力の無駄です。というわけで、クロスプラットフォームで動作するアプリケーションやフレームワークにおいては、クロスプラットフォームのオーディオI/O APIを使うことがよくあります。
今日はクロスプラットフォームのオーディオAPIをいくつか紹介します。参考資料として以下のサイトを挙げておきます。
- https://camlorn.net/posts/december2014/horror-of-audio-output/
- https://github.com/andrewrk/libsoundio/wiki/libsoundio-vs-PortAudio を始めとする各種APIとlibsoundioの比較ページ
portaudio
この方面では一番有名なライブラリ(少なくとも「だった」)と言えるものです。web上の情報も一番多く、そこそこ枯れていると言えるでしょう(もっとも、わたしがC#バインディングを作ったときはAPI非互換変更を食らったりもしていましたが…)。wasapiなど新しいドライバーについては不安定である(であった)、みたいな話も上記のブログには載っています。
wasapiはまだドライバーがあるだけマシで、Linuxに行くとpulseaudioのバインディングすらありません。jackかalsaか、みたいな選択になると、他のオーディオ出力をブロックしたくないわたしみたいなLinux開発者にとっては選択肢になりません…
設計が古いことによる問題として一番大きいのは、2チャンネルまでの出力しかまともに扱えないというものでしょう。複数チャンネルのストリームをどう扱うかは、プラットフォームによって異なるので、モダンなクロスプラットフォームの
オーディオライブラリではこの問題にちゃんと対応する設計になっています。
…とまあ、さまざまな問題があるわけですが、goでもrustでもportaudioを使う例が一番多いようです。
rtaudio
rtaudioはThe Synthesizer Toolkit (TheTsk) プロジェクトで開発されているC++のAPIです。これもそれなりに歴史があり、多チャンネル時代には合わない設計であるのはportaudioと同様です。ソースコードが10000行ちょいあるRtMidi.cppだけ(いわゆるsingle-source module)なので、読みやすいかどうかは読む人次第でしょう。上記ブログではこれがクソだみたいな書きぶりですが、わたしはSSMは全く嫌いではないです(シンボルの検索性とか)。
個人的には、rtaudioはAPIをグローバルに指定して開かないといけないようなビミョい設計にあまり肯定的な気持ちをもてないので、敬遠しています…(C APIにしてC#バインディングを作ろうとしたこともあるのですが公開まではしなかったり)。libsoundioのページではデバイス変更の検出も出来ないような話が書かれていて、APIの指定の仕方がこうではそうかなあ、と思っています(未確認)。
SDL, OpenAL, JUCE
生オーディオの処理だけに特化したライブラリではありませんが、SDLやOpenAL、JUCEなども結果的にクロスプラットフォームのオーディオI/O機能を提供しています。
どうしても余計な色がつくのと、対象動作環境が不必要なところで限定されることがあるので、純粋に生オーディオI/Oを使えば足りる場面においては、個人的にはやはり敬遠してしまいます。
前述のブログではもっと踏み込んで、OpenALでは実質的に16bit encodingしかサポートされていないとか、デバイスの未サポート機能を使うと容赦なく落ちるみたいな話が載っていて、悪口のオンパレードかと思いきや一番良さそうな選択肢みたいにも書かれていたりするので、興味があれば読み込んでみると面白いかと思います。
SDLはゲームにはちょうどいいけどオーディオライブラリで使うと重複初期化していないかのチェックなどで無駄に煩雑になるのでやめたほうがいい、みたいな話もあるようです。
libsoundio
libsoundioは、わたしが知る限りでは一番モダンなAPIで、多チャンネル時代に対応していて、Cだけで書かれているので他言語のバインディングにも使いやすいライブラリです(わたしもC#バインディングを作っていますが、他にも何人かいます)。
APIそのものよりも、むしろ他のAPIとの比較などがきっちり行われている(前述のリンクなど)が面白いところです。hacker newsのスレッドでいろいろ議論されているので、これが客観的な評価としては一番有用でしょう: https://news.ycombinator.com/item?id=10171802
あとlibsoundioについては去年ちょっと書いたこともあるので、これも補足資料として出しておきます: http://atsushieno.hatenablog.com/entry/2017/12/07/041145
まだまだある
クロスプラットフォームのオーディオライブラリはまだいくつかあるので、日を改めてまた書きます(予定)。