Python (C/C++ 拡張) ライブラリを作ってる話
最近 Python のライブラリを色々と作ってる。きっかけは自社の Python SDK で、これは C++ SDK を nanobind を利用して Python から利用できる用にしているのだが、この nanobind が本当に便利で良い。
なんで Python ?
Python は昔 (Python 2 時代) 触っていたのだが、スケールアップやスケールアウトが難しかったため、Erlang/OTP をメイン言語に切り替えてからはシェルスクリプト代わり程度しか触ってこなかった。
ただ、LLM 関連と自社製品の SDK などを連携する場合は Pyhton が一番だし、何よりも pytest は本当に便利。自社製品ミドルウェアの E2E テストは pytest で実現している。ちなみに大量の GitHub Actions Runner を 100 近く並べて数時間かかるテストを数分で終わるようにしている。
nanobind
Python は残念ながら速度面で有利に立てる事はないので、自分が専門とする音声や映像をリアルタイムに取り扱うことには向いていない。ただ C/C++ 拡張であれば話は別となる。
この C/C++ 拡張は昔 Python 触っていた頃はかなり大変だったのだが、今は nanobind と scikit-build-core と uv と CMake を組み合わせることで、マルチプラットフォーム対応のビルドが想像以上に簡単にできる。
とくに scikit-build-core が PyPI でインストールできる cmake と ninja があるのが凄く良い。CMake をビルドしてインストールしたりしなくて良い。
公開しているPython ライブラリ
PyPI に登録してある nanobind を利用した Python ライブラリを紹介していきたい。
sora-sdk
時雨堂の主力製品である WebRTC SFU Sora の Python SDK。nanobind 利用第一弾。libwebrtc に Sora 関連の仕組みを追加してある Sora C++ SDK を nanobind でバインディングして実現している。
普通に 1080p の 60fps の映像を配信できたりと、Python か!?と思えない性能がでる。受信も普通にできる。OpenCV を使う事で画面に表示するのも簡単。
Python なので取得した映像を LLM で解析してから映像を配信するとかもできるし、受信した音声の文字起こしを LLM で行うとかもできる。
uv add sora-sdk
でインストールできる。
libdatachannel-py
OBS が採用している WebRTC ライブラリである libdatachannel を Python 経由で利用できるようにしたライブラリ。もちろん nanobind を利用している。
libwebrtc は本当にすばらしいライブラリなのだが「細かいところが融通聞かない」という問題があり、ちょっと特殊な E2E テストをしたいときに不便だったので、libdatachannel を Python から利用できる用にすることで、イレギュラーなテストを実現している。
コーデック関連が一切ないので、自前で libopus や libvpx や libaom などを自前ビルドして利用できる用にしている。
uv add libdatachannel-py
でインストールできる。
blend2d-py
映像配信のテストする際にダミー映像が欲しくなるのだが、それを動的に生成したくなる際に C++ では Blend2D という高性能な 2D ベクターグラフィックスエンジンを利用してダミー映像を作成していた。このライブラリは CPU だけで 120fps の映像を作成したりできるので本当に快適。
ということで Python からも Blend2D を利用できる用にしてみた。このライブラリから scikit-build-core を利用し始めた。CMakeLists.txt で blend2d をソースでダウンロードして macos arm64 / ubuntu x86_64 / ubuntu arm64 / windows x86_64 向けにビルドしている。
あとは uv build --wheel
で whl ファイルを作成し PyPI へ登録している。
Python で 720p 120fps のダミー映像が問題無く作成できるの本当に便利。
uv add blend2d-py
でインストールできる。
libwebm-py
後述する WebCodecs の Python 実装を作っていたら、そもそもエンコードした映像を保存する仕組みが欲しくなり、WebM であれば簡単にできだろうと考え作成。Google が開発している libwebm を Python から利用できる用にした。
uv add libwebm-py
でインストールできる。
開発しているPython ライブラリ
webcodecs-py
libdatachannel-py を開発しているときに困ったのがコーデック問題。libwebrtc には音声や映像のコーデックが含まれているため困らなかったが、libdatachannel にはコーデックは一切含まれていないため、Python からコーデックを利用する仕組みが必要になる。
libdatachannel-py には codec というモジュールを追加して、コーデックを利用できる用にしたが、そもそもこれが独立してた方が今後いいよな ... と思っていた。
そんなときに MOQT のライブラリを Python で作る際も同じ課題があることから、WebCodecs API に寄せた webcodecs-py を作れば色々な場面で活躍するのでは?と考え開発をしている。
仕様としては WebCodecs の API にできるだけ寄せつつもハードウェアアクセラレーターの詳細な設定を想定している。
- libvpx
- libopus
- libaom
- libdav1d
- libyuv
- Apple Video Toolbox
今のところこの辺りを意識することなく利用できる用になる予定。コツコツ作っているのでそのうち公開したい。
moqt-py
Media over QUIC Transport (MOQT) の Python ライブラリで、やはり Python で書けたほうが良いだろうという判断からスタート。
Media over QUIC クライアント/サーバーを Python だけで実現しつつ、性能もある程度担保できるようにしたい。もちろんサーバーはおまけ。
MOQT ライブラリには Google の QUIC 実装である Quiche (C++/Bazel) を採用した。ビルド周りが整備されていないので、自分たちで moqt-build というライブラリを作って公開している。
moqt-build はかなり力技で実現しているが、ここに iOS/Android も追加予定。
mp4-py
libwebm のライブラリは便利で良かったが、MP4 も普通に Python で使いたいのでは ... ? と考え MP4 のライブラリも開発することにした。
方針としては Rust で書いてある自社の mp4-rust に C API を生やして nanobind 経由で利用する方針。Rust で書いてあることから PyO3 なども利用できるが、自社では Rust は C/C++ の代替というスタンスなので、C API 経由での利用を行う。C API から利用できるのは想像以上に大事。
雑感
ここ一ヶ月くらい Python ライブラリを色々作ってきたが、とても快適としか言い様がない。とにかく nanobind と scikit-build-core と uv の組み合わせが欲で来すぎている。
また CMake や Ninja について LLM が無駄に詳しすぎる。CMakeLists.txt も想像以上に万能で困ることが無い。
Python でライブラリを作っておくと、LLM と組み合わせやすいので多くのユーザーが使ってくれることもわかっている。性能面では C/C++ なのでネガティブは GIL くらい。
ただ Python 3.13 からは Free-Threading (No-GIL) が入り、Python 3.14 では色々整備される模様。nanobind も Free Threading 対応が充実しており、今後は nanobind + Python C/C++ 拡張 + Free Threading にフルベットしていきたい。
サーバーは Erlang/OTP で、ブラウザやモバイル以外のクライアントは Python (C/C++) というのが自分にはしっくりきている。