4月の開発記録 (2022)

4月が終わろうとしています。今月は副業で某XIVの冒険者を始めてしまって進捗がひどいことに…

ADC21セッション動画鑑賞会 #1 をやった

4/6にひさしぶりにオーディオ開発の勉強会を開催しました。

music-tech.connpass.com

見たのはVitalの開発者がウェーブテーブルの最適化手法について語っているセッションです。

www.youtube.com

ADC動画鑑賞会やろうとは昨年から思っていたのですが、例年だとカンファレンスの1週間後くらいに全部上がっていたのが、今年はなぜかいつまで待っても公開されず、出てきたと思えば毎週1本程度ちょろちょろと出すだけ…という体たらくで、いつ出来るのやら…という感じでした。今回の動画も含めて、実は今でも大半の動画は既にアップロードされているのにunlistedのままだったりします(公開動画から他の動画にリンクされているのに気づけば掘り出せます)。

この種の勉強会をひさびさに開催したこともあってか、当日は参加者の方々からさまざまな補足説明や参考情報の提供をいただけて(主催した自分が雑談の20%もしゃべっていないくらい)、非常に有益で楽しい会になったと思います。もともと録画して公開する予定はなかったのですが、録画くらいはしておいてもよかった…。初回からちょっと難易度が高すぎるかな?と思ったのですが、結果的には大正解だったと思います。1/5くらいはわたしがDSPの素人に毛が生えた程度の知識しかなくて話をリードする役に立たなかったことに起因しています(!) これはいいことなので継続してそうやっていきたいですね(!?)

第2回もカジュアルに1週間くらいでふらっと計画・実行したいと思っています。

AAP extensions

今月は、ほぼ1ヶ月ずっとandroid-audio-plugin-framework拡張機能を実現する方式を見直し、設計を書いては投げ、実装しては投げ…をやっていました。どんくらいずっとかというと、githubにほとんど草が生えないくらい(mainブランチでやっていないから一切が草にならない、という要因もありますが、開発ブランチでも更新頻度は低い)

拡張機能はAAPのようなプラグインフレームワークAPI安定性を維持するために「コアだけは安定させ、追加機能は全部拡張APIとして作り込む」というもので、VST3でもVST-MAとして実現していますし、LV2も大量のモジュールで編成されていますし、CLAPも数多くのfundamental extensionsを設計しています。

AAPも建前上はLV2やCLAPに近い拡張機能をサポートしていることになっているのですが、一方でもともとJUCEモジュールとして作り始めたこともあって(JUCEではAudioProcessorさえサポートできればよく、AudioUnitも対応しているので拡張機能なにそれおいしいの状態)、ちゃんとしたサポートが後回しになっていました。proof of conceptとして当初は「プラグインとホストで共有するASharedMemoryを管理する構造体」やら「MIDI 1.0を使うか2.0を使うか指定する構造体」だのをextensionとして定義して(本来前者は拡張にするのが相当ではない)、何となくやったつもりでした。

しかし今月になって「MidiDeviceServiceで出音を調整するのは無理があるし、カジュアルにプログラムチェンジで音を切り替えられるようにpresetsを実装するか〜」と雑に思いながら、実際に「拡張機能」に相応しいAPIを設計しようと思って着手してみると、LV2やCLAPとは全く異なるレベルの問題を解決しなければならず、実はこれめっちゃ難しいやつでは…!?となりました。メモリ空間が別々にあるので関数コールバックは使えない、ということは以前から把握していたのですが、もう少し上位の視点でまとめると、AAPはAudioPluginServiceとclient hostの分離プロセスの構造の上で以下の3つを実現する必要がありました:

  • プラグイン開発者もホスト開発者も自然に拡張機能を使えるようにする
  • 拡張機能の開発者がAAPの内部構造に依存せずに実装を提供できるようにする
  • libandroidaudiopluginがホストでもプラグインでも任意の拡張機能をハードコーディングなしでサポートできるようにする

これ、LV2やCLAPどころかAUv3でも実現していないやつでは…!? (デスクトップのプラグインフレームワークではdlopen/dlsymで一発だし、AUv3に拡張機能らしいものは無さそう)

これを実現するためには、AAPコア部分でやっているのと同じように、Binder等を使って拡張機能の呼び出しを処理できなければなりません。しかしプラグイン開発者がBinderまで意識してその処理を実装しなければならないという事態は(aap-juceやaap-lv2のような中間層が全部吸収することになるとはいえ)避けたいですし、ホスト開発者にプラグイン拡張機能を外から操作するコードをBinderをいじるレベルで実装させるというのも避けたいところです。

