動画を送ろう!!RTMP

目次

1. 1. はじめに

2. 2. RTMPとは

1. 1. RTMPで出来ること・出来ないこと

2. 2. 実装について

3. 3. RTMP概観

1. 1. TCP

2. 2. RTMP Chunk Stream Protocol

1. 2.1. Protocol Control Message

3. 3. RTMP Message

1. 3.1. User Control Message

2. 3.2. RTMP Command Messages

1. 3.2.1. Action Message Format

2. 3.2.2. 動画・音声のフォーマット

4. 4. RTMP Chunk Stream

1. 1. バイナリフォーマットについて

2. 2. TCPでの接続

3. 3. ハンドシェイク

1. 3.1. 仕様書に書いてあるハンドシェイク

2. 3.2. 真のハンドシェイク

3. 3.3. ハンドシェイクのエラー処理

4. 4. チャンクの送受信

1. 4.1. チャンクの基本ヘッダー

2. 4.2. チャンクメッセージヘッダー

1. 4.2.1. タイプ0

2. 4.2.2. タイプ1

3. 4.2.3. タイプ2

4. 4.2.4. タイプ3

5. 4.2.5. ヘッダの省略とチャンクストリームID

3. 4.3. チャンクストリームIDの割り当て

4. 4.4. 拡張タイムスタンプ

5. 4.5. チャンクデータ

6. 4.6. メッセージの分割

7. 4.7. Acknowledgement

5. 5. RTMP Control Message

1. 5.1. Set Chunk Size メッセージ (タイプID:1)

2. 5.2. Abort メッセージ (タイプID:2)

3. 5.3. Acknowledgement メッセージ (タイプID:3)

4. 5.4. Window Acknowledgement Size メッセージ (タイプID:5)

5. 5.5. Set Peer Bandwidth メッセージ (タイプID:6)

6. 6. RTMP Chunk Streamのまとめ

5. 5. RTMP Message

1. 1. RTMP Message Format

2. 2. RTMP Messageの種類

3. 3. User Control Message (タイプID:4)

1. 3.1. Stream Begin イベント (イベントタイプ:0)

2. 3.2. Stream EOF イベント (イベントタイプ:1)

3. 3.3. StreamDry イベント (イベントタイプ:2)

4. 3.4. SetBufferLength イベント (イベントタイプ:3)

5. 3.5. StreamIsRecorded イベント (イベントタイプ:4)

6. 3.6. PingRequest イベント (イベントタイプ:6)

7. 3.7. PingResponse イベント (イベントタイプ:7)

8. 3.8. User Control Messageまとめ

4. 4. Data Message (タイプID:15または18)

5. 5. Audio Message (タイプID:8)

6. 6. Video Message (タイプID:9)

7. 7. Shared Object Message (タイプID:16または19)

8. 8. Aggregate Message (タイプID:22)

9. 9. Command Message (タイプID:17または20)

10. 10. NetConnectionコマンドとNetStreamコマンド

11. 11. connectコマンド (NetConnection)

12. 12. createStreamコマンド (NetConnection)

13. 13. deleteStreamコマンド (NetConnection)

14. 14. closeコマンド (NetConnection)

15. 15. その他のコマンド (NetConnection)

1. 15.1. releaseStreamコマンド (NetConnection)

2. 15.2. FCPublishコマンド (NetConnection)

3. 15.3. FCUnpublishコマンド (NetConnection)

16. 16. onStatusコマンド (NetStream)

17. 17. playコマンド (NetStream)

18. 18. play2コマンド (NetStream)

19. 19. pauseコマンド (NetStream)

20. 20. seekコマンド (NetStream)

21. 21. receiveAudioコマンド (NetStream)

22. 22. receiveVideoコマンド (NetStream)

23. 23. publishコマンド (NetStream)

6. 6. AMF0/3

1. 1. AMF0

1. 1.1. 数値型 (マーカー:0)

2. 1.2. 真偽値型 (マーカー:1)

3. 1.3. 文字列型 (マーカー:2、12)

4. 1.4. オブジェクト型 (マーカー:3)

5. 1.5. null型 (マーカー:5)

6. 1.6. undefined型 (マーカー:6)

7. 1.7. 参照型 (マーカー:7)

8. 1.8. 連想配列型 (マーカー:8)

9. 1.9. 普通の配列型 (マーカー:10)

10. 1.10. 日時型 (マーカー:11)

11. 1.11. Unsupported型 (マーカー:13)

12. 1.12. XML型 (マーカー:15)

13. 1.13. 型付きオブジェクト型 (マーカー:16)

14. 1.14. AMF3への切り替え (マーカー:17)

2. 2. AMF3

1. 2.1. AMF3の整数値

2. 2.2. AMF3の文字列

3. 2.3. AMF3の値

4. 2.4. undefined型 (マーカー:0)

5. 2.5. null型 (マーカー:1)

6. 2.6. false型、true型 (マーカー:2、3)

7. 2.7. 整数値型 (マーカー:4)

8. 2.8. 浮動小数点数値型 (マーカー:5)

9. 2.9. 文字列型 (マーカー:6)

10. 2.10. XMLドキュメント型、XML型 (マーカー:7、11)

11. 2.11. 日時型 (マーカー:8)

12. 2.12. 配列型 (マーカー:9)

13. 2.13. オブジェクト型 (マーカー:10)

14. 2.14. バイト配列型 (マーカー:12)

15. 2.15. AMF3の参照について

3. 3. RTMP Messageでの型の扱いについて

7. 7. RTMPで通信しよう

1. 1. 共通処理

2. 2. 切断処理

3. 3. クライアントからサーバーへ動画配信したい

4. 4. 動画の視聴をしたい(サーバーからクライアントへの動画の配信をしたい)

8. 8. FLVファイル

1. 1. FLVファイルの構造

1. 1.1. FLVファイルヘッダー

2. 1.2. FLVタグ

3. 1.3. バックポインター

2. 2. FLVファイルまとめ

9. 9. 参考文献

1. 1. ドキュメント類

1. 1.1. RTMPの仕様書

2. 1.2. WikipediaのRTMPのページ

3. 1.3. FLV・SWF・AMF0/3の仕様書

4. 1.4. MPEG-4の仕様書

5. 1.5. ActionScriptのドキュメント

2. 2. ツール類

1. 2.1. Adobe Media Server

2. 2.2. RTMPDump・librtmp

3. 2.3. Open Broadcaster Software

4. 2.4. FFmpeg

5. 2.5. PeerCastStation

図の一覧

1. 4.1. ハンドシェイクの手順

2. 4.2. C0/S0パケットの構造

3. 4.3. C1/S1パケットの構造

4. 4.4. C2/S2パケットの構造

5. 4.5. チャンクの構造

6. 4.6. チャンク基本ヘッダーの構造

7. 4.7. チャンクメッセージヘッダー タイプ0の構造

8. 4.8. チャンクメッセージヘッダー タイプ1の構造

9. 4.9. チャンクメッセージヘッダー タイプ2の構造

10. 4.10. チャンクメッセージヘッダー タイプ3の構造

11. 4.11. Set Chunk Sizeメッセージの内容

12. 4.12. Abortメッセージの内容

13. 4.13. Acknowledgementメッセージの内容

14. 4.14. Window Acknowledgement Sizeメッセージの内容

15. 4.15. Set Peer Bandwidthメッセージの内容

16. 4.16. RTMP Chunk Stream通信の流れ

17. 5.1. User Control Messageの内容

18. 5.2. Stream Beginイベントの内容

19. 5.3. Stream EOFイベントの内容

20. 5.4. StreamDryイベントの内容

21. 5.5. SetBufferLengthイベントの内容

22. 5.6. StreamIsRecordedイベントの内容

23. 5.7. PingRequestイベントの内容

24. 5.8. PingResponseイベントの内容

25. 5.9. Data Messageの内容

26. 5.10. Audio Messageの内容

27. 5.11. Video Messageの内容

28. 5.12. Aggregate Messageの内容

29. 5.13. RTMP Message Formatの構造

30. 5.14. Command Messageの内容

31. 5.15. connectコマンドの内容

32. 5.16. connectコマンドの結果の内容

33. 5.17. createStreamコマンドの内容

34. 5.18. createStreamコマンドの結果の内容

35. 5.19. deleteStreamコマンドの内容

36. 5.20. closeコマンドの内容

37. 5.21. アプリケーション定義コマンドの内容

38. 5.22. アプリケーション定義コマンドの結果の内容

39. 5.23. onStatusコマンドの内容

40. 5.24. playコマンドの内容

41. 5.25. play2コマンドの内容

42. 5.26. pauseコマンドの内容

43. 5.27. seekコマンドの内容

44. 5.28. receiveAudioコマンドの内容

45. 5.29. receiveVideoコマンドの内容

46. 5.30. publishコマンドの内容

47. 7.1. 接続からの共通処理

48. 7.2. 接続からの共通処理詳細

49. 7.3. 切断処理

50. 7.4. publish処理

51. 7.5. publish処理詳細

52. 7.6. play処理

53. 8.1. FLVファイルの構造

54. 8.2. FLVファイルヘッダーの構造

55. 8.3. FLVタグ構造

表の一覧

1. 4.1. チャンクメッセージヘッダ省略の例

2. 4.2. チャンクメッセージヘッダ省略の合成例

3. 4.3. メッセージの複数チャンクへの分割の例

4. 5.1. メッセージタイプIDとその中身

5. 5.2. User Control Messageのイベントタイプ

6. 5.3. onMetaDataのプロパティ

7. 5.4. onXMPDataのプロパティ

8. 5.5. Audio MessageのコーデックID

9. 5.6. Audio Messageのサンプリング周波数

10. 5.7. Audio Messageの量子化ビット数

11. 5.8. Audio Messageのチャンネル数

12. 5.9. Video Messageのフレーム種別

13. 5.10. Video MessageのコーデックID

14. 5.11. connectコマンドのコマンドオブジェクトのプロパティ

15. 5.12. connectコマンドのaudioCodecs

16. 5.13. connectコマンドのvideoCodecs

17. 5.14. connectコマンドのvideoFunction

18. 5.15. connectコマンド結果のコマンドオブジェクトのプロパティ

19. 5.16. connectコマンド結果のInformationのプロパティ

20. 5.17. connectコマンド結果のcodeとlevel

21. 5.18. その他のコマンド結果のcodeとlevel

22. 5.19. onStatusコマンドのInfoのプロパティ

23. 5.20. play2コマンドのパラメーターのプロパティ

24. 5.21. play2コマンドのtransition

25. 5.22. publishコマンドの配信タイプ

26. 6.1. AMF0のマーカー

27. 6.2. U29の値範囲とバイナリ表現

28. 6.3. AMF3のマーカー

29. 6.4. ActionScriptの数値型への変換規則

30. 6.5. ActionScriptの真偽値型への変換規則

31. 6.6. ActionScriptの文字列型への変換規則

32. 8.1. FLVファイルの種別フラグ

33. 8.2. FLVタグタイプID

第1章 はじめに

この本はいまさらながらRTMPを解説する本です。

一般ユーザーによる動画の生配信が一般的になり、動画配信サービスも沢山ある今日このごろ。

動画配信の視聴と言えばちょっと前まではFlashがよく使われておりましたが、今となってはもう過去の物となりはて、HTML5(というかWebブラウザだけ)で視聴ができる便利な世の中になりました。

現在動画を視聴しようとするとHTTPベースのストリーミングプロトコル(HTTP Live StreamingやMPEG-DASH)がよく使われるということはご存知の方も多いでしょう。

逆に視聴でなく自分で配信したい！と思ったときにどんなプロトコルで送ってるのかな？と不思議に思った人も少なくないはずです。いや実は少ないと思っていますが、少なくないことにしておきます。 不思議に思わないまでも、HTTPで視聴できるんだから配信も逆にサーバーにHTTPで送ってるだけでしょー？と思っている人も居るんじゃないでしょうか。

だがしかし！実は配信元からサーバーへの動画送信にはHTTPは使われておらず、Flashで使われていたプロトコルのRTMPがデファクトスタンダードになっています。 Flashを使わずともAdobe Media Server(旧Flash Media Server)とやりとりするためのプロトコルなので、一応RTMPはFlashとは独立しており仕様も公開されています。 そんな事情もあり色々な配信用のソフトウェアやハードウェアでも実装され、逆に既存の配信用環境に合わせるようにサーバー側もAdobe Media ServerでなくともRTMPを受信できるよう対応し、というのが続いてデファクトスタンダードになってしまったようです。

というわけで、この本はそんなRTMPについて解説する本です。

RTMP自体はもう改定されそうもないですし、新しいコーデックへの対応もされないため、今後はまた別なプロトコルに置き換わっていくだろうと予想されます。しかしこれを書いている2019年初頭の時点ではまだこれといって有力な代替候補が定まっているとは言い難い状況ですので、そろそろRTMPを解説できる最後の機会かなーと思ってこんな本を書きました。

元々Flashのプロトコルなのですが、いまとなっては動画配信にしか使われないと思いますし、書いてる自分自身がそんなにFlashに詳しくないこともありまして、動画関係を中心に解説していきます。

第2章 RTMPとは

RTMPとは！Real-Time Messaging Protocolの略で、Adobe Flash(旧Macromedia Flash)とAdobe Media Server(旧Flash Media Server)の通信プロトコルです。

Flashで使われるActionScript[1]から簡単に接続でき、動画や音声のやりとりができるため、Flashベースのインターネットアプリケーション、ビデオ会議や動画配信を作るのに使われました。

メディアサーバーなんぞは他にも沢山ありましたが、RTMPの特徴としてはActionScriptのオブジェクトをやりとりできるプロトコルという点があるでしょう。 動画や音声だけでなくActionScriptのデータがいっしょに載せられることで、動画と同期してスクリプトでの動作を行ったりすることもできました。 Flash Media Serverはサーバー側アプリケーションもActionScriptで開発するので、クライアントとサーバーでシームレスにオブジェクトをやりとりできると非常に便利です。 動画や音声を扱わない、Flashベースのオンラインゲームにも使われました。

今となってはFlashとAdobe Media Serverでアプリケーションを作ることはもうほとんど無いでしょうし、それで作るにしてもRTMFPという後継に近いプロトコル[2]があるので、RTMPが元の用途で使われることは少ないんじゃないかと思います。

しかしながら、動画をやりとりするのにFlashが便利だった時代はそれなりに長く、クライアントからサーバーに動画を送るのにRTMPを採用したソフトウェアやハードウェアがいくつも作られました。それらを使えるようにするため、また他にちょうど良いプロトコルがあったわけでもないという理由もあるためか、もうFlashもAdobe Media Serverを使ってなくともクライアントからサーバーに動画(と音声)を送るプロトコルとしてRTMPが採用され、デファクトスタンダードの地位を確立しました。

RTMPは仕様が公開されており[3]、TCPで1本つなげばずっとやりとりできる比較的シンプルなプロトコルといったあたりも数多く実装された理由の一つでしょう。

1. RTMPで出来ること・出来ないこと

RTMPの機能は次のようなものがあります。

- TCPで接続

- シリアライズされたActionScriptオブジェクトの送受信

- AMF0

- AMF3

- 音声の送受信

- ADPCM

- MP3

- NellyMoser

- AAC

- Speex

- 動画の送受信

- Sorenson (Flash Video)

- Flash Video Screen V1・V2

- On2 (VP6)

- H.264

- 上記各種データ(オブジェクト・音声・動画)の1ストリームへの混載

- オブジェクトデータも含めてタイムスタンプ付き

- 一つの接続に複数ストリームの重畳

- サーバー・クライアントの双方向通信

オブジェクトも含めてタイムスタンプが付いているので、オブジェクトと動画・音声を同期することができるのは嬉しいですね。

複数ストリームの重畳ができるので、1接続で複数の動画やオブジェクトをそれぞれ非同期で流したりすることもできます。

もちろんいずれにせよアプリケーションが対応しないと意味ないですけど……。

逆に出来ないことは以下のようなものがあります。

- 暗号化

- P2P通信

- 新しいコーデックへの対応

TLSなどを使わない普通のTCP通信なので暗号化はされていません。抜けば抜けます。 SSLで暗号化するRTMPSや、もう少し軽い暗号化であるRTMPEというプロトコルがあるようですが、詳細なドキュメントがないためわかりません。 まあ少なくとも現在のクライアントからサーバーへの動画配信では使われてはいないようです。

TCPであるためP2P通信はできません。いやTCPだからというわけではないですがNAT越えなどを考えるとTCPは不利ですし。UDPベースのRTMFPだとP2P通信もできるようになっています。

音声や動画のコーデックは独自の値が割り当てられていますので、仕様の改定がない限りは増えませんし、増やすこともできません。最新のコーデックがAAC・Speex・H.264なので、もっと新しいH.265やAV1、Opusなんかを使いたくても対応できません。 この点がRTMPは実用的限界でもありますので、万が一バージョンアップでもされない限りは今後は使われなくなることでしょう。

[1] Flashで使われるスクリプト言語。だいたいJavaScriptみたいなもの。

[2] UDPベースでRTMPと名前は似てるけど中身はだいぶ別物

[3] https://www.adobe.com/devnet/rtmp.html ただしいつまであるかはわからない。

2. 実装について

前にも述べた通り、仕様は公開されていますし、それほど複雑なものでもないため仕様をよく読めば誰でも実装することができます。

……と言いたいところだが。

仕様書が公開されているのはいいんですが、あちこち曖昧だったり省略されていたり古かったりして、他のデータの仕様書やドキュメントと突き合わせないとわからないところがいくつもあります。 公式のドキュメントを見て実装しようとすると何度も首をかしげることになるでしょう！ さすが旧Macromediaだぜ！！

この本ではなんとかそれなりに実装できる程度にはまとめたつもりなので、参考になれば幸いです。

第3章 RTMP概観

詳細に入るまえにざっくりとRTMP全体を見渡してみましょう。

RTMPはいくつかのプロトコル階層によって成り立っています。

1. TCP

2. RTMP Chunk Stream Protocol

3. RTMP Message

建前的にはそれぞれの階層は独立したプロトコルになっています。 実際には密接に関連している部分があるのですが、ある程度独立になってる建前で仕様書が書かれているので逆に読みづらくなっているのが困ったところです。

1. TCP

まずベースはTCPです。普通のTCPなので特に説明するところもないです。

TLS/SSLは使わず、素のTCPを使います。 TLS/SSLを使うRTMPSというのもあるらしいですが、詳細はよくわからない(仕様書にはない)ので割愛します。 まあたぶん普通にTLS/SSLの上に乗せただけのものですけど。

ポート番号は1935です。もちろんサーバーとクライアント双方が指定すれば他のポートでも使えますが、どのアプリも特にポート番号を指定しなければ標準の1935を使うでしょう。

2. RTMP Chunk Stream Protocol

ここからRTMP独自のプロトコルになります。

TCPの上にRTMP Chunk Stream Protocolというバイナリフォーマットで接続を確立します。 TCP接続が確立されると、次にこのレイヤーでハンドシェイクが行われ、さらに上位の通信を行います。

Chunk Stream Protocolという名の通り、このプロトコルではチャンク(=バイナリの塊)単位でやりとりを行います。

一つのチャンクには次のような情報が含まれます。

- チャンクフォーマット

- チャンクストリームID

- タイムスタンプ

- メッセージ長

- メッセージタイプID

- メッセージストリームID

- ペイロード(メッセージボディ)

Chunk Stream ProtocolにはチャンクストリームIDというのが含まれますが、このIDが異なるチャンクは仮想的に異なる接続として扱われます。 つまり一つのTCP接続上にIDが異なるチャンクストリームを多重化することで、仮想的に複数の接続を確立することができます。

それと別にメッセージストリームIDというのも持っており、これはより上位のRTMP MessageのストリームIDで、チャンクストリームIDとは別物です。混同しないように注意しましょう。

2.1. Protocol Control Message

