取り込みと再生

エンコーダー・カメラ・ブラウザーから映像・音声を取り込む方法(RTMP・SRT・WHIP・キャプチャデバイス)と、視聴者へ届ける方法(HLS・LL-HLS・RTMP・SRT・RTSP・WHEP・MoQ)を説明します。

配信と録画

このページの見出し
  1. 全体の流れ
  2. ストリームを作成する
  3. ストリームのフィールド
  4. RTMP / RTMPS で配信する
  5. SRT で配信する(listener)
  6. WHIP で配信する(WebRTC)
  7. キャプチャデバイスから取り込む
  8. 再生 URL(署名付き)
  9. HLS
  10. 試してみる: RTMP で配信して HLS で再生する
  11. RTMP play
  12. SRT play
  13. RTSP play
  14. WHEP
  15. MoQ と HTTP/3
  16. 静止画
  17. 配信者の接続が切れたとき・入れ替わったとき
  18. どのノードからでも再生できる(クラスター)
  19. よくあるトラブル
  20. 関連するページ

エンコーダー・カメラ・ブラウザーから映像と音声を受け取り、視聴者へ届けるまでの設定をまとめたページです。どのプロトコルで受けてどれで配信するか、遅延をどこまで詰めるかを決めるときに使ってください。

全体の流れ

ストリームは配信 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 playplay:{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_profilestandardstandard・low・interactive遅延の目標です。low にすると、LL-HLS の part(セグメントをさらに細かく分けたもの)を出します。interactive は WebRTC や MoQ で見ることを前提に、さらに遅延を小さくします(下の「HLS」を参照)
codec_profileanyany・royalty_freeroyalty_free にすると、特許プールのあるコーデック(H.264・H.265・AAC)のトラックを捨てます。このとき hls.container は fmp4 にする必要があります(コーデックと特許 を参照)
hls.segment_ms2000500〜10000セグメントの長さの目標です(ミリ秒)。実際には、キーフレームの位置で区切ります
hls.window63〜60ライブのプレイリストに含めるセグメントの数です
hls.containertsts・fmp4セグメントのコンテナー形式です。録画も同じ形式になります
recording.enabledfalsetrue にすると、配信が始まったときに自動で録画します(録画 を参照)
recording.retention_daysnull1〜36500自動で録画したものを残しておく日数です。null は無期限です
snapshotsnullinterval_secs 1〜86400、keep 1〜10000一定の間隔で静止画を保存します(下の「静止画」を参照)
limits.max_viewersnull0 以上同時に見られる視聴者の数の上限です(ノードごと)。下の「視聴者と入力の上限」を参照してください
limits.max_ingress_bpsnull0 以上配信者から受け取るビットレートの上限です(bps)。下の「視聴者と入力の上限」を参照してください
metadata{}32 個まで。キーは 1〜64 文字、値は 1024 文字まで自由に付けられる追加の情報です(値は文字列だけ)
desired_staterunningrunning・stoppedstopped の間は、配信を受け付けません。配信中に stopped にすると、配信者からの受信を止め、その時点で配信を終わらせます
captionsnull字幕と翻訳 を参照音声認識と翻訳の設定です

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・WHEP503(Retry-After: 5)
RTSP503
RTMP playNetStream.Play.Failed(too many viewers)
SRT play接続を拒否(SRT_REJX_OVERLOAD)
MoQREQUEST_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):

種類従来の FLVEnhanced 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、本文は offer201、本文は answer(application/sdp)、Location: /whip/{stream_id}/{session}
trickle ICE・ICE restartPATCH {Location}、Content-Type: application/trickle-ice-sdpfrag204、または 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.codecvp9映像のエンコード形式です。H.264/H.265 を選ぶと、GPU でのエンコードが必要になります(GPU で同時に使えるセッションの数は、グリッドが管理します)
options.width・height・fps1280・720・30映像の幅・高さ・フレームレートです
options.bitrate_bps2500000映像のビットレート(bps)です
options.audio_deviceなし音声のデバイスです。指定したときだけ、音声も取り込みます
options.audio_codecopus音声のエンコード形式です

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}.mp4fMP4 の初期化セグメント(設定が変わるたびに番号が増えます)
…/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 との違いは次のとおりです。

項目lowinteractive
LL-HLS の part の長さの目標min(500 ms, segment_ms / 4)200 ms(PART-HOLD-BACK は 0.6 秒)
映像と音声の片方しか届かないときに、HLS を出し始めるまで待つ時間1 秒0.25 秒
RTMP play で、送信待ちのデータがこの量を超えたら次のキーフレームまで飛ばす8 MiB1 MiB
SRT(配信・再生)でノードが相手に伝える受信遅延200 ms80 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プロトコル
h3HTTP/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-22QUIC の上で直接動く 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.jsonhang 形式の 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:read200 image/jpeg(既定)または image/png
保存済みの一覧GET /streams/{id}/snapshotsstream:read{"items":[{id, taken_at, reason, width, height, bytes}]}(古い順)
1 枚を取得GET /streams/{id}/snapshots/{snapshot_id}stream:readJPEG
削除DELETE /streams/{id}/snapshots/{snapshot_id}stream:write204

静止画は、直近の 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)に合わせています。