DroidKaigi 2025で、Android Audio: Beyond Winning On Itという謎タイトルで、Androidオーディオアプリケーション開発の最前線でプラットフォームに足りないオーディオ機能は何なのかという話をしてきた。なんとセッション動画が翌日には上がっていた:
セッションスライドはspeakerdeckで公開してある:
今回は「AndroidはiOSと比べて音楽アプリケーションが少ないので、その問題にどうやって対処していくべきか」という話を伝えるためにやっている。タイトルには「Androidの中だけで『よくできました』って言ってるだけじゃダメなんだ」というニュアンスが込められている。しかしproposalにいろいろ書き並べすぎた。書いたことは全部言及したつもりだけど、これにかなり縛られてしまったので反省している。当時話そうと思っていた話に全く言及できなかった部分もある(MIDI2の意義とか)。40分ではとても話しきれなかったことがあるので、いろいろ補足しながら文章でまとめておこうと思う。
音楽アプリケーション is 何?
DroidKaigiはオーディオアプリケーションの開発者が集まるイベントではないので(そんな場面は多くは無いが、無いわけではない)、オーディオ機能はAndroidのようなOS・プラットフォームにおいてどのように提供されるものなのかをざっくり説明していた。
デスクトップで動作するコマンドラインツールのような小さなものを除けば、多くのアプリケーションでは音に関連する何らかの機能が使われている事が多い。一番わかりやすくサウンドをトリガーするのは通知機能だ。プラットフォームの通知機能を使えば、標準的な設定ではユーザーがミュートしていなければ何らかの音が出ることになる。ターミナルでユーザーが無効な操作を送ったら警告音が出たり、アプリケーションがエラーを通知したり、あるいはアプリケーション自体がエラーを起こしたら、エラーダイアログとともに音が出たりする。この目的ではオーディオ機能をアプリケーション開発者が直接呼び出すことはほとんどない。せいぜい単発のオーディオファイルを再生する程度だろう。たとえばWindowsのSystem.Media.SystemSounds.Beep.Play()
といった感じだ。
音楽に関連するという意味でのオーディオアプリケーションはどうか。一般的なユーザーであれば、音楽を聴いたり動画を見たりするわけで、この意味ではまだ一般ユーザーの使い方に深く関連する話ではある。いわゆるmedia APIの守備範囲ということになるが、音声や動画のようなメディアを適切なユーザー体験で再生するにはそれなりの工夫が必要になるし、プラットフォームのメディアAPIが弱いとこのレベルでも実現が危うくなってくる。
Androidはもちろんこのレベルでの心配は無いし、2025年現在はJetpack Media3(旧ExoPlayer)のAPIを使えばほぼ解決する。Google Play Storeにはメディアプレイヤーの類が数多く公開されていて、YouTube、TikTok、Spotify、なんならApple Musicまで存在する。この方面でAndroidがAppleに大きく水を開けられているわけではないので、この分野でのアプリ開発の話をする意義は小さい。DroidKaigiではメディアストリーミングのサービスを提供しているスポンサー企業がいくつかブースを出していて、会社によってはMedia3のソースに手を加えたうえで利用しているところもあるようだ。
Androidに足りていないのは、再生用のアプリケーションではなく、クリエイティブ・クラスのためのアプリケーションだ。DAWなどDTM(デスクトップPCではないモバイル環境でこの単語が妥当するのかはおいといて)で使うような音楽制作のアプリケーションでは、より厳密なリアルタイム オーディオ処理が求められる(なぜ、みたいな話は後述)。
Androidプラットフォームオーディオ機能の現状 (2025)
オーディオ開発者は、さまざまな理由でモバイル開発に後ろ向きで、さらにAndroid開発についてはiOS開発よりも後ろ向きになっている。ここでは「なぜ」そういう傾向が出てきたのかを何点か議論する。
「Androidはオーディオレイテンシーが大きい」
Androidオーディオについて、2010年代頃によく言われていたのは、Androidはリアルタイムオーディオ機能が無い・弱い、というものだった。もう史料としての価値しか無いが、superpoweredがAndroid 5.xの頃に公開したAndroidのオーディオ遅延を分析した文書が詳しい:
これは事実の側面があるが、2020年代、特に2025年にはほぼ時代遅れの主張と言っていい。Androidでは、Android 2.3(API Level 7)からアプリケーションがJavaではなくC/C++でオーディオ処理を実装できるようになり、Android 8.1(API Level 27)ではMMAP-ed streamをサポートするAAudioが使えるようになった((厳密にはAAudio自体はAndroid 8.0で使えるのだが、MMAP-ed streamのサポートが無いので、単にAPIが新しくなっただけで低遅延オーディオの実装は追いついていなかった))。
もう一つ押さえておくべきは、Androidデバイスを名乗るためにGoogleがデバイスベンダーに品質基準への準拠を求めているCDD (Compatibility Definition Document) に含まれている、Audio Latency (5.6)とProfessional Audio (5.10)の項目だ。これらはオーディオ遅延に関する基準を定めており、たとえばPro Audio準拠デバイスであると認められるためには、ラウンドトリップ遅延が20msec.以内でなければならないとされている。Androidプラットフォームはソースコードのレベルでは2025年まではAOSPに代表されるオープンなものであり、理論上はどんな低機能なデバイスでも開発できるので、低レイテンシーオーディオを求めるのに「Androidは〜」という括り方で語るのは適切ではない。Pro Audio準拠のデバイスは十分実用的であるといえる。
遅延の原因となる時間のかかる処理は、ALSAとHAL以下(android.hardware.audio.serviceプロセスまたはバスとDAC/ADC)、またはAAudioを含むAudioFlingerの実装にあって、Googleが実装するAudioFlingerの部分は低レイテンシー オーディオの目的では十分な仕事をしていて、品質基準も提示している以上、これ以上Googleに求めるべきこと(Googleができること)は何も無いだろう。((ちなみに、この方面では「AAudio以前のオーディオ処理はAudioFlinger」のように勘違いした技術解説を見ることもたまにあるけど、ソースコードをきちんと追跡してみれば、AAudioもAudioFlingerを経由してオーディオ処理を行っている。))
ちなみにDroidKaigiはオーディオ開発者のためのイベントではないので、そもそもオーディオ遅延が起こると何が問題になるか…とか、リアルタイムオーディオ処理というのは…といった話も時間をとって解説した。セッションでは言及しなかったけど、興味がある人はRoss BencinaのReal-Time audio programming 101: time waits for nothingやADC19のReal-Time 101のセッション動画を見てほしい。
あと、セッションでは言及しなかったが、実際のデバイスごとの遅延に関する統計もいくつか存在していた。しかし…
- https://superpowered.com/latency : 前出の通りAndroid 5.xの頃。情報が古く、もはや史料としての価値しか無い
- https://juce.com/maq : 2018年頃にJUCEが集めた統計。もはやページが消失している。
- https://ntrack.com/android-latency/devices : n-track studioというDAWを作っている会社が出している数字なのだが、Pixelデバイスだけを取ってみても遅延が一番小さいのがPixel 4a、Pixel 10が一番遅い、みたいな数字になっていて、根本的に信頼性が低い。
…といった有り様で、一応資料として出せたのはこの2点だけだった:
- https://android-developers.googleblog.com/2021/03/an-update-on-androids-audio-latency.html : Androidオーディオチームで当時オーディオのdeveloper advocateだったDon Turnerが書いたブログ記事。ただこれは2021年当時の平均的なAndroidデバイスの遅延について書かれたものであって、低遅延オーディオを求めるユーザーが読みたい記事とはちょっと言い難い。数値としても平均30msec.という遅延はまだまだ悪い。
- https://chromium.googlesource.com/external/github.com/google/oboe/+/devicelist/docs/Devices.md : ChromiumチームがAndroid Pro Audio準拠デバイスのオーディオ遅延についてまとめた資料で、20msec.以下の遅延が実現できていることが示されている。これはPixelどころかNexusの名前すら出ており、上記ブログよりさらに古いデバイスの情報がメインだ。
まあこれらも古いのだけど、「オーディオ遅延が大きすぎる」という情報は過去情報だと参考にならない一方で、「オーディオ遅延はだいぶ改善された」という情報は過去情報でも基本的には有用だ。(とはいえiOS 14のように「MIDIのレイテンシーが大きくなった」と言われるような退化もあり得るので、「基本的には」という留保付きではある。)
「AndroidはJavaだから…」
「AndroidはJavaだから」という声が出てくるとき、それは具体的には2つの別々の話をしていると考えられる:
(1) Javaはリアルタイムオーディオ処理に使える言語ではないから使えない、という議論。C#もJavaScriptもPythonもRubyも、大抵の言語はRT-safeではない。だからAndroidはオーディオアプリケーションに向いていない、という主張だが、これはまあ素人的な勘違いだ。ちゃんとしたオーディオアプリ開発者はこんなことは言わない。オーディオ処理が求められるのはリアルタイムオーディオスレッド上の話であり、Androidのオーディオ処理はもちろんC/C++で行われる。Android 2.2までの開発者はOpenSL ESを使えなかったので、当時ならこの主張は正しいが、2025年にGingerbreadを使っているユーザーはほぼ皆無だろう。
(2) オーディオ処理以外の部分でJavaを使ってコーディングしなければならないのは面倒くさい、という議論。これは正しいといえば正しいが、2点指摘しておくべきことがある:
- (a) 複数言語を使ったアプリケーション開発は、別にAndroidに限られた話ではないということだ。WPFを使ってWindows用オーディオアプリケーションを開発するなら、オーディオ処理はC++、GUIはC#でWPFのような構成になるだろう。同じネイティブコードランタイム上でUIとオーディオのコードを動作させることができるAppleプラットフォームですら、SwiftUIでSwiftとC++ということになれば、一緒にビルドして相互運用するにはそれなりの工夫が必要で、少なくともObjective-C++のようにはいかない。
- (b) オーディオアプリ開発者は、複数のプラグインフォーマットのサポートなどもカバーするクロスプラットフォーム開発を好む傾向が他の分野のアプリよりも強く、GUIもクロスプラットフォームのC++ APIで実装することが多い。JUCE、OpenGL/Vulkan、QtなどはAndroidでも使えるAPIなので、この意味でAndroidが排除されるとしたら、それは利用しているライブラリがAndroidをサポートしていない場合に限られる。DPFやiPlug2はAndroidをサポートしていないが、DPFプラグインでもGUI以外の部分はAndroidでも動作するようだし(いくつかaap-lv2の応用として移植している)、iPlug2はOSSではほとんど使われていないので(non-OSSのことは知らない)ほぼ問題にはならない。
まああと、今回は時間が足りなくて言及しなかったのだけど、モバイルプラットフォーム向けには個別にGUIを設計するべきだ。自分はJUCEのプラグインをいくつもAndroidに移植して動かしているけど、いくらJUCEがAndroidでの動作をサポートしているといっても、デスクトップ用に作られた個別のプラグインのGUIが使い物になったためしはない。
「iOSはオーディオ機能が充実しているけどAndroidはそれほどでもない」
Appleがオーディオ機能に積極的にコミットしている企業でGoogleは全然そうではない、というのは概ね正しい。しかしAndroidプラットフォームには実のところさまざまなオーディオ機能が追加されている。たとえば…
- MIDI 2.0サポート: Android 13でUSB-MIDI 2.0プロトコルのサポートが、Android 15で仮想MIDI 2.0デバイスのサポートが追加されている。AppleはBLE-MIDIのMIDI 2.0対応版を開発・公開していないので、これでMIDI 2.0サポートはフル機能が揃っている((近年ではMIDIデバイスの入出力にもリアルタイム性がシビアに求められていて、BLEはリアルタイム性能を引き出せないし、原理的にRT-safeではない。))。USB-MIDI 2.0サポートはALSAに先んじて実装されたので、MIDI 1.0とは異なりALSA rawmidiの無い状態でJavaベースで実装されていた程度には先行している。Windowsは未だにMIDI 2.0をサポートしていない。
- ちなみにMIDI 2.0仕様は2023年に大きく様変わりして、Appleが実装していたようなMIDI-CIのサポートはプラットフォームAPIでは不要になった(AppleもMIDI-CI APIはdeprecatedとしている)。その程度に拙速な仕様ではある。
- 去年あたりからAndroid XRという単語が聞こえ始めたが、Android 12ではハードウェア(主にヘッドトラッカー)でアクセラレーションが可能になるようなSpatializerのサポートが追加されている。
- BLE Audioのコーデックの実装。これはPixel Budsを販売しているのでプラットフォームのレベルで積極的にコミットしているといえる。
- Android Autoで必要になるようなDSPの機能を調整できるカーオーディオプラグイン機能のサポートがAndroid 14で追加されている。
これ以上Googleが何かプラットフォームのオーディオ機能をサポートする必要があるだろうか? と書くと反語のように見えるが、実際にはある。それがオーディオプラグイン機能のサポートである。AndroidにはiOSにあるAudioUnit V3に相当するオーディオプラグインの機能が存在しないので、オーディオプラグインの仕組みを前提として作られているDAWの機能やプラグイン製品全般が存在しないのである。これはどうにかして解決しないといけない。
「モバイルプラットフォームは面倒くさい」
最後に出すのも何だが、基本的にデスクトップメインの開発者にとって、モバイルプラットフォームはただただめんどくさい。メモリもストレージも容量が小さく、CPUも強くはなく、1つのスクリーンに1つのアプリケーションしか出せない。DAWのフル機能をモバイルで見せても小さすぎて打ち込めない。iOSもAndroidもアプリケーションインストレーションの境界が厳格で、アプリケーション同士のやりとりが難しい。
こういった問題は基本的にアプリ開発者では解決できないので、モバイルプラットフォームに合わせた製品を出すのが妥当なアプローチだ。モバイル画面でもできるようなこと、たとえばシンセのプリセットのエディットなどは寝っ転がりながらモバイルで操作して、イイ音が作れたらクラウド経由でPCに転送する、みたいな補完的な作業を進められると良さそうだ。
モバイルが面倒なのはもちろんAndroidだけでなくiOSも同様なので、iOSと比べて何が劣っているのか、みたいな文脈では、この話を掘り下げても得られるものは多くないだろう。
オーディオプラグインに何の意味があるの?
ここまで書いてきた中では、Androidが特にiOSと比べて解決しなければならない課題として残っているのはオーディオプラグインの不在くらいだった。これは解決できない課題なのだろうか?
DroidKaigiのオーディエンスはオーディオ開発者ではないので、そもそもオーディオプラグインとは何なのかという話を解説する必要があったのだけど、これは(知らない人は)スライドを見てもらいたい。「DAWはIDEみたいなもん」とか「オーディオプラグインフォーマットはプログラミング言語ラインタイムみたいなもん」「プラグインフォーマットはLSPみたいなもん」みたいな雑な説明は、プログラマーというかソフトウェア技術者向けだからこそできる話ではある。
Androidのようにオーディオプラグインフォーマットが不在の環境では、DAW製品をリリースするときに、そのDAWで利用できる楽器は、そのDAWがすべて用意しなければならないことになってしまう。しかしピアノにもSteinwayとかYAMAHAとかKAWAIみたいにいろいろブランドがあり、ギターにもGibsonやIbanezなどいろいろブランドがあるように、ソフトシンセの開発者にも独特の音がある。それを楽器ベンダーではなくDAWベンダーがすべて用意するのは無理があるし、DAWベンダーがオンラインマーケット等を設計できたとしてそこにある楽器しか使えないというのでは、自由な作曲ができない。
今回のスライドではCubasis3のレビューで「iOS版みたいにプラグインが使えない」というネガティブなフィードバックに対して開発者が「仕組みが無いからしょうがないんだ」と返している例を挙げたが(個別のレビューへのリンクが取れないのでVST
等で検索してもらいたい)、FL Studio Mobileには同様のレビューがもっとある。プラグインフォーマットの不在はユーザーの不満としてレビューに影響するのである。
なお、音楽ソフト以外でもエフェクトの類は利用場面があり得る。たとえばOBS Studioのようなライブ配信アプリで音声に加工を施したい場合は、VSTを差し込んだりできる(ここにVST”3″を差せるかどうかは別問題だけど)。
Androidでのオーディオプラグイン機構を阻む障壁
Androidで使えるプラグインフォーマットが無いなら作ればいいんじゃないか。Apple以外のプラットフォームでは、WindowsからVST2, VST3を開発したのはSteinbergであってMicrosoftではないし(MicrosoftはなんならDirectX Pluginというオーディオプラグインの仕組みを過去に作っていたこともあるし、APOという仕組みも持っている。使われてないけど)、主にLinuxで使われているけどWindowsでもMacでも使えるLV2はLinuxオーディオコミュニティの1人が作り始めたものだ。iOSでも、AudioUnit V3が公開されるまでは、AudioBusというAppleエコシステム上のアプリ開発者が作った仕組みが使われていた。Androidのプラグインフォーマットも、別にGoogleが作る必要は無いのではないか?
…という発想で筆者はAAP: Audio Plugins For Androidというプロジェクトを開発している(スライドでは最後にちょろっとリンクを出した程度で本編では名前すら出していない)。その開発体験から、Androidのようなプラットフォームで実現のハードルが高いプラグインフォーマットの機能について、いくつか説明しようと思う。
ちなみにAAPについては2年前のshibuya.apkで、だいぶ似たような内容をもう少しAAP寄りに(日本語で)解説したことがある。ただ内容がいささか古い:
DAWがプラグインをdlopen()できない
デスクトップではDAWはプラグインの実行プログラムをdlopen()
(macOSならNSBundle
のAPI)やLoadLibrary()
でロードしてDAWと同じプロセスで実行できるが、Androidはアプリケーションごとにunixユーザーが別々に作成される程度に隔離された仕組みで動作していて、DAWとプラグインは別々のプロセスで動作することになるので、IPC(プロセス間通信)によってDAWとプラグインの間でやり取りを実現しなければならない。
もう少し突っ込んだ話をすると、Androidではアプリが他のアプリケーション等からネイティブライブラリを渡されて動的にロードすること自体は技術的に不可能ではないが、そのようなコードはGoogle Play Storeのポリシーで禁止されている。そのようなアプリはGoogle Play Store外で配布することになる。セッション中でも雑談レベルで紹介したが、筆者が6月に参加していたLinux Audio Conference 2025では、rooted Androidデバイスで自由にALSAにアクセスしてオーディオ機能を使うLDSPというプロジェクトのセッションがあった。
DroidKaigiではこの程度の粒度の話しかしなかったが、こっちにはもう少しきめ細かい議論を書いておこう:
デスクトップの場合、DAWのオーディオエンジンとオーディオプラグインは、原則、1つのプロセスで動作する。これとは対照的に、一般的なWebブラウザのプラグイン機構の場合、サンドボックス機構が備わっており、プラグインがクラッシュしてもブラウザ本体は死なないことで安全性が維持される。IDEはそこまでクリアではない。VSCodeにはExtension Host Processの仕組みがあるが、IntelliJ IDEではプラグインはIDEプロセスで動作する。一般的に、プロセス分離モデルでは堅牢性が上がるが、ホストとプラグインの間でメモリ空間を共有できないので、やり取りのコストが上がるし、プラグイン開発も煩雑になる。
リアルタイムプログラミングの基本原則のひとつに “no system calls” がある。システムコールの実装はシステム依存であり、システムコールの実装によっては、リアルタイム安全原則に反するコードが含まれている可能性があるので、基本的に信用しないことになっている。実際にはmemcpy()
などいくつかのsyscallsは普通に使われていて問題がないことが多い(あまり良いとは言えない)。IPCに必要なsyscallsは通常であればRT-safeなものではない。
DAW「のオーディオエンジン」と書いたのは、DAWのGUIスレッドで動作するメイン部分とオーディオエンジンとでプロセスが別れている設計を念頭に置いているからだ。プラグインはオーディオエンジンのプロセスでロードして、もしクラッシュして同一プロセス上のすべてのプログラムを巻き添えにするとしても、メイン部分を巻き込まずに済ませるほうが良い。個別のプラグインをそれぞれのプロセスでロードするほうが巻き添えの範囲は狭くなるが、プロセスの境界を超えるやり取りはコストが高く、リアルタイム処理という厳しい制約の中でオーディオエンジンと全てのプラグインの間でプロセスの境界を超えるデメリットは、オーディオエンジンのクラッシュが相対的に頻発する程度のコストより大きいだろう。Bitwig Studioにはそのような動作モードのオプションがあるが、ほぼ特殊なデバッグ用途だと思ってよい。
Binder IPCがRT-safeでない
Androidにはその初期からLinuxカーネル上で低遅延IPCを実現したBinderと呼ばれる仕組みがあるが、Android 8.0でこれが進化して、realtime priority inheritanceを実現するようになった。priority inheritanceというのは、IPCでやり取りする2つのスレッドが、制御をもう一方のアプリに引き渡すとき、そのスレッドの優先度を引き上げることができる、というものだ。これが無いIPCでは、たとえばrealtime優先度で回っていたAAudioのスレッドから、もうひとつのアプリケーションのプロセスに制御を渡したとき、そのスレッドはnormal優先度で回っていて、他のスレッドの処理を待ってから制御を渡してもらうことになって、最終的に呼び出し元のAAudioのスレッドはこれをひたすら待機することになってしまう。いわゆるpriority inversionである。
Binderのpriority inheritanceには重大な欠点があって、これはframeworkドメインでは機能しない。Androidプラットフォーム本体の開発者やベンダーのデバイスドライバー等でしか利用できない。一般ユーザーが自前のServiceで使うBinderはframeworkドメインで動作するので、優先度が継承されない。われわれAndroidアプリ開発者がオーディオプラグインフォーマットを設計して、内部でBinderを使うようにしてると、オーディオ処理はリアルタイムでは行われないということになってしまう。
ここに現状のAndroidでオーディオプラグインの仕組みを作り込めない限界点が1つある。
プラグインGUIと1アプリ1スクリーンの原則
Androidアプリケーションは原則としてone app per screenで動作する。DAWとプラグインが別々のアプリケーションであれば、それらのGUIを同時に表示することは原則できない。そういうわけで、何かしらの対応策が必要になる。
オーディオプラグインにGUIは必須ではなく、パラメーターやプリセットのリストを取得して操作するやり方も可能ではあるし、実際AAPではそのようなデフォルトGUIがJetpack Composeで作られているが、一般的には操作性が悪い。複雑なビジネスロジックをオブジェクト化してPropertyGridを介して操作すれば何でもできる、というのに近い。GUI表示はサポートできたほうがよい。
前述のshibuya.apkでAAPについて話したときは、以下の2つの実現アプローチを紹介した:
- Web UI: ホストのプロセスでプラグインの実行コードをロードできないのであれば、ホスト上にWebViewを表示して、その中にプラグインのGUIとしてプラグインから提供されたWeb UIリソースをロードすればよい、というアプローチ。プラグインのパラメーター操作などはIPCを経由して行えるので、GUIから行えるのは最大で「ホストからIPCで行える操作すべて」ということになる(あくまで最大限であって、全てをサポートする必要は無い)。
- SystemAlertWindow: ユーザーから特別にパーミッションを得ることで、ホストからの特殊なリクエストに応じてGUIをプラグインプロセス上に残したまま、ホストUIの上にオーバーレイ表示できる。
ところがその後、次のアプローチがあり得ることに気付き、現在ではこのアプローチが最適解だろうと考えている:
-
SurfaceControlViewHost
: プラグイン側にある任意のandroid.view.Viewをホスト側がIPCを介してSurfaceView
に転写する機能がAndroid 11で追加されたので、これを使うアプローチ。UIイベントはプラグイン側のView実装でハンドリングする。ホスト側はsurface control関連のAPIを制御できるほか、SurfaceView
自体のshow/hideなどを制御できる。デスクトップDAWのオーディオプラグインの制御に近いことができる。ホストが自ら明示した他のアプリケーションのUIを表示するものだから、one app per screenによって排除していたUIハイジャックのようなセキュリティ上の問題がこの仕組みで再発する心配は無い。
Web UIのアプローチで面倒なのはプラグインのDSPとプラグインのGUIがメモリ空間を共有できないことにある。このモデルによく適合するのはLV2フォーマットを前提に開発されたプラグインで、LV2ではDSPとGUIが別々の動的ライブラリになっていて、メモリ空間を共有しない前提になっている。とはいえLV2でWeb UIを利用するプラグインは一部のDPFプラグインのみで、Android移植への普遍的な再利用が可能かは不明だ。
SurfaceControlViewHostのアプローチは、任意のViewを扱える上にプラグインプロセス上でメモリ空間を共有できるので、デスクトップのGUIコードがAndroidでも動作するなら、そのまま移植してくることが原理的には可能になる。ただし、GUIフレームワークによっては、SurfaceViewに転送するだけでは期待通りに動かないこともあり、たとえば筆者はJUCE(後述)のGUIの表示まではできたものの、プラグインプロセス上での操作を正常動作させたことが一度も無い。JUCEのAndroidサポートはそこまでのユースケースを念頭に置いてしっかり作り込まれてはいない。
SurfaceControlViewHostの使い方については、日本語では2年前にTechBoosterのINSIDE [技術のヒミツ] で詳しく解説している。英語圏でもAndroid 11リリース当時に公開されたCommonsWareのブログ記事くらいしか無い。あと、このAPIについては今年いちどMobile Dev Japanのmeetupで軽くしゃべった(さわりだけ)。
GUIに関しては、SurfaceControlViewHostを使いこなすという技術的なハードルは「困難」ではあるけど「不可能」ではなくなったと理解していいと思う。
附論: Binder IPC前提のプラグインフォーマットと前方・後方互換性の維持
これはDroidKaigiでもshibuya.apkでもいつも時間が足りなくてスキップしてしまうのだけど、オーディオプラグインフォーマットというものは前方互換性・後方互換性を考慮したAPIの設計が必要になる。新しいAPIのプラグインは古いDAWではロードできないとか、古いAPIのプラグインは新しいDAWではロードできないといった事態が起こると、プラグインエコシステムが縮退してしまう。互換性の維持は重要課題だ。LV2もCLAPも、拡張機能を個別のAPIとして定義することでコア部分を変更せずに済んでいるし、VST3はVST3-MAというCOMライクなクエリインターフェースによってこれを担保している。AudioUnitはVST2と同様のアプローチで…これを解説するのは骨が折れるので、今回は省略したい(話の本筋でもないし)。
既存のデスクトップ用プラグインフォーマットのAPIは、いずれも動的ライブラリをロードできる前提で構築されているので、モバイルプラットフォームにはそのままでは準用できないし、単にproxy client / serverを介してやり取りするだけでは、この前方・後方互換性の問題を解決できない。
プラグインフォーマットをIPCで構築している場合、前方・後方互換性は、新しいメソッドを生やすことによってしか対応できない。これがProtocol Bufferのように一度アサインされたデータインデックスを固定できるのであれば、前方・後方互換性を維持できるかもしれないが、Binderで使うAIDLでメソッドを削除したら、単にインデックスが変わって非互換のインターフェースになってしまうだろう。そうなると複数バージョンのプロトコルをサポートしてギャップを埋めなければならず、Android Audio HALのようにバージョンのギャップを吸収する面倒な仕組みが必要になってしまう(Androidのオーディオの低レイヤーにはHIDL・AIDLという違いもあってさらに面倒なのだが、余談すぎるのでこの辺まで)。
そういうわけで、プラグインフォーマットのAPIは、可能な限りAIDLに依存しないかたちでやり取りする構造にしておいたほうがよい、というのが筆者の所見だ。AAPではプラグインAPIの拡張機能に基づく操作は全てMIDI 2.0のSysExによって実現している。拡張機能のSysExに反応しないプラグインやホストはそれらの命令を無視するというわけだ。
AppleはiOSとAudioUnit V3でどのように技術的課題を克服したのか
Appleは元々AudioUnit V2というデスクトップ用のプラグインフォーマットを自前で持っていたので、それをiOSのアーキテクチャでも適合するようにAPIを構築しただけである。といっても、プラグインフォーマットをフルスクラッチで設計するよりは相対的に簡単というだけで、iOSの特に分離プロセスのアーキテクチャに適合するようにプラグインフォーマットを設計するのは容易ではない(ということをここまで延々と書いてきたのが伝わっていてほしい)。
Appleはこの技術的課題をどうやって克服してきたか。
まず、iOSプラットフォームにApp Extensionという仕組みが構築された。これはプラグイン機構をiOSやmacOSというプラットフォーム上で動作する任意のアプリケーションで使えるようにした、ということで、AudioUnit V3はDAWに対するApp Extensionとして動作するよう設計されている。App Extensionのホストとプラグインのやり取りはIPCで行われることになる。これはAndroid Serviceでやっていることに近い。
GUI表示はAudioUnit GUI Extensionとして開発することになる。SurfaceControlViewHostと同様、DAWが明示的に拡張機能としてロードするものなので、UIハイジャックの懸念は無い。
App ExtensionによるIPCそのものにはBinderのようなリアルタイムIPCのサポートは無かったが、iOS 12からXNUカーネルにTurnstilesというIPCリアルタイム優先度継承の仕組みが導入された。Turnstilesの設計アイディアはApple独自のものではなく、古くは199x年代にSun Microsystems(当時)がSolarisで実現していたようだ。1999年のSunWorldの記事が詳しい(hacker news経由)。そしてこの機能はAudioUnit V3で利用できるようになった。
XNU turnstilesによってDSPが単一スレッドで動作している場合はリアルタイム優先度でAUv3のDSPが処理できるようになったので、これだけでもすでにAndroidに先行してRT-safetyを実現できるようになったのだが、iOS 14ではさらにAudio Workgroupsという機能が追加された。これはAUv3ホストが実行しているオーディオスレッドと、AUv3プラグインでオーディオ処理関数を動かしているDSPスレッドの2つだけでなく、他のrealtime優先度で動作しているスレッドも「グループ化」することで、協調動作のリアルタイム性を維持するためのもので、そういうrealtimeスレッドをプラグイン上で作成していなければ、基本的には必要ないものだ。これはWWDC2020のセッション動画が詳しい。
こうやって追跡してみると、「やっぱりAppleはオーディオ機能で先行している」という印象を持つ読者もいるかもしれないが、実際にXNU turnstilesが実装されたiOS 12がリリースされたのは2018年であり、割と「最近」のことだ。それからAppleが2023年にLogic Pro for iPadをリリースするまで5年が経過している。
Logic Pro for iPadのリリースを受けて筆者が書いたブログ記事でも解説しているのだが、このタイミング(筆者の勘違いでなければ)で、AudioUnit V3ではcompact viewという機能が追加されている。これはiPadのような狭い画面(相対的に)で各トラックに差された複数のプラグインを一望しながら調整できるように、ほんの2, 3個の主要パラメーターのknobだけを表示できる程度のUIを、メインのプラグインUIとは別に構築できる機能だ。こういったUXの最適化によってAUv3をサポートするDAWのUXが実現しているのである。もちろんこれは単なるプラグインフォーマットの取り決めのレベルの話に過ぎないので、Androidで動作するプラグインフォーマットでもこれを実現するのは容易いだろう。
Androidプラットフォームはどのように対応していくべきか
Androidプラットフォームの課題
Androidプラットフォームとして不足している機能は、オーディオプラグイン以外は無さそうだが、内部設計の最適化という方面では、今でも進化している。Android 16で追加された興味深い機能のひとつに、in-process media codecといわれるものがある。ハードウェアベンダーでmedia codecを「safe languageで」実装したものは、アプリケーションのプロセスの中で呼び出して実行できる、というものだ。safe languageと書かれているけど、これは現状ではRustを差していると評されている。そうでない従来型のmedia codecは、引き続きサンドボックスの中で実行されるので、アプリケーションとのやり取りにはIPCが必要となる(IPCでデータの受け渡しをするのではなく、制御コマンドをやり取りするというレベルではあるはずだが)。これについてはAndroidAuthorityの記事が詳しい。
(追記: AOSPではこの辺にローダーの実装がある)
Androidオーディオチームのこの方向性はやや興味深い。安全なプラグインのDSPこーどであればホストのプロセスでロードできるようにする、というのは、ひとつの解決策としては考えられる。
ただ、問題はそんなに単純ではない。オーディオスレッドで動作するDSPコードはそれ以外のコード(GUIとは限らない)と協調動作する必要がある。たとえばサンプラープラグインであれば、ファイルストリームをロードして準備してオーディオスレッドにRT-safeな方法で渡すコードをnon-RT-safeなスレッドで実装することになる。このスレッドがオーディオ処理のスレッドと別のプロセス空間で動作することになるとしたら、今度はプラグインの実装の中でIPCが必要になってしまう。これは既存のプラグイン開発のパラダイムからはだいぶ離れることになってしまうし、コードの共通化には逆行することになるだろう。
Binderのprioriry inheritanceがframeworkドメインで開放されない問題を、safe codeで実装されたDSPについては開放するというアプローチではどうか。これならまだあり得るように思える。ファイルI/Oなどの操作は非DSPコードで行うのだからDSPスレッドでは呼び出されないようにすれば良い。ただ、関数のコールグラフ単位で安全性検証が行えるものなのかはよくわからないし、現状安全性検証の内容が不明すぎる。ソースはAOSPにあるかもしれないが、追っかけられていない状態だ。
もうひとつの課題は、そもそもAndroid NDKがRustを公式にサポートしていないことにある。2021年からずっとgithub issuesに記録されているが一向に進まない課題だ。ユーザースペースで公式言語として使用できない言語のコードが求められている状況だ。あと、オーディオ開発者がC++を使い続けるのは弊害のひとつではあるが、プラグイン開発者というのはリアル楽器のようにブランドがいろいろあって開発者もそんなに意識高くRustに乗り換えたりとかできないだろうし、プラグインはクロスプラットフォームコードで開発され続けることになるだろうから、AndroidのためにRustに乗り換えるという事態にはならないだろう。この意味では、まだsafe C++、alt-C++といった方向性でC++コードを流用しながらビルドするというアプローチのほうが、まだあり得るかもしれないと思っている。その意味でGoogleが外から見ると何をやっているのかよくわからないように見えるCarbonなんかは解決策のひとつになるのかもしれない(あんましalt-C++言語界隈で評価が良いとも言えないが)。
オーディオ開発コミュニティの課題
とりあえずプラットフォーム側の機能が揃うのを待っていたらオーディオアプリケーションのエコシステムは後れを取る一方なので、ユーザーデベロッパーコミュニティでできることは何とかやっておきたい。
Android単体で利用できるフレームワークやプラグインSDKを、オーディオ開発者に直接使ってもらうのは難しいだろう。これはAudioUnitSDKやVST3SDKでも同様で、GitHubなどでOSSのプラグインプロジェクトを探すと、圧倒的に多いのがJUCEプロジェクトだ。JUCEはクロスプラットフォーム・マルチプラグインフォーマットのオーディオアプリ開発用フレームワークで、この分野でだけよく使われている。VST3SDKを直接使うプロジェクトは珍しい。モバイルプラットフォームで例えるなら、誰もがKotlinではなくFlutterやReact Nativeでアプリを書いているような状況だ。
JUCEはプラットフォームやプラグインフォーマットの拡張がそれなりに作りやすいように設計されているので、筆者のAAPでもaap-juceというJUCEモジュール(これも独特の世界なのだけど今日のトピックではないので省略する)でAAPを吐き出せたりAAPのホスティングができたりする。もしAAPのようなプラグインフォーマットプロジェクトを自分で立ち上げるなら、JUCEモジュールを作っておいたほうがいい。
難しいのは、JUCEは商用ライセンスとAGPLv3のデュアルライセンスという商用製品(実質的に)で、自社の現在の方向性と無関係なcontibututionは無視する閉鎖的なプロジェクトだ(contributionを完全に拒否しないだけでもだいぶマシになったほうではある)。Androidサポートも他のプラットフォームと比べるとそんなに力を入れていないが、他のプラグインSDKはそもそもAndroidをサポートしていないものが大半なので、それに比べたら相対的には手厚い。なので、JUCEはJUCEで、在るものは活用しつつ、DPFなど他のプラグインSDKのAndroidサポートを支援していく、というアプローチが現状では有効そうだ。
あとは、せっかくMIDI 2.0が各社オーディオプラグインフォーマットでばらばらになった機能を最大公約数的に共通化しているのだから、MIDI 2.0でできることはMIDI 2.0でやって、オーディオプラグインでしかできないことはそっちでやる、という方向性を積極的に採用した開発を進めていくのが、現状打破に繋がるだろうと考えている。これはプラグイン側ではなくホスト側の開発の話だが、プラグインでもMIDI 2.0入力を受け付ければプラグインフォーマット固有のイベントなどを処理する必要はなくなり、移植性が高まる。JUCEは次のメジャーバージョンアップでMIDI 2.0入力に対応するとされているので、この方面の未来は明るい。
また、プラグインGUIサポートでも、Web UIの積極的な活用などがAndroidでのGUI付きプラグインのサポートを容易にする可能性がある。とはいえ、プラグイン開発フレームワークでWeb UIをどのようにサポートするかはまだそれぞれのフレームワークの固有のソリューションなので、モバイルプラットフォーム等でシームレスに使えるようにするために何が必要になるかは、ある程度のリサーチプロジェクトが必要になってくるだろう。WebViewでGUIをホストしたときに、オーディオバッファや複雑な型のデータのやり取りをどう可能にするかといった課題もある(オーディオストリームは現状ではWebViewからはURL resolverでのリソース解決というかたちで紐づけられたりしている)。
まとめ
Androidプラットフォームは、DAWなどの音楽アプリケーションの開発でも、プラグイン機構の欠落を除けばそんなに遜色ないが、プラグインフォーマットが無いと楽器のエコシステムが発展しないので何とかしたほうがいい、ただしそのためにはIPCのリアルタイム優先度継承というややこしい課題をプラットフォームのレベルで何とか解決する必要があるし、当面はアプリ開発コミュニティでAndroidサポートを拡充していくべき、という話を書いた。
Views: 0