Chunk Stream Protocolでやりとりされるメッセージは基本的にはより上位のRTMP Messageになるのですが、一部Chunk Streamレベルで設定や通知するメッセージがあり、これらはProtocol Control Messageと呼ばれます。

Protocol Control Messageは接続全体のメッセージとなります。多重化はされずに常にチャンクストリームIDが2番が使われます。

3. RTMP Message

RTMP Messageはよりアプリケーションに近いメッセージです。

RTMP Chunk Stream Protocolまではアプリケーションに関係なく、RTMPプロトコルであれば必ず決まった動作をするものですが、RTMP Messageは主にアプリケーションの責任で送受信するものです。 RTMP Messageが正しく送受信できるところまで作れれば、あとはアプリケーションを作るだけと言えるでしょう。やったね！

RTMP Messageには以下のような情報で構成されます。

- メッセージ長

- メッセージタイプID

- メッセージストリームID

- ペイロード(メッセージボディ)

おっとどこかで見たような……。Chunk Streamのチャンクに含まれる情報ですね。

RTMPではこれらはChunk Streamのチャンクに載ってきますので、チャンクのやりとりさえできていれば、RTMP Messageで独自に規定されるのはメッセージタイプ毎の内容くらいです。

3.1. User Control Message

RTMP Messageでもアプリケーションが基本的に処理するのはRTMP Command Messageなのですが、他に接続制御みたいなのを行うUser Control Messageというのが存在します。 Chunk Stream ProtocolにおけるProtocol Control Messageが、RTMP MessageレベルではUser Control Messageとして用意されています。

内容はProtocol Control Messageほど重要なものでもなく、ヒント程度の情報を通知するものです。

3.2. RTMP Command Messages

RTMP Command Messagesはアプリケーションがなんか処理する必要があるメッセージです。

この中でもいくつかメッセージの種類があります。

Command Message

サーバー(またはクライアント)への関数呼び出しをリクエストします。

Data Message

なんらかのデータを送ります。が、通常は動画や音声のメタデータ(タイトル情報とか)を送信するのに使われます。

Video Message

動画データを送ります。

Audio Message

音声データを送ります。

Shared Object Message

ActionScriptの共有オブジェクトやそれらに対する変更について送ります。

Aggregate Message

複数のメッセージをまとめるメッセージです。何に使うんだろう……。

アプリケーションで使うのは基本的にCommand Messageで、動画の再生開始だとか配信開始だとかをこれで通知しますし、受け取った側はその内容に応じた処理をします。 あとは再生や配信が開始されるとData MessageやVideo・Audio Messageがだらだらと流れます。

Shared Object MessageやAggregate Messageは(少なくとも動画配信では)使いません。

3.2.1. Action Message Format

Command MessageではActionScriptの関数(メソッド)呼び出しの形でリクエストが送られますが、引数や戻り値もActionScriptのオブジェクトとして送受信されます。

そのためActionScriptのオブジェクトをエンコード(シリアライズ)する必要があるのですが、そのフォーマットがAction Message Format(AMF)です。 AMFは0と3という2つのバージョンがありますが、そのいずれもがRTMPで使えるようになっています。 ちょっと面倒ですがどちらも扱えるように実装しておかないといけません。

ちなみにAMF3はActionScript3でのフォーマットで、AMF0はそれ以前のActionScriptでのフォーマットのようです。AMF0の方が当然簡単です。

3.2.2. 動画・音声のフォーマット

Video MessageやAudio Messageには動画や音声が格納されますが、その内容は仕様書には書かれていません。

Flashで使う動画ファイルフォーマットの仕様書にある、FLVファイルの仕様を見るとそれらしきものが見つかります[4]。基本的にはコーデック毎の生データが入っていますが、いくらか付加情報があるのでこれを見ないと再生はできないでしょう……。

[4] https://www.adobe.com/devnet/f4v.html のFLV and F4V File Format Specification。例によっていつまであるかわからない。

第4章 RTMP Chunk Stream

ここからは、ついにRTMPの詳細に入っていきます。

なんとなく実装できそうなくらいには解説するつもりですが、コードとかは無いので本気で書く場合には頑張ってください。

1. バイナリフォーマットについて

詳しく見ていく前にちょっとバイナリフォーマットについて書いておきましょう。

まず1バイトは8ビットとします。1オクテットと書く方が正確でしょうけど面倒なので1バイトと書きます。 1バイトが8bitじゃない環境は今や見たことないくらい稀ですしね。

複数バイトにまたがる数値がある場合は、主にネットワークバイトオーダー(=ビッグエンディアン)が使われます[5]。主に、ということでたまーにリトルエンディアンなところがありますが、そこはその都度注記します。特に記述が無い限りはビッグエンディアンです。

ビット単位で見ていくこともありますが、その場合は0ビット目をMSB(最上位ビット)とします。 1バイトのデータの0ビット目と言えば一番上のビットですし、7ビット目と言えば一番下のビットになります。

実際に実装しない限りは流しちゃっていいですが、実装する時には気を付けて見てもらえばいいかと思います。

[5] ネットワークバイトオーダーとかビッグエンディアンとかいまいちわからんという人は調べてみてください。そんなに難しくはないんですが長くなるのでここでは解説しません。

2. TCPでの接続

TCPについては普通なので特に解説しません。TCPでつないでください。ポート番号は1935です。

IPv6でもIPv4でもプロトコル上は関係ないので、どちらでも安心して使えます。

RTMPSのプロトコルを使う場合はTCPでつないだ上でSSL/TLSを使うと良いようです。ポート番号とかはよくわからないですね……。

3. ハンドシェイク

TCPでつながったら、まずはRTMP Chunk Streamのハンドシェイクを行います。 ハンドシェイクとは直訳してしまうと握手ですが、つまりお互いにデータをやりとりしはじめる前の初期通信です。

ハンドシェイクのやり方は仕様書に書いてあるのですが、実はこれは古いハンドシェイクでFlashのいつからか(10くらい？)新しいハンドシェイクが行われているようです。が、仕様書に書いてないので詳細はわかりません。

とりあえず古い方のハンドシェイクでも問題なくつながるので、まずは仕様書通りにやるのが良いでしょう。

3.1. 仕様書に書いてあるハンドシェイク

ハンドシェイクではクライアントとサーバーが互いに3つのパケットを送り合います。

図4.1 ハンドシェイクの手順

それぞれC0、C1、C2とS0、S1、S2という名前が付いていて、C0～C2はクライアントから送るパケット、S0～S2はサーバーから送るパケットです。C0～2のCはクライアント(Client)の頭文字、S0～2のSはサーバー(Server)の頭文字ですね。

まずクライアントがC0パケットを送信します。中身は1バイトのバージョン番号が入ります。

図4.2 C0/S0パケットの構造

バージョン番号は3固定です。以前は0～2もあったようですが仕様もないのでわかりませんが、どっちにしろ使われてないので気にしなくて良いでしょう。4以降は現在無いようです。

サーバーはC0パケットを受け取って中身が3だったらS0パケットを返します。中身は同じく1バイトのバージョン番号で3を入れます。

サーバーがS0パケットを送ったら、次はクライアントからC1パケット、サーバーからS1パケットを送り合います。 どちらが先でも大丈夫そうに見えますが、とある事情によりC1が先になるようにしましょう。 クライアントがC1を送って、サーバーがC1を受け取ってからS1を返すべきです。

図4.3 C1/S1パケットの構造

C1/S1パケットの中身もどちらも同じです。まず4バイトのタイムスタンプ、4バイトの0、そして1528バイトのランダム値が続きます。

タイムスタンプは、通信開始時点からの経過時間をミリ秒単位で送るのですが、今通信開始するところなので当然は0が入ります。他の接続と同期したい時にそっちの通信開始時点からの経過時間を送るという場合があるようですが、まあ使わないでしょうし0を入れとけばいいでしょう。

4バイトの0はそのまま4バイト分0を入れておきます。

1528バイトのランダム値はなんでもいいので1528バイト分なんか適当な値を入れて送ります。固定値でもかまいません。

C1/S1パケットを受けとったら、返事としてC2/S2パケットを返します。

図4.4 C2/S2パケットの構造

C2/S2パケットの中身もどちらも同じです。まず4バイトのタイムスタンプ返信、次に4バイトの受信時タイムスタンプ、そして1528バイトのランダム値返信が続きます。

タイムスタンプ返信と1528バイトのランダム値返信は、さっき受け取ったC1/S1に入っていたタイムスタンプ値とランダム値をそのままコピーして入れます。

受信時タイムスタンプには、前のC1/S1を受け取った時点のタイムスタンプを設定します。 たとえばクライアントがC1パケットにタイムスタンプ0を入れて送信後、3ミリ秒経ってS1パケットを受け取ったとしましょう。その時C2パケットの受信時タイムスタンプには0+3で3を設定して送ります。 まあ実際のところだいたいで大丈夫です。

で、あとはC2/S2として返ってきたタイムスタンプとかランダム値が、C1/S1で送ったのと一致していればハンドシェイク完了、本番の通信に入ることができます。

実際のところ大して何か処理することもなく、固定データを送って、受け取ったC1/S1パケットをそのまま送り返してやるだけでも通ります。それでいいのかっていう気もするけど、楽ちんだね。

まあ一応通信にかかる時間(RTT)を計測したいという意図はありそうなので、できればC2/S2のタイムスタンプくらいはちゃんと設定した方が良いんじゃないかと思います。

3.2. 真のハンドシェイク

仕様書に書いてある簡単ハンドシェイクは上記の通りですが、仕様書に書いていない真のハンドシェイクが実は存在します。真のというかたぶん新しいだけなんですけど。

ただ簡単ハンドシェイクでもなんら問題なく動作します。一部を除いて。

H.264(AACもかも？)の再生をする時だけは簡単ハンドシェイクだと動きません。 他のコーデックを使っている場合は特に問題なく動くんですが、H.264の動画を再生しようとすると、Adobe Media Serverにplayコマンドを送っても何のエラーも返さないくせに動画データだけ全く送ってこなくなります。 Flash Playerでも、新しいハンドシェイクに対応していないサーバーではFlash Playerが送ってきたplayコマンドに対してH.264の動画データを返しても無視されます。 何のエラーも出ないのでさっぱりわかりませんが、実は全く関係なさそうなハンドシェイクの時点で判別されるようです。

一方で再生でなく録画/配信する時には関係なさそうです。簡単ハンドシェイクで済ましても、サーバーに送ったH.264のデータは受け付けてくれるように見えましたし、各種エンコーダーもH.264のデータを送ってくれます。

真のハンドシェイクの方法についてもできればここで書いておきたいところですが、署名なんかの処理があって結構複雑ですし、署名の鍵データも必要になって紙面をとられてしまいます。というか載せていいのかそれ。 Flash Player(やAIR)でRTMP経由で動画再生したい時くらいにしか使わないですし、今需要があるか怪しいので今回は見送ります。

ちょこっとだけ書いておきますと、新しい形式のハンドシェイクでは、C1/S1でタイムスタンプの後の常に0だったところに1が入ってきます。 クライアントが送ってきたC1に1が入ってきたら、サーバーもそれに対応した新しいS1を返さないといけません。 これがC1を受け取ってからS1を返すべき事情です。 無視して簡単ハンドシェイクのS1を返した場合は簡単ハンドシェイクのまま進みます。

詳しく知りたい場合はオープンソースのRTMP実装でいくつかみつかります。というか仕様はないのでそれしかないです。でもこれ最初に書いた人はどうやって解析したんだろう……。

3.3. ハンドシェイクのエラー処理

ハンドシェイクに失敗した場合はどうしたら良いでしょう。たとえば期待したバージョン番号が違うとか。

失敗した場合は特に規定がないので、そのまま接続を切るしかなさそうです。ぶち切りましょう。 通信できないんだもん仕方ないよね。

4. チャンクの送受信

ハンドシェイクが完了したらチャンクの送受信ができるようになります。

チャンクは次の図のような構成になっています。

図4.5 チャンクの構造

4.1. チャンクの基本ヘッダー

1つのチャンクはまず1～3バイトのチャンク基本ヘッダー(Chunk basic header)から始まります。

図4.6 チャンク基本ヘッダーの構造

最初の1バイトのうち、上位2ビット(ビット0～1)には後に続くチャンクメッセージヘッダー(Chunk message header)のフォーマットタイプが入っています。 下位6ビット(ビット2～7)にはチャンクストリームIDが入っています。

チャンクストリームIDは6ビット分あるので0～63まで表すことができますが、0番と1番はより大きなIDを使いたい時のための拡張用として予約されています。

チャンクストリームID部分に0が入っていたら1バイト、1が入っていたら2バイトの拡張チャンクストリームIDが続きます。 拡張チャンクストリームIDには0～255か0～65535の値が入っていますが、これに64を足したものが実際のチャンクストリームIDとなります(0～63だったら1バイトのヘッダーで足りるので)。つまり64～319番または64～65599番までのチャンクストリームIDはこの拡張チャンクストリームIDで表されます。

2バイトの拡張チャンクストリームIDを使う場合、拡張部分はリトルエンディアンになっていることに気をつけてください。

このチャンクストリームIDというのは自由に決められるのですが(0～2を除く)、63より大きなチャンクストリームIDを使うとチャンク毎のデータが大きくなってしまいます。 基本的には3～63の範囲のチャンクストリームIDを使い、拡張はよほどのことがない限り使わないと思います。

4.2. チャンクメッセージヘッダー

チャンク基本ヘッダー(と拡張チャンクストリームID)の後には、チャンクメッセージヘッダーが続きます。 このヘッダーのフォーマットはチャンク基本ヘッダーの上位2ビットの値(チャンクメッセージヘッダーのタイプ)によって変わります。

チャンクメッセージヘッダーには、タイムスタンプ・メッセージ長・メッセージタイプID・メッセージストリームIDが含まれるのですが、メッセージ毎にこれを送っていると送りたいメッセージボディのデータに対してヘッダ部分が大きくて通信効率が悪くなってしまいます。 そのため、前のチャンクと同じ値は省略してヘッダサイズを小さくできるフォーマットが用意されています。

4.2.1. タイプ0

チャンク基本ヘッダーの上位2ビットの値が0の場合はタイプ0と呼ばれるチャンクメッセージヘッダーが入ります。

タイプ0のチャンクメッセージヘッダは全部入りの省略無しパターンです。これが基本形です。

図4.7 チャンクメッセージヘッダー タイプ0の構造

タイムスタンプには、チャンクの絶対タイムスタンプ(通信開始時からの経過時間をミリ秒単位)を指定します。 3バイトありますが、もし足りなくなったら0xFFFFFF(16777215)を入れておき、後に続く4バイトの拡張タイムスタンプを使用します。

メッセージ長には後に続くメッセージデータのバイト数が入ります。3バイトあるので最大は16777215です。それより長いメッセージは作れません。

メッセージタイプIDには、1バイトのメッセージタイプが入ります。メッセージタイプIDによってメッセージの中身が動画か音声かはたまた各種コマンドかといった判別をします。どんな種類があるかはまたあとで見ていきましょう。

メッセージストリームIDには4バイトでそのままメッセージストリームIDが入ります。ここだけなぜかリトルエンディアンで入っていますので注意してください。 4バイトありますが有効なのは24bit分で、(リトルエンディアンなので)最後の8bitは常に0が入ります。 ちなみに、メッセージストリームIDとチャンクストリームIDは別物です。ストリームIDて前にあったような？と混乱しますが気をつけてください。

4.2.2. タイプ1

基本ヘッダの上位2ビットの値が1の場合はタイプ1と呼ばれるチャンクメッセージヘッダが入ります。

タイプ1のチャンクメッセージヘッダは、メッセージストリームIDが省略されタイムスタンプが差分になったパターンです。

図4.8 チャンクメッセージヘッダー タイプ1の構造

タイプ0で絶対タイムスタンプが入っていたところは、差分タイムスタンプが入っています。 差分タイムスタンプには、「前のチャンクからの」経過時間がミリ秒単位で入ります。 通信開始時からの経過時間ではないところが重要です。

長い時間通信していると通信開始時からの経過時間は3バイトで収まらなくなり、拡張タイムスタンプを使いまくることになってしまいますが、前のチャンクからの経過時間であればよほどのことがない限り3バイトで収まるはずです。これで比較的少ないバイト数で時間経過を表現することができます。

メッセージ長とメッセージタイプIDの内容はタイプ0と同じです。

メッセージストリームIDは省略されており、最後のタイプ0チャンクで指定されたものをそのまま使用します。

4.2.3. タイプ2

基本ヘッダの上位2ビットの値が2の場合はタイプ2と呼ばれるチャンクメッセージヘッダが入ります。

タイプ2のチャンクメッセージヘッダは、差分タイムスタンプだけが残ったパターンです。

図4.9 チャンクメッセージヘッダー タイプ2の構造

差分タイムスタンプの意味はタイプ1のものと同じです。

メッセージ長とメッセージタイプIDも省略されており、最後のタイプ0またはタイプ1のチャンクで指定されたものをそのまま使用します。

メッセージストリームIDも省略されており、最後のタイプ0チャンクで指定されたものをそのまま使用します。

特に音声データなどは固定長のデータを連続して送ることが多くなりますので、メッセージタイプID(動画だとか音声だとか)は連続していれば変わりませんし、固定長であればメッセージ長も変わりません。ヘッダ内で変わるのは時刻だけ、という場合にタイプ2のチャンクメッセージヘッダが使われます。

4.2.4. タイプ3

基本ヘッダの上位2ビットの値が3の場合はタイプ3と呼ばれるチャンクになります。

タイプ3のチャンクメッセージヘッダはありません。全部省略されちゃいました。

図4.10 チャンクメッセージヘッダー タイプ3の構造

メッセージ長とメッセージタイプID、メッセージストリームIDは最後のタイプ0(または1)チャンクで指定されたものをそのまま使用します。

差分タイムスタンプもないですが、最後のチャンクの差分タイムスタンプ(または絶対タイムスタンプ)の値を、このチャンクの差分タイムスタンプとして採用します。 わかりやすく言うと、この前のチャンクがタイプ1かタイプ2だった場合は、それの差分タイムスタンプの値を、このチャンクの差分タイムスタンプとして使います。 前のチャンクがタイプ0だった場合は通信開始時からの絶対タイムスタンプが入っていますが、そうだったとしても、その値を無理矢理このチャンクの差分タイムスタンプとして使います。

4.2.5. ヘッダの省略とチャンクストリームID

チャンクメッセージヘッダは省略して前のチャンクのものを採用することがありますが、どのチャンクの物も採用するわけではありません。

別なチャンクストリームIDのチャンクは別な接続として扱うべきで、省略された値は同じチャンクストリームIDの前の値を採用してください。

たとえば、以下のチャンクがA→B→C→Dの順に流れたとします。

表4.1 チャンクメッセージヘッダ省略の例

通信順番

チャンク ストリームID

チャンクタイプ

タイムスタンプ

メッセージ ストリームID

メッセージ長

チャンクA

2

タイプ0

絶対:0

0

42

チャンクB

3

タイプ0

絶対:100

1234

42

チャンクC

2

タイプ0

絶対:100

0

42

チャンクD

3

タイプ1

差分:100

-

52

この時に、最後のチャンクDはチャンクストリームIDが同じ3であるチャンクBからの差分であるものとします。 一つ前はチャンクCですが、チャンクストリームIDが異なるので差分の元としては採用しません。

上のチャンクで省略されたのを合成した結果は以下のチャンクA’～D’のようになります。

表4.2 チャンクメッセージヘッダ省略の合成例

通信順番

チャンク ストリームID

タイムスタンプ

メッセージ ストリームID

メッセージ長

チャンクA’

2

絶対:0

0

42

チャンクB’

3

絶対:100

1234

42

チャンクC’

2

絶対:100

0

42

チャンクD’

3

絶対:200

1234

52

これにより一つの接続に複数のストリームを同時に載せてもチャンクメッセージヘッダが大きくならずに済みます。

