Edamame は、ライブ HLS 配信用のインメモリ配信サーバである。 プレイリストの生成とセグメントファイルの配信を別々のプロセスに分けることで、配信帯域の負荷を複数のサーバへ分散する。
一般的な HLS 配信では、エンコーダが .m3u8(プレイリスト)と .ts(セグメントファイル)の両方をオリジンサーバに書き出す。
これに対し、Edamame では次の 4 つの役割に分けて配信を行う。
- publisher は、映像を MPEG-TS(
.ts)のセグメントファイルに分割してcontrollerへPUTする送信側プログラムである。プレイリストは作成も送信もしない。付属のedamame-publisherやscripts/loop-publisher.pyのほか、HTTP API に従う任意のプログラムを使える。 - controller は、
publisherから受け取ったセグメントファイルに0始まりの連番(sequence)を割り当て、原本としてメモリ上に保管する。また、稼働中のworkerが途切れずにキャッシュしているセグメントの範囲を基に、リクエストのたびにプレイリスト(.m3u8)を生成してclientに返す。 - worker は、一定間隔で
controllerと同期(sync)し、不足しているセグメントファイルを取得してメモリ上にキャッシュする。clientが再生する.tsファイルの配信を担当し、複数台並べることで配信帯域の負荷を分散する。 - client は、視聴者が使う HLS プレイヤーである。
controllerからプレイリスト(.m3u8)を取得し、その中に書かれたworkerの URL からセグメントファイル(.ts)を取得して再生する。
flowchart LR
encoder["映像送信側<br>SRT / RTMP"] -->|"ライブ映像"| publisher
publisher -->|"PUT セグメント (.ts)"| controller
worker -->|"PUT 登録 / POST sync / GET セグメント"| controller
client -->|"GET プレイリスト (.m3u8)"| controller
client -->|"GET セグメント (.ts)"| worker
client -.->|"GET 暗号鍵(key_uri 使用時)"| keys["外部の鍵配信サーバー"]
各コンポーネントの設定と動作、HTTP API、運用の詳細は ドキュメント案内 から辿れる。
- Rust 1.99.1(edition 2024)でビルドを確認済み。
tomyouは Rust 1.93 以降を要求する - ネットワークの接続条件:
publisherとworkerから、controllerの HTTP または HTTPS アドレスへ接続できることclientから、controllerのアドレス(プレイリスト取得用)へ接続できることclientから、workerの公開 URL(--public-url、セグメント取得用)へ接続できること
edamame-publisherを動かす場合は、SRT と RTMP に対応したffmpegとffprobe、およびエンコーダに応じた GPU 環境:- NVEnc(
h264_nvenc)を使う場合:NVIDIA の GPU とドライバ - QSV(
h264_qsv)を使う場合:Intel の GPU、VA-API ドライバ、oneVPL の GPU ランタイム - ソフトウェアエンコード(
libx264)や映像コピー(copy)だけで動かす場合は GPU 不要
- NVEnc(
リポジトリのルートで compose.yaml を使い、controller と worker を起動する。
ホスト上の client から再生する前提で、公開 URL には 127.0.0.1 を使い、ポートもホストの 127.0.0.1 にだけ公開する。
docker compose up -d
docker compose ps起動を確認する。
curl --fail --show-error http://127.0.0.1:9000/health
curl --fail --show-error http://127.0.0.1:9001/health停止する場合は docker compose down を使う。
別ホストの client から再生する場合は、worker の EDAMAME_PUBLIC_URL を client から到達できるホスト名または IP アドレスに変更し、ports の 127.0.0.1 を外す。
controller を複数台で動かす場合は、各 controller の EDAMAME_PUBLIC_URL を peer、worker、client のすべてから到達できる名前にする(controller の冗長化)。
ソースから直接起動する場合は controller と worker の手順を使う。
各オプションの必須、任意、既定値、制約は、コンポーネントごとの文書にまとめている。
各オプションは環境変数でも指定できる。
環境変数の名前は、オプション名の - を _ に変えて先頭に EDAMAME_ を付けたものにする(例:--bind は EDAMAME_BIND)。
コマンドライン引数と環境変数の両方を指定した場合は、コマンドライン引数を優先する。
controller の --auth-token は必須であり、worker と publisher にも同じ token を設定する。
--public-url には、peer、worker、client のすべてから到達できる機体固有の URL を指定する。
cluster の処理は常に有効であり、seed を省略した場合は自分 1 台で始まる。
複数台に増やすときは、参加する機体の --seed に起動済みの controller の URL を指定する。
leader の優先度を変える場合は --metric を指定する。
DNS の共有名から各機へ到達させる起動例は controller の冗長化、交代による状態消失と通信分断時の制約は controller の冗長化 を参照する。
crates/edamame-publisher は、SRT または RTMP で受け取ったライブ映像を処理するプロセスである。
ffmpeg で複数画質(quality)の MPEG-TS セグメントに変換し、AES-128-CBC で暗号化して controller へ PUT し続ける。
GPU と ffmpeg を使うため、通常は controller や worker とは別のホストで動かす。
GPU と入力の設定が必要なため、既定の compose.yaml には含めていない。
cargo run --release -p edamame-publisher -- \
--config crates/edamame-publisher/publisher.example.toml \
--auth-token example-token設定ファイルの書き方、暗号化と鍵登録の仕組み、失敗時の扱いは publisher で説明する。
例の [[controller]] の URL は実際の controller に置き換える。
1 台だけ使う場合は [[controller]] を削除し、--controller-url を指定する。
複数の controller へ送る場合は、設定ファイルの [[controller]] に url と metric を書く。
metric が小さい controller を優先し、先頭が落ちたときは次の controller へ送信する。
ローカルの MP4 ファイルを無限ループさせてテスト配信を行うスクリプトは scripts/README.md で説明する。
crates/tomyou は、拠点の OBS から Edamame と ImageFlux へ同時に映像を送る GUI アプリである。
OBS の配信先をローカルの RTMP サーバへ変更し、Edamame 向けの RTMP と ImageFlux 向けの WHIP を別プロセスで送る。
cargo run --release -p tomyou
cargo run --release -p tomyou -- --config /path/to/config.toml設定、OBS の条件、入力チェック、GStreamer の必要機能、ImageFlux の扱い、fallback 登録、障害時の動作は tomyou で説明する。
tomyou-v* タグを push すると GitHub Actions が Windows / macOS / Linux のバイナリをビルドし、GitHub Release に添付する。
ImageFlux のプレイリストと鍵は、live2025-server へ fallback として登録する。
fallback 登録を使う場合は、[stream_credentials] に live2025-server のベース URL と STREAM_INGEST_TOKEN を指定する。
通常用の URL と鍵の登録は、既存の publisher の設定を使う。
publisher のコンテナイメージには ffmpeg と Intel の GPU ドライバが含まれるため、Dockerfile の publisher ターゲットを指定してビルドする。
NVIDIA の GPU は NVIDIA Container Toolkit(--gpus all)で、Intel の GPU は --device /dev/dri:/dev/dri でコンテナに割り当てる。
起動例と設定は publisher を参照する。
docker build --target publisher -t edamame-publisher .controller と worker を起動した直後は、channel がまだ作られていないため、プレイリストの取得には 404 Not Found が返る。
最初のセグメントファイルを PUT した時点で、channel(以下の例では live)と quality(720p)が自動的に作られる。
-
テスト用のセグメントファイルを
controllerにPUTする。URL 末尾のsegment-1は、publisherがセグメントごとに付ける任意の識別子である。printf 'segment-1' > /tmp/segment-1.ts curl -i -X PUT \ -H 'Authorization: Bearer example-token' \ --data-binary @/tmp/segment-1.ts \ 'http://127.0.0.1:9000/segments/live/720p/segment-1?duration_ms=6000&bandwidth=3000000&resolution=1280x720'
controllerは新しい識別子に0始まりのセグメント番号を割り当て、レスポンス JSON のsequence(この例では{"sequence": 0})として返す。 -
controllerからプレイリストを取得する。PUTしたセグメントは、すぐにはプレイリストに出ない。--publish-wait-ms(上の例では 2000 ミリ秒)が過ぎ、さらにworkerがsyncでそのセグメントを取得し終えるとプレイリストに反映される。 反映される前のリクエストには503 Service Unavailableが返るため、--retryを付けて取得する。curl -i --retry 30 --retry-delay 1 --retry-connrefused \ http://127.0.0.1:9000/channels/live/master.m3u8 curl -i --retry 30 --retry-delay 1 --retry-connrefused \ http://127.0.0.1:9000/channels/live/720p.m3u8
-
メディアプレイリスト(
720p.m3u8)にworkerの公開 URL(http://127.0.0.1:9001/segments/live/720p/0.ts)が出力されていることを確認する。続けて、その URL からセグメントファイルを取得する。curl -i http://127.0.0.1:9001/segments/live/720p/0.ts
503 が解消しない場合は、worker が起動しており、--controller-url と --auth-token が controller の設定と一致しているかを確認する。
ソースからビルドする場合は次のコマンドを使う。
cargo build --workspace変更後は、整形、静的解析、テストを実行する。
cargo fmt --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace- インメモリ保持と再起動時の挙動 :
controllerのセグメントファイル、メタデータ、採番状態と、workerのキャッシュは、すべてメモリ上にだけ保持される。プロセスの再起動や leader の交代で、これらのデータは失われる。controllerを再起動するとセグメント番号は0から振り直され、workerは再登録した時点で手元のキャッシュを捨てて取り直す。 - 容量上限と自動削除 :
controllerは、メモリ上のセグメントの合計サイズが--storage-capacityを超えると、書き込んだ順に古いセグメントから削除する。プレイリストの公開範囲にあるセグメントまで削除されると、プレイリストの取得には422 Unprocessable Entityが返る。必要なメモリ容量は 運用 の計算式で見積もり、余裕を持たせて設定する。 - 画質間のセグメント識別子 :複数の画質(
quality)を配信する場合、同じ時間帯のセグメントには同じセグメント識別子(identifier)を付けてPUTする。controllerは同じ識別子に対して同じセグメント番号(sequence)を割り当てる。 - publisher 停止時の公開停止 :
publisherが新しいセグメントを送らなくなって、最後のPUTからduration_ms × --playlist-segments + --publish-wait-msを超えると、そのqualityのメディアプレイリストは503 Service Unavailableになり、マスタープレイリストから除外される。再開後は停止前のセグメントを公開せず、新しいセグメントだけを公開する。 - プレイリスト反映の遅延と独立性 :セグメントを
PUTしてからプレイリストに反映されるまでには、--publish-wait-msの待ち時間と、workerがsyncでセグメントを取得する時間がかかる。公開範囲はqualityごとに独立して決まるため、workerの停止やセグメントの欠落が起きると、メディアプレイリストのセグメント番号(#EXT-X-MEDIA-SEQUENCE)は前に進むだけでなく、戻ったり飛んだりすることがある。 - キャッシュ上限の推奨値 :
workerの--cache-limitは、controllerの--playlist-segmentsより 1 以上大きく設定することを推奨する。公開待ちの間も、直前の公開範囲と新しいセグメントの両方を同時にキャッシュへ残せるようにするためである。 - 認証と通信保護 :
--auth-tokenによる Bearer 認証は、セグメントのPUT、workerの登録、外部配信の登録と heartbeat に適用される。GETとPOST /workers/{worker_id}/syncには認証がない。controllerとworkerは単体での TLS 終端と ACME による証明書取得に対応している。TLS を使わない場合は、内部通信をプライベートネットワーク内にとどめ、外部へ公開する場合はリバースプロキシなどで TLS を終端する。 - 障害時の扱いと関連仕様 :各コンポーネントが停止したときの動作は 運用、HTTP ステータスコードの詳細は HTTP API、プレイリストの出力形式は HLS の出力 で説明する。