Func::main

RTMPとは

正式名称はReal Time Messaging Protocol1である。 主に以下の特徴を持つ。

  • TCP/IP上で通信を行う。
  • サーバのインスタンスひとつに対し、データの送受信双方にクライアントが存在する。つまり、一般的なPublisher-Subscriberモデルの実装のひとつである。2
  • あらゆるメッセージフォーマットが当時のサーバ、プレイヤーおよびActionScriptの中で利用する前提で定義された。
  • 同様に、実際に送受信されるデータもまた、FLV(Flash Video)のコンテナフォーマットに格納した上で処理される。

RTMPによる通信を実装された製品

2025年現在、RTMPによる通信を実装された製品は、オープンソースの範疇では主に以下の3つを確認できる。

  • FFmpeg3
  • OBS (Open Broadcaster Software)4
  • Red55

FFmpeg

多様なコーデックに対応するメディアコンバータである。 以下の要素に対応する。

  • 音声端末および映像端末からの入力
  • RTMPを含む通信プロトコルを介するデータの送受信
  • C言語による実装
  • 任意のメディアファイルから他のメディアファイルへのコーデック変換

OBS

ライブ配信サービスへの音声/映像の送信を主な機能とするRTMPクライアント製品である。 以下の要素に対応する。

  • 音声端末および映像端末からの入力
  • RTMPを含む通信プロトコルを介するデータの送信
  • C言語およびC++言語による実装

Red5

多様な機能を備えるRTMP通信向けのクライアント製品およびサーバ製品である。 以下の要素に対応する。

  • 音声端末および映像端末との入出力
  • RTMPを含む通信プロトコルを介するデータの送受信
  • Java言語による実装
  • 複数のクライアントからの要求に備える負荷分散
  • リモート環境ないしクラウド環境へのデプロイメント

太字はPro版で実装される機能。

通信の流れ

RTMPでは、サーバもクライアント(送受信側双方)も以下の手順を踏む必要がある。

RTMP の主な処理の流れ

  1. サーバとクライアントの両者はハンドシェイクを行い、相互に存在を確認する。 この時、SHA-256によるダイジェストを利用したメッセージ認証を同時に行うことがある。
  2. 両者はデータの送受信に際して必要な設定情報(通信帯域の幅、送受信に利用するファイル名等)を交換する。
  3. 相互確認と設定情報の交換が完了した後、実際のデータの送受信を開始する。

ハンドシェイク

RTMPでは、TCP/IPによる3ウェイ・ハンドシェイクに加えて、当該プロトコル独自のハンドシェイクも行う。 ハンドシェイクにはそれぞれ以下のチャンクが要求される。

  1. C0/S0 (ハンドシェイクのバージョン指定)
  2. C1/S1およびC2/S2 (実際のハンドシェイクデータ)

C0/S0

フィールド名長さ(バイト単位)内容参考画像
バージョン1ハンドシェイクのバージョン
当該チャンクで指定された値により、ハンドシェイク全体の暗号化の有無およびその方式が決定される。具体的には、以下のバージョンに対応する。
  • 3: 暗号化なし
  • 6: ディフィー・ヘルマン鍵交換
  • 8: XTEA
  • 9: Blowfish
ハンドシェイクのバージョンを指定するチャンク。1バイトの領域のみから成る。

なお、当該チャンクで指定するバージョンは、クライアントとサーバの双方で一致していることが期待されることがある。

C1/S1およびC2/S2

フィールド長さ(バイト単位)内容
タイムスタンプ4ハンドシェイク送信時点の時刻(ミリ秒単位)
バージョン4サーバまたはプレイヤーのバージョン
ランダムデータ1528通信相手の識別に用いられるランダムデータ

参考画像

実際のハンドシェイクデータ。4バイトのタイムスタンプ、4バイトのバージョン書き込み領域および1528バイトのランダムデータ書き込み領域から成る。

ハンドシェイクの手順

RTMPでは上記の構造のチャンクを以下の手順で相互に交換する。

ハンドシェイクの通信手順

  1. クライアントはサーバに対してチャンクの暗号化方式(C0)とハンドシェイクデータ(C1)を送信する。
  2. サーバも同様にクライアントにチャンクの暗号化方式(S0)とハンドシェイクデータ(S1)を送信する。
    同時に、サーバはクライアントにC1を返送する(S2)。
  3. クライアントも同様に、サーバに対してS1を返送する(C2)。