逆に実装する側としてはチャンクストリームID毎に、前のチャンクを保持してないといけません。

あと、前のチャンクがないチャンクストリームIDでいきなりタイプ1～3の省略されたチャンクが来た場合に、省略された部分をどうすべきかなのですが、これも仕様書にははっきり書かれていません。 そんなことすんなよてのが正直なところですが出来てしまうので、もし来た時には省略された部分の前の値は0が入っていたものとするのが良いかと思われます。知らんけど……。

4.3. チャンクストリームIDの割り当て

以前にチャンクストリームIDは0～2を除いて自由に使えると説明しましたが、自由にと言われてもざっくりしすぎてて困りますね。

どうしたもんかというと、まあ本当に自由に決めていいので、3番固定とかでもかまいません。マジで。

ただ複数のメッセージストリームを重畳する場合は、3番固定ですとチャンクメッセージヘッダに頻繁にメッセージストリームIDを載せないといけないので省略が効きづらくて効率が落ちてしまいます。 ですので、メッセージストリームID一つにたいし、チャンクストリームID一つを割り当てるのが良さそうです。

新しいメッセージストリームIDを割り当てたら、チャンクストリームID3番を、また新しいメッセージストリームIDを割り当てたら、チャンクストリームID4番を、というように使っていくのが良いと思います。

普通に割り当てられるメッセージストリームIDは1番からなので、チャンクストリームIDもメッセージストリームID+2で自動的に割り当てるのも可能です。 しかしメッセージストリームIDは1～16,777,215番まで自由に使えるのに対し、チャンクストリームIDは3～63までなら1バイト、64～309なら2バイトと、値が小さい方が通信効率が良くなります。 メッセージストリームIDの割り当てはサーバー側で行うため[6]、メッセージストリームIDに何が入ってくるかわからない場合にはチャンクストリームIDには独立した値を割り当てた方が安心です。 もしあなたがサーバーを書いている場合は、メッセージストリームIDをなるべく1～61の間で割り当ててしまう手もありでしょう。

4.4. 拡張タイムスタンプ

タイプ0～2の絶対タイムスタンプや差分タイムスタンプに0xFFFFFF(16777215)が入っていた場合は、チャンクメッセージヘッダの後に4バイトの拡張タイムスタンプが続きます。

拡張タイムスタンプがある場合は、ここの値を絶対タイムスタンプや差分タイムスタンプのかわりに採用します。

さすがに差分タイムスタンプが4バイトで足りないことはないと思いますが、通信開始時からの経過時間を入れる絶対タイムスタンプでは4バイトで足りなくなることがあります。 だいたい50日弱で足りなくなるようですが、その場合は単純にループして0に戻ります。ループしてもちゃんと動くようにしておきましょう。

4.5. チャンクデータ

拡張タイムスタンプ(あれば)の後にはチャンクデータが続きます。

チャンクデータの長さはチャンクメッセージヘッダに書かれているメッセージ長とチャンクの最大長によって決まります。 チャンクの最大長はあとで説明するSet Chunk Sizeメッセージで設定されますが、既定値は128バイトです。 メッセージ長とチャンク最大長のどちらか短い方の分だけチャンクデータが続きます。

チャンクデータの中身はメッセージタイプIDによって決まります。

4.6. メッセージの分割

チャンクデータはチャンク最大長分までしか入りませんが、メッセージ長はそれより大きい場合があります。 その場合は複数のチャンクに分割してメッセージを送信します。

ただ、メッセージの分割については仕様にあまりはっきり書いてません。 分割する場合はタイプ3で続きのチャンクデータを送るのが普通、という感じには書いてありますが、もしそれ以外のタイプで送られた場合にどうすべきかというのがはっきりしません。

普通に考えると、タイプ3相当の場合には続きであると判定するのが良さそうです。

- 前のチャンクとチャンクストリームIDが同じ

- 前のチャンクでまだメッセージ長分のデータを受け取っていない(残りがある)

- 前のチャンクとメッセージストリームIDが同じ

- 前のチャンクとタイムスタンプが同じ

- 前のチャンクとメッセージ長が同じ

以上の条件が成立する場合には続きのチャンクデータと扱いましょう。

たとえばチャンク最大長が128バイトの場合にメッセージ長が131バイトあった時には以下のように分割されます。

表4.3 メッセージの複数チャンクへの分割の例

通信順番

チャンク ストリームID

タイムスタンプ

メッセージ ストリームID

メッセージ長

チャンクデータ

チャンクA

3

差分:7144

1234

131

128バイト

チャンクB

3

差分:7144

1234

131

3バイト

メッセージが完成していないうちに、途中に別なチャンクストリームIDのチャンクが入った場合は完成していないメッセージはそのまま保持しておくべきですが、同じチャンクストリームIDでメッセージストリームIDが別なチャンクが挟まった時はどうすべきでしょうね。 仕様には何も書いてないのでわかりませんけど、破棄してしまって良さそうな気もします。そういうことすんなよってのはもっともですけど。

4.7. Acknowledgement

RTMP Chunk Streamではなぜか輻輳制御のような機能が付いており、一定バイト数を受け取ったらAcknowledgementメッセージを返さなければなりません。Acknowledgementメッセージの中身については後で説明します。

一定バイト数、というのは後で説明するWindow Acknowledgement Sizeメッセージで通信相手から指定されます。 通信開始時、または前回Acknowledgementメッセージを送ってから、指定のバイト数を受信した時点でAcknowledgementメッセージを送る必要があります。 そうしないとそれ以上のデータが送られてきません。

逆に送信する時も、Window Acknowledgement Sizeメッセージで指定したバイト数を送信したらAcknowledgementメッセージが受信されるまでそれ以上送信してはいけません。

Window Acknowledgement Sizeで指定されるバイト数は送受信でそれぞれ異なる値になることがあるので、別に管理してください。

Acknowledgementを送信するまでの受信バイト数は、チャンクストリームのボディのバイト数等ではなく、TCPで送受信するバイト数です。チャンクのハンドシェイクやヘッダも含みますし、チャンクストリームIDに関係なく全チャンクストリーム共通で管理してください。

それTCPレベルでやってるからいらなくない？という気もするんですけど、何故かやらないといけないんですよねえ……。

[6] クライアントがあとで説明するcreateStreamコマンドを発行することで、サーバーが新しいメッセージストリームIDを割り当てて返します。

5. RTMP Control Message

ここまででチャンクの送受信ができるようになりました！上手くいっていればな。

あとは受信できたメッセージの中身を処理します。

メッセージの中身はメッセージタイプIDから判別されます。

その中でも1、2、3、5、6の5つはRTMP Control Messageといって、接続全体の制御や設定に使うメッセージです。 RTMP Control Messageは必ずチャンクストリームIDは2番、メッセージストリームIDは0番を指定します。

5.1. Set Chunk Size メッセージ (タイプID:1)

メッセージタイプIDの1番はSet Chunk Sizeメッセージです。 自分が送信する最大のチャンク長を相手に通知します。

図4.11 Set Chunk Sizeメッセージの内容

メッセージの中身は4バイトのチャンク最大長です。 最上位1ビットは必ず0になるので、有効なのは31ビットで0x7FFFFFFFまで指定できますが、メッセージ長の最大が0xFFFFFFなのでそれ以上の値は事実上意味がありません。 また下限は1で、0は指定できません。ただし128以上が推奨されているのでそんなに小さくしない方が良いでしょう。

チャンク最大長はクライアントとサーバーそれぞれで管理します。たとえばクライアントがSet Chunk Sizeメッセージで0xFFFFFFを指定し、サーバーがSet Chunk Sizeメッセージで1024を指定した場合、クライアントは最大で16,777,215バイトのボディを持ったチャンクを送信できますが、サーバーは最大で1,024バイトのボディを持つチャンクしか送信しません。

チャンク最大長は既定で128バイトです。 Set Chunk Size メッセージを送信・受信しない限り、チャンク最大長は128バイトとしてください。

こちらからチャンク最大長を指定するならいくらにすべきかですが、一つのTCP接続で複数のチャンクストリームを載せる場合は一つのストリームで占有しないくらいのサイズにすべきでしょう。 ただ一つのTCP接続に複数のチャンクストリームを載せることはあんまりしないと思います。その場合は0xFFFFFFを指定しちゃいましょう。分割しないで済むならその方が良いです。

5.2. Abort メッセージ (タイプID:2)

メッセージタイプIDの2番はAbortメッセージです。 途中まで送ったメッセージの破棄を通知します。

図4.12 Abortメッセージの内容

メッセージの中身は4バイトのチャンクストリームIDです。 32bit指定できますが、チャンクストリームID自体がそこまで無いので、3～65,599の範囲だけが有効です。

指定したチャンクストリームIDにメッセージが分割で送られているときに、残りの部分を送らずにメッセージ全体をキャンセルする場合に使います。 これを受け取ったら、指定のチャンクストリームIDで分割の残り待ちしているメッセージを破棄してください。 使う理由がほとんどないので、たぶん使われないんじゃないかと思います。長いメッセージを送っている途中で接続を切りたい場合に、残りはないことを示すのに使うそうです。

5.3. Acknowledgement メッセージ (タイプID:3)

メッセージタイプIDの3番はAcknowledgementメッセージです。 一定バイト数受信したことを通知します。

図4.13 Acknowledgementメッセージの内容

メッセージの中身は4バイトのシーケンス番号です。 シーケンス番号とは単純に接続開始から今まで受信した総バイト数です。 総バイト数が4GB越えてたらどうするか書いてませんが、ループして0に戻ればいいんじゃないかと思います。

前にAcknowledgementの項で書きましたが、こちらから指定したAcknowledgement Window Sizeバイトだけ受信したらAcknowledgementを返信しない限り、相手からそれ以上の送信が行われません。 逆に相手に指定されたAcknowledgement Window Sizeバイトだけ送信したら、次にAcknowledgementを受信できるまで、それ以上の送信を行ってはいけません。

しかし、ちょうど双方がAcknowledgement待ちになってしまうとデッドロックしてしまいますね。 仕様書には何も書いてないのでどうすべきなのかはわかりません。 現実的にはAcknowledgement受信できるまで送信を止めるというのは無視してもいいんじゃないかと思います。 もちろんAcknowledgementの返信はしっかりと実装すべきですが。

5.4. Window Acknowledgement Size メッセージ (タイプID:5)

メッセージタイプIDの5番はWindow Acknowledgement Sizeメッセージです。 Acknowledgement メッセージを待たずに送信できるバイト数を通知します。

図4.14 Window Acknowledgement Sizeメッセージの内容

メッセージの中身は4バイトのAcknowledgement Window Size(単位はバイト)です。 32bitあるので0x00000000～0xFFFFFFFFまで指定できそうに見えますが、実装によってはここの値を符号付きで扱ってしまうものがあるようなので、0x7FFFFFFFまでにしておいた方が無難です。あとごく小さい値も指定できますが、ほとんどメッセージが送れなくなるのでやめましょう。小さくても4096以上にはしておいた方が無難でしょう。

Acknowledgement Window Sizeの既定値は困ったことに指定されていません。全くWindow Acknowledgement Sizeを受けとらなかった時にどうすべきかは書いていません。 指定されない限りはAcknowledgementを送る必要はないのかもしれませんが、よくわからないのでconnectコマンドの直後くらいにはWindow Acknowledgement Sizeメッセージで指定しておくのが無難でしょう。

こちらから指定するAcknowledgement Window Sizeはいくらにするべきかですが、これを積極的に使う理由が見当たらないので、0x7FFFFFFFとかとても大きい値を指定してしまっていいんじゃないかと思います。

5.5. Set Peer Bandwidth メッセージ (タイプID:6)

メッセージタイプIDの6番はSet Peer Bandwidthメッセージです。 相手にAcknowledgement Window Sizeをこのくらいにしてほしいなあというのを通知します。

図4.15 Set Peer Bandwidthメッセージの内容

メッセージの中身は4バイトのAcknowledgement Window Size(単位はバイト)と、1バイトの制限タイプです。

中身が似ているのでWindow Acknowledgement Sizeメッセージとの違いがわかりづらいのですが、Window Acknowledgement Sizeメッセージがこの値を使いますと相手に通知するものである一方で、Set Peer Bandwidthはこの値でWindow Acknowledgement Sizeメッセージを送ってくれませんか？と相手にお願いするものです。

このメッセージを受け取った場合は、制限タイプ(0～2)によって次のような動作をすべきです。

制限タイプの値が0の場合は、制限タイプは「ハード」です。通知されたAcknowledgement Window Sizeの値を使ってWindow Acknowledgement Sizeメッセージを送るべきです。

制限タイプの値が1の場合は、制限タイプは「ソフト」です。通知されたAcknowledgement Window Sizeが、前にWindow Acknowledgement Sizeメッセージを送った時のより大きければ、新しい値であらためてWindow Acknowledgement Sizeメッセージを送ります。通知された値が以前の値と同じかより小さければ特に何もしません。

制限タイプの値が2の場合は、制限タイプは「ダイナミック」です。前回のSet Peer Bandwidthメッセージの制限タイプが「ハード」だった場合、このメッセージも「ハード」として扱い、新しい値であらためてWindow Acknowledgement Sizeメッセージを送ります。前回のSet Peer Bandwidthメッセージの制限タイプがそれ以外だったら何もしません。

ただ、Set Peer Bandwidthメッセージですが、必ずこの動作をしないといけないわけではなく、しなくてもかまいません。SHOULD(すべきである)としか書かれていないので、Window Acknowledgement Sizeメッセージを送らなくても動作はします。

まあ動作自体はそんなに難しくないので実装してあげるといいんじゃないでしょうか。 相手にでかいAcknowledgement Window Sizeを指定してもらいたい時には自分から積極的に送るのも良いでしょう。

6. RTMP Chunk Streamのまとめ

RTMP Chunk Streamについては以上です。 いかがだったでしょうか。 なんだかめんどくさいですね。

RTMP Chunk Streamは仕様に書いてないところがあったり、チャンクストリームIDとメッセージストリームIDが別だったり、通信の設定をするのにメッセージの送受信ができないといけない循環構造になってたり、微妙な輻輳制御が入っていたり、読んでる分にはわかるような気がするけど実装しようとすると混乱しそうな点が詰まっています。

ここでRTMP Chunk Streamでの通信の流れをまとめておきます。

図4.16 RTMP Chunk Stream通信の流れ

まずハンドシェイクから始まり、クライアントとサーバーがC0～C2、S0～S2のパケットを送り合います。 これが終わって初めてメッセージの送受信ができるようになります。

次にRTMP Messageレベルの接続メッセージとして、connect コマンドが発行されます。 connectコマンドというのはこれから説明するRTMP Messageの物で、ここからRTMP Messageでの通信をするよーという意味です。今のところはあんま気にしなくて良いです。

接続できたらSet Chunk SizeやWindow Acknowledgement Sizeで通信設定をします。 しなくても動きますが、標準のチャンクサイズが128バイトと小さかったり、Acknowledgement Window Sizeがいくつなのかわからなかったりするので、最初に設定しておくのが良いでしょう。 また、Set Peer Bandwidthを送って相手にもAcknowledgement Window Sizeを送ってもらうよう通知すると良いかもしれません。

User Control MessageのStream Beginや_resultコマンドというのもあとで説明するRTMP Messageの物で、connect コマンドの返信です。

そのあと、しばらく通信すると設定したAcknowledgement Window Size分だけ受信すると思いますので、その度にAcknowledgementを返してください。 相手から通知されたAcknowledgement Window Size分だけ送信したら、Acknowledgementを受信するまで送信をやめてください。 ……ということになっていますが、現実的にはAcknowledgementを受信するまで送信をやめるというのはやらなくて良さそうです。 Acknowledgement送信ができなくてデッドロックする可能性がありますので。

このフローのようにすれば、なんとかRTMP Chunk Streamでの通信はできると思います。 RTMP Chunk Streamはなかなか面倒かつ微妙にふんわりした仕様で不安になりますが、これで通信が上手くいきさえすれば、さらに上のRTMP Messageはまだわかりやすいので頑張りましょう。

第5章 RTMP Message

RTMP MessageはRTMP Chunk StreamのチャンクやRTMP Control Messageよりアプリケーションに近いメッセージです。

RTMP Control MessageなんかはアプリケーションによらずRTMP Chunk Streamで通信する時にはいつも同じ処理をするものでしたが、RTMP Messageはアプリケーションによって処理される想定になっており、Flash Media ServerやFlash Playerでは(直接ではないにしろ)ActionScriptから操作できるようになっています。

RTMP Messageはアプリケーションに近いメッセージということもあって、いくらかActionScriptのオブジェクトについての知識が必要になっていきますが、それも都度説明していこうと思います。

1. RTMP Message Format

RTMP Message FormatはRTMP Messageのエンコードフォーマットです。 仕様書には記載されていますが、RTMP Chunk StreamでRTMP Messageをやりとりする場合にはほぼ使いません。

というのも、RTMP Message Formatに載るデータはメッセージタイプID・メッセージデータ長・タイムスタンプ・メッセージストリームIDとメッセージデータであり、RTMP Chunk Streamのレベルで既に載っているので通常は不要なのです。

使うのはあとで説明するAggregate Messageというメッセージ内にメッセージを入れる場合だけです。 なのでRTMP Message Formatの構造についてはAggregate Messageのところで説明しましょう。

2. RTMP Messageの種類

RTMP Messageもいくつか種類の種類に分けられます。

まず大きくはUser Control MessageとRTMP Command Messagesに分かれます。

User Control MessageはRTMP Mesageレベルの接続制御用メッセージです。 これはRTMP Messageではありますが、ほぼ機械的に処理されるメッセージです。

それ以外のRTMP Command Messagesがアプリケーションで処理すべきメッセージとなっています。

メッセージの種類はメッセージタイプIDによって識別され、対応は以下の表になっています。

表5.1 メッセージタイプIDとその中身

メッセージ タイプID

メッセージ種類名

簡単な内容

4

User Control Message

接続制御用のメッセージです。メッセージデータに持つイベントタイプによってさらに細かくわかれます。

17または20

Command Message

通信相手側の関数(メソッド)を実行するRPC用のメッセージです。メッセージデータには関数名やパラメーターを持ちます。17はAMF3用、20はAMF0用です。

15または18

Data Message

なんかしらのActionScriptのデータを送るメッセージです。典型的には動画や音声のメタデータ(ビットレート等)を送るのに使われます。15はAMF3用、18はAMF0用です。

16または19

Shared Object Message

ActionScriptのオブジェクト共有をするためのメッセージです。Data Messageと違い一度共有したオブジェクトの変更点だけを送ったりできます。16はAMF3用、19はAMF0用です。

8

Audio Message

音声データを送るメッセージです。

9

Video Message

動画データを送るメッセージです。

22

Aggregate Message

複数のメッセージをグループ化して一つにまとめたメッセージです。

RTMP MessageのメッセージタイプIDはRTMP Control MessageのメッセージタイプIDと被らないようになっているので無事に判別可能です。 一つの項目にレベルの違うプロトコルのデータが混ざってるのはやべえ気がしますけどそれがRTMPなので諦めましょう。

AMF0用やAMF3用と書いてあるのは、メッセージのデータ(主にオブジェクトデータ)がどんなフォーマットでエンコードされているかの違いです。 データのエンコードにAMF0とAMF3というフォーマットがあるのですが、どちらも使えるためメッセージタイプIDによって判別するようになっています。 AMFの中身についてはまたあとで説明します。

3. User Control Message (タイプID:4)

メッセージタイプIDの4番はUser Control Messageです。 User Control MessageはRTMP Messageレベルでの通信制御メッセージです。

User Control Messageは接続単位で処理されるメッセージなので多重化されません。常にチャンクストリームIDが2、メッセージストリームIDが0で送られます。

図5.1 User Control Messageの内容

メッセージの中身は2バイトのイベントタイプと、イベントタイプに応じたイベントデータです。

