WebCodecs の Python 実装が動いた
最近は Python で AV1 や Opus が WebCodecs API に近いかたちで利用できるライブラリを開発していて、なんとか無事動いて OpenCV で取得したカメラ映像を AV1 にエンコードしてファイルに保存することができるようになりました。
経緯
もともと libwebrtc にはコーデックが含まれているのですが、他の WebRTC 実装である libdatachannel にはコーデックは含まれていないので Python から libdatacahnnel を利用する libdatachannel-py には無理矢理コーデックを組み込んでいました。
ただ、あまりしっくりきていないのと、できればコーデック部分は分けたい・・という思いがありました。
そんなとき Media over QUIC の Python ライブラリを作ろうかなとふと思ったとき、こちらも結局コーデックライブラリがブラウザ以外での利用はほとんどなく、ブラウザからの利用前提になってしまっていました。
そこでブラウザのコーデックを利用する仕組みである WebCodecs API にできるだけ寄せた Python ライブラリを自作することにしました。
これがれば気軽に FFmpeg や GStreamer に依存することなく、Python からコーデックを利用する事ができます。
実装方針
まずは最低限の方針としては音声は Opus 、映像は AV1 に対応するという方針を決めました。それ以外は基本的にはハードウェアアクセラレーターベースに仕様と考えました。多くの場合でハードウェアアクセラレーターを利用できる環境が圧倒的に有利な分野ということもあります。
もちろん Python での実装ではなく C++ 実装です。実装には nanobind を採用し、ビルドには scikit-build-core を採用しました。Windows への対応も必須と考えていたので CMakeList.txt をゴリゴリ書くことにしました。
結果
色々と省略していますがこんな感じで無事動いています。IVF というのは検証用の簡易コンテナフォーマットです。
既にテストはかなり十分かいたので、コードを整理してサンプルを用意して PyPI への登録と Apache-2.0 での公開に向けて進めていきます。
from webcodecs import (
EncodedVideoChunkType,
VideoEncoder,
VideoEncoderConfig,
VideoFrame,
VideoFrameBufferInit,
VideoPixelFormat,
)
# エンコーダーを初期化
encoded_frame_count = 0
def on_output(chunk):
nonlocal encoded_frame_count
# IVF ファイルに書き込み
frame_data = chunk.get_data()
ivf_writer.write(frame_data, encoded_frame_count)
encoded_frame_count += 1
# エンコードされたフレームのサイズを表示
chunk_type = "Key" if chunk.type == EncodedVideoChunkType.KEY else "Delta"
print(
f" フレーム {encoded_frame_count:4d}: {chunk_type:5s} {chunk.byte_length:6d} bytes, "
f"timestamp={chunk.timestamp}"
)
def on_error(error):
print(f"エンコーダーエラー: {error}", file=sys.stderr)
encoder = VideoEncoder(on_output, on_error)
config: VideoEncoderConfig = {
"codec": "av01.0.04M.08",
"width": actual_width,
"height": actual_height,
"bitrate": args.bitrate,
"framerate": float(args.fps),
"bitrate_mode": "constant",
"latency_mode": "realtime",
}
encoder.configure(config)
print("エンコーダーを初期化しました")
print(f" コーデック: {config['codec']}")
print(f" ビットレート: {args.bitrate} bps ({args.bitrate / 1000:.0f} kbps)")
print(f" ビットレートモード: {config['bitrate_mode']}")
print(f" レイテンシモード: {config['latency_mode']}")
print()
# フレームをキャプチャしてエンコード
frame_count = 0
timestamp = 0
frame_duration = 1_000_000 // args.fps # マイクロ秒単位 (WebCodecs API 準拠)
print("フレームのキャプチャとエンコードを開始します...")
print("Ctrl+C で中断できます")
print()
start_time = time.time()
last_frame_time = start_time
try:
while args.frames is None or frame_count < args.frames:
ret, bgr_frame = camera.read()
current_time = time.time()
if not ret:
print("エラー: フレームを読み込めませんでした", file=sys.stderr)
break
# フレーム間隔をログ出力(最初の10フレームのみ)
if frame_count < 10:
interval = (current_time - last_frame_time) * 1000 # ミリ秒
print(f"フレーム {frame_count}: 間隔 {interval:.1f} ms")
last_frame_time = current_time
# BGR → I420 変換
i420_data = bgr_to_i420(bgr_frame)
# VideoFrame を作成
init = VideoFrameBufferInit(
format=VideoPixelFormat.I420,
coded_width=actual_width,
coded_height=actual_height,
timestamp=timestamp,
)
video_frame = VideoFrame(i420_data, init)
# エンコード(最初のフレームと 20 秒ごとにキーフレームを強制)
# WebCodecs API ではアプリケーション側で明示的にキーフレームを制御する
keyframe = frame_count == 0 or frame_count % (args.fps * 20) == 0
encoder.encode(video_frame, {"keyFrame": keyframe})
video_frame.close()
frame_count += 1
timestamp += frame_duration
雑感
WebCodecs の Python 実装は Claude Code と Codex を利用しています。ただし結構手を入れてさらに相当時間をかけてレビューをしているので Vibe Coding とは言いづらいかも知れません。
ただ構想から 2 週間程度で動くところまで持ってこれたのは LLM の恩恵といって間違いないでしょう。実際かなり詳細な比較ドキュメントも作ったりしています。

Python の C++ 拡張を色々と作って言っていますが、かなりの速度で開発が進みます。ただレビューやら細かい修正やら仕様検討は相当疲れます。結局は人間がボトルネックでもっと強くならねば ... と実感する日々です。