メッセージ認証

サーバのバージョン3以降およびプレイヤーのバージョン9以降から、双方はHMAC(SHA-256)によるハンドシェイクデータへの署名と認証をサポートされた。 メッセージ認証の要不要は、ハンドシェイクデータのバージョン指定領域に書き込まれるチャンク送信時点でのサーバあるいはプレイヤーのバージョンを確認することで判断する。 参考までに、双方の最終的なバージョン番号を以下に記す。

  • サーバ: 5.0.17
  • プレイヤー: 32.0.0.465

なお、現存する各RTMP対応製品はメジャーバージョン(先頭1バイト分)のみをチェックする仕様であることが多いため、その後に続くバージョン番号が1バイト以内に収まらなくても問題ない(切り捨ててもよい)。

ダイジェストの書き込み

簡単のため、ハンドシェイクデータを HH と置き、添え字は HH の各位置にバイト単位で一致するものとする。 そして、暗号化の有無に応じて、ダイジェストの書き込み開始位置 PP をそれぞれ以下の式で計算する。

  • 暗号化なしの場合
    P=i=03H8+imod728+12P = \displaystyle\sum_{i=0}^3 H_{8+i} \mod 728 + 12

  • 暗号化ありの場合
    P=i=03H772+imod728+776P = \displaystyle\sum_{i=0}^3 H_{772+i} \mod 728 + 776

PP から続く32バイト分の領域がダイジェストの書き込み範囲となるので、当該領域を除くハンドシェイクデータ全体の値をダイジェストの原文とする。 ここで、双方がダイジェストの生成に際して必要になる鍵の内容はそれぞれ以下の通りである。

  • サーバ : Genuine Adobe Flash Media Server 001
  • クライアント: Genuine Adobe Flash Player 001

上記の鍵を用いて得られたダイジェストを当該領域に書き込んだ上で、相手側にハンドシェイクデータを送信する。

署名の書き込み

以下の手順で署名データを書き込む。

  1. ハンドシェイクデータに書き込まれるダイジェストを原文として、署名鍵を作成する。
    ここで、双方が署名鍵の生成に際して必要になる追加の鍵の内容はそれぞれ以下の通りである。
    • サーバ : Genuine Adobe Flash Media Server 001 + 共通のバイト列
    • クライアント : Genuine Adobe Flash Player 001 + 共通のバイト列
    • 共通のバイト列: F0EEC24A8068BEE82E00D0D1029E7E576EEC5D2D29806FAB93B8E636CFEB31AE
  2. 末尾32バイト分の領域を除くハンドシェイクデータ全体を原文として、手順1で作成した署名鍵を用いて署名を作成する。
  3. 手順2で作成した署名を末尾32バイト分の領域に書き込んだ上で、相手側にハンドシェイクデータを返送する。

ダイジェストや鍵の検証については、それぞれを上記の手順で生成した上で書き込み範囲のデータと比較すればよいだけなので割愛する。

製品の振る舞いに注意

サーバとプレイヤーはそれぞれ上述のバージョン以降にメッセージ認証をサポートされたにも関わらず、現存のRTMP対応製品の中にはその変化に十分に追従しなかったものが存在する。 2025年時点で筆者が確認済みの対応状況を以下に記す。

製品名ダイジェストへの対応署名への対応備考
OBS 非対応 非対応バージョン列は0で固定6
FFmpeg 対応済み 非対応バージョン列に書き込まれる値はメッセージ認証対応済みのものである(にも関わらず、何故か署名を書き込まない)。7
Red5 対応済み 対応済み先頭1バイトには確かに9以上(クライアント側)あるいは3以上(サーバ側)の数値が書き込まれているが、その数値の根拠は不明である。8

設定交換

ハンドシェイクの後、サーバおよびクライアントの双方はデータの送受信に必要な設定情報を交換する。 以降、以下の形式に従うチャンクを用いてデータの送受信を行う。

  • 基本ヘッダ
  • メッセージヘッダ
  • 拡張タイムスタンプ(任意)
  • メッセージデータ

基本ヘッダ

フィールド名長さ(ビット単位)内容
メッセージヘッダの形式2送信されるメッセージヘッダのパターン(0以上3以下)
チャンクID6以上チャンクの分類を示すID
ただし、01は除く(後述)。