イベントタイプは以下のようになっています。

表5.2 User Control Messageのイベントタイプ

イベント タイプ

イベント名

送信方向 (S=サーバー、 C=クライアント)

内容

0

Stream Begin

S → C

新しいメッセージストリームの開始を通知します。

1

Stream EOF

S → C

指定したメッセージストリームの終了を通知します。

2

StreamDry

S → C

メッセージストリームでの通信が一定時間行われていないことを通知します。

3

SetBufferLength

C → S

クライアントが保持できる動画や音声のバッファ長をミリ秒単位で通知します。

4

StreamIsRecorded

S → C

指定したストリームが録画(録音)されているものであることを通知します。

6

PingRequest

S → C

クライアントに対して接続性確認のためPingResponseを返すように通知します。

7

PingResponse

C → S

サーバーの送ってきたPingRequestへの返信を通知します。

イベントタイプの5番はなぜか欠番です。

イベント名にスペースが入ってるものと入ってないものがあるのはなんなんでしょうね。仕様書に合わせたのでこうなってますけど大した意味はなさそうな気がします……。

3.1. Stream Begin イベント (イベントタイプ:0)

Stream Beginイベントはサーバーからクライアントに新しいメッセージストリーム開始を通知します。

図5.2 Stream Beginイベントの内容

イベントデータには4バイトのメッセージストリームIDが入ってきます。 このメッセージストリームIDはビッグエンディアンで入ります。

このメッセージストリームIDはサーバーからクライアントにメッセージを送るのに、これからこの番号を使いますよ、という通知です。

クライアントがサーバーに送るのに使うメッセージストリームIDと、サーバーがクライアントに送るのに使うメッセージストリームIDは同じなのか別なのかが気になるところですが、仕様にはなんとも書いていません。ただ、別にすると問題が出る箇所があるので同じ物として扱った方が良さそうです。

このイベントがどういう時に通知されるかというと、サーバーからクライアントにメッセージを送りたくなる前です。 基本的にはクライアントからサーバーに接続した時や、クライアントが新しいメッセージIDでサーバーにメッセージを送った時に、その返事を返すのに通知されます。

クライアントは今までこのイベントで通知された以外のメッセージストリームIDでメッセージを受け取った場合は、無視してかまいません。

3.2. Stream EOF イベント (イベントタイプ:1)

Stream EOFイベントはサーバーからクライアントにメッセージストリームの終了を通知します。

図5.3 Stream EOFイベントの内容

イベントデータには4バイトのメッセージストリームIDが入ってきます。 このメッセージストリームIDはビッグエンディアンで入ります。

指定したIDのメッセージストリームは終了、もうこれ以上メッセージを送りません、というのを通知します。 このイベントを受け取ったクライアントは、それ以降にこのメッセージストリームIDのメッセージを受け取っても無視してください。

3.3. StreamDry イベント (イベントタイプ:2)

StreamDryイベントはサーバーからクライアントに、メッセージストリームでの通信がしばらくないことを通知します。

図5.4 StreamDryイベントの内容

イベントデータには4バイトのメッセージストリームIDが入ってきます。 このメッセージストリームIDはビッグエンディアンで入ります。

指定したIDのメッセージストリームでサーバーからのメッセージ送信が一定時間ないことを通知します。

どういう時に使うのか書いていないのではっきりしませんが、おそらく動画再生時などにサーバー側にもデータが無い事を通知するのに使うのだと思います。 たとえば、ライブ配信を視聴していて配信側の問題でデータが途切れてしまった場合に、サーバーからクライアント間の接続は問題ないもののサーバー側にデータがないのでStreamDryイベントでそのことを通知するもの、じゃないかなあ……。

3.4. SetBufferLength イベント (イベントタイプ:3)

SetBufferLengthイベントは、クライアントが保持できる動画や音声のバッファ長をサーバーに通知します。

図5.5 SetBufferLengthイベントの内容

イベントデータには4バイトのメッセージストリームIDと4バイトのバッファ長(ミリ秒単位)が入ってきます。 このメッセージストリームID(とバッファ長)はビッグエンディアンで入ります。

このイベントはクライアントからサーバーに通知されるものです。 動画や音声再生の時に、クライアント側で保持できるバッファをサーバーに教えます。

ここで設定したバッファ長を埋められるようにサーバーが動画や音声を送れない場合はStreamDry イベントを通知するものじゃないかと思われます。

3.5. StreamIsRecorded イベント (イベントタイプ:4)

StreamIsRecordedイベントは、クライアントに動画や音声が録画(録音)されているものであること(ライブじゃないこと)を通知します。

図5.6 StreamIsRecordedイベントの内容

イベントデータには4バイトのメッセージストリームIDが入ってきます。 このメッセージストリームIDはビッグエンディアンで入ります。

指定したIDのメッセージストリームで送られる動画や音声がライブ配信でない時に通知されます。 追い掛け再生みたいな時はどうするのかは……まあアプリによって決めればいいでしょう。

3.6. PingRequest イベント (イベントタイプ:6)

PingRequest イベントは、クライアントに対して接続性確認のためPingResponseを返すように通知します。

図5.7 PingRequestイベントの内容

イベントデータには4バイトのタイムスタンプが入ってきます。 タイムスタンプの値は送信時の絶対タイムスタンプ値(通信開始時からの経過ミリ秒数)です。

通信ができてるか確認するイベントです。 クライアントはこのイベントを受け取ったら次のPingResponseイベントを返しましょう。

どんな時にこのイベントが送られるのかは謎です。

3.7. PingResponse イベント (イベントタイプ:7)

PingResponse イベントは、サーバーの送ってきたPingRequestへの返信を通知します。

図5.8 PingResponseイベントの内容

イベントデータには4バイトのタイムスタンプが入ってきます。 タイムスタンプの値はPingRequest受信時の絶対タイムスタンプ値(通信開始時からの経過ミリ秒数)です。

クライアントがPingRequestを受け取ったらこのイベントで返信しましょう。 サーバーが無事イベントを受け取れたら、クライアントと通信ができていることが確認されます。

3.8. User Control Messageまとめ

User Control Messageのイベントは以上です。

受け取った時の動作が具体的に決まっているのはStream Begin、Stream EOF、PingRequestくらいです。 その他はヒントのようなものなので、あんまり処理しなくても良いでしょう。 PingRequestは実際使うことないような気もしますし、特別処理しなくても動くんで、ほぼ全部処理する必要ないくらいではありますが……。

サーバーを実装する場合は少なくともStream Beginはちゃんと送るようにしましょう。他は最悪どうでもいいんじゃないかな。

4. Data Message (タイプID:15または18)

Data MessageはなんかしらのActionScriptのデータを送るメッセージです。

アプリケーション固有のデータを送るのに使いますが、典型的には動画や音声のメタデータ(ビットレート等)を送るのに使われます。

このメッセージはメッセージストリーム毎に送られますので、メッセージストリームIDは0以外が使われます。

図5.9 Data Messageの内容

Data Messageにはデータ名と値の列が入ってきます。 このデータ名と値はそれぞれActionScriptのデータ型で格納されます。 ActionScriptのデータ型のフォーマットはAMF0とAMF3というのがあり、どっちのフォーマットで入っているかによってメッセージタイプIDが異なります。 メッセージタイプIDが15の場合はAMF3、18の場合はAMF0で入っています。 違うのはフォーマットだけなので値の意味は同じです。 AMF0とAMF3についてはまたあとで詳しく説明します。

データ名と値にどんな値が入るかはアプリケーションによって決まりますので基本的には規定されていません。最低でもデータ名と値が1つは入りますが、値が2つ以上続く場合もあります。

動画や音声のメタデータについてはデータ名と値に関して規定がありますので紹介しましょう。

メタデータを送る時には、名前に文字列でonMetaData、値にオブジェクトまたは配列型でメタデータのプロパティ名と値の組を設定します。 onMetaDataの値に入るプロパティは以下のようになっています。

表5.3 onMetaDataのプロパティ

プロパティ名

値の型

値の内容

duration

数値

動画・音声の長さを秒単位で格納します。

filesize

数値

ファイルの長さをバイト単位で格納します。FLVでのみ使います。

creationdate

文字列

作成日時を文字列で格納します。

canSeekToEnd

真偽値

最後の動画フレームがキーフレームかどうかを格納します。

audiocodecid

数値

音声のコーデックIDを格納します。Audio Messageに入ってるコーデックIDと同じ値が入ります。

audiodatarate

数値

音声のビットレートをkbps単位で格納します。

audiodelay

数値

音声コーデックにより音声の遅延を秒単位で格納します。

audiosamplerate

数値

音声のサンプリング周波数を格納します。

audiosamplesize

数値

音声のサンプリング解像度を(たぶんビット単位で)格納します。

stereo

真偽値

音声がステレオかどうかを格納します。

videocodecid

数値

動画のコーデックIDを返します。Video Messageに入ってるコーデックIDと同じ値が入ります。

videodatarate

数値

動画のビットレートをkbps単位で格納します。

width

数値

動画の幅をピクセル単位で格納します。

height

数値

動画の高さをピクセル単位で格納します。

framerate

数値

動画の秒間フレーム数を格納します。

これらのプロパティはかならずしも全部入ってくるとは限りませんし、入れなくてもかまいません。特に上の方のduration・filesize・creationdate・canSeekToEndなんかはライブだと入れようがないですしね。

また、onMetaDataに入っているのは主に表示用の参考情報であり、再生に必要な情報は全てAudio MessageやVideo Message側に入っています。 onMetaDataは必須ではないので、無くても再生はできるようになっています。

他にXML形式のメタデータであるXMPを格納する、onXMPDataというのもあります。 onXMPDataの値に入るプロパティは以下のようになっています。

表5.4 onXMPDataのプロパティ

プロパティ名

値の型

値の内容

liveXML

文字列

XMPメタデータをXMLで格納します。

XMPメタデータについてはここでは解説しません。 気になる方はXMPの仕様[7]を確認してみてください。 ただこれがRTMPで実際使われているかはわかりません……。

また、配信時にはクライアントからサーバーに@setDataFrameと@clearDataFrameというデータが送られてきます。 これらはデータをその場で処理するのではなく、配信データとして記録することを指示しています。

@setDataFrameの場合は値が2つ続き、一つ目は記録するデータ名の文字列(onMetaDataとか)、二つ目は記録するデータの値(onMetaDataに設定するオブジェクトまたは配列など)が入ります。

@clearDataFrameの場合は値が1つ続き、データ名の文字列(onMetaDataとか)が入ります。

@setDataFrameが来た場合には、現在配信されているまたはこれ以降に配信される動画や音声のファイルに、メタデータとして名前と値の列を記録するように覚えておきます。 @clearDataFrameが来た場合には、前の@setDataFrameで覚えていたメタデータをクリアして、それ以降に配信される動画や音声のファイルにもメタデータとして記録しないようにします。

なんだかちょっと難しいですね。クライアントからサーバーへの配信時には、@setDataFrameで一段ラップされたonMetaDataが入ってくるという認識で大丈夫です。

[7] https://www.adobe.com/devnet/xmp.html

5. Audio Message (タイプID:8)

Audio Messageは音声データを送るメッセージです。

このメッセージもメッセージストリーム毎に送られますので、メッセージストリームIDは0以外が使われます。

図5.10 Audio Messageの内容

Audio Messageは先頭に1バイトのフォーマット情報が付いて、残りは基本的に各コーデックでエンコードされたデータです。 ただしコーデックがAACの場合のみフォーマット情報の後にもう1バイトのAACパケット種別が付いてきてから、AACのデータが続きます。

フォーマット情報にはさらにビット単位で情報が詰め込まれています。

フォーマット情報の0～3ビット目(上位4ビット)がコーデックIDです。コーデックIDは以下の表のようなものがあります。 この表以外にも番号は定義されていますが、実際は使えないようなので省略しています。 ただここに挙げてある中でも、今の動画配信だとMP3かAACしか使えないと思います……。

表5.5 Audio MessageのコーデックID

コーデックID

内容

1

ADPCM

2

MP3

3

リニアPCM(リトルエンディアン)

4

Nellymoser 16kHz モノラル

5

Nellymoser 8kHz モノラル

6

Nellymoser

10

AAC

11

Speex 16kHz モノラル

フォーマット情報の4～5ビット目(2ビット)はサンプリング周波数です。 2ビットだとそのままは入らないので以下の表から実際のサンプリング周波数を計算します。

表5.6 Audio Messageのサンプリング周波数

数値

サンプリング周波数

0

5.5kHz

1

11kHz

2

22kHz

3

44kHz

フォーマット情報の6ビット目(1ビット)は量子化ビット数が8bitか16bitかを表します。

表5.7 Audio Messageの量子化ビット数

数値

量子化ビット数

0

8bit

1

16bit

量子化ビット数はリニアPCMにのみ適用されます。 他のコーデックでは基本的に16bitか、コーデック固有のデータに含まれるヘッダで決定されます。

フォーマット情報の7ビット目(下位1ビット)はチャンネル数を表します。

表5.8 Audio Messageのチャンネル数

数値

チャンネル数

0

モノラル

1

ステレオ

コーデックによってはサンプリング周波数、量子化ビット数、チャンネル数は無視されます。 サンプリング周波数とチャンネル数が固定されているコーデックはもちろん、MP3とAACもコーデック固有のデータとして入っているヘッダ側であらためて指定されるため、フォーマット情報の値は無視されます。

コーデックがAAC(コーデックIDが10)の場合のみ、フォーマット情報の後にはAACパケット種別が1バイトの続きます。

AACパケット種別は0の場合はデータとしてAudioSpecificConfigが入ります。1の場合はAACの生データが入ります。

残りのデータはコーデック毎の生データが入ります。

MP3の場合はMP3ファイルのMPEGオーディオフレームデータが入ります。

AACの場合はAACパケット種別によって変わりますが、AACパケット種別が0の場合はAudioSpecificConfigが入ります。1の場合はAACのフレームデータが入ります。どちらも中身はMPEG-4のAAC仕様を参照してください[8]。

Speexは16bit、16kHz、モノラルで固定で、生データが入ります。

Nellymoserは正確にはAsaoというコーデックのようですが、詳細は非公開なようなので不明です。

Audio MessageはAAC以外のコーデックではどのパケットを取り出しても再生できるようになっています。 AACだけは最初にAudioSpecificConfigが必要になります。 AudioSpecificConfigさえあればその後はどのパケットを取り出しても再生可能です。

[8] AACのフォーマットはISO/IEC 14496-3を参照のこと。仕様書のお値段が高い。

6. Video Message (タイプID:9)

Video Messageは動画データを送るメッセージです。

このメッセージもメッセージストリーム毎に送られますので、メッセージストリームIDは0以外が使われます。

一つのVideo Messageは1フレーム分のデータを格納し、1フレーム分のデータを複数のVideo Messageに分割することは許されていません。 ただし、全部キーフレームというわけではなく、イントラフレーム(中間フレーム)でもかまいません。イントラフレーム自体が分割されてはならないという意味です。

図5.11 Video Messageの内容

Video Messageも先頭に1バイトのフォーマット情報が付いて、残りは基本的に各コーデックでエンコードされたデータです。 ただしコーデックがAVC(H.264)の場合のみフォーマット情報の後にもう1バイトのAVCパケット種別と3バイトのComposition Timeが付いてきてから、AVCのデータが続きます。

フォーマット情報にはさらにビット単位で情報が詰め込まれています。

フォーマット情報の0～3ビット目(上位4ビット)はフレーム種別です。フレーム種別は以下の表のようになっています。

表5.9 Video Messageのフレーム種別

フレーム種別

内容

1

キーフレーム(AVCの場合はシーク可能フレーム)

2

イントラフレーム(AVCの場合は非シーク可能フレーム)

3

破棄可能なイントラフレーム(H.263のみ)

5

ビデオ情報/コマンドフレーム

フォーマット情報の4～7ビット目(下位4ビット)はコーデックIDです。コーデックIDには以下の表のようなものがあります。

表5.10 Video MessageのコーデックID

コーデックID

内容

2

Sorenson H.263 (Flash Video)

3

Screen Video

4

On2 VP6

5

On2 VP6 (アルファチャンネル付き)

6

Screen Video V2

7

AVC (H.264)

Screen VideoはFlash独自のコーデックで、デスクトップの画面取り込みに特化したものです。 Sorenson H.263は標準のH.263をいじったもので、Flash Videoと呼ばれることもあるコーデックです。 On2 VP6は(今は亡き)On2 Technologiesという会社が作ったコーデックで、アルファチャンネル付きで透過ビデオが作れるのは面白いですね。 いずれにせよ、今の動画配信だとAVC(H.264)しか使えないと思いますが……。

コーデックがAVC(コーデックIDが7)の場合のみ、フォーマット情報の後には1バイトのAVCパケット種別と3バイトのComposition Timeが続きます。

AVCパケット種別は0の場合はデータとしてAVCDecoderConfigurationRecord、1の場合はAVCのNAL unitの列、2の場合はAVCのEnd of Sequenceを表します。

Composition TimeにはAVCパケット種別が1の時のみComposition Time Offsetがミリ秒単位で入ります。それ以外は常に0です。

残りのデータはコーデック毎のデータが入ります。

コーデック毎のデータはほぼFlash専用になるのでここではAVCのみ紹介します。他のコーデックについての詳細はSWFの仕様書[9]にありますので知りたい場合はそちらを参照してください。

コーデックがAVCの場合は、AVCパケット種別によって変わります。

AVCパケット種別が0の場合はAVCDecoderConfigurationRecordが、1の場合はNALUSampleが入ります。これらはmp4ファイルにAVCのデータを入れる時のフォーマットなので詳しくはそちらを参照してください[10]。2のEnd of Sequenceは何が入ってるかよくわかりません……。

NALUSampleはNAL Unitの列ですが、この先頭にはエンコーダーによってAccess Unit Delimiter[11]が入っていたりいなかったりするようです。 一つのVideo Messageは完成したピクチャ(フレーム)を一つだけ含むことになっているのでAccess Unit Delimiterは必要ないのですが、入れてくるエンコーダーが存在する以上はあってもなくても正しく処理できるようにしておくべきでしょう。

[9] https://www.adobe.com/devnet/swf.html

[10] より正確にはISO base media file formatですが、つまりmp4ファイルなので細かいことは気にするな。mp4にAVCなんかを入れるフォーマットはISO/IEC 14496-15を参照のこと。仕様書のお値段が高い。

[11] AVC(H.264)でフレームの区切りを表すためのデータ

7. Shared Object Message (タイプID:16または19)

Shared Object MessageはActionScriptのオブジェクトを複数のクライアントやサーバー間で共有するためのメッセージです。

このメッセージもメッセージストリーム毎に送られますので、メッセージストリームIDは0以外が使われます。たぶん。

Data Messageでもオブジェクトの送信はできますし、Command Messageでメソッド呼び出しはできるのですが、Shared Object Messageは変更点の通知などを行うことができます。

が、ここでは解説しません。 動画配信では関係ないのと、ActionScriptにそんなに詳しくないので具体的な使い方がよくわからないためです……。

仕様書には一応データ構造は書いてありますので気になる方は見てみてください。 例によって詳細は書いてないですけど。

8. Aggregate Message (タイプID:22)

Aggregate Messageは複数のRTMPメッセージをグループ化して一つにまとめたメッセージです。

図5.12 Aggregate Messageの内容

メッセージはRTMP Message Formatで並べ、メッセージ間にはバックポインターというものが挟まります。

RTMP Message Formatは次のような形になっています。Aggregate Messageはこれにバックポインターを付けたものを並べただけです。

図5.13 RTMP Message Formatの構造

バックポインターは4バイトで、一つ前のメッセージのサイズ(ヘッダを含む)をバイト単位で格納します。 しかしバックポインターの使い道は無いです。FLVファイルとなぜかフォーマットを合わせるために入っているようです。