それならば、拡張機能の C APIを呼び出すホストと、拡張機能のC APIを実装するプラグインでは、シンプルなAPIの入り口だけを用意して、それらがBinderでクライアント・サービスを実現する部分については拡張機能の開発者が提供するようにすれば良いのではないか、と考え、extensionの開発者にはextension serviceも実装してもらう、という方針にしました。拡張APIで利用される「関数」は、ホスト側ではBinderを使ったいわゆるproxyが担うことになる、というわけです。ホスト「を」開発する人はそんなことを意識しなくても、get_extension(URI)だけでそのextensionの構造体が返ってくる、みたいな仕組みです。

プラグイン開発者が実装した拡張機能のコードが動作するのはプラグイン(サービス)側のアプリなので、プラグインによって拡張機能が呼び出されて処理している範囲では難しいことは何もありません。一方、そのプラグインアプリ全体としては、その拡張機能を呼び出すコードは、ホストからのリクエストをさばくAAPフレームワークのAudioPluginService(のBinder)であり、AAPフレームワーク開発者としては「誰が書いたかわからない拡張機能のコードをプラグインのプロセス空間で実行できる必要があります。そのためには、「Binder経由のリクエストで指定された拡張機能の指定された関数を呼び出す」ためのルーディングを実装して、それらのリクエストを拡張機能の開発者に渡して処理してもらう必要があります。

AAPとプラグインを繋ぐ部分と、AAPとホストを繋ぐ部分は、それぞれその拡張機能で出来ること・やるべきことを掌握している拡張機能の開発者が担うのが適切です。わたしは当初この部分を甘く見ていて、たとえば「ホスト開発者はpresets拡張機能をサポートするコードを自分で書いているのだからホスト側が直接presetsをサポートするコードを書けばいい」と考えていたのでした。それだとAAPのフレームワークのレベルで実装が追加されない限り、誰もこのproxy部分を開発できないわけです。

機能要件としては、この3〜4者(プラグイン開発者、ホスト開発者、拡張機能開発者、AAP開発者 = 自分)の役割をきちんと切り分ける必要があり、またそれぞれがアクセスできるAPIを適切に限定するのもフレームワーク設計でやるべきことです。特に現状どれもC APIとして提供することにしているので(拡張機能くらいはC++限定でも許されたいところですが)、実装言語であるC++との調整作業でさらにバグにハマって神経を削る…という感じです。

そのへんの話をgithub issueやドキュメントとしてまとめる作業もやってあって、ただ設計初期の内容なので更新しないとな…という感じです(あくまで雰囲気だけ共有):

ちなみに、これだけ設計を練っていても設計時点で解決できていない課題があって、この拡張機能はオーディオ処理の中で使えるようにはできていません。拡張機能をホスト側から操作することはあっても、プラグイン側からホストを操作できるようにはなっていませんし、オーディオ処理自体がBinderのリクエストの処理となっているので、その中でさらに拡張機能をBinder経由で「操作」することは考えられないためです。その代わり、ホストとは共有メモリのチャンネルを確立しているので、ホストからプラグインが必要としそうな情報を共有メモリ上に展開しておくことで、一般的なリアルタイム需要は満足できるはず、という設計になっています。たとえばLV2 Timeが提供する現在の演奏位置やテンポといった情報は、共有メモリデータだけで実現できるでしょう。

一方で「拡張機能がホストに追加情報を問い合わせて処理を続行する」ような仕組みにもできないので、典型的な拡張機能が何かしら実装できないという可能性もまだあります。たとえば、拡張機能ではありませんがjuce::AudioProcessor::getPlayHead()juce::AudioPlayHeadを取得してtransportRewind()を指示するような機能は実現できません。この辺は実際に拡張機能をひとつひとつ実装してみないと見えてこないでしょう。

5月は…

…というわけで、今月はこの課題にずっとかかりっきりでアウトプットの少ない日々でした(まあそんなことより冒険者としての副業がry) この辺の知見もいずれ改めてまとまった文章として英語版を公開したいと思っています(未定)。

3月には「Android 13のMIDI 2.0をいじったり…」みたいなことを書いていたのですが、どうも現状でUSB MIDI 2.0しかサポートしていなくてMidiDeviceServiceではまだ使えないみたいなので、extensionsとpresetsの実装が終わったら、たぶん新しいaudio ports (channels)に踏み込んだりするんじゃないかなと思います(今書いてて気付いたけどportsって書くとaudio port / midi portの話なのかAndroidへの移植 = ports, portingの話なのか一見して分からないな? 前者の話です)。