基本ヘッダはチャンクIDの大きさによって3パターンに分岐される。 各パターンは1バイト目のチャンクID領域に2未満の値が書き込まれているかどうかで判断する。

そして、基本ヘッダの読み書きの際には以下の点に注意が必要である。

  • チャンクIDの読み書きの際には、バイト順(エンディアン)を考慮する。
  • 2バイトパターンおよび3バイトパターンのチャンクIDは読み書きの際に64だけ増減させる
    (1バイト目のチャンクID領域はパターンの区別に用いられるため)。

ここで、基本ヘッダの各パターンの仕様を以下に記す。

基本ヘッダのパターン1バイト目のチャンクID領域の値チャンクIDの範囲チャンクIDのバイト順参考画像
1バイト2以上2以上
63以下
ビッグエンディアン1バイトパターンの基本ヘッダ。2ビットのメッセージヘッダの形式番号と6ビットのチャンクIDから成る。
2バイト064以上
320以下
ビッグエンディアン2バイトパターンの基本ヘッダ。2ビットのメッセージヘッダの形式番号と1バイトのチャンクIDから成る。形式番号直後の6ビットは0で置き換えられる。
3バイト164以上
65599以下
リトルエンディアン3バイトパターンの基本ヘッダ。2ビットのメッセージヘッダの形式番号と2バイトのチャンクIDから成る。形式番号直後の6ビットは1で置き換えられる。

メッセージヘッダ

フィールド名長さ(バイト単位)内容
タイムスタンプ3チャンク送信時点の時刻(ミリ秒単位)
メッセージ長3メッセージデータの長さ
メッセージ型ID1メッセージデータの種類を示す番号
メッセージID4クライアントに対して割り当てるID

メッセージヘッダは基本ヘッダの先頭2ビットに書き込まれる値によって、以下の4パターンに分岐される。

形式長さ(バイト単位)フィールド参考画像
011
  • タイムスタンプ
  • メッセージ長
  • メッセージ型ID
  • メッセージID
形式0のメッセージヘッダ。3バイトのタイムスタンプ、3バイトのメッセージ長、1バイトのメッセージ型IDおよび4バイトのメッセージIDから成る。
17
  • タイムスタンプ
  • メッセージ長
  • メッセージ型ID
形式1のメッセージヘッダ。3バイトのタイムスタンプ、3バイトのメッセージ長および1バイトのメッセージ型IDから成る。
23
  • タイムスタンプ
形式2のメッセージヘッダ。3バイトのタイムスタンプのみから成る。
30ヘッダなし

そして、メッセージヘッダの読み書きの際には、以下の点に注意が必要である。

  • どの形式のメッセージヘッダが必要になるかを決定するためには、直前に送信した基本ヘッダおよびメッセージヘッダの状態を記憶しておく必要がある。
  • メッセージIDの読み書きのみリトルエンディアンである。

拡張タイムスタンプ

フィールド名長さ(バイト単位)内容参考画像
タイムスタンプ4チャンク送信時点の時刻(ミリ秒単位)拡張タイムスタンプ。4バイトの時刻書き込み領域から成る。

メッセージヘッダに書き込むタイムスタンプ値が3バイトの範囲を超える場合、タイムスタンプフィールドを符号なし3バイト整数の最大値に置き換える代わりに、拡張タイムスタンプ領域に実際のタイムスタンプ値を書き込む。 タイムスタンプ値が3バイトの範囲を超えない場合は、このフィールドは書き込まれない点に注意。

メッセージデータ

基本的に、当該ステップでは以下の2種類の型のメッセージが送受信される。

  • コマンド(メッセージ型ID: 20)
  • ユーザ制御(メッセージ型ID: 4)
コマンド

コマンドを用いる設定交換の際にはAction Message Format version 0(ActionScript向けに定義されたメッセージ形式。以下、AMF0)9による変換が必要である。現在設定交換中に用られる形式を以下に記す。

AMF0型マーカ型名形式
0数値IEEE 754形式の浮動小数点数
1真偽値8ビット整数値の範囲内で表現される真偽値(現状は0を偽とされる実装が主)
2文字列UTF-8でエンコードされる文字列
実際の文字列の前に文字列長(2バイト)を書き込まれる。
3オブジェクト長さ不定のKey-Valueの組(ECMAScriptにおけるオブジェクトとほぼ同義)
8ECMA風配列長さ固定のKey-Valueの組(ECMAScriptにおける配列とほぼ同義)
実際の配列の前に配列長(4バイト)が書き込まれる。

