RTMPとは
正式名称はReal Time Messaging Protocol1である。 主に以下の特徴を持つ。
- TCP/IP上で通信を行う。
- サーバのインスタンスひとつに対し、データの送受信双方にクライアントが存在する。つまり、一般的なPublisher-Subscriberモデルの実装のひとつである。2
- あらゆるメッセージフォーマットが当時のサーバ、プレイヤーおよびActionScriptの中で利用する前提で定義された。
- 同様に、実際に送受信されるデータもまた、FLV(Flash Video)のコンテナフォーマットに格納した上で処理される。
RTMPによる通信を実装された製品
2025年現在、RTMPによる通信を実装された製品は、オープンソースの範疇では主に以下の3つを確認できる。
FFmpeg
多様なコーデックに対応するメディアコンバータである。 以下の要素に対応する。
- 音声端末および映像端末からの入力
- RTMPを含む通信プロトコルを介するデータの送受信
- C言語による実装
- 任意のメディアファイルから他のメディアファイルへのコーデック変換
OBS
ライブ配信サービスへの音声/映像の送信を主な機能とするRTMPクライアント製品である。 以下の要素に対応する。
- 音声端末および映像端末からの入力
- RTMPを含む通信プロトコルを介するデータの送信
- C言語およびC++言語による実装
Red5
多様な機能を備えるRTMP通信向けのクライアント製品およびサーバ製品である。 以下の要素に対応する。
- 音声端末および映像端末との入出力
- RTMPを含む通信プロトコルを介するデータの送受信
- Java言語による実装
- 複数のクライアントからの要求に備える負荷分散
- リモート環境ないしクラウド環境へのデプロイメント
太字はPro版で実装される機能。
通信の流れ
RTMPでは、サーバもクライアント(送受信側双方)も以下の手順を踏む必要がある。
- サーバとクライアントの両者はハンドシェイクを行い、相互に存在を確認する。 この時、SHA-256によるダイジェストを利用したメッセージ認証を同時に行うことがある。
- 両者はデータの送受信に際して必要な設定情報(通信帯域の幅、送受信に利用するファイル名等)を交換する。
- 相互確認と設定情報の交換が完了した後、実際のデータの送受信を開始する。
ハンドシェイク
RTMPでは、TCP/IPによる3ウェイ・ハンドシェイクに加えて、当該プロトコル独自のハンドシェイクも行う。 ハンドシェイクにはそれぞれ以下のチャンクが要求される。
- C0/S0 (ハンドシェイクのバージョン指定)
- C1/S1およびC2/S2 (実際のハンドシェイクデータ)
C0/S0
フィールド名 | 長さ(バイト単位) | 内容 | 参考画像 |
---|---|---|---|
バージョン | 1 | ハンドシェイクのバージョン 当該チャンクで指定された値により、ハンドシェイク全体の暗号化の有無およびその方式が決定される。具体的には、以下のバージョンに対応する。
| ![]() |
なお、当該チャンクで指定するバージョンは、クライアントとサーバの双方で一致していることが期待されることがある。
C1/S1およびC2/S2
フィールド | 長さ(バイト単位) | 内容 |
---|---|---|
タイムスタンプ | 4 | ハンドシェイク送信時点の時刻(ミリ秒単位) |
バージョン | 4 | サーバまたはプレイヤーのバージョン |
ランダムデータ | 1528 | 通信相手の識別に用いられるランダムデータ |
参考画像
ハンドシェイクの手順
RTMPでは上記の構造のチャンクを以下の手順で相互に交換する。
- クライアントはサーバに対してチャンクの暗号化方式(C0)とハンドシェイクデータ(C1)を送信する。
- サーバも同様にクライアントにチャンクの暗号化方式(S0)とハンドシェイクデータ(S1)を送信する。
同時に、サーバはクライアントにC1を返送する(S2)。 - クライアントも同様に、サーバに対してS1を返送する(C2)。
メッセージ認証
サーバのバージョン3以降およびプレイヤーのバージョン9以降から、双方はHMAC(SHA-256)によるハンドシェイクデータへの署名と認証をサポートされた。 メッセージ認証の要不要は、ハンドシェイクデータのバージョン指定領域に書き込まれるチャンク送信時点でのサーバあるいはプレイヤーのバージョンを確認することで判断する。 参考までに、双方の最終的なバージョン番号を以下に記す。
- サーバ: 5.0.17
- プレイヤー: 32.0.0.465
なお、現存する各RTMP対応製品はメジャーバージョン(先頭1バイト分)のみをチェックする仕様であることが多いため、その後に続くバージョン番号が1バイト以内に収まらなくても問題ない(切り捨ててもよい)。
ダイジェストの書き込み
簡単のため、ハンドシェイクデータを と置き、添え字は の各位置にバイト単位で一致するものとする。 そして、暗号化の有無に応じて、ダイジェストの書き込み開始位置 をそれぞれ以下の式で計算する。
-
暗号化なしの場合
-
暗号化ありの場合
から続く32バイト分の領域がダイジェストの書き込み範囲となるので、当該領域を除くハンドシェイクデータ全体の値をダイジェストの原文とする。 ここで、双方がダイジェストの生成に際して必要になる鍵の内容はそれぞれ以下の通りである。
- サーバ :
Genuine Adobe Flash Media Server 001
。 - クライアント:
Genuine Adobe Flash Player 001
。
上記の鍵を用いて得られたダイジェストを当該領域に書き込んだ上で、相手側にハンドシェイクデータを送信する。
署名の書き込み
以下の手順で署名データを書き込む。
- ハンドシェイクデータに書き込まれるダイジェストを原文として、署名鍵を作成する。
ここで、双方が署名鍵の生成に際して必要になる追加の鍵の内容はそれぞれ以下の通りである。- サーバ :
Genuine Adobe Flash Media Server 001
+ 共通のバイト列 - クライアント :
Genuine Adobe Flash Player 001
+ 共通のバイト列 - 共通のバイト列:
F0EEC24A8068BEE82E00D0D1029E7E576EEC5D2D29806FAB93B8E636CFEB31AE
- サーバ :
- 末尾32バイト分の領域を除くハンドシェイクデータ全体を原文として、手順1で作成した署名鍵を用いて署名を作成する。
- 手順2で作成した署名を末尾32バイト分の領域に書き込んだ上で、相手側にハンドシェイクデータを返送する。
ダイジェストや鍵の検証については、それぞれを上記の手順で生成した上で書き込み範囲のデータと比較すればよいだけなので割愛する。
製品の振る舞いに注意
サーバとプレイヤーはそれぞれ上述のバージョン以降にメッセージ認証をサポートされたにも関わらず、現存のRTMP対応製品の中にはその変化に十分に追従しなかったものが存在する。 2025年時点で筆者が確認済みの対応状況を以下に記す。
製品名 | ダイジェストへの対応 | 署名への対応 | 備考 |
---|---|---|---|
OBS | ❎ 非対応 | ❎ 非対応 | バージョン列は0 で固定6 |
FFmpeg | ✅ 対応済み | ❎ 非対応 | バージョン列に書き込まれる値はメッセージ認証対応済みのものである(にも関わらず、何故か署名を書き込まない)。7 |
Red5 | ✅ 対応済み | ✅ 対応済み | 先頭1バイトには確かに9 以上(クライアント側)あるいは3 以上(サーバ側)の数値が書き込まれているが、その数値の根拠は不明である。8 |
設定交換
ハンドシェイクの後、サーバおよびクライアントの双方はデータの送受信に必要な設定情報を交換する。 以降、以下の形式に従うチャンクを用いてデータの送受信を行う。
- 基本ヘッダ
- メッセージヘッダ
- 拡張タイムスタンプ(任意)
- メッセージデータ
基本ヘッダ
フィールド名 | 長さ(ビット単位) | 内容 |
---|---|---|
メッセージヘッダの形式 | 2 | 送信されるメッセージヘッダのパターン(0 以上3 以下) |
チャンクID | 6以上 | チャンクの分類を示すID ただし、 0 と1 は除く(後述)。 |
基本ヘッダはチャンクIDの大きさによって3パターンに分岐される。
各パターンは1バイト目のチャンクID領域に2
未満の値が書き込まれているかどうかで判断する。
そして、基本ヘッダの読み書きの際には以下の点に注意が必要である。
- チャンクIDの読み書きの際には、バイト順(エンディアン)を考慮する。
- 2バイトパターンおよび3バイトパターンのチャンクIDは読み書きの際に
64
だけ増減させる
(1バイト目のチャンクID領域はパターンの区別に用いられるため)。
ここで、基本ヘッダの各パターンの仕様を以下に記す。
基本ヘッダのパターン | 1バイト目のチャンクID領域の値 | チャンクIDの範囲 | チャンクIDのバイト順 | 参考画像 |
---|---|---|---|---|
1バイト | 2 以上 | 2 以上63 以下 | ビッグエンディアン | ![]() |
2バイト | 0 | 64 以上320 以下 | ビッグエンディアン | ![]() |
3バイト | 1 | 64 以上65599 以下 | リトルエンディアン | ![]() |
メッセージヘッダ
フィールド名 | 長さ(バイト単位) | 内容 |
---|---|---|
タイムスタンプ | 3 | チャンク送信時点の時刻(ミリ秒単位) |
メッセージ長 | 3 | メッセージデータの長さ |
メッセージ型ID | 1 | メッセージデータの種類を示す番号 |
メッセージID | 4 | クライアントに対して割り当てるID |
メッセージヘッダは基本ヘッダの先頭2ビットに書き込まれる値によって、以下の4パターンに分岐される。
形式 | 長さ(バイト単位) | フィールド | 参考画像 |
---|---|---|---|
0 | 11 |
| ![]() |
1 | 7 |
| ![]() |
2 | 3 |
| ![]() |
3 | 0 | ヘッダなし |
そして、メッセージヘッダの読み書きの際には、以下の点に注意が必要である。
- どの形式のメッセージヘッダが必要になるかを決定するためには、直前に送信した基本ヘッダおよびメッセージヘッダの状態を記憶しておく必要がある。
- メッセージIDの読み書きのみリトルエンディアンである。
拡張タイムスタンプ
フィールド名 | 長さ(バイト単位) | 内容 | 参考画像 |
---|---|---|---|
タイムスタンプ | 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におけるオブジェクトとほぼ同義) |
8 | ECMA風配列 | 長さ固定のKey-Valueの組(ECMAScriptにおける配列とほぼ同義) 実際の配列の前に配列長(4バイト)が書き込まれる。 |
クライアントはサーバに対して以下の形式のコマンド要求を送信する。
フィールド名 | AMF0データ型 | 設定内容 |
---|---|---|
コマンド名 | 文字列 | 送信するコマンドの名前 |
トランザクションID | 数値 | 現在処理されているコマンドの順序を示すID 仕様上、 1 から始めることになっているが、場合によっては0 を設定する必要がある点に注意。 |
設定データ | 不定 | 各コマンドのメッセージ交換の際に必要になる設定情報 詳細は後述。 |
対して、サーバはコマンドメッセージを受信した際、応答メッセージを送信する必要がある。 応答メッセージはクライアントから受信したコマンドの種類に応じて設定内容に差異がある。 ここで、現在使用されているコマンドの分類および対応するコマンド名を以下に記す。
コマンドの分類 | 対応するコマンド | 備考 |
---|---|---|
NetConnection |
| releaseStream およびgetStreamLength は仕様に定義されていない。 |
NetStream |
| |
未定義 |
| 左記のいずれも仕様に定義されていない。 |
そして、コマンドの分類に応じた応答メッセージの形式を以下に記す。
NetConnection
フィールド | AMF0データ型 | 設定内容 |
---|---|---|
コマンド名 | 文字列 | _result もしくは_error |
トランザクションID | 数値 | 受信したコマンドのものと同等のID |
設定データ | 不定 | 各コマンドの処理結果を伝達するためのデータ |
NetStream
フィールド | AMF0データ型 | 設定内容 |
---|---|---|
コマンド名 | 文字列 | onStatus |
トランザクションID | 数値 | 0 |
設定データ | オブジェクト | 各コマンドの処理結果を伝達するためのデータ |
未定義
FCPublish
フィールド | AMF0データ型 | 設定内容 |
---|---|---|
コマンド名 | 文字列 | onFCPublish |
FCSubScribe
フィールド | AMF0データ型 | 設定内容 |
---|---|---|
コマンド名 | 文字列 | onFCPublish |
set_playlist
フィールド | AMF0データ型 | 設定内容 |
---|---|---|
コマンド名 | 文字列 | playlist_ready |
上記各コマンドはトランザクションIDや応答情報の書き込みを行わない。
ユーザ制御
ユーザ制御のメッセージデータの形式は以下の通りである。
フィールド | 長さ(バイト単位) | 設定内容 |
---|---|---|
イベント型 | 2 | ユーザ制御メッセージの型マーカ |
イベントデータ | 不定 | 通信相手に伝達する追加の設定内容 |
通信手順
クライアントがデータの送信側か受信側のどちらであるかによって、通信内容に差異がある。
送信側
送信側クライアントはサーバとの間で以下のメッセージを交換する。
connect
releaseStream
FCPublish
createStream
publish
受信側
一方、受信側クライアントは以下のメッセージを交換する。
connect
- ウィンドウ通知サイズ(サーバ帯域幅)
createStream
FCSubscribe
getStreamLength
(FFmpegの場合)/set_playlist
(OBSの場合)play
- バッファ長設定
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データ型 | 設定内容 |
---|---|---|
ストリーム名 | 文字列 | 受信するデータの名前 |
開始位置 | 数値 | 受信するデータの再生開始位置 サーバに対する送信方法の指示も兼ねるため、一部の負数の書き込みが許容される。 具体的には、以下のパターンに対応する。
|
応答内容
設定データ | AMF0データ型 | 設定内容 |
---|---|---|
応答情報 | オブジェクト | connect コマンドと同様 |
バッファ長設定
フィールド | 設定内容 |
---|---|
イベント型 | 2 |
イベントデータ | 以下ふたつのデータが書き込まれる。
|
応答情報
connect
およびpublish
/play
の応答メッセージには、以下の形式の設定データを書き込み、通信相手に処理の成否とその内訳を伝達する必要がある。
フィールド | AMF0データ型 | 設定内容 |
---|---|---|
level | 文字列 |
|
code | 文字列 | 要求されたコマンドの処理結果を表すショートコード 例:
|
description | 文字列 | 要求されたコマンドの処理結果を伝える人間可読な説明 |
データ送受信
上記各種設定情報の交換が完了した後、サーバとクライアントの双方は実際にデータの送受信を開始する。
Footnotes
-
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 ↩
-
https://github.com/obsproject/obs-studio/blob/master/plugins/obs-outputs/librtmp/rtmp.c#L4011 ↩
-
何らかの手段で画面出力したり、受信したバイト列の要素をひとつひとつ比較・検証していけばこの事象を確認できるのだが、そのように実装された箇所をまだ特定できていない。 ↩
-
https://github.com/Red5/red5-server/blob/main/client/src/main/java/org/red5/client/net/rtmp/OutboundHandshake.java#L132-L135 ↩
-
https://rtmp.veriskope.com/pdf/amf0-file-format-specification.pdf ↩