配信と録画
このページの見出し
エンコーダー・カメラ・ブラウザーから映像と音声を受け取り、視聴者へ届けるまでの設定をまとめたページです。どのプロトコルで受けてどれで配信するか、遅延をどこまで詰めるかを決めるときに使ってください。
全体の流れ
ストリームは配信 1 本の単位で、同時に接続できる配信者は 1 つだけです。配信者から届いた AU(エンコード済みのフレーム)は、ストリームごとのハブで 1 回だけ HLS のセグメントに変換されます。ライブの HLS も、録画も、RTMP/SRT/RTSP/WHEP の視聴者も、出力や字幕も、その同じデータを受け取ります。
映像の受け取り方は、配信者のほうから送ってくる push、SENCA が取りに行く pull、ノードにつないだキャプチャデバイスの 3 通りです。
| 取り込み方法 | プロトコル | 認証 |
|---|---|---|
| push(配信者が接続する) | RTMP・RTMPS・SRT(listener)・WHIP | ストリームキー(sk_...) |
| pull(SENCA が取得しに行く) | RTMP・RTSP・SRT(caller)・HLS | 入力の定義(入力と出力) |
| キャプチャデバイス | 入力の protocol: "device" | 入力の定義(下の「キャプチャデバイスから取り込む」) |
視聴者へは次の方法で届けられます。どれも有効期限と署名の入った URL を使います。
| 届け方 | URL の取得元 | 必要な設定 |
|---|---|---|
| HLS(TS / fMP4、LL-HLS) | 署名付き URL の hls | --http(--public-http-url) |
| RTMP play | 署名付き URL の rtmp | --rtmp(--public-rtmp-url) |
| SRT play | play:{stream_id}?exp=..&sig=..(下の「SRT play」) | --srt |
| RTSP play | 署名付き URL の rtsp | --rtsp と --public-rtsp-url |
| WHEP | 署名付き URL の whep | --webrtc-udp |
このページの例では、次の変数を使います。TOKEN は管理 API のトークンで、1 台のノードを初めて起動したときなら {data-dir}/control/bootstrap-admin-token に書かれています。
API=http://127.0.0.1:8080/api/v1
TOKEN=$(cat /var/lib/senca/control/bootstrap-admin-token)
H=(-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json")
公開 URL は必ず設定してください。 API が返す URL(
ingest.*と再生 URL)は、待ち受けるアドレスではなく--public-rtmp-url・--public-http-url・--public-srt-url・--public-rtsp-urlから組み立てます。既定は
--public-http-urlがhttp://127.0.0.1:8080、--public-rtmp-urlがrtmp://127.0.0.1:1935で、待ち受けるアドレスを変えてもついてきません。--public-srt-urlと--public-rtsp-urlには既定値がなく、指定しなければingest.srt_urlと再生 URL のrtspは応答に出てきません(設定 を参照)。
ストリームを作成する
まずストリームを作ります。この例では、配信が始まったら自動で録画するようにしています。
curl -s -X POST "$API/streams" "${H[@]}" -d '{"name":"cam1","recording":{"enabled":true}}'
応答(一部を抜粋):
{
"id": "189f0fe84d8fc18358226d7380800f32",
"name": "cam1",
"observed_state": "waiting_input",
"ingest": {
"rtmp_url": "rtmp://127.0.0.1:24935/live",
"stream_key": "sk_QE7_qJh12KNHNNfYUI19WlUT4VqV02XOwYDUcib3qL8",
"srt_url": "srt://127.0.0.1:24900",
"srt_streamid_format": "publish:{stream_key}",
"whip_url": "http://127.0.0.1:24080/whip/189f0fe84d8fc18358226d7380800f32"
},
"live": null
}
stream_key に値が入るのは、作成したときと、キーを再発行したとき(POST /streams/{id}/keys)の応答だけで、ほかのときに取得すると null です。SENCA はキーの SHA-256 しか持っていないので、なくしたら再発行するしかありません。再発行した時点で古いキーは使えなくなります。Idempotency-Key を付けた POST を再送した場合も、応答のキーは null になり、stream_key_redacted: true が付きます。
srt_url と srt_streamid_format は --public-srt-url を指定したときだけ、whip_url は HTTP の公開 URL があるときだけ応答に入ります。
observed_state は waiting_input(配信者がいない)、live(配信中)、stopped(desired_state が stopped)のどれかです。配信中は live に配信者の情報が入り、たとえば次のようになります。
"live": {"transport": "rtmp", "epoch": 0, "started_at": "2026-11-03T01:15:53.255Z", "bitrate_bps": 956271, "viewers_hint": 0,
"viewers": {"total": 3, "hls": 2, "rtmp": 1, "srt": 0, "rtsp": 0, "webrtc": 0, "moq": 0},
"tracks": [{"track": 0, "codec": "h264", "width": 640, "height": 360},
{"track": 1, "codec": "aac", "sample_rate": 48000, "channels": 1}]}
viewers はこのノードで見ている視聴者の数です(数え方は下の「視聴者と入力の上限」)。viewers_hint は、SENCA の中でストリームを受け取っている処理まで含めたおおよその数です。epoch は、配信者が再接続するたびに 1 ずつ増えます(下の「配信者の接続が切れたとき・入れ替わったとき」を参照)。
ストリームのフィールド
ストリームの作成(POST /streams)と変更(PATCH /streams/{id})で指定できるフィールドです。変更には If-Match ヘッダーが必須で、知らないフィールドがあると 400 になります。
| フィールド | 既定 | 制約 | 説明 |
|---|---|---|---|
name | (必須) | 1〜64 文字。英数字と - _ . が使えます。. で始めることはできません。テナントの中で重複できません(重複すると 409) | 表示名 |
latency_profile | standard | standard・low・interactive | 遅延の目標です。low にすると、LL-HLS の part(セグメントをさらに細かく分けたもの)を出します。interactive は WebRTC や MoQ で見ることを前提に、さらに遅延を小さくします(下の「HLS」を参照) |
codec_profile | any | any・royalty_free | royalty_free にすると、特許プールのあるコーデック(H.264・H.265・AAC)のトラックを捨てます。このとき hls.container は fmp4 にする必要があります(コーデックと特許 を参照) |
hls.segment_ms | 2000 | 500〜10000 | セグメントの長さの目標です(ミリ秒)。実際には、キーフレームの位置で区切ります |
hls.window | 6 | 3〜60 | ライブのプレイリストに含めるセグメントの数です |
hls.container | ts | ts・fmp4 | セグメントのコンテナー形式です。録画も同じ形式になります |
recording.enabled | false | true にすると、配信が始まったときに自動で録画します(録画 を参照) | |
recording.retention_days | null | 1〜36500 | 自動で録画したものを残しておく日数です。null は無期限です |
snapshots | null | interval_secs 1〜86400、keep 1〜10000 | 一定の間隔で静止画を保存します(下の「静止画」を参照) |
limits.max_viewers | null | 0 以上 | 同時に見られる視聴者の数の上限です(ノードごと)。下の「視聴者と入力の上限」を参照してください |
limits.max_ingress_bps | null | 0 以上 | 配信者から受け取るビットレートの上限です(bps)。下の「視聴者と入力の上限」を参照してください |
metadata | {} | 32 個まで。キーは 1〜64 文字、値は 1024 文字まで | 自由に付けられる追加の情報です(値は文字列だけ) |
desired_state | running | running・stopped | stopped の間は、配信を受け付けません。配信中に stopped にすると、配信者からの受信を止め、その時点で配信を終わらせます |
captions | null | 字幕と翻訳 を参照 | 音声認識と翻訳の設定です |
PATCH は送ったフィールドだけを置き換えます。ただし hls・recording・limits・metadata は中のオブジェクトごと置き換わるので注意してください。{"hls":{"segment_ms":1000}} だけを送ると、書かなかった window と container は既定値に戻ります。snapshots: null や captions: null を送れば、その設定を消せます。
次の例では、今の ETag を取ってきて If-Match に入れ、変更を送っています。
ETAG=$(curl -s -D - -o /dev/null "$API/streams/$SID" -H "Authorization: Bearer $TOKEN" | awk -F': ' 'tolower($1)=="etag"{print $2}' | tr -d '\r')
curl -s -X PATCH "$API/streams/$SID" "${H[@]}" -H "If-Match: $ETAG" \
-d '{"latency_profile":"low","hls":{"segment_ms":2000,"window":6,"container":"fmp4"}}'
hls と latency_profile の変更が効くのは、次に配信が始まってからです。配信中の HLS の分割処理は作り直しません。limits の変更はすぐに効きます。
視聴者と入力の上限(limits)
limits.max_viewers に達すると、新しく見に来た人を断ります。すでに見ている人はそのまま見続けられます。
数に入るのは、HLS の視聴者と、RTMP・SRT・RTSP での再生、WHEP、MoQ の接続です。録画、字幕、出力、パイプライン、クラスターの中継は数えません。HLS には接続という単位がないので、プレイリスト(index.m3u8・master.m3u8)を取りに来たクライアントを送信元の IP アドレスと User-Agent の組で見分け、最後に取ってから segment_ms の 3 倍(最低 10 秒)の間は見ているものとみなします。同じ NAT の内側にある同じブラウザーは 1 人と数えられます。
上限はノードごとです。クラスターでは、視聴者を受けたノードがそれぞれ上限まで受け付けます。断るときの応答はプロトコルによって違います。
| 方式 | 応答 |
|---|---|
| HLS・WHEP | 503(Retry-After: 5) |
| RTSP | 503 |
| RTMP play | NetStream.Play.Failed(too many viewers) |
| SRT play | 接続を拒否(SRT_REJX_OVERLOAD) |
| MoQ | REQUEST_ERROR(EXCESSIVE_LOAD) |
今の視聴者の数は、ストリームの live.viewers で見られます。
limits.max_ingress_bps は、配信者から受け取るビットレートを直近 3 秒の平均で監視します。数えるのはエンコード済みのデータ(映像・音声の AU)の大きさで、プロトコルのヘッダーは含みません。上限を超えた状態が 5 秒続くと配信者を切断し、ログに publisher disconnected: ingress bitrate exceeded limits.max_ingress_bps=… と出します。
切断から 10 秒間は新しい配信者を受け付けません(RTMP では NetStream.Publish.Rejected、ingress bitrate limit exceeded)。10 秒を過ぎてからの再接続は、再接続の猶予時間の中であれば同じ配信の続きになります。
RTMP / RTMPS で配信する
接続先は ingest.rtmp_url({public-rtmp-url}/live)、RTMP の stream name(ストリームキー)は stream_key です。
ffmpeg -re -i input.mp4 -c:v libx264 -g 60 -c:a aac -f flv "rtmp://media.example.com:1935/live/$KEY"
OBS なら「サーバー」に rtmp://media.example.com:1935/live、「ストリームキー」に sk_... を入れます。
application 名として受け付けるのは live、live で始まる名前、play で、それ以外は接続(connect)の段階で断ります。ストリームキーの ? から後ろは無視します。30 秒間何も届かなければ切断します。
配信を断るときの応答は 2 種類あります。キーの誤り、ストリームが stopped、すでに別の配信者がいる(後から来たほうを断ります。already publishing)場合は NetStream.Publish.BadName です。live の予約の時間帯の外(録画 を参照)と、ビットレートの上限で切断された直後は NetStream.Publish.Rejected です。
RTMPS: --rtmps ADDR と --tls-cert・--tls-key を指定すると、TLS で待ち受けます。URL は rtmps://{host}:{port}/live で、キーは RTMP と同じです。
ingest.rtmp_url は RTMP の公開 URL から作られるので、RTMPS の URL は配信者の側で組み立ててください。
対応コーデック(FLV・Enhanced RTMP v2):
| 種類 | 従来の FLV | Enhanced RTMP(FourCC) |
|---|---|---|
| 映像 | H.264(CodecID 7)、H.265(非公式の CodecID 12) | avc1(H.264)・hvc1(H.265)・av01(AV1)・vp09(VP9)・vp08(VP8) |
| 音声 | AAC(10)、G.711 A-law(7)・μ-law(8) | mp4a(AAC)・Opus |
Enhanced RTMP の Multitrack(1 つの接続で複数のトラックを送る機能)には対応しておらず、使うとエラーで切断します。MP3 のように表にない音声コーデックを送った場合も切断します。
SRT で配信する(listener)
--srt ADDR(UDP)を指定すると SRT で待ち受けます。SENCA が listener(待ち受ける側)、配信者が caller(接続しに行く側)です。
Stream ID には publish:{stream_key} か、SRT の Access Control 形式の #!::r={stream_key},m=publish を書きます。中身は MPEG-TS なので、コーデックは TS に入るもの(H.264・H.265・AAC・Opus)を選んでください。SENCA 側の受信遅延(latency)は 200 ms です。キーが誤っていれば接続の時点で断ります(X_FORBIDDEN)。
ffmpeg -re -i input.mp4 -c:v libx264 -g 60 -c:a aac -f mpegts \
"srt://media.example.com:9000?streamid=publish:$KEY"
暗号化(パスフレーズ)
テナントに srt-passphrase という名前のシークレットを作ると、そのテナントのストリームへの SRT 接続は、配信も再生もそのパスフレーズでの暗号化が必須になります。パスフレーズがない接続や違う接続は断り、暗号化なしに切り替えて通すことはしません。シークレットがなければ暗号化は使いません。長さは SRT の仕様で 10〜79 文字です。
シークレットを作ってから、パスフレーズ付きで配信する例です。
curl -s -X POST "$API/secrets" "${H[@]}" -d '{"name":"srt-passphrase","value":"correct-horse-battery"}'
ffmpeg -re -i input.mp4 -c copy -f mpegts \
"srt://media.example.com:9000?streamid=publish:$KEY&passphrase=correct-horse-battery"
シークレットを作るには tenant:admin の権限が要ります。なお、SENCA から外へつなぐ SRT(srt_pull の入力と srt_push の出力)のパスフレーズはこれとは別で、入力・出力の credential_ref で指定します(入力と出力 を参照)。
WHIP で配信する(WebRTC)
WHIP は、ブラウザーなどから WebRTC で配信するための HTTP の手順です。使うには --webrtc-udp ADDR を指定します。SENCA は ICE-lite(接続の候補を自分からは探さない簡易な方式)で動き、1 つの UDP ポートを全セッションで共有します。
0.0.0.0:ポート で待ち受けると、ノードのネットワークインターフェースのアドレスを ICE の候補として相手に伝えます。NAT や Docker の内側で動かしていて配信者から見えるアドレスが違うときは、--webrtc-public-ip でそのアドレスを指定してください(設定 を参照)。
| 操作 | リクエスト | 応答 |
|---|---|---|
| 開始 | POST /whip/{stream_id}、Authorization: Bearer {stream_key}(または publish 権限の API トークン)、Content-Type: application/sdp、本文は offer | 201、本文は answer(application/sdp)、Location: /whip/{stream_id}/{session} |
| trickle ICE・ICE restart | PATCH {Location}、Content-Type: application/trickle-ice-sdpfrag | 204、または 200 と sdpfrag(ICE restart) |
| 終了 | DELETE {Location} | 200 |
パスに入れるのはストリームの名前ではなく ID です。ingest.whip_url をそのまま使えば間違いません。
Authorization には、ストリームキーの代わりに、同じテナントで publish の権限を持つ API トークン(または JWT)を入れてもかまいません。配信の機材にストリームキーを渡したくないときに便利です(セキュリティ を参照)。
失敗したときは、キーやトークンの誤りなら 401、Content-Type の誤りなら 415、offer を受け付けられなければ 400 が返ります。--webrtc-udp を指定していないノードでは 404 です。
PATCH と DELETE は、推測できない 64 ビットのセッション ID だけを手がかりに受け付けます。Location は第三者に知られないようにしてください。CORS にも対応していて、OPTIONS に応答し、Location と ETag のヘッダーをブラウザーから読めるようにしてあります。
受け取れるコーデックは H.264(Constrained Baseline・High、packetization-mode 1)、VP8、VP9 profile 0、AV1、Opus、PCMU、PCMA です。サイマルキャスト(解像度の違う複数の映像を同時に送る方式)で送られてきた場合は、最初の映像だけを使います。HLS のセグメントを区切るため、SENCA は 2 秒ごとに配信者へキーフレームを要求します(PLI)。
ブラウザーから配信するためのページ(リポジトリの apps/sender-web)も用意してあり、--sender-dir を指定すると /sender/ で開けます。
curl -s -X POST "http://media.example.com:8080/whip/$SID" -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/sdp" --data-binary @offer.sdp -D headers.txt -o answer.sdp
キャプチャデバイスから取り込む
ノードにつないだカメラやキャプチャカードは、入力(protocol: "device")としてストリームに取り込みます。デバイスの SDK を読み込むのは senca-node ではなく別プロセスの senca-capture-worker で、取り込みとエンコードまでをそちらで済ませ、エンコード済みの AU だけを senca-node に渡します。senca-capture-worker は senca-node と同じディレクトリに置いてください。
どのデバイスが使えるかは、GET /api/v1/devices の応答の nodes[].capture でわかります(node:admin の権限が必要です)。
V4L2 のカメラと ALSA のマイクを入力に追加する例です。
curl -s -X POST "$API/streams/$SID/inputs" "${H[@]}" -d '{
"protocol": "device",
"url": "device:v4l2:/dev/video0",
"options": {"codec": "vp9", "width": "1280", "height": "720", "fps": "30", "bitrate_bps": "2500000",
"audio_device": "alsa:hw:1,0", "audio_codec": "opus"}
}'
| 項目 | 既定 | 説明 |
|---|---|---|
url | (必須) | device:{デバイス ID} の形で指定します(例: device:v4l2:/dev/video0・device:decklink:0)。300 文字までです |
options.codec | vp9 | 映像のエンコード形式です。H.264/H.265 を選ぶと、GPU でのエンコードが必要になります(GPU で同時に使えるセッションの数は、グリッドが管理します) |
options.width・height・fps | 1280・720・30 | 映像の幅・高さ・フレームレートです |
options.bitrate_bps | 2500000 | 映像のビットレート(bps)です |
options.audio_device | なし | 音声のデバイスです。指定したときだけ、音声も取り込みます |
options.audio_codec | opus | 音声のエンコード形式です |
options の値は、数値も含めてすべて文字列で書きます。ストリームが royalty_free なら、senca-capture-worker もそれに従ってエンコード形式を選びます。
キャプチャの入力は、グリッドのワークロードとして、そのデバイスを持つノードに割り当てられます(クラスターとグリッド を参照)。入力の状態や再接続の動きは、ほかの入力と変わりません(入力と出力 を参照)。手元に機材がないときは、--capture-synthetic を付けると試験用の仮想デバイス(device:synthetic:0)が使えます。
再生 URL(署名付き)
再生には /api/v1 の外にある URL を使います。Authorization ヘッダーは要らず、URL に含まれる有効期限と HMAC 署名で確かめます。発行には subscribe の権限が必要です。
有効期間 600 秒の再生 URL を発行する例です。
curl -s -X POST "$API/streams/$SID/playback-url" "${H[@]}" -d '{"ttl_secs":600}'
{
"expires_at": "2026-11-03T01:26:00Z",
"playback": {
"hls": "http://127.0.0.1:24080/s/1793669160/MGO1aub_Vkp8D2OwIHZjGw/live/189f0fe8.../index.m3u8",
"rtmp": "rtmp://127.0.0.1:24935/play/189f0fe8...?exp=1793669160&sig=MGO1aub_Vkp8D2OwIHZjGw",
"whep": "http://127.0.0.1:24080/whep/189f0fe8...?exp=1793669160&sig=MGO1aub_Vkp8D2OwIHZjGw",
"rtsp": "rtsp://127.0.0.1:24554/live/189f0fe8...?exp=1793669160&sig=MGO1aub_Vkp8D2OwIHZjGw"
}
}
本文は省略でき、そのときの ttl_secs は 300 秒です。指定できるのは 1 秒から --max-playback-ttl(既定 86400 秒)までです。
応答に入る URL はノードの設定で変わります。rtsp は --public-rtsp-url を指定したときだけ入り、whep は --webrtc-udp がないノードでは null です。moq(MoQ の WebTransport の URL。下の「MoQ と HTTP/3」を参照)は --quic を指定したノードでだけ入ります。
署名は、live/{stream_id}/{exp} に対する HMAC-SHA256 の先頭 16 バイトを base64url にしたものです。ファイル名は署名の対象外なので、同じ /s/{exp}/{sig}/live/{id}/ の下にあるファイル(セグメント、master.m3u8、字幕)にはどれも同じ署名が使えます。署名の鍵は設定と状態の一部としてクラスター全体で共有しているので、どのノードでも確かめられます。期限切れや署名の誤りには 403 を返します。
発行した URL は、期限が来るまで取り消せません。ttl_secs は短めにしておいてください(セキュリティ を参照)。
HLS
HLS は、映像を短いファイル(セグメント)に分けて HTTP で配信する方式です。たいていのプレイヤーで再生でき、ブラウザーでも hls.js などを使えば見られます。
| パス | 内容 |
|---|---|
/s/{exp}/{sig}/live/{id}/index.m3u8 | ライブのプレイリスト |
…/{seq}.ts・…/{seq}.m4s | セグメント(どちらになるかは hls.container で決まります) |
…/{seq}.{part}.m4s(.ts) | LL-HLS の part |
…/init{n}.mp4 | fMP4 の初期化セグメント(設定が変わるたびに番号が増えます) |
…/master.m3u8・sub_{lang}.m3u8・sub_{lang}_{n}.vtt | 字幕があるときのプレイリストと字幕のファイル(字幕と翻訳 を参照) |
セグメントはメモリから返し、ディスクには書きません。プレイリストの範囲から外れたセグメントも、4 本分はしばらく取得できます。キャッシュの指定は、プレイリストが Cache-Control: no-cache、セグメントが max-age=60 です。どの応答にも Access-Control-Allow-Origin: * が付きます。
セグメントの番号は、配信者が再接続しても続きから増え、EXT-X-MEDIA-SEQUENCE が戻ることはありません。配信が終わる(再接続の猶予時間が過ぎる)と、プレイリストに #EXT-X-ENDLIST が付きます。
HLS に出せるのは、コンテナーに入るコーデックだけです。TS なら H.264・H.265・AAC・Opus、fMP4 なら H.264・H.265・AV1・VP9・AAC・Opus です。
映像や音声が HLS に出てこないときは、POST /streams/{id}/plan で確かめられます。今の入力の各トラックが出力ごとにそのまま詰め替えられる(remux)か、扱えない(unsupported)かが、理由付きで返ります。ストリーム自体は変換をしないので、変換(transcode)が出ることはありません。royalty_free で捨てたトラックは、どの出力でも理由付きの unsupported になります。コーデックを変えたい場合は パイプライン を使ってください。
LL-HLS と遅延のプロファイル
LL-HLS(Low-Latency HLS)は、セグメントをさらに細かい part に分けて配信し、HLS の遅延を縮める仕組みです。
latency_profile を "low" にすると LL-HLS になります。part の長さの目標は min(500 ms, segment_ms / 4) で、200 ms より短くはしません。プレイリストには EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,CAN-SKIP-UNTIL=…,PART-HOLD-BACK=…、EXT-X-PART-INF、EXT-X-PART、EXT-X-PRELOAD-HINT が入ります。
"interactive" は、WebRTC(WHEP)や MoQ で見ることを前提に、あちこちの待ち時間をさらに詰めます。standard と low の動きには影響しません。low との違いは次のとおりです。
| 項目 | low | interactive |
|---|---|---|
| LL-HLS の part の長さの目標 | min(500 ms, segment_ms / 4) | 200 ms(PART-HOLD-BACK は 0.6 秒) |
| 映像と音声の片方しか届かないときに、HLS を出し始めるまで待つ時間 | 1 秒 | 0.25 秒 |
| RTMP play で、送信待ちのデータがこの量を超えたら次のキーフレームまで飛ばす | 8 MiB | 1 MiB |
| SRT(配信・再生)でノードが相手に伝える受信遅延 | 200 ms | 80 ms(実際の遅延は、両端の値の大きい方になります) |
どちらでもセグメントの長さ(hls.segment_ms)は変わりません。ノード間の中継はもともとデータをためずに送るので、プロファイルによる差はありません。
LL-HLS のプレイリストは、次のクエリを付けて取得できます。
| クエリ | 動作 |
|---|---|
_HLS_msn=N | セグメント N が完成するまで待ってから返します(ブロッキングリロード) |
_HLS_msn=N&_HLS_part=P | セグメント N の part P ができるまで待ちます |
_HLS_skip=YES(または v2) | 差分のプレイリストを返します(古いセグメントを EXT-X-SKIP で省略) |
待つのは最長 6 秒で、間に合わなければ 503(Retry-After: 1)を返します。配信が終わっていれば待たずに返します。作成中のセグメントより 2 つ以上先の番号や、100 以上の _HLS_part、数値でない値は 400 です。
プレイリストの例(fMP4・LL-HLS、segment_ms は 2000):
#EXTM3U
#EXT-X-VERSION:6
#EXT-X-TARGETDURATION:2
#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,CAN-SKIP-UNTIL=12.000,PART-HOLD-BACK=1.500
#EXT-X-PART-INF:PART-TARGET=0.500
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-MAP:URI="init0.mp4"
…
#EXT-X-PART:DURATION=0.467,URI="6.0.m4s",INDEPENDENT=YES
#EXT-X-PART:DURATION=0.500,URI="6.1.m4s"
#EXT-X-PRELOAD-HINT:TYPE=PART,URI="6.2.m4s"
試してみる: RTMP で配信して HLS で再生する
ここまでの内容を手元のノードで通して試す手順です。実際に動くことを確かめてあります。
# 1. ストリームを作成する
curl -s -X POST "$API/streams" "${H[@]}" -d '{"name":"cam1"}' > s.json
SID=$(python3 -c "import json;print(json.load(open('s.json'))['id'])")
KEY=$(python3 -c "import json;print(json.load(open('s.json'))['ingest']['stream_key'])")
# 2. テスト信号を RTMP で配信する(別の端末で実行)
ffmpeg -re -f lavfi -i testsrc2=size=640x360:rate=30 -f lavfi -i sine=frequency=440:sample_rate=48000 \
-c:v libx264 -g 60 -pix_fmt yuv420p -c:a aac -b:a 128k -f flv "rtmp://127.0.0.1:1935/live/$KEY"
# 3. 再生 URL を取得して再生する
HLS=$(curl -s -X POST "$API/streams/$SID/playback-url" "${H[@]}" | python3 -c "import json,sys;print(json.load(sys.stdin)['playback']['hls'])")
curl -s "$HLS" # #EXTM3U … 0.ts 1.ts …
ffplay "$HLS" # または ffprobe / ブラウザーの hls.js
セグメントはキーフレームの位置でしか区切れません。キーフレームの間隔は、-g 60(30 fps で 2 秒)のようにセグメントの長さに合わせてください。
RTMP play
署名付き URL の rtmp(rtmp://{host}/play/{stream_id}?exp=..&sig=..)をそのまま使います。application 名は play、stream name は {stream_id}?exp=..&sig=.. です。署名の誤りや期限切れには NetStream.Play.StreamNotFound を返します。
ffplay "rtmp://127.0.0.1:1935/play/$SID?exp=$EXP&sig=$SIG"
送る形式は FLV(Enhanced RTMP を含む)です。受信の遅い視聴者には、送信待ちのデータが 8 MiB を超えた時点で次のキーフレームまで飛ばします。
SRT play
Stream ID は play:{stream_id}?exp={exp}&sig={sig} か #!::r={stream_id}?exp=..&sig=..,m=request で、署名は再生 URL と同じものです。SENCA は MPEG-TS を 1316 バイト単位で送ります。テナントに srt-passphrase があればパスフレーズも必要です。
URL の streamid= に書くときは、値の中の ?・&・= をパーセントエンコードしてください。そのままだと値が & で切れて署名が届かず、接続を断られます。
ffplay "srt://127.0.0.1:9000?streamid=play:$SID%3Fexp%3D$EXP%26sig%3D$SIG"
EXP と SIG は、再生 URL の rtmp などのクエリから取り出してください。AV1・VP8・VP9 は TS に入らないので、SRT では再生できません。
RTSP play
--rtsp ADDR で待ち受けを始め、--public-rtsp-url を設定すると、再生 URL に rtsp(rtsp://{host}:{port}/live/{stream_id}?exp={exp}&sig={sig})が入ります。
転送方式は TCP interleaved(RTSP の TCP 接続の中でメディアも送る方式)だけです。UDP での SETUP は 461 で断るので、プレイヤー側で TCP を指定してください(ffmpeg なら -rtsp_transport tcp)。署名が誤っていれば DESCRIBE に 404、配信者がいないときや RTP で送れるトラックがないときは 415 を返します。
ffplay -rtsp_transport tcp "rtsp://127.0.0.1:8554/live/$SID?exp=$EXP&sig=$SIG"
WHEP
WHEP は WebRTC で視聴するための HTTP の手順で、WHIP の視聴版にあたります。whep の URL に Content-Type: application/sdp で offer を POST すると、201 と answer、Location: /whep/{stream_id}/{session} が返ります。PATCH と DELETE の使い方は WHIP と同じです。
AAC の音声は WebRTC で送れないので、WHEP には出ません。 SENCA は特許への対応方針から AAC をデコードしないため、Opus に変換することもできません。WHEP で音声も届けたいなら、配信者の側で Opus を使ってください。SDP で提示するのは、ストリームがいま持っているコーデック(H.264・VP8・VP9・AV1・Opus・PCMA・PCMU)だけで、送れるトラックが 1 つもなければ 422 を返します。
署名の誤りや期限切れには 403 を返します。接続が確立するまでは直近の GOP を手元に残しておき、つながったらキーフレームから送ります。
MoQ と HTTP/3
MoQ(Media over QUIC)は、QUIC の上でライブのメディアを配信するための新しい方式で、いま標準化が進んでいます。既定では無効です。--quic ADDR と --tls-cert・--tls-key を指定すると、1 つの UDP ポートで QUIC v1 を受け付け、接続ごとに ALPN(TLS の中でプロトコルを取り決める仕組み)でどのプロトコルを話すかが決まります。
| ALPN | プロトコル |
|---|---|
h3 | HTTP/3 です。TCP の HTTP と同じパスが使えます(/api/v1、LL-HLS のブロッキングリロードを含む署名付き HLS、WHIP/WHEP、/healthz など)。SSE(/api/v1/events)と /signaling(WebSocket)は 501 を返すので、これらには HTTP/1.1 を使ってください。同じ接続で /moq への拡張 CONNECT を送ると、WebTransport の上の MoQ になります |
moqt-22 | QUIC の上で直接動く MoQ です(draft-ietf-moq-transport-22) |
--quic を指定すると、HTTP・HTTPS の応答に Alt-Svc: h3=":ポート"; ma=86400 が付き、これを受け取ったブラウザーは次のリクエストから HTTP/3 に切り替えられます。証明書は TCP の HTTPS と同じ --tls-cert と --tls-key です。
MoQ の仕様はまだ Internet-Draft(標準になる前の草案)なので、バージョンは draft-22 に固定しています。ほかのバージョンのクライアントは、ALPN か wt-available-protocols での取り決めの段階で断ります。
購読(MoQ で視聴する)
配信中のストリームは、どれも namespace senca/live/{stream_id} で購読できます。別のノードが所有しているストリームなら、中継で受け取った複製から配信します。購読できる track は次の 2 種類です。
| track | 内容 |
|---|---|
catalog.json | hang 形式の catalog(moq-dev/moq と同じ形式)で、track の一覧と中身の説明です。更新するたびに 1 group・1 object で送ります。video・audio の renditions に、codec(RFC 6381)、description(avcC などを 16 進で表したもの)、container: {"kind":"cmaf","init": base64(ftyp+moov)} が入ります |
video・audio(2 本目からは video2 など) | CMAF 形式です。1 object が 1 fragment(moof+mdat、1 サンプル)です。映像は GOP ごとに、音声はおよそ 1 秒ごとに新しい group になります |
購読の可否は、再生 URL と同じ署名(exp・sig)で確かめます。署名の渡し方は 3 通りあります。
- MoQ の AUTHORIZATION TOKEN(SETUP か SUBSCRIBE)の値に
exp=..&sig=..を入れる - QUIC で直接つなぐ場合は、PATH に入れる(
moqt://host:port/?exp=..&sig=..) - WebTransport の場合は、URL のクエリに入れる
署名がない、誤っている、期限が切れている SUBSCRIBE・FETCH・TRACK_STATUS は UNAUTHORIZED で断ります。SUBSCRIBE_NAMESPACE には、署名が有効なストリームだけを返します。--quic を指定したノードでは、再生 URL の moq に WebTransport の URL(https://{host}:{port}/moq?exp=..&sig=..)が入ります。host は --public-http-url のものです。
途中から購読すると、今の group の先頭(キーフレーム)から届きます。draft-22 の FILL_PARAMETERS(current-group join)が指定されていれば、group の先頭から今までの分を fill の stream で送り、続きを購読の中で送ります。直近の 2 group は FETCH でも取得できます。
moq-dev/moq のコマンドで購読し、fMP4 のファイルに書き出す例です。
Q="exp=1793669160&sig=MGO1aub_Vkp8D2OwIHZjGw" # 再生 URL の moq のクエリ
moq --connect "moqt://media.example.com:4433/?$Q" --connect-version moq-transport-22 \
--broadcast "senca/live/$SID" export fmp4 > live.mp4
配信(MoQ で送信する)
ストリームキー(sk_...)を AUTHORIZATION TOKEN(SETUP か PUBLISH_NAMESPACE)に入れて、PUBLISH_NAMESPACE senca/live/{stream_id} を送ります。するとノードのほうから catalog.json と、そこに書かれた track を購読しに行き、そのストリームの配信者として扱います(live.transport は moq になります)。
受け付けるコンテナーは cmaf(init と fragment)だけで、hang の legacy や loc の track が混じっていればセッションごと閉じます。moq-dev の moq import は legacy で出すので、そのままでは配信できません。配信者の側から始める PUBLISH にも対応していません(NOT_SUPPORTED)。1 つのセッションで配信できるのは 1 ストリームです。「live」の予約の時間帯の外では、ほかの配信方法と同じく断ります。
静止画
配信中の映像から静止画を取り出したり、一定の間隔で保存したりできます。
| 操作 | リクエスト | 権限 | 結果 |
|---|---|---|---|
| 今の画像を取得 | GET /streams/{id}/snapshot[?format=jpeg|png] | stream:read | 200 image/jpeg(既定)または image/png |
| 保存済みの一覧 | GET /streams/{id}/snapshots | stream:read | {"items":[{id, taken_at, reason, width, height, bytes}]}(古い順) |
| 1 枚を取得 | GET /streams/{id}/snapshots/{snapshot_id} | stream:read | JPEG |
| 削除 | DELETE /streams/{id}/snapshots/{snapshot_id} | stream:write | 204 |
静止画は、直近の GOP を senca-codec-worker でデコードして作ります。そのため H.264・H.265 では GPU のデコーダーが必要で、ないと 503 CAPACITY_UNAVAILABLE(retryable: false)になります。AV1(dav1d)と VP8/VP9(libvpx)はソフトウェアでデコードできます(コーデックと特許 を参照)。
取得に失敗したときの応答は次のとおりです。
| 状況 | 応答 |
|---|---|
| 配信中でない | 409 |
| キーフレームが 5 秒待っても届かない | 504(INPUT_TIMEOUT、再試行できます) |
| クラスターで、配信を別のノードが受けている | 409(OWNERSHIP_LOST)。details.owner_api のノードに要求し直してください |
| E2EE のルームからエクスポートしたストリーム(メディアパイプライン を参照) | 422(中身を見られないため) |
保存された静止画には理由(reason)が付きます。ストリームの snapshots の設定で定期的に保存したものは interval で、keep 枚を超えると古いものから消えます。snapshot の予約で保存したものは schedule です(録画 を参照)。パイプラインの snapshot_on_change による静止画(board_change)は、ストリームではなくパイプラインの側に保存されます(GET /pipelines/{id}/snapshots。パイプライン を参照)。
保存先は {data-dir}/snapshots/{stream_id}/ で、ストリームを削除すると一緒に消えます。録画のサムネイルは 録画 で説明しています。
今の画像を保存し、続けて 60 秒ごとに保存して 1440 枚(ちょうど 1 日分)まで残す設定にする例です。
curl -s -o now.jpg "$API/streams/$SID/snapshot" -H "Authorization: Bearer $TOKEN"
curl -s -X PATCH "$API/streams/$SID" "${H[@]}" -H "If-Match: $ETAG" -d '{"snapshots":{"interval_secs":60,"keep":1440}}'
配信者の接続が切れたとき・入れ替わったとき
配信者の接続が切れても、--reconnect-grace(既定 30000 ms)の間は配信を終わらせません。その間に同じキーで再接続すれば、epoch が 1 増え、同じ配信の続きになります。HLS は EXT-X-DISCONTINUITY で区切られ、セグメントの番号は続きから増えます。録画も 1 本のまま続きます(録画 を参照)。
再接続はプロトコルが変わってもかまいません。RTMP で切れたあと SRT で再接続しても、同じ配信として続きます。
猶予時間が過ぎると配信は終わります。HLS のプレイリストに ENDLIST が付き、RTMP/SRT/RTSP/WHEP の視聴者の接続が閉じられ、録画が確定します。猶予を待たずに終わらせたいときは、desired_state を stopped にしてください。配信者からの受信がすぐに止まります。
どのノードからでも再生できる(クラスター)
クラスターでは、配信者の接続を受けたノードがそのストリームの所有者になります。所有者でないノードに再生のリクエスト(HLS、RTMP/SRT/RTSP での再生、WHEP)が来ると、そのノードは所有者から中継で AU を受け取って手元に複製を作り、そこから配信します。視聴者が何人いても、上流からの転送は 1 本です。
最初の HLS のリクエストは、複製の最初のプレイリストができるまで最長 6 秒待たされます。使われなくなった複製は 30 秒後に消します。中継が使えないときは、所有者のノード(media_addr)へ 307 でリダイレクトします。
録画と字幕の作成は所有者のノードだけが行います。字幕のファイル(master.m3u8・sub_{lang}.m3u8・sub_{lang}_{n}.vtt)は複製側のノードが所有者から取ってきて返すので、どのノードの再生 URL でも字幕は見られます。取ってきたファイルは、プレイリストなら 1 秒、字幕のセグメントなら 30 秒使い回します。
詳しくは クラスターとグリッド を参照してください。
よくあるトラブル
| 症状 | 考えられる原因 |
|---|---|
| RTMP の配信が拒否される | BadName の場合は、キーの誤り、すでに別の配信者がいる、ストリームが stopped のどれかです。Rejected の場合は、live の予約の時間帯の外か、ビットレートの上限で切断された直後です |
| 配信が数秒で切断される | 配信のビットレートが limits.max_ingress_bps を超え続けた(ログに ingress bitrate exceeded と出ます) |
再生が 503 になる、RTMP play が NetStream.Play.Failed になる | 視聴者の数が limits.max_viewers に達している(live.viewers で確認できます) |
| SRT の接続がすぐに拒否される | Stream ID の書き方の誤り、キーの誤り、またはテナントに srt-passphrase があるのにパスフレーズを指定していない(ログに unsecure と出ます) |
| SRT play が拒否される | streamid の値をパーセントエンコードしていない |
| HLS が 404 になる | 最初のセグメントがまだできていない(キーフレームの間隔が長い)、または URL のホストが誤っている(--public-http-url を確認) |
| HLS に映像や音声が出ない | コーデックがコンテナーに入れられない(POST /streams/{id}/plan で確認)、または royalty_free のために H.264/AAC が捨てられた |
| 静止画が 503 になる | H.264/H.265 なのに GPU がない、または senca-codec-worker が起動していない |
| WHEP で音声が出ない | 音声が AAC になっている |
関連するページ
この文書の原文は、リポジトリの docs/guide/ja/04-ingest-playback.md にあります。内容は現在の版(v0.0.1)に合わせています。