クライアントはサーバに対して以下の形式のコマンド要求を送信する。

フィールド名AMF0データ型設定内容
コマンド名文字列送信するコマンドの名前
トランザクションID数値現在処理されているコマンドの順序を示すID
仕様上、1から始めることになっているが、場合によっては0を設定する必要がある点に注意。
設定データ不定各コマンドのメッセージ交換の際に必要になる設定情報
詳細は後述。

対して、サーバはコマンドメッセージを受信した際、応答メッセージを送信する必要がある。 応答メッセージはクライアントから受信したコマンドの種類に応じて設定内容に差異がある。 ここで、現在使用されているコマンドの分類および対応するコマンド名を以下に記す。

コマンドの分類対応するコマンド備考
NetConnection
  • connect
  • releaseStream
  • createStream
  • getStreamLength
releaseStreamおよびgetStreamLengthは仕様に定義されていない。
NetStream
  • publish
  • play
未定義
  • FCPublish
  • FCSubscribe
  • set_playlist
左記のいずれも仕様に定義されていない。

そして、コマンドの分類に応じた応答メッセージの形式を以下に記す。

NetConnection
フィールドAMF0データ型設定内容
コマンド名文字列_resultもしくは_error
トランザクションID数値受信したコマンドのものと同等のID
設定データ不定各コマンドの処理結果を伝達するためのデータ
NetStream
フィールドAMF0データ型設定内容
コマンド名文字列onStatus
トランザクションID数値0
設定データオブジェクト各コマンドの処理結果を伝達するためのデータ
未定義
  • FCPublish
フィールドAMF0データ型設定内容
コマンド名文字列onFCPublish
  • FCSubScribe
フィールドAMF0データ型設定内容
コマンド名文字列onFCPublish
  • set_playlist
フィールドAMF0データ型設定内容
コマンド名文字列playlist_ready

上記各コマンドはトランザクションIDや応答情報の書き込みを行わない

ユーザ制御

ユーザ制御のメッセージデータの形式は以下の通りである。

フィールド長さ(バイト単位)設定内容
イベント型2ユーザ制御メッセージの型マーカ
イベントデータ不定通信相手に伝達する追加の設定内容

通信手順

クライアントがデータの送信側か受信側のどちらであるかによって、通信内容に差異がある。

送信側

送信側クライアントはサーバとの間で以下のメッセージを交換する。

  1. connect
  2. releaseStream
  3. FCPublish
  4. createStream
  5. publish
受信側

一方、受信側クライアントは以下のメッセージを交換する。

  1. connect
  2. ウィンドウ通知サイズ(サーバ帯域幅)
  3. createStream
  4. FCSubscribe
  5. getStreamLength (FFmpegの場合)/set_playlist (OBSの場合)
  6. play
  7. バッファ長設定
connect
設定データAMF0データ型設定内容
コマンドオブジェクトオブジェクトサーバとの間で必要になる基本的な設定情報

connectコマンドは送信側と受信側とで以下のように設定内容が異なる。

送信側
キー名AMF0データ型設定内容
app文字列サーバのインスタンスの名前
type文字列nonprivate
flashVer文字列FLVフォーマットへのエンコーダのバージョン
tcUrl文字列クライアント起動時に入力した接続先URL
受信側
キー名AMF0データ型設定内容
app文字列サーバのインスタンスの名前
flashVer文字列プレイヤーのバージョン
tcUrl文字列クライアント起動時に入力した接続先URL
fpad真偽値プロキシを介するかどうか。
capabilities数値15
audioCodecs数値プレイヤーが対応する音声コーデックの種類
videoCodecs数値プレイヤーが対応する映像コーデックの種類
videoFunction数値0もしくは1(プレイヤーが正確なフレームの移動に対応するか。)
応答内容
設定データAMF0データ型設定内容
プロパティオブジェクトサーバのバージョン等
応答情報オブジェクト応答情報の設定内容を参照。
releaseStream
設定データAMF0データ型設定内容
パス文字列サーバにデータを保存させる際のファイル名等
応答内容

設定データなし。 応答コマンド名とトランザクションIDのふたつのみである。

FCPublish