Aggregate Messageに含まれる各メッセージのタイムスタンプは、このAggregate Message自身のタイムスタンプからの差分で格納します。 つまり、Aggregate Message自身のタイムスタンプは2345だったときに、含まれる最初のメッセージのタイムスタンプが0だったら、最初のメッセージのタイムスタンプは2345(0+2345)として処理されます。 含まれる二番目のメッセージがタイムスタンプ10だったら、2355(10+2355)として処理してください。

Aggregate Messageを使う意味ですが、仕様書によるとメッセージサイズを大きくできるのが利点とのことです。 最大チャンク長を大きく設定すると一つのチャンクで大きなメッセージを送信できるので、Aggregate Messageでまとめるとチャンクヘッダの分が節約される、という言い分です。

が、しかし。 Aggregate Messageは含まれるメッセージ一つずつにRTMP Message Formatのヘッダがフル(11バイト)で載る上に、4バイトの(本来不要な)バックポインターまで付いてきます。 RTMP Message Formatのヘッダに載る情報はRTMP Chunk Streamなら省略して小さくできるのに、Aggregate Messageだとフルで載せないといけないので逆に通信効率が落ちてますね。

使わねえわこれ。

9. Command Message (タイプID:17または20)

Command Messageは通信相手側の関数を実行するRPC用のメッセージです。メッセージデータには関数名やパラメーターを持ちます。17はAMF3用、20はAMF0用です。

Command Messageが一番アプリケーションレベルに近いメッセージと言いますか、ActionScriptからは主にこれを操作することになります。

このメッセージはコマンドによってメッセージストリームIDが0だったりそれ以外が使われたりします。

図5.14 Command Messageの内容

中身は全てActionScriptの値で、AMF0またはAMF3でエンコードされて入っています。

先頭のコマンドID、トランザクションID、コマンドオブジェクトはどのコマンドでも共通で入っています。 それ以降にはコマンドによっていくつか値が続いたり続かなかったりします。

コマンド名は文字列で、コマンド名(つまり関数名)を表します。当然必須です。

トランザクションIDは数値で、戻り値を受け取る時の識別子を設定します。戻り値が不要な場合は0が設定されます。 トランザクションIDに0以外が設定されている場合は、受け取った側はコマンド実行した戻り値を_resultコマンド(または_errorコマンド)に入れて返しますが、その際に同じトランザクションIDを設定することでどのコマンド実行の戻り値かを識別します。 何番を割り当てるかはコマンドを発行する側が適当に決めます。

コマンドオブジェクトはオブジェクト型(か配列型)またはnullが入り、なんかしらコマンドの情報を設定します。これはコマンドによって内容が変わります。使わない場合はnullが設定されます。

コマンドによってはコマンドオブジェクトの後にもいくつか値が続きます。受信時はメッセージの長さ分まで値を読み出してください。

Command Messageでやりとりできるコマンドについては次から見ていきましょう。

10. NetConnectionコマンドとNetStreamコマンド

Command MessageでやりとりするコマンドにはNetConnectionコマンドとNetStreamコマンドに分かれます。 中身は違いがないんで、仕様を読んでいても分かれてる意味を読み取るのが難しいところです。

まずNetConnectionとNetStreamというのはActionScriptのクラス名です。 ActionScriptでRTMP(やRTMFP)通信をしようとすると、この二つのクラスを使うことになります。

NetConnectionオブジェクトは接続自体を表し、こいつのconnect()メソッドを呼ぶとサーバーへの接続を確立します。 NetConnectionオブジェクトはデータの送受信は行わず、接続確立と切断など接続全体にかかわるコマンドの発行しかできません。

動画の再生や配信要求など、アプリケーションレベルのデータや要求の送受信はNetStreamオブジェクトを使います。

NetStreamクラスはコンストラクタにNetConnectionオブジェクトを受け取り、NetConnectionの接続上にメッセージのやりとりができるストリームを構築します。 一つのNetConnectionに対して複数のNetStreamオブジェクトを構築することも可能です。 つまり一つのNetConnectionに対して複数のNetStreamを多重化することができます。

察しの良い方はもうわかったかもしれませんが、NetConnectionオブジェクトはRTMP Chunk Streamの接続に対応します。 NetStreamオブジェクトはそれぞれがメッセージストリームID一つに対応します。

このためNetConnectionコマンドは常にメッセージストリームIDが0番で送受信されます。 ただ、メッセージストリームIDは0番ですが、チャンクストリームIDは2番以外を使ってください。 チャンクストリームID2番はRTMP Control MessageとUser Control Messageでのみ使います。

NetStreamコマンドはメッセージストリームIDが0番以外で送受信されます。 チャンクストリームIDももちろん2番以外を使ってください。

流れとしては、まずNetConnectionコマンドでアプリケーションレベルの接続を確立して、そのあとのNetStreamコマンドでアプリケーション毎の処理を行うようになります。

11. connectコマンド (NetConnection)

connectコマンドは一番最初に発行するコマンドです。クライアントからサーバーに送られます。 NetConnectionのコマンドなのでメッセージストリームIDは0番で送ります。 チャンクストリームIDは2番以外を使ってください。

図5.15 connectコマンドの内容

コマンド名は当然ながらconnectです。 connectコマンドは戻り値を受け取りますので、トランザクションIDには常に1が入ります。 connectコマンドの情報はコマンドオブジェクトにほぼ入ってきます。

ユーザー指定の引数にはアプリケーションで指定した追加の引数が入ってきます。 ActionScriptではNetConnectionオブジェクトのconnectメソッドに好きなだけ追加の引数が渡せるようになっているのですが、その値がユーザー指定の引数に入ってきます。 アプリケーション固有の物なので内容は決まっていません。

RTMPのハンドシェイクが終わったら、クライアントはなによりもまずこのコマンドを発行してください。 サーバーはconnectコマンドが来ないまま他のコマンドが来ちゃったら、無視するか切るかするといいんじゃないでしょうか。

重要なのはコマンドオブジェクトの内容です。ここにはオブジェクト型(または配列型)でプロパティと値の組が入ってきます。ちなみにプロパティ名は常に文字列です。

表5.11 connectコマンドのコマンドオブジェクトのプロパティ

プロパティ名

型

内容

値の例

app

文字列

接続対象のサーバーアプリケーションを指定し ます。

liveapp

flashver

文字列

クライアントのFlash Playerのバージョンを表 します。

FMSc/1.0

swfUrl

文字列

接続元のFlash(swf)ファイルのURLを表します。

http://example.com/hoge.swf

pageUrl

文字列

接続元のHTMLファイルのURLを表します。

http://example.com/hoge.html

tcUrl

文字列

接続対象のURLを指定します。

rtmp://example.com/liveapp/livestream

fpad

真偽値

プロキシを使っているかどうかを表します。

false

audioCodecs

数値

クライアントがサポートしている音声コーデック を表します。

0x0404

videoCodecs

数値

クライアントがサポートしている動画コーデック を表します。

0x0080

videoFunction

数値

クライアントがサポートしている動画操作を 表します。

1

objectEncoding

数値

クライアントがサポートしているオブジェクト エンコード方式(AMF0かAMF3)を表します。 AMF0のみの場合は0、 AMF3をサポートする場合は3が入ります。

3

場合によってはこのプロパティが全部あるとは限りませんし、ここに無いものが入ってくるかもしれません。たとえばクライアントが動画エンコーダーだったらswfUrlやpageUrlは無いでしょうし。

audioCodecsには以下の値(フラグ)を論理和(OR)で組み合わせた値が入ってきます。 フラグ名は仕様書に書いてあるので一応ここにも書いていますが、データとしては特に使いません。

表5.12 connectコマンドのaudioCodecs

値

フラグ名

対応コーデック

0x0001

SUPPORT_SND_NONE

リニアPCM

0x0002

SUPPORT_SND_ADPCM

ADPCM

0x0004

SUPPORT_SND_MP3

MP3

0x0020

SUPPORT_SND_NELLY8

Nellymoser 8kHz モノラル

0x0040

SUPPORT_SND_NELLY

Nellymoser 5～44kHz

0x0200

SUPPORT_SND_NELLY16

Nellymoser 16kHz モノラル

0x0400

SUPPORT_SND_AAC

AAC

0x0800

SUPPORT_SND_SPEEX

Speex

0x0FFF

SUPPORT_SND_ALL

全部

videoCodecsにも以下の値(フラグ)を論理和(OR)で組み合わせた値が入ってきます。 こちらのフラグ名もデータとしては特に使いません。

表5.13 connectコマンドのvideoCodecs

値

フラグ名

対応コーデック

0x0004

SUPPORT_VID_SORENSON

Sorenson H.263

0x0008

SUPPORT_VID_HOMEBREW

Screen Video

0x0010

SUPPORT_VID_VP6

On2 VP6

0x0020

SUPPORT_VID_VP6ALPHA

On2 VP6 (アルファチャンネル付き)

0x0040

SUPPORT_VID_HOMEBREWV

Screen Video V2

0x0080

SUPPORT_VID_H264

AVC(H.264)

0x0FFF

SUPPORT_VID_ALL

全部

videoFunctionにも以下の値(フラグ)を論理和(OR)で組み合わせた値が入ってきます。 詳しい意味はよくわかりません……。

表5.14 connectコマンドのvideoFunction

値

フラグ名

内容

0x0001

SUPPORT_VID_CLIENT_SEEK

クライアントがフレーム精度のシークができることを表します。

プロパティで重要なのはappとtcUrlでしょう。 一つのサーバーでは複数のアプリケーションをホスティングできるのでappでどのアプリケーションに接続したいか判別します。

tcUrlではサーバー名、アプリケーション名に加えて追加のパスも指定されています。 このURLはプロトコル名://ホスト名:ポート番号/アプリケーション名/インスタンス名の形で入ってきます。 プロトコル名はRTMPでつないでいるので当然rtmp、ホスト名はサーバーのホスト名、ポート番号は標準の1935の場合は省略されます。アプリケーション名はappに入っているのと同じで、インスタンス名はアプリケーションの複数のインスタンスがある場合にどのインスタンスに接続するかを指定するものです。省略した場合はデフォルトのインスタンスに接続します。

例のrtmp://example.com/liveapp/livestreamのうち、example.comはサーバー名、liveappはアプリケーション名で、livestreamがインスタンス名です。

インスタンスについては特に規定はないので、アプリケーションで適当に処理します。 複数のインスタンスに対応するか、対応する場合はどんな名前にするか、名前をどうやってクライアントに伝えるかはアプリケーション次第です。

場合によってはtcUrlには?user=name&pass=1234のようにパラメーターが付く場合があります。これらのパラメーターの処理はアプリケーションによります。

サーバーはconnectコマンドを受け取ったら結果を返す必要があります。

結果は_resultまたは_errorコマンドをクライアントに送るのですが、その前にUser Control MessageでStream Beginイベントを通知しておきましょう。 というのも、connectコマンドを受け取った時点ではサーバーからクライアントへのメッセージ送信は初めてのはずなので、サーバーからクライアントへの通信にメッセージストリームID0番を使いますよーと通知しておきます。これはメッセージストリームID0番でも必要なようです。無くても動く気はしますけど……。

図5.16 connectコマンドの結果の内容

コマンド名には_resultまたは_errorを入れます。使い分けについて詳しく書いてないのですが、まあ普通に接続できれば_result、アプリケーション名やインスタンス名が間違っていたりその他対応できない理由により続けられそうにない場合は_errorを返せばいいのではないでしょうか。

トランザクションIDはconnectコマンドに1が入ってきたはずなので、そのまま1にして返します。

コマンドオブジェクトにはサーバー側の接続情報が入ります。 またそのあとにInformationとして結果の追加情報が入ります。

結果のコマンドオブジェクトに以下のような情報が入ります。

表5.15 connectコマンド結果のコマンドオブジェクトのプロパティ

プロパティ名

型

内容

値の例

fmsVer

文字列

サーバーのバージョン(?)

FMS/3,5,5,2004

capabilities

数値

不明

31

mode

数値

不明

1

全部不明です。仕様書には何も書いてないので……。 サーバーに接続するとこんな値が返ってきてるっぽいというだけで、中身は不明です。 クライアントを作る場合には無視するしかないでしょう。 サーバーを作る場合には同じような値を返すしかないんじゃないかな……。

Informationには以下のような情報が入ります。

表5.16 connectコマンド結果のInformationのプロパティ

プロパティ名

型

内容

値の例

level

文字列

成功したかどうかを格納します。 成功の場合はstatus、 失敗の場合はerrorを入れます。

status

code

文字列

エラーコード(もしくは成功した状態コード) を格納します。

NetConnection.Connect.Success

description

文字列

エラーコードに対応した人間が読める メッセージを格納します。

Connection succeeded

data

オブジ ェクト

サーバーの情報を格納します。

{ "version", "3,5,5,2004" }

clientId

数値

クライアント毎に割り当てた ユニークなIDを格納します。

12356

objectEncoding

数値

主に使用するオブジェクトエンコード方式 (AMF0かAMF3)を格納します。 AMF0は0、AMF3は3を入れます。

3

こちらも仕様書には内容はほぼ書いてないんですが、まあまあ分かります。 data、clientId、objectEncodingについてはなんとなく入ってますが本当に必要かどうかは謎です。

level・code・descriptionがエラーや成功状態についての詳細を格納します。 descriptionは人が読むメッセージなのでなんでもいいです。 codeもアプリケーションによってしまうのでなんでも良いと言えばいいんですが、ある程度決まったコードがあるのでそれを使うのが良いでしょう。 levelはコードによってstatusかerrorが決まっているのでそれを使います。

connectコマンドの返信に入るcodeやlevelは以下のようなものがあります。

表5.17 connectコマンド結果のcodeとlevel

code

level

意味

NetConnection.Connect.AppShutdown

error

アプリケーションが終了しています。

NetConnection.Connect.Failed

error

接続試行が失敗しました。

NetConnection.Connect.InvalidApp

error

アプリケーション名が無効です。

NetConnection.Connect.Rejected

error

アプリケーションへのアクセス許可がありません。

NetConnection.Connect.Success

status

正常に接続できました。

これらの値を受け取った時にクライアントがどう反応すべきかは特に規定されていません。

接続失敗した時にはサーバー側は_errorコマンドを返すべきですが、そのあとどうしたらいいかも特に規定されていません。実装しようとするとどうしたらいいのかと悩むところですが、どうしようもないのでまあ切るしかないんじゃないでしょうか……。

12. createStreamコマンド (NetConnection)

connectコマンドで接続を確立したら、createStreamコマンドでサーバーにメッセージストリームIDを割り当ててもらって、NetStreamを使えるようにします。

createStreamコマンドはクライアントからサーバーに送られます。 NetConnectionのコマンドなのでメッセージストリームIDは0番で送ります。 チャンクストリームIDは2番以外を使ってください。

図5.17 createStreamコマンドの内容

コマンド名はcreateStreamです。 createStreamコマンドは戻り値を受け取りますので、トランザクションIDには0以外を入れます。connectコマンドと違って1である必要はありません。 コマンドオブジェクトにはnullが入ります。

サーバー側はこのコマンドを受け取ったら、新しいメッセージストリームIDを割り当てて返します。 新しいメッセージストリームIDは他と被ってなければ(ついでに0番でもなければ)何番でもかまいません。

サーバーからの返信は以下のようになります。

図5.18 createStreamコマンドの結果の内容

コマンド名は_resultです。 トランザクションIDにはcreateStreamコマンドに入ってきたものがそのまま入ります。 コマンドオブジェクトにはnullが入ります。 最後に新しいメッセージストリームIDを数値で入れます。

クライアントは受け取った新しいメッセージストリームIDを使って、以降のNetStreamコマンドを呼び出すことができます。

13. deleteStreamコマンド (NetConnection)

使わなくなったメッセージストリームIDはdeleteStreamストリームコマンドで削除を要求します。

deleteStreamコマンドはクライアントからサーバーに送られます。 NetConnectionのコマンドなのでメッセージストリームIDは0番で送ります。 チャンクストリームIDは2番以外を使ってください。

図5.19 deleteStreamコマンドの内容

コマンド名はdeleteStreamです。 戻り値は不要なので、トランザクションIDには0を入れます。 コマンドオブジェクトにはnullが入ります。 最後に削除するメッセージストリームIDを数値で入れます。

仕様書にはNetStreamのコマンドと書いてあるのですが、実際はNetConnectionのコマンド(メッセージストリームIDが0番)として送られるようです。そりゃそうよな。

14. closeコマンド (NetConnection)

接続を閉じるのを要求します。

closeコマンドはサーバーとクライアントどちらからも送ることができるんじゃないかと思います。 思います、というのは仕様には名前だけ書いてあって中身について書いてないからです……。 クライアントから送られることがあるかは不明ですけど。

NetConnectionのコマンドなのでメッセージストリームIDは0番で送ります。 チャンクストリームIDは2番以外を使ってください。

図5.20 closeコマンドの内容

コマンド名はcloseです。 戻り値は不要なので、トランザクションIDには0を入れます。 コマンドオブジェクトにはnullが入ります。 その後に何か値が続くかは不明ですがたぶん続きません。

これを受け取った側は接続を切ります。

15. その他のコマンド (NetConnection)

その他のコマンドとして、アプリケーション定義のコマンド呼び出しを行うことができます。

先にconnectコマンドを成功させていないと呼び出しできません。 NetConnectionのコマンドなのでメッセージストリームIDは0番で送ります。 チャンクストリームIDは2番以外を使ってください。

図5.21 アプリケーション定義コマンドの内容

コマンド名にはアプリケーションで指定したコマンド名が入ります。アプリケーション定義なのでなんでもいいんですが、既存のコマンドと被るのは明らかにまずいでしょう。 トランザクションIDには戻り値が不要なら0、必要なら0以外の適当な値が入ります。 コマンドオブジェクトはアプリケーション定義でなんかオブジェクトが入ります。無ければnullが入ります。 たぶん常にnullが入ってそうな気がします。

引数の値にはアプリケーションで指定した追加の引数が入ってきます。 アプリケーション固有の物なので内容は決まっていません。

戻り値を返す場合は以下のような形で返します。内容は決まっていません。

図5.22 アプリケーション定義コマンドの結果の内容

コマンド名には_resultまたは_errorを入れると良いと思います。たぶん。仕様にはなんとも書いてませんが。

トランザクションIDはリクエストに入ってきたものをそのまま入れます。

コマンドオブジェクトはわかりませんが、たぶんnullで大丈夫かと思います。

_resultで返す場合は戻り値にはアプリケーション固有の値を入れればよさそうです。

_errorで返す場合には次のような値の入ったオブジェクトを入れます。 必要であれば他の値を入れることも可能です。

表5.18 その他のコマンド結果のcodeとlevel

プロパティ名

型

内容

値の例

level

文字列

成功したかどうかを格納します。 成功の場合はstatus、 失敗の場合はerrorを入れます。

error

code

文字列

エラーコード(もしくは成功した状態コード) を格納します。

NetConnection.Call.Failed

description

文字列

エラーコードに対応した 人間が読めるメッセージを格納します。

Call failed

関数呼び出しの失敗時のコードはNetConnection.Call.Failedくらいしか規定されてません。

その他のコマンドとして、仕様には書かれていないものの巷で実際送られてくるものがいくつかあるので紹介します。 詳しくはどこにも書いてないので、具体的にどう処理したらいいものかわからないのですが、特に処理しなくても動きます……。

15.1. releaseStreamコマンド (NetConnection)

配信時にクライアントから送られてきたりします。

コマンドオブジェクトはnullで、引数にはファイル名が入った文字列が入ります。

このファイル名にこれから配信したいから使ってたら解放してください、という感じの通知なんじゃないかと思われます。

15.2. FCPublishコマンド (NetConnection)

配信時にクライアントから送られてきたりします。

コマンドオブジェクトはnullで、引数にはファイル名が入った文字列が入ります。

このファイル名にこれから配信するつもりです、という感じの通知のように見えます。