releaseStreamと同様の設定内容である。

応答内容

設定データなし。 分類上未定義であるコマンドの応答内容を参照。

createStream

設定データなし。

クライアントはサーバに対して、メッセージIDの付与を要求する。

応答内容
設定データAMF0データ型設定内容
メッセージID数値クライアントに対して一意に割り当てるID
publish
設定データAMF0データ型設定内容
公開名文字列受信側に公開するデータの名前
releaseStreamに書き込まれるものと同一のものを使用する実装が主。
公開型文字列サーバ側に対するデータの保存方法についての指示
応答内容
設定データAMF0データ型設定内容
応答情報オブジェクトconnectコマンドと同様
ウィンドウ通知サイズ(サーバ帯域幅)
フィールド長さ設定内容
通知ウィンドウサイズ4バイト受信者側の通信帯域の制限値

受信メッセージひとつあたりのデータ量がこの値を超えた場合、受信者は送信者に対して通知型のメッセージにてその旨を伝えなければならない(後述)。

FCSubscribe
設定データAMF0データ型設定内容
パス文字列サーバにデータを送信させる際のファイル名等
応答内容

各種RTMP実装共になし。

getStreamLength
設定データAMF0データ型設定内容
パス文字列ストリーム長を取得する際にサーバに参照させるファイル名等
応答内容
設定データAMF0データ型設定内容
ストリーム長数値FLVコンテナのメタデータに設定されたdurationフィールドの値
set_playlist
設定データAMF0データ型設定内容
プレイリストECMA風配列サーバ側にプレイリストとしてストリームさせるデータのリスト
応答内容

設定データなし。 分類上未定義であるコマンドの応答内容を参照。

play
設定データAMF0データ型設定内容
ストリーム名文字列受信するデータの名前
開始位置数値受信するデータの再生開始位置
サーバに対する送信方法の指示も兼ねるため、一部の負数の書き込みが許容される。
具体的には、以下のパターンに対応する。
  • -2
    指定したデータがライブストリームとして存在しない場合、保存済みのデータを送信させる。
  • -1
    指定したデータをライブストリームとしてのみ送信させる。
  • 0 以上
    指定したデータを保存済みのデータとしてのみ送信させる。その際、当該データに書き込んだ数値の分だけ再生開始位置を変更させる。
応答内容
設定データAMF0データ型設定内容
応答情報オブジェクトconnectコマンドと同様
バッファ長設定
フィールド設定内容
イベント型2
イベントデータ以下ふたつのデータが書き込まれる。
  • メッセージID(4バイト)
  • バッファ長(4バイト、ミリ秒単位)
    仕様上は「バッファの長さ」となっているが、実際は指定した分だけバッファに蓄積するよう指示するための「時間の長さ」である点に注意。
応答情報

connectおよびpublish/playの応答メッセージには、以下の形式の設定データを書き込み、通信相手に処理の成否とその内訳を伝達する必要がある。

フィールドAMF0データ型設定内容
level文字列
  • status
  • warning
  • error
code文字列要求されたコマンドの処理結果を表すショートコード
例:
  • NetConnection.Connect.Success
  • NetStream.Play.Start
description文字列要求されたコマンドの処理結果を伝える人間可読な説明

データ送受信

上記各種設定情報の交換が完了した後、サーバとクライアントの双方は実際にデータの送受信を開始する。

Footnotes

  1. https://rtmp.veriskope.com/pdf/rtmp_specification_1.0.pdf

  2. https://ja.m.wikipedia.org/wiki/%E5%87%BA%E7%89%88-%E8%B3%BC%E8%AA%AD%E5%9E%8B%E3%83%A2%E3%83%87%E3%83%AB

  3. https://ffmpeg.org/

  4. https://obsproject.com/

  5. https://www.red5.net/

  6. https://github.com/obsproject/obs-studio/blob/master/plugins/obs-outputs/librtmp/rtmp.c#L4011

  7. 何らかの手段で画面出力したり、受信したバイト列の要素をひとつひとつ比較・検証していけばこの事象を確認できるのだが、そのように実装された箇所をまだ特定できていない。

  8. https://github.com/Red5/red5-server/blob/main/client/src/main/java/org/red5/client/net/rtmp/OutboundHandshake.java#L132-L135

  9. https://rtmp.veriskope.com/pdf/amf0-file-format-specification.pdf