15.3. FCUnpublishコマンド (NetConnection)

配信終了時にクライアントから送られてきたりします。

コマンドオブジェクトはnullで、引数にはファイル名が入った文字列が入ります。

このファイル名への配信を終わります、という感じの通知のように見えます。

16. onStatusコマンド (NetStream)

ここからNetStreamのコマンドになります。

NetStreamのコマンドなのでメッセージストリームIDはcreateStreamで割り当てた番号を使います。

onStatusコマンドはサーバーからクライアントに送信されるコマンドです。 NetStreamのコマンドは多くが戻り値を使用しません。 かわりにサーバーはonStatusコマンドで結果や状態をクライアントに通知するようになっています。

中身はだいたい_resultとか_errorと同じです。

図5.23 onStatusコマンドの内容

コマンド名にはonStatusが、トランザクションIDは0、コマンドオブジェクトにはnullが入ります。

Infoには以下のような値の入ったオブジェクトを入れます。 必要であれば他の値を入れることも可能です。

表5.19 onStatusコマンドのInfoのプロパティ

プロパティ名

型

内容

値の例

level

文字列

成功したかどうかを格納します。 成功の場合はstatus、 失敗の場合はerrorを入れます。

error

code

文字列

エラーコード(もしくは成功した状態コード) を格納します。

NetStream.Play.FileNotFound

description

文字列

エラーコードに対応した 人間が読めるメッセージを格納します。

File Not Found

onStatusコマンドは特定のコマンドの戻り値ではなくサーバーの状態を通知するだけのコマンドです。 前に要求したコマンドのどれかが失敗したぽい……というくらいにしか通知されないので処理するのが面倒ですが、コードを見るとコマンド名に近い物が入っているので、ここから頑張って判別するしかないです。

17. playコマンド (NetStream)

playコマンドはサーバーに動画や音声の再生(送信)をリクエストします。

NetStreamのコマンドなのでメッセージストリームIDはcreateStreamで割り当ててもらった番号を使います。

図5.24 playコマンドの内容

コマンド名にはplayが、トランザクションIDは0、コマンドオブジェクトにはnullが入ります。

ストリーム名には再生したいファイル名を文字列で指定します。 ファイル名の指定にはなぜかフォーマットによる制限があり、flvファイルを再生したい場合は拡張子を含まないファイル名のみ(sampleなど)、mp3ファイルを再生したい場合は拡張子を含まないファイル名の頭にmp3:を付けたもの(mp3:sampleなど)、mp4のファイルを再生したい場合は拡張子まで含んだファイル名の頭にmp4:を付けたもの(mp4:sample.m4vなど)を指定することとなっています。

ストリーム名には文字列でなくfalseが入ってくる場合もあります。この場合は再生停止です。残りのパラメーターは無視されます。

再生開始時刻にはどこから再生開始するかを数値(秒単位)で指定します。この値は省略でき、その場合は-2が指定されたものとします。 ライブ配信を再生する場合には-2または-1を指定します。-2を指定した場合はライブ配信が行なわれている場合はそれを、行われていない場合は録画があれば録画を先頭から再生開始します。-1を指定した場合はライブ配信が行われている場合のみ再生開始します。 0以上の数値を指定すると、録画されているもののみ指定の秒数から再生開始します。

再生時間には再生する秒数を数値で指定します。この値は省略でき、その場合は-1が指定されたものとします。 -1を指定した場合はライブ配信または録画の終了まで再生し続けます。0を指定した場合は、1フレームだけ再生します。それより大きい値を指定した場合はその秒数分(それより早く終わったら最後まで)だけ再生します。

プレイリストのリセットにはすぐ再生するか、プレイリストに入れるかを真偽値または数値で指定します。 この値は省略でき、その場合はtrueまたは1が指定されたものとします。 falseや0を指定した場合は、既に再生中のものがあればそれが終わってから今回指定したファイルが再生されるように、内部的なプレイリストに追加します。 trueや1を指定した場合は、既に再生中のものや内部的なプレイリストは破棄し、すぐに指定したファイルが再生されるようします。 2と3はいまいち意味がよくわからないのですが、2はプレイリストを維持したまま、3はプレイリストを破棄し、どちらも全てのストリームメッセージを一括で返すようです。ストリームメッセージを一括で返す、の意味がよくわからない……。

playコマンドは戻り値を要求しません。そのかわりサーバーはonStatusコマンドで実行結果を通知します。

再生が上手くできる場合には、codeがNetStream.Play.ResetのonStatusコマンドと、codeがNetStream.Play.StartのonStatusコマンドの2つを送ります。levelはどちらもstatusです。 Resetが0やfalseだった場合はNetStream.Play.StartのonStatusコマンドだけ送ります。

再生が上手くできない場合には、codeがNetStream.Play.StreamNotFoundかNetStream.Play.FailedのonStatusコマンドを送ります。levelはerrorです。

再生停止を実行した場合には、codeがNetStream.Play.StopのonStatusコマンドを送ります。levelはstatusです。

再生開始できた場合は、この後にサーバーからVideo MessageやAudio Messageが垂れ流されます。 クライアントはそれを受け取って再生しましょう。

再生してたファイルが終わったら、サーバーはcodeがNetStream.Play.CompleteのonStatusコマンドを送ります。levelはstatusです。

18. play2コマンド (NetStream)

play2コマンドはサーバーに動画や音声の再生および切り替えをリクエストします。

基本的にはplayコマンドと同じく動画や音声の再生ができますが、現在再生中の動画や音声から上手いこと切り替えるためのパラメーターが増えています。

NetStreamのコマンドなのでメッセージストリームIDはcreateStreamで割り当ててもらった番号を使います。

図5.25 play2コマンドの内容

コマンド名にはplayが、トランザクションIDは0、コマンドオブジェクトにはnullが入ります。

パラメーターには以下のような値の入ったオブジェクトを入れます。

表5.20 play2コマンドのパラメーターのプロパティ

プロパティ名

型

内容

streamName

文字列

新しく再生したいファイル名を指定します。playコマンドのStream Nameと同じです。

oldStreamName

文字列

切り替え元のファイル名を指定します。切り替え前のファイルが無い場合はnullを指定します。

start

数値

新しく再生したいファイルの再生開始秒数を指定します。playコマンドのStartと同じです。

len

数値

新しく再生したいファイルの再生する秒数を指定します。playコマンドのDurationと同じです。

transition

文字列

新しく再生したいファイルへの切り替え方法を指定します。

offset

数値

transitionがswitchだった時に何秒後に切り替えるかを指定します。-1を指定すると可能な限りすぐに切り替えます。

transition以外はだいたいplayコマンドと同じですね。 transitionには次のような値が指定できます。

表5.21 play2コマンドのtransition

transitionの値

意味

append

新しいファイルをプレイリストの最後に追加して再生開始します。playコマンドのResetをfalseにしたのと同じです。

appendAndWait

新しいファイルをプレイリストに追加しますが再生は開始しません。resumeでプレイリストを再生開始することができます。

reset

プレイリストを破棄して新しいファイルを再生開始します。playコマンドのResetをtrueにしたのと同じです。

resume

前回の接続で止まったところから再生を再開します。

stop

再生を停止してプレイリストを破棄します。playコマンドのストリーム名をfalseにしたのと同じです。

swap

プレイリスト内のoldStreamNameをstreamNameで置き換えます。oldStreamNameが既に再生中の時は失敗します。

switch

再生中のoldStreamNameからstreamNameに再生を切り替えます。oldStreamNameがプレイリストに入っている場合はstreamNameで置き換えます。

resumeは接続エラーか何かで止まっちゃった時に続きから再生するのに使うようです。実現するにはサーバーがだいぶ頑張らないといけないですね。

switchは同じ動画の別ビットレートに切り替えるのに使います。全く別の動画への切り替えは想定していないようなので失敗する可能性があります。

サーバー側の返信はplayコマンドと同じonStatusコマンドを送って通知します。

再生の切り替えが行なわれた場合は、codeがNetStream.Play.Transition、levelがstatusのonStatusコマンドが送られるようです。

19. pauseコマンド (NetStream)

pauseコマンドはサーバーに再生中の動画や音声の一時停止と再開をリクエストします。

NetStreamのコマンドなのでメッセージストリームIDはcreateStreamで割り当ててもらった番号を使います。

図5.26 pauseコマンドの内容

コマンド名にはpauseが、トランザクションIDは0、コマンドオブジェクトにはnullが入ります。

一時停止/再開フラグには一時停止するのか再開するのかが真偽値で入ります。trueの場合は一時停止、falseの場合は再開です。

停止位置にはクライアントがどこで一時停止したのかをミリ秒単位の数値で指定します。 再開する時にはサーバーはこの時間以降のデータのみ送信します。

pauseコマンドもサーバーはonStatusコマンドで実行結果を通知します。

一時停止できた時はcodeがNetStream.Pause.Notify、再開できた時はcodeがNetStream.Unpause.NotifyのonStatusコマンドを送ります。levelはどちらもstatusです。

仕様によると操作が失敗した時は_errorコマンドを返すことになっていますが、トランザクションIDが0なんで_errorコマンドは返るか怪しいですね。実際どういう動作をするかはわかりません……。

20. seekコマンド (NetStream)

seekコマンドはサーバーに再生中の位置の変更をリクエストします。

NetStreamのコマンドなのでメッセージストリームIDはcreateStreamで割り当ててもらった番号を使います。

図5.27 seekコマンドの内容

コマンド名にはseekが、トランザクションIDは0、コマンドオブジェクトにはnullが入ります。

再生開始位置には再生開始する位置をミリ秒単位の数値で指定します。 プレイリストに複数のメディアが入っている場合には、プレイリストの先頭から時間を指定するようです。

位置の変更ができた場合はcodeがNetStream.Seek.Notify、levelがstatusのonStatusコマンドを送ります。この場合、seekPointというプロパティに変更した位置を数値で格納します。

失敗した場合はcodeがNetStream.Seek.FailedかNetStream.Seek.InvalidTime、levelがerrorのonStatusコマンドを送ります。 NetStream.Seek.InvalidTimeの場合はdetailsというプロパティに、変更できる最後の位置を数値で格納します。

仕様によると操作が失敗した時は_errorコマンドを返すことになっていますが、トランザクションIDが0で_errorコマンドは返せないので、失敗した時もonStatusコマンドで通知するのが正しいんじゃないかと思います。

21. receiveAudioコマンド (NetStream)

receiveAudioコマンドはクライアントが音声を受信するかどうかをサーバーに通知します。

NetStreamのコマンドなのでメッセージストリームIDはcreateStreamで割り当ててもらった番号を使います。

図5.28 receiveAudioコマンドの内容

コマンド名にはreceiveAudioが、トランザクションIDは0、コマンドオブジェクトにはnullが入ります。

音声受信可否には音声を受信するかどうかを真偽値で指定します。trueの場合は受信する、falseの場合は受信しません。

受信しないを通知した場合はサーバーはそれ以降音声データを送りません。受信するを再設定すれば音声の送信が再開されます。

受信するを設定した場合はサーバーはcodeがNetStream.Seek.NotifyとNetStream.Play.StartのonStatusコマンド2つを送ります。levelはどちらもstatusです。 受信しないを設定した場合はサーバーからの返答は何もありません。

22. receiveVideoコマンド (NetStream)

receiveVideoコマンドはクライアントが動画を受信するかどうかをサーバーに通知します。

NetStreamのコマンドなのでメッセージストリームIDはcreateStreamで割り当ててもらった番号を使います。

図5.29 receiveVideoコマンドの内容

コマンド名にはreceiveVideoが、トランザクションIDは0、コマンドオブジェクトにはnullが入ります。

動画受信可否には動画を受信するかどうかを真偽値で指定します。trueの場合は受信する、falseの場合は受信しません。

受信しないを通知した場合はサーバーはそれ以降動画データを送りません。受信するを再設定すれば動画の送信が再開されます。

受信するを設定した場合はサーバーはcodeがNetStream.Seek.NotifyとNetStream.Play.StartのonStatusコマンド2つを送ります。levelはどちらもstatusです。 受信しないを設定した場合はサーバーからの返答は何もありません。

23. publishコマンド (NetStream)

publishコマンドはサーバーにクライアントから動画(や音声)の配信を開始することを通知します。

NetStreamのコマンドなのでメッセージストリームIDはcreateStreamで割り当ててもらった番号を使います。

図5.30 publishコマンドの内容

コマンド名にはpublishが、トランザクションIDは0、コマンドオブジェクトにはnullが入ります。

ストリーム名には配信するファイル名(ストリーム名)を文字列で指定します。 指定できる名前はplayコマンドと同じです。 各種動画配信アプリではストリーム名はストリームキーという名前の項目で設定できることが多いです。 空文字列やnullの場合もあります。

配信タイプには配信種別を文字列で指定します。次のどれかが指定できます。 nullだった場合はliveと同じです。

表5.22 publishコマンドの配信タイプ

配信タイプの値

内容

live

ライブ配信です。ファイルには保存しません。既に同じ名前のファイルがあった場合は削除されます。

record

サーバーにファイルとして保存します。既に同じ名前のファイルがあった場合は上書きします。

append

サーバーにファイルとして保存します。既に同じ名前のファイルがあった場合は追記します。

publishコマンドもサーバーはonStatusコマンドで実行結果を通知します。

サーバーが受け入れる場合にはcodeがNetStream.Publish.Start、levelがstatusのonStatusコマンドを返します。

失敗した場合にはcodeがNetStream.Publish.BadName、levelがerrorのonStatusコマンドを返します。

再生開始できた(codeがNetStream.Publish.StartのonStatusを受け取った)あとはクライアントからVideo MessageやAudio Messageが垂れ流されます。 サーバーはそれを受け取って保存するなりライブ配信用にバッファリングするなりしましょう。

第6章 AMF0/3

度々出てきたものの、まだ解説していなかったAMF0とAMF3について説明しましょう。

AMFはAction Message Formatの略で、どちらもActionScriptの値をエンコード(シリアライズ)するフォーマットです。 ActionScriptを知らない人向けに簡単に説明しておくと、ActionScriptはJavaScriptとだいたい同じです。つまりAMFはJavaScriptの値をバイナリにシリアライズするフォーマットと思ってOKです。

RTMP Chunk Streamでは整数値ぐらいしか使いませんでしたけど、AMFでは文字列、数値(浮動小数点数および整数)、真偽値、null、undefined、日時、キーと値を組にした配列やオブジェクトなんかをエンコードできます。

AMF0はActionScript2まで用のフォーマット、AMF3はActionScript3用のフォーマットで、AMF3はエンコードできる型が増えてたりしますが、RTMPで使う分には大して違いはありません。 できることは大した違いがないのに中身は別物です。実装が面倒なだけですけど、仕方ないですね……。

ここだけの話、Adobeの製品はともかく他の実装はAMF0を主に使いますので、AMF3は実装しなくても動くかもしれません。

この先はAMF0とAMF3両方について詳細を説明していきます。 ただ、RTMPでは使わない部分については細かく説明しません。 必要であれば仕様書を確認してください。

1. AMF0

ここではAMF0の詳細について説明していきます。

まずAMF0内での数値のエンディアンについてですが、全部ネットワークバイトオーダー(ビッグエンディアン)になっています。RTMPと同じです。良かった。

AMF0での文字列は全部UTF-8で、先頭に2バイトまたは4バイトの整数値で文字列の長さ(バイト単位)が付きます。 つまり2バイト(または4バイト)の文字列長とそれに続く実際のUTF-8文字列で構成されます。 文字列の長さは基本的に2バイトの物が使われます。

AMF0のデータはまず1バイトのマーカーから始まります。 マーカーは値のデータ型を表わします。

表6.1 AMF0のマーカー

マーカー

型

0

数値

1

真偽値

2

文字列

3

オブジェクト

5

null

6

undefined

7

参照

8

連想配列

9

オブジェクト終端

10

普通の配列

11

日時

12

長い文字列

13

Unsupported

15

XML

16

型付きオブジェクト

17

AMF3への切り替え

マーカーの後は型ごとのデータが続きます。

これだけです。簡単ですね。AMF0の値が複数続く時にはまたマーカーから始まるだけです。

型ごとのデータ内容も説明していきましょう。

1.1. 数値型 (マーカー:0)

数値型は浮動小数点数が入ります。

ActionScript2ではJavaScriptと同じく数値は全部浮動小数点数なので、AMF0でも整数型はありません。

データとしては8バイトで浮動小数点数(IEEE-754の倍精度浮動小数点数)が入っています。

1.2. 真偽値型 (マーカー:1)

真偽値型はtrueかfalseを表す値が入ります。

データとしては1バイトの整数値で、0ならfalse、それ以外はtrueとして扱います。

1.3. 文字列型 (マーカー:2、12)

文字列型はUTF-8の文字列が入ります。

マーカーが2番の文字列では、データとして2バイトの長さ付き文字列が入ります。つまり最初に文字列の長さ(バイト単位)を表す2バイト整数が入り、その後に実際の文字列データが入ります。 文字列の長さが2バイト分しかないので655,535バイトまでの文字列だけが格納できます。

マーカーが12番の長い文字列では、データとして4バイトの長さ付き文字列が入ります。最初に文字列の長さ(バイト単位)を表す4バイト整数が入り、その後に実際の文字列データが入ります。 こちらは655,535バイトより長い文字列が格納できます。

空文字列の場合は文字列の長さとして0`が入り、残りの文字列データがないものとなります。

1.4. オブジェクト型 (マーカー:3)

オブジェクト型はプロパティ名と値の組を複数格納できるデータです。 プロパティ名は文字列のみですが、値はAMF0で表現できる好きな値を保持できます。

データはまず2バイトの長さ付き文字列が入り、その後にAMF0でエンコードされた値(AMF0のマーカーから始まる)が入ります。 長さ付き文字列の方がプロパティ名で、AMF0でエンコードされた値の方がプロパティの値になります。 そのあとには、また長さ付き文字列とAMF0でエンコードされた値が続き……というようにプロパティ名と値の組が続きます。 最後はプロパティ名に空文字列(長さ0)と値にオブジェクト終端(マーカー9番)が来るので、ここで終わりです。

マーカー9番のオブジェクト終端は、データは0バイトです。 つまりマーカーの1バイト分だけでオブジェクトの終端を表す値です。 ここだけでしか使われません。

オブジェクト型のプロパティの値にオブジェクトが入るような、入れ子構造にもなることに注意しましょう。

1.5. null型 (マーカー:5)

null型はnullそのものを表します。よく考えるとなんだか難しそうな言葉になってしまっていますが、真面目に考えないようにしましょう。

データはありません。つまりマーカーの1バイト分だけで、nullを表します。

1.6. undefined型 (マーカー:6)

undefined型はundefinedそのものを表します。これまた難しそうな言葉になってしまっていますが、そういうものとして受け入れましょう。

データはありません。つまりマーカーの1バイト分だけで、undefinedを表します。

1.7. 参照型 (マーカー:7)

参照型は、以前に出現したオブジェクトや配列への参照を表します。

同じオブジェクトや配列をもう一度参照するのに使うのですが、RTMPではほぼ使わないでしょうから詳しくは省略します。

データは参照インデックスとして2バイトの整数値が入ります。

1.8. 連想配列型 (マーカー:8)

連想配列は、ほぼオブジェクト型と同じで、プロパティ名と値の組を複数格納できるデータです。 連想配列はECMAScriptの配列とも呼ばれますが、ECMAScript(=JavaScript)の配列ってほぼオブジェクトと同じ扱いなので、AMF0に入っていても同じになります。

データは、まずエントリー数(プロパティ名と値の組数)が4バイトの整数値で入ります。 そのあとに2バイトの長さ付き文字列とAMF0でエンコードされた値(AMF0のマーカーから始まる)が交互に、それぞれエントリー数分だけ続きます。

オブジェクト型は空文字列のプロパティ名とオブジェクト終端の値が来たら終わりでしたが、連想配列型は最初に組数が付いているのが違いです。 逆に言うと違いはそれだけです。

1.9. 普通の配列型 (マーカー:10)

普通の配列型は、値のリストです。普通は配列と言うとこれだよなあというデータです。

データは、まずエントリー数(値の数)が4バイトの整数値で入ります。 そのあとにAMF0でエンコードされた値(AMF0のマーカーから始まる)がエントリー数分だけ続きます。

1.10. 日時型 (マーカー:11)

日時型はそのまま日時を表します。 内部的には1970年1月1日00:00:00(UTC)からの経過時間(ミリ秒単位)の数値を保持します。

データは、日時を表す8バイトで浮動小数点数(IEEE-754の倍精度浮動小数点数)とタイムゾーンを表す2バイト整数が入ります。 ただしタイムゾーンは常に0が入るのでUTCしか表現できません。

1.11. Unsupported型 (マーカー:13)

Unsupported型はエラーマーカーです。 なんかエンコードできないデータをエンコードしようとした時にこのマーカーを入れます。

通常は使いませんし、これに出会った時はエラーとしてください。

データはありません。

1.12. XML型 (マーカー:15)

XMLのドキュメントを表す型です。が、AMF0ではただの長い文字列として格納されます。

データは長い文字列(マーカー:12)と同じで、4バイトの長さ付き文字列が入ります。

1.13. 型付きオブジェクト型 (マーカー:16)

型付きオブジェクトはクラスのインスタンスを表します。 RTMPではクラスとか気にしないので、クラス名が付いてくるだけで扱いは普通のオブジェクトと同じです。

データは、まずクラス名が2バイトの長さ付き文字列で入ります。 その後にオブジェクト型(マーカー3番)と同じように、プロパティ名と値の組が続きます。 プロパティ名に空文字列(長さ0)と値にオブジェクト終端(マーカー9番)が来たら終わりです。

1.14. AMF3への切り替え (マーカー:17)

これ以降の値はAMF3でエンコードされていることを表すマーカーです。RTMPでは使わないでしょう。

データはありません。

2. AMF3

次にAMF3についても説明します。

AMF3内での数値のエンディアンも全部ネットワークバイトオーダー(ビッグエンディアン)になっています。

2.1. AMF3の整数値

AMF3ではU29という整数値の可変長エンコードが使われます。

これが大変めんどくさいのですが、最大29ビットの整数値を1～4バイトで表すフォーマットです。

各最上位1ビットを次のバイトもあるかどうかのフラグに使い、最上位1ビットが0だったらそこまでで終了、1だったら次のバイトも有りとなります。 ただし4バイト目(あれば)は最後なので最上位1ビットも数値データに含めます。

説明では難しいので図を見た方が早いですね。

表6.2 U29の値範囲とバイナリ表現

エンコードする値

1バイト目

2バイト目

3バイト目

4バイト目

0x00000000 ～ 0x0000007F

0xxxxxxx

なし

なし

なし

0x00000080 ～ 0x00003FFF

1xxxxxxx

0xxxxxxx

なし

なし

0x00004000 ～ 0x001FFFFF

1xxxxxxx

1xxxxxxx

0xxxxxxx

なし

0x00200000 ～ 0x3FFFFFFF

1xxxxxxx

1xxxxxxx

1xxxxxxx

xxxxxxxx

二進数の方のxは数値データとして採用するビットを表します。

これで小さな値はなるべく少ないバイト数で表しつつ、大きな値まで対応することができます。

2.2. AMF3の文字列

AMF3の文字列表現も解説しておきます。

AMF3の文字列もAMF0と同じように先頭に長さを表す整数値が付いたUTF-8文字列を扱います。

ただしAMF0とは違って長さを表す整数値がU29型です。 さらにU29で表された整数値の下位1ビットが、参照かどうかのフラグに使われます。 下位1ビットが0だったら残りの上位(最大)28ビット分を参照インデックスとして扱います。 下位1ビットが1だったら残りの上位(最大)28ビット分を文字列の長さ(バイト単位)として扱います。

数値が参照インデックスだった場合(下位1ビットが0だった場合)は後には文字列データは続きません。一連のAMF3データ中で出てきた(参照でない)文字列の、参照インデックス番目(0番目が最初)のものを値として採用してください。 つまりAMF3のデータをデコードする時に(参照でない)文字列が出てきたら、あとから参照されるものとして記憶しておく必要があります。

数値が参照インデックスでなかった場合(下位1ビットが1だった場合)は、残りの上位28ビット分だけそのあとに文字列データが続きます。あと参照されるものとして記憶しておいてください。

たとえば、「hello」という文字列は0x0B 「hello」となります。0x0Bは「hello」の長さである5を左に1ビットシフトして最下位ビットに1を論理和(OR)したものです((5 << 1) | 1)。

同様に空文字列は0x01になります。

2.3. AMF3の値

整数値と文字列のエンコードが面倒な以外はAMF3もAMF0もあまり変わりません。

AMF3の値も型を表す1バイトのマーカーから始まります。

表6.3 AMF3のマーカー

マーカー

型

0

undefined

1

null

2

false

3

true

4

整数値

5

浮動小数点数値

6

文字列

7

XMLドキュメント

8

日時

9

配列

10

オブジェクト

11

XML

12

バイト配列

マーカーの後は型ごとのデータが続きます。 AMF3の値が複数続く時にはまたマーカーから始まるだけです。

マーカーの番号や型こそ違いますが、形はAMF0と同じですね。

2.4. undefined型 (マーカー:0)

undefined型はundefinedそのものを表します。

データはありません。マーカーの1バイト分だけで、undefinedを表します。

2.5. null型 (マーカー:1)

null型はnullそのものを表します。

データはありません。マーカーの1バイト分だけで、nullを表します。

2.6. false型、true型 (マーカー:2、3)

false型、true型は真偽値のそれぞれfalseとtrueを表します。

データはありません。マーカーの1バイト分だけでfalseまたはtrueを表します。

AMF0と違い、false・trueそれぞれにマーカーの番号が割り振られているおかげでマーカーの1バイト分だけで真偽値を表すことができます。

2.7. 整数値型 (マーカー:4)

符号無し整数値を表します。

データはU29での整数値が入ります。

負の整数値やU29の最大値である0x3FFFFFFFを越える整数値は入りません。 それらは浮動小数点数としてエンコードされます。

2.8. 浮動小数点数値型 (マーカー:5)

浮動小数点数値型は浮動小数点数を表します。 負の整数値やU29の最大値である0x3FFFFFFFを越える整数値も浮動小数点数値型として入れられます。

データとしては8バイトで浮動小数点数(IEEE-754の倍精度浮動小数点数)が入っています。

2.9. 文字列型 (マーカー:6)

文字列型は文字列を表します。

データとしては、AMF3での文字列形式が入ります。

2.10. XMLドキュメント型、XML型 (マーカー:7、11)

XMLドキュメント型とXML型はXMLのデータを表します。 何が違うかというとActionScriptでのクラスの違いのようですが、AMF3上ではどちらもただの文字列として格納されます。

データとしては、AMF3での文字列形式が入ります。

2.11. 日時型 (マーカー:8)

日時型はそのまま日時を表します。 内部的には1970年1月1日00:00:00(UTC)からの経過時間(ミリ秒単位)の数値を保持します。

データは、まずU29で参照インデックスが入ります。参照インデックスの下位1ビットが0であれば、上位28ビット分を日時の参照インデックスとして扱い、これ以降のデータはありません。 参照インデックスの下位1ビットが1であれば、上位28ビットは無視し、このあとに8バイトで日時を表す浮動小数点数(IEEE-754の倍精度浮動小数点数)が入ります。

2.12. 配列型 (マーカー:9)

配列型はActionScript3の配列を表します。

AMF0では連想配列型と普通の配列型がありましたが、AMF3では両方が混ざった配列を表現できます。その分複雑です。つらい。

データは、まずU29で参照インデックスまたはエントリー数が入ります。参照インデックスまたはエントリー数の下位1ビットが0であれば、上位28ビット分を配列の参照インデックスとして扱い、これ以降のデータはありません。

参照インデックスまたはエントリー数の下位1ビットが1だった時は、上位28ビット分を配列の(普通の配列部分の)エントリー数として扱います。 普通の配列部分のエントリー数であるところが注意点です。連想配列部分のエントリー数は不定です。

配列が参照でなかった場合は、エントリー数の後には連想配列部分のエントリーが入ります。

連想配列部分はキーとなる文字列とAMF3でエンコードされた値の組が任意の数だけ続き、空文字列で終わります。AMF0のオブジェクト型と似ていますが、最後は空文字列だけで終わることに気をつけてください。オブジェクト終端の値はありません。キーに空文字列が来たらその瞬間に終わります。 連想配列部分が空の場合は、空文字列が1つだけが入ります。

連想配列部分の後には普通の配列部分が続きます。普通の配列部分はエントリー数分だけAMF3でエンコードされた値が入ります。

2.13. オブジェクト型 (マーカー:10)

オブジェクト型はActionScript3のオブジェクトを表します。 クラスが前提になった形式になっているため複雑です。つらい。

データは、まずU29で参照インデックス等の数値が入ります。参照インデックス等の数値の下位1ビットが0であれば、上位28ビット分を配列の参照インデックスとして扱い、これ以降のデータはありません。

参照インデックス等の数値の下位2ビットが01であれば、上位27ビット分をクラス情報の参照インデックスとして扱います。 以降にはプロパティの値だけが入ります。

参照インデックス等の数値の下位3ビットが111であれば、クラスは外部クラスであることを示します。上位26ビット分は無視されます。後には文字列で外部クラス名と、外部クラス固有のバイナリデータが続きます。外部クラス固有のバイナリデータは長さも内容不定で、外部クラスについてサーバー・クライアント間で合意が無いと使えません。つまりどういうことかというと、RTMPではほぼ使えないので気にすんなってことです。

参照インデックス等の数値の下位4ビットが0011であれば、クラスは通常クラスであることを表し、1011であれば動的プロパティを持つクラスであることを表します。残りの上位25ビット分はクラスの固定プロパティ数を表します。いずれもこの後にはクラス情報とプロパティの値が続きます。

クラス情報は参照インデックス等の数値の後に続きます(下位4ビットが0011か1011の時だけ)。 まずクラス名が文字列で入り、クラスのプロパティ名が参照インデックス等の数値に入っていた固定プロパティ数分だけ文字列で続きます。

クラス情報の後は固定プロパティの値が続きます。 クラス情報の固定プロパティ数分だけAMF3でエンコードされた値が入ります。

クラスが動的プロパティを持つクラスだった時のみ、最後に動的プロパティの値が入ります。 プロパティ名の文字列とAMF3でエンコードされた値の組が任意の数だけ続き、空文字列で終わります。 AMF0のオブジェクト型と似ていますが、最後は空文字列だけで終わることに気をつけてください。 オブジェクト終端の値はありません。プロパティ名に空文字列が来たらその瞬間に終わります。

AMF3のオブジェクト型はフォーマットだけ見るとめっちゃ難しいですね。 まず、外部クラスは使わないので無視しましょう。 そうすると、オブジェクト自体が参照のパターン、クラス情報だけ参照のパターン、全部入っているパターンの3つに分けられます。

全部入っているパターンでは、データはクラス情報とプロパティの値が続いています。 いくつプロパティの値が入っているか(そして動的プロパティがあるかどうか)はクラス情報に定義されています。

クラス情報だけ参照のパターンでは、データはプロパティの値だけが入ります。 いくつプロパティの値が入っているか(そして動的プロパティがあるかどうか)はクラス情報に定義されていますが、クラス情報はここには入ってきません。以前に出てきたクラス情報をインデックスで参照してください。

オブジェクト自体が参照のパターンでは、参照インデックスの後にデータは続きません。クラス情報もプロパティの値も前に出てきたオブジェクトを参照してください。

RTMPでは特にクラスが指定されているところがないのでAMF3のオブジェクト型が使われるのかはかなり疑問ですが、もし入ってきたとしてもクラスは無視されて、単にプロパティ名と値の組として扱われるでしょう。こんなに面倒なのに。

2.14. バイト配列型 (マーカー:12)

バイト配列型はバイナリデータを格納します。

データとしては、AMF3での文字列形式と同じように最初に長さまたは参照インデックスを表すU29の整数値が入り、下位1ビットが1だったら残り上位の28ビット分をバイト長としたバイナリデータが続きます。

下位1ビットが0だったら残り上位の28ビット分を参照インデックスとして、一連のAMF3データ中で出てきた(参照でない)バイト配列型データの、参照インデックス番目(0番目が最初)のものを値として採用してください。

2.15. AMF3の参照について

AMF3では文字列・日時・配列・オブジェクト・クラス情報・バイト配列・XMLドキュメント・XMLが参照インデックスで参照できるようになっています。

この参照インデックスってどんな値が入るかですが、最初に出現した参照でない値を0番、次の出現した参照でない値が1番……という形でテーブルを保持して、そのテーブルへのインデックス(0番とか1番とか)が入ります。

参照テーブルは文字列・クラス情報・その他の3つがあって、それぞれで別個に0番からインデックスを振っていきます。 その他の参照テーブルには日時・配列・オブジェクト・クラス情報・バイト配列・XMLドキュメント・XMLの全部がごっちゃに入ります。 インデックスが間違って型が違う値を参照してたらどうするんでしょうね。知らん。

ただ、この参照はRTMPではほぼ使いません。

参照テーブルは最初に出てきた値を0番としないといけないのですが、メッセージ毎にAMF0とAMF3を切り替えることができることからも、一メッセージ毎に参照テーブルをリセットするのが妥当なところでしょう[12]。

ただその場合、参照を使うところがないのです。メッセージ一つに同じ値が複数回出現するかというと、文字列でもしかしたらあるかも？程度です。なのでRTMPで使う場合はせいぜい文字列の参照について管理して、他は諦めるという手もあるでしょう。 自分からAMF3にエンコードする場合も、RTMPではなるべく参照は使わない方が良いと思われます。

[12] この「最初」の定義ははっきり規定はされてないのですが、もし接続の最初からだとした場合は、通信中に出現する値を全部保持しつづける参照テーブルを持つはめになり現実的ではありません。

3. RTMP Messageでの型の扱いについて

AMF0とAMF3について説明しましたが、RTMP Messageでは型はあまり細かく扱われません。

RTMP Messageで使われる型は文字列・数値・真偽値・オブジェクトくらいです。

数値は浮動小数点数も整数も同じに扱われます。

オブジェクトはプロパティ名と値の組として扱えればなんでもかまいません。 普通のオブジェクト、連想配列、型付きのオブジェクトのどれでも同じものとして扱ってください。 自分からエンコードする場合もどれでもいいです。まあ連想配列か普通のオブジェクトのどっちかが良いと思いますが。

想定する型と違う型の値が来た時にどう処理すべきかですが、ActionScriptの型変換規則に従うのが良さそうです。 この規則に収まらない場合(オブジェクトを期待するところに数値が来たとか)はエラー値として扱うしかないでしょう。

ActionScriptの型変換規則の概要を挙げておきます。

表6.4 ActionScriptの数値型への変換規則

変換元の値

数値への変換結果

真偽値

trueの場合は1、それ以外の場合は0

日時

1970年1月1日00:00:00(UTC）からの経過時間(ミリ秒単位)

null

0

オブジェクト

0

文字列

数値に変換できる場合は数値、それ以外の場合はNaN

undefined

NaN

表6.5 ActionScriptの真偽値型への変換規則

変換元の値

真偽値への変換結果

文字列

空文字列の場合はfalse、それ以外の場合はtrue

null

false

数値

NaNまたは0の場合はfalse、それ以外の場合はtrue

オブジェクト

true

表6.6 ActionScriptの文字列型への変換規則

変換元の値

文字列への変換結果

配列

すべての要素を文字列化したもの

真偽値

「true」または「false」

日時

日時の文字列表現

数値

数値の文字列表現

null

「null」

オブジェクト

「[object Object]」

第7章 RTMPで通信しよう

プロトコルの中身についての解説は一通り終わりました。

でもいざ通信してみようとすると、いつ何をどう送ったらいいものやらわかりませんね。

ここからは目的毎にどう通信していけばいいのか見ていきましょう。

1. 共通処理

まずは、何をするにも必ず通る道を見ていきましょう。

図7.1 接続からの共通処理

まずRTMPのハンドシェイクから始まります。ハンドシェイクについては前に説明したので詳細は省きます。

ハンドシェイクができたら、なにはさておきクライアントはconnectコマンドを発行します。 connectコマンドはNetConnectionのコマンドなのでメッセージストリームIDは常に0番です。

connectコマンドが来たらサーバーは_resultコマンド(失敗してたら_errorコマンド)を返します。 と行きたいところですが、サーバーが新しいメッセージストリームIDを使う場合はそれが0番だとしてもクライアントに通知する必要があります。 そのため、_resultコマンドを返す前にUser Control MessageのStreamBeginイベントでメッセージストリームID0番を使うことを通知します。

StreamBeginイベントでメッセージストリームID0番を使うことを通知したら、メッセージストリームID0番で_resultコマンドを返し、接続できたことを通知します。

クライアントはこの後NetStreamのコマンドを使いたくなるのですが、NetStreamのコマンドを使うには0番以外のメッセージストリームIDが必要になるので、createStreamコマンドで新しいメッセージストリームIDの発行を要求します。 createStreamコマンドもNetConnectionのコマンドなのでメッセージストリームIDは常に0番です。

サーバーは新しくメッセージストリームIDを適当な番号で割り当て_resultコマンドで返します。 この_resultコマンド自身のメッセージストリームIDは0番で、中に次から使えるメッセージストリームID(1番以降の値、ここでは1234番としましょう)が入っています。

これが一番簡単な流れです。最低限これだけあればたぶん動くんじゃないかな……。

実際にはもうちょっとだけ複雑になります。というのも接続確立した時点でいくらか通信設定をしたくなるので、RTMP Control Messageが飛び交います。

図7.2 接続からの共通処理詳細

クライアントがconnectコマンド発行したところで、Window Acknowledgement SizeやSet Peer Bandwidthなどのメッセージが飛び交うようになりました。 必ずしもこの順番やこのタイミングで行う必要はありませんが、Window Acknowledgement Sizeは早い段階でやっておいた方が良さそうなので、ここで指定するのが良いでしょう。 Window Acknowledgement SizeやSet Peer Bandwidthはクライアントから先に送ってもかまいません。

ついでにこのタイミングでSet Chunk Sizeを送ってチャンクの最大長を大きくしてもいいですね。 もちろん大きなデータを送るタイミングでやるのでもかまいません。

2. 切断処理

切断する時は大して手間はかかりません。

図7.3 切断処理

createStreamコマンドで割り当ててもらったメッセージストリームIDを、deleteStreamコマンドで解放要求しましょう。 deleteStreamコマンドはNetConnectionコマンドなのでこれ自身のメッセージストリームIDは0番です。中に解放したいメッセージストリームID(ここでは仮に1234番)を指定します。

あとは接続を切りましょう。ばちーん。

3. クライアントからサーバーへ動画配信したい

2019年にもなってRTMPを使う理由はクライアントからサーバーへ動画配信したいという目的くらいしかないでしょうし、まずこれから見ていきましょう。

図7.4 publish処理

まず共通処理まで済ませておいてNetStreamを使えるようにします。 割り当てたメッセージストリームIDは仮に1234だとしておきます。 実際には1234に限ったものではなく、1以降でサーバーが適当に割り当てた番号になります。

クライアントがpublishコマンドで配信開始を要求します。 このpublishコマンドのメッセージストリームIDは1234番です。 配信先のファイル名はhogeだとしておきましょう。

サーバーは配信開始の準備が出来たらonStatusコマンドでクライアントに通知します。 ここではonStatusコマンドはメッセージストリームID1234番で送り返したいのですが、サーバーが新しいメッセージストリームIDを使う場合にはStreamBeginイベントでクライアントに通知する必要があるのでした。 先にUser Control MessageのStreamBeginイベントでサーバーも1234番を使うことを通知し、それからonStatusコマンドを返します。 onStatusコマンドにはcodeがNetStream.Publish.Start、levelがstatusのオブジェクトを入れときます。

クライアントはcodeがNetStream.Publish.StartなonStatusコマンドを受け取ったら、あとは動画のデータを送りまくります。

その前にSet Chunk Sizeを送っておくのをおすすめします。チャンクの最大長設定はここでやらなくとももっと以前に十分なサイズを設定していればそれで良いですし、最悪初期値の128バイトのままでも動作はします。しかしながら動画や音声のデータは大きくなりがちで、あまりチャンクの分割が発生すると通信効率が落ちますので、動画や音声データを送る前には適当にチャンクの最大長を大きくしておくのをおすすめします。

動画や音声を送り始めたいところですが、まずメタデータを最初に入れておく必要があります。Audio MessageやVideo Messageより先にData MessageでonMetaData(やその他のメタデータ)を送りましょう。 その際に、クライアントからサーバーに配信する時には@setDataFrameという名前でData Messageを送る必要があります(中身はonMetaDataとその値)。単純にonMetaDataという名前のData Messageではだめなので注意してください。

あとは終わるまでAudio MessageやVideo Messageを送り続けて、気が済んだら切断処理をして終わりです。

受け取ったAudio MessageやVideo Messageやメタデータをサーバー側がどうするかは、サーバーの自由です。 普通はなんかしらのフォーマットで書き出すなり変換して保持して、他のクライアントに配信するとか保存することになると思いますが、どんなフォーマットで書き出すかやどうやって変換するかはサーバー(およびサーバーアプリケーション)の実装によるのでRTMPでは規定されません。

これが最低限の流れですが、実際の動画配信ソフトではいくつかNetConnectionコマンドでの追加の通知が挟まります。

図7.5 publish処理詳細

createStreamコマンドの前にreleaseStreamコマンドとFCPublishコマンドが挟まりました。 また切断処理の前にFCUnpublishコマンドも追加されました。 これらはNetConnectionのコマンドなので常にメッセージストリームID0番で送られます。 内容はいずれも配信ファイル名(今回の例ではhoge)を引数に一つ持つだけのコマンドです。 具体的な処理は特に書いてないのですが、サーバー側での追加の処理をするための通知コマンドなので、送っておいた方が良いでしょう。サーバーがこれらを受け取ったら、何かやりたかったらやればいいし、不要なら何もする必要はありません。

動画配信はこれで全部です。 細かい実装はわりと面倒なのですが、流れとしてはそれほど複雑ではないです。

4. 動画の視聴をしたい(サーバーからクライアントへの動画の配信をしたい)

2019年にもなって、サーバーからクライアントへの動画の配信をRTMPでしたいということはないでしょう。 他の方法を使ったほうがいいよ？

とはいえ、せっかくplayコマンドの説明も書いたので解説はしておきましょう。

図7.6 play処理

まず共通処理まで済ませてNetStreamを使えるようにしたら、クライアントがplayコマンドで再生開始を要求します。 このplayコマンドのメッセージストリームIDは1234番です。 配信先のファイル名はここでもhogeだとしておきましょう。

サーバーは再生開始の準備が出来たらonStatusコマンドでクライアントに通知します。

その前に再生するファイルが録画されていたものであれば、StreamIsRecordedイベントで1234番のストリームが録画であることをクライアントに通知しておきます。ライブ配信の場合にはこの通知は送りません。

onStatusコマンドはメッセージストリームID1234番で送り返したいのですが、先にUser Control MessageのStreamBeginイベントでサーバーも1234番を使うことを通知します。

playコマンドでプレイリストのリセットが要求されていた場合はcodeがNetStream.Play.Reset、levelがstatusのonStatusコマンドを先に送ります。

そして再生開始できたことを通知するためcodeがNetStream.Play.Start、levelがstatusのonStatusコマンドを送ります。

その後はサーバーから動画や音声データを送ります。

と、行きたいところですが、その前にサーバーからもSet Chunk Sizeを送っておきましょう。もちろんもっと前にもっと以前に十分なサイズを設定していればここでは必要ないですし、なんならもっと後でもいいんですが、まあこの辺りが妥当じゃないでしょうか。

動画や音声データを送り始める時には最初にメタデータを送っておきます。 Data MessageでonMetaData(やその他のメタデータ)を送りましょう。 サーバーからクライアントに再生用にメタデータを送る時には、@setDataFrameを使わずに直接onMetaDataという名前のData Messageを送ってください。 @setDataFrameを使うのはクライアントからサーバーへの配信時だけです。

あとは終わるまでAudio MessageやVideo Messageを送り続けます。

ファイル(やライブ配信)が終わったら、サーバーからcodeがNetStream.Play.Complete、levelがstatusのonStatusコマンドが送られます。

もしくは、クライアントがファイル名にfalseを指定したplayコマンドを送ると停止になり、サーバーからcodeがNetStream.Play.Stop、levelがstatusのonStatusコマンドが送られます。

いずれにせよ、こうなると再度再生開始しない限りはAudio MessageやVideo Messageは送られてきません。 切断処理をして終了しましょう。

play2コマンドの方は基本的に同じなので特に解説しません。 切り替え系の機能が増えていますが、詳細な動作に関してはサーバーの実装に強く依存しそうな感じです。

第8章 FLVファイル

RTMPで動画がやりとりできるくらいの通信方法を解説しましたが、 実際に実装してみると、本当にちゃんと送受信できてるかテストしようとして詰まることがあります。

というのも、送信した動画データを準備したり、受け取った動画データがちゃんと再生できる形になっているのか確認するのに動画ファイルの読み書きをしたいのですが、動画ファイルのフォーマットを調べたり形式を変換したりは結構大変です。 最終的にはやることになるかもしれませんが、とにかくRTMPで送受信できるか確認したいだけの時にはなんとか手間をかけずに済ませたいところです。

そんなあなたにおすすめのファイルフォーマットがFLVです！

FLVはFlashで使われていた動画フォーマットですが、なんとRTMP Messageのフォーマットがほぼそのまま入ります。 なのでRTMPで受信した動画データにちょこっとヘッダーを付けて保存するだけでFLVファイルが出来上がりますし、FLVファイルを読み込んでRTMPで送信するのも比較的簡単です。

FLVファイルの再生はオープンソースのメディアプレイヤー(VLCとか)で可能ですし、ffmpegなどを使えば他のフォーマットとの変換もできるので、少なくともテスト用には十分な使い勝手を持っています。

ここではFLVファイルの構造を紹介します。

1. FLVファイルの構造

FLVファイルの構造は以下のようになっています。

図8.1 FLVファイルの構造

最初にFLVファイルのヘッダが入り、その後はバックポインターとFLVタグが交互に続いていきます。 バックポインターは4バイトで、一つ前のメッセージのサイズ(ヘッダを含む)をバイト単位で格納します。 最初のバックポインターだけは0が入ります。

この構造は前にちらっと書きましたが、RTMP MessageのAggregate Messageとほぼ同じです。 FLVタグというのはほぼRTMP Messageそのままなので、Aggregate MessageはFLVファイルヘッダと最初のバックポインターが無いくらいの違いしかありません。 Aggregate Messageの読み書きとコードが共用できそうですね。 まあAggregate Messageは使われませんけど。

ちなみにFLVファイルに入っている数値データも基本的にはビッグエンディアンです。

1.1. FLVファイルヘッダー

FLVファイルヘッダーは以下のような構造です。

図8.2 FLVファイルヘッダーの構造

最初は"FLV"の文字、そのあとに1バイトのバージョン(中身は0x01)で始まります。 次に1バイトの種別フラグが入ります。

種別フラグは次の値を論理和(OR)で組み合わせたものが入ります。

表8.1 FLVファイルの種別フラグ

種別フラグ値

内容

1

動画を含む

4

音声を含む

最後に4バイトのデータオフセットが入ります。データオフセットにはファイル先頭からファイルボディまでのバイト数を格納します。常に9が入ります。

めっちゃ簡単。5バイト目以外は固定値ですし、普通は動画も音声も入っているファイルでしょうし、全部固定値でもいいくらいですね。

1.2. FLVタグ

FLVタグは以下のような構造です。

図8.3 FLVタグ構造

RTMP Message Formatとほぼ同じです。

最初の1バイトはタグタイプIDが入ります。RTMPのメッセージタイプIDと同じですが、8と9と18しか入りません。

表8.2 FLVタグタイプID

タグタイプID

タグ名

簡単な内容

8

Audioタグ

音声データが入るタグです。

9

Videoタグ

動画データが入るタグです。

18

Scriptタグ

メタデータ(ビットレート等)がActionScriptのオブジェクトで入ります。エンコードはAMF0です。

ScriptタグはRTMPのData Messageに相当するものですね。エンコードはAMF0しか使えません。

次に3バイトのメッセージデータ長が入ります。FLVタグのヘッダを除くバイト数が入ります。

メッセージデータ長の後には4バイトのタイムスタンプが入りますが、格納方法が普通でないので気をつけてください。。 最初の3バイトにはビッグエンディアンでタイムスタンプの下位24分が入っており、次の1バイトはタイムスタンプの上位8bit分として扱われます。 タイムスタンプはFLVファイル先頭からの経過秒数をミリ秒単位で格納します。

タイムスタンプの後にメッセージストリームIDが3バイト続きます。FLVファイルの場合はメッセージストリームIDは常に0が入ります。

残りはタグタイプIDによりAudioタグかVideoタグかScriptタグのデータが続きます。

それぞれのデータ内容はRTMP MessageのAudio Message、Video Message、Data Messageと全く同じなので省略します。 Scriptタグでは名前に@setDataFrameは使わず、onMetaDataなどをそのまま指定する点だけ気をつけてください。 またAMF0しか使えないので、メッセージタイプIDが15番のAMF3のData Messageを受け取った時はAMF0に変換して18番にして保存するようにしてください。

1.3. バックポインター

FLVファイルヘッダーおよびFLVタグの後には4バイトのバックポインターが続きます。

バックポインターには直前のFLVタグのバイト数(ヘッダも含む)を入れます。 FLVタグのヘッダが11バイトなので、11+FLVタグのメッセージデータ長が入っているはずです。 ただしFLVファイルヘッダー直後のバックポインターだけは常に0が入ります。

2. FLVファイルまとめ

FLVファイルの構造は以上です。簡単ですね。

RTMPで受信したデータはFLVファイルヘッダーとFLVタグヘッダーとバックポインターを付けるくらいでFLVファイルになってくれます。 気をつけるところはタイムスタンプの格納方法がちょっと変なくらいです。

古い形式ではありますが、手軽に読み書きできるフォーマットなのでテスト用にはいろいろ使えるでしょう。

第9章 参考文献

RTMPは仕様書だけあっても何も全然わからないので参考文献はいっぱいあるよー！

1. ドキュメント類

この本でもだいたい説明したとは思うのですが、やっぱり細かいところを調べるのは重要です。

さらに他の解説あれば理解が深まるでしょうから、他のドキュメントも挙げておきます。

1.1. RTMPの仕様書

RTMPの仕様書は公開されています。

Real-Time Messaging Protocol (RTMP) specification (Version 1.0)

https://www.adobe.com/devnet/rtmp.html

仕様書がひどいようなことを散々言いはしましたが、とはいえだいたいは書いてあるのでまずはこれを見る必要はあるでしょう。

上記のページにはRTMPの仕様に関してのライセンスも載っています。

基本的にはストリーミングで配信されているRTMPの動画や音声をぶっこ抜いたり、暗号化の回避をするような物を作ることが禁止されているだけなので、まあ問題になるようなライセンスではないでしょう。 そもそも暗号化の回避ができるような仕様まで書いてないですしね。

1.2. WikipediaのRTMPのページ

WikipediaのRTMPのページ(英語版)がなにげにわかりやすくて参考になります。

Real-Time Messaging Protocol From Wikipedia, the free encyclopedia

https://en.wikipedia.org/wiki/Real-Time_Messaging_Protocol

仕様書にも書いていないような情報が書かれてたりしますが、はたしてその情報はいったいどこから……？

1.3. FLV・SWF・AMF0/3の仕様書

RTMPのVideo MessageやAudio Message、Data Messageなんかの中身はRTMPの仕様書には書いておらず、FLVの仕様を見る必要があります。

FLV and F4V File Format Specification

https://www.adobe.com/devnet/f4v.html

F4VはMP4とだいたい同じのファイルフォーマットで、最近はFLVは非推奨でこちらが使われているようです。 ただRTMPとは直接関係ないので、読む必要があるのは仕様書の後半にあるFLVのフォーマットだけです。

Video MessageやAudio Messageのコーデック毎にどんなデータが入っているかはFLVの仕様書にもあまり詳しくは書いていません。 それらはSWFの仕様書を見るともう少し詳しく書いてあります。

SWF File Format Specification (version 19)

https://www.adobe.com/devnet/swf.html

SWFファイルもFLVなんかとちょっと似た形式ですが、ちょっと似てるだけで結構違うのでコーデック部分以外はあんまり参考にしない方が良いでしょう。 まあRTMPとは関係なく読んでみる分には面白いですが。

AMFの仕様書も上記のSWFファイル仕様書と同じページに置いてあります。

1.4. MPEG-4の仕様書

Audio MessageやVideo MessageにAACだのAVCが入っているときの中身の詳細については、MPEG-4の仕様書を見ないとわかりません。

ISO/IEC 14496-3:2009 Information technology – Coding of audio-visual objects – Part 3: Audio

https://www.iso.org/standard/53943.html

ISO/IEC 14496-15:2017 Information technology – Coding of audio-visual objects – Part 15: Carriage of network abstraction layer (NAL) unit structured video in the ISO base media file format

https://www.iso.org/standard/53943.html

上のがAACの仕様で、下のがAVC(H.264)をMP4ファイルフォーマットに入れるための仕様です。

問題はMPEG-4の仕様書は有料かつめちゃくちゃお値段が高くて、パート毎に2万円を越えてきます。上記の2パートだけ欲しくても合計4万円越えです。 ちょっとだけ調べたいなあというには高すぎてなかなか手が出ないので、図書館などで探して必要な部分だけコピるのがおすすめです。 図書館にもそうそうないんですけど……。

1.5. ActionScriptのドキュメント

RTMPはActionScriptから使うのを想定した通信形式であるため、ActionScriptのドキュメントが役に立ちます。というかこれも見ないとエラーコードとかわかりません。

NetConnection (Adobe Flash Platform 用 ActionScript 3.0 リファレンスガイド)

https://help.adobe.com/ja_JP/FlashPlatform/reference/actionscript/3/flash/net/NetConnection.html

NetStream (Adobe Flash Platform 用 ActionScript 3.0 リファレンスガイド)

https://help.adobe.com/ja_JP/FlashPlatform/reference/actionscript/3/flash/net/NetStream.html

とりあえずNetConnectionとNetStreamクラスあたりをおさえておけばいいんじゃないかな。

あとサーバーサイドActionScriptというのもあります。これはAdobe Media Serverで動かすActionScriptです。こちらのドキュメントも参照しましょう。

Server-Side ActionScript Language Reference

https://helpx.adobe.com/adobe-media-server/ssaslr/topics/server-side-actionscript-language-reference.html

2. ツール類

RTMPを実装して試したいという時に便利なツールや、ソースが参考にもなるオープンソースソフトウェアについても紹介しておきます。

2.1. Adobe Media Server

Adobe Media ServerはAdobe純正のメディアサーバーでRTMPで通信するアプリケーションを作ることができます。

有償製品ではありますが、体験版が無料でダウンロードできます。 以前はAdobe Media Server Starterという無料版がダウンロードできたのですが、執筆時点ではStarterのダウンロードページに行くと30日間体験版のダウンロードページに行ってしまうので無くなったんでしょうか……。

Starterは接続数や接続していられる時間に強い制限が付いていて、実質開発にしか使えないバージョンでしたが、開発に使うにはとても便利でした。

Adobe Media Server

https://www.adobe.com/jp/products/adobe-media-server-family.html

一番右下もしくは機能比較ページからStarter版のダウンロードに行けますが、リンク先は体験版のダウンロードと出てくるのでStarter版はもうないかも？いずれにせよ要登録です。

上のサーバーサイドActionScriptのドキュメントもそうですが、Adobe Media Serverの開発者向けドキュメントは無料で読めます。動かせると実際の動作が検証できるので便利なんですが、ドキュメントだけでも参考になるでしょう。

Adobe Media Server Developer Center

https://www.adobe.com/devnet/adobe-media-server.html

2.2. RTMPDump・librtmp

C言語で書かれたオープンソースのRTMP実装であるlibrtmpと、それを使ったRTMPのダンプツールのRTMPDumpです。

RTMPDumpはテスト用に使えますし、librtmpは結構あちこちで使われているらしいです。

RTMPDump

http://rtmpdump.mplayerhq.hu/

オープンソースなので実装の参考にもなると思います。 RTMPDumpはGPLv2ですが、librtmpはライセンスがLGPLv2.1なのでプロプライエタリなアプリから使うのも可能ですね。

2.3. Open Broadcaster Software

オープンソースの配信用ソフトです。

デスクトップ画面(やカメラ画像)を取り込んでエンコードし配信するRTMPクライアントです。

RTMPを受信できる多くの配信サイトに対応しており、カスタムサーバーにも接続できるので自前のサーバーでテストする時に便利です。

オープンソースではありますが、RTMP部分はlibrtmpを使っているので、ソースを参考にするというよりは純粋にツールとして使えば良いかと思います。

Open Broadcaster Software

https://obsproject.com/

2.4. FFmpeg

言わずと知れた、オープンソースのメディア操作用ツールです。

FLVと他のフォーマットで変換に使えます。

また、FFmpeg自体もRTMPに対応しており、RTMPから動画を受信してファイルに保存したり、ファイルから読み取った動画をRTMPで配信したりというのも出来ます。 ちょっとコマンドがめんどくさいのでおすすめはしませんが。

RTMPから動画を受信して再生するにはffplayという動画再生ツールの方が便利でしょう。

また、RTMPもlibrtmpでなく独自で読み書きしていますし、FLVの読み書き対応もありますのでソースも参考になります。

FFmpeg

https://ffmpeg.org/

2.5. PeerCastStation

正直これを紹介するのはどうかとも思ったんですが一応……。

拙作のP2Pでストリーミング配信するソフトウェアです。

RTMPで受け取った動画をFLVにして独自プロトコルに入れてP2Pで流し、受け取ったクライアントはFLVのまま、もしくはMPEG-2 TSにしたり、またRTMPに戻してプレイヤーで視聴したりということができます。最近はHTTP Live Streamingでの視聴もベータ版レベルながら実装されました。

ツールとしてはだいぶニッチなものなので使えるところもないと思いますが、C#で書かれたRTMP実装やFLV(またはRTMP)からMPEG-2 TSへの変換としてソースは参考になる……かもしれません。

奥付

著者紹介

本文:kumaryu.

PeerCastStationとかいうP2Pなライブ配信ソフトを作ってる人。

本を書いてるとプログラム書く暇がなくてつらい。

表紙:ぬか.

twitter:@nuka000

ゲームをつくったり絵をかいたりしています。

ライセンス

この作品の本文(表紙を除く)は、クリエイティブ・コモンズの 表示 - 継承 4.0 国際 ライセンスで提供されています。ライセンスの写しをご覧になるには、http://creativecommons.org/licenses/by-sa/4.0/をご覧頂くか、Creative Commons, PO Box 1866, Mountain View, CA 94042, USA までお手紙をお送りください。

奥付

タイトル

動画を送ろう!!RTMP

発行日

2019年4月14日 初版発行

サークル名

あれくま

発行者

kumaryu

Web

https://www.kumaryu.net/

メール

kumaryu@kumaryu.net