フロントエンドエンジニアのための生放送と RTMP 通信基礎
前回「フロントエンドエンジニアのための動画ストリーミング技術基礎」では HTTP ベースのストリーミング技術に関して勉強会を実施しました。視聴者に映像を届けるためのストリーミング技術に関してのお話でした。
本記事は、AbemaTV の生放送番組で撮影機材から送られた映像がエンコーダーを介してリアルタイムに放送する部分について勉強会を実施した際の資料です。
生放送における動画データの通信
AbemaTV では生放送で撮影した動画データのやりとりに RTMP というプロトコルを利用しています。
RTMP とは
RTMP は Real-Time Message Protocol の略で、その名前の通りリアルタイムにコミュニケーションを行うためのプロトコルです。Web 業界では Photoshop などでお馴染の Adobe Systems 社が開発しています。Adobe Flash Player がメディア配信サーバーとの間で音声や動画などのデータをやりとりするためのストリーミングのためのプロトコルとして開発されました。
前回紹介した HLS や MPEG-DASH もストリーミングプロトコルでしたが、RTMP はこれらと異なり、HTTP ベースではありません。ですので、HLS や MPEG-DASH のように通常の Web サーバーでコンテンツを配信できるわけではなく、専用の RTMP サーバーが必要になります。
HTTP に対する優位性
HTTP における通信は必ずクライアントのリクエストから始まります。そのため、動画をストリーミングしようとする際、クライアントはサーバーに対して任意のインターバルで動画のセグメントをリクエストし続ける必要があります。HLS や MPEG-DASH におけるストリーミングはこの方式ですが、クライアントのタイミングで動画データをリクエストするため、本当の意味でのリアルタイム性はありません。動画データを生成しているサーバー側が送信したいタイミングでクライアントにデータをプッシュできる方が遅延が発生することがなく、効率的です。
それに対して、RTMP におけるデータ通信は持続的に接続した状態で双方向に行われます。そのため、サーバーがクライアントに送信したいタイミングでプッシュ送信することができ、遅延が発生しません。
また HTTP の場合、HTTP レスポンスヘッダーは冗長で一般的に数百バイトになります。返したいペイロードサイズに対してのオーバーヘッドを大きくしてしまっています。それに対し、RTMP パケットのヘッダーは固定長で 12 / 8 / 4 / 1 バイトのうちどれかになり、ペイロードサイズに対するオーバーヘッドは小さいです。
生放送の現場ではリアルタイム性を重視
AbemaTV の生放送の現場では撮影機材で撮れた映像を Wirecast などのエンコーダーでエンコードし、それを RTMP 通信で Wowza などのメディアストリーミングサーバーに届けています。そして、メディアストリーミングサーバーに届けられた映像を確認しながら、生で撮影しているその映像に対して遅延を極力少ない状態で CM 入りや視聴者参加型コンテンツなどのトリガーとなるシグナルを通信できる環境を構築しています。
RTMP の種類
RTMP にはいくつかの派生種があります。
- RTMPT - HTTP でカプセル化した RTMP
- RTMPS - TLS/SSL で暗号化して HTTPS でカプセル化した RTMP
- RTMPE - こちらも暗号化された RTMP ですが、設計に欠陥があり RTMPS の使用が推奨されている
- pRTMP - Adobe Primetime DRM がかかった RTMP
RTMP は一般的に 1935
ポートを使用します。しかし、セキュリティの厳しい環境ではこの 1935
ポートが使えないこともしばしばあります。そのため、HTTP( 80
ポート)や HTTPS( 443
ポート)を装って通信するという手段を取ることが可能です。それが RTMPT と RTMPS になります。
更にリアルタイム性を重視したデータ通信
RTMP は TCP を利用したプロトコルですが、別に RTMFP という UDP を利用したプロトコルもあります。UDP を利用するプロトコルは TCP を利用するプロトコルと比べて通信速度面において利点があります。TCP はパケット・ロストに対して再送する仕組みですが、UDP はパケット・ロストに対して再送することはありません。その分再送のオーバーヘッドなく通信することができます。
データ量の大きな双方向データ通信
RTMP は双方向のデータ通信が可能なプロトコルですが、両方向とも送信するデータ量が大きな通信サービスを構築する場合は UDP ベースの RTMFP が好まれます。たとえばテレビ会議やビデオチャットなどは双方が送信するデータが動画のため、データサイズが大きいにも関わらず、スムーズなコミュニケーションのためにリアルタイム性が求められます。
TCP で パケット・ロストによる再送で遅延の頻度が高まると、音声や映像が遅れた状態になる可能性が高くなります。特にテレビ会議やビデオチャットなどはデータの抜け落ちが発生したとしてもノイズ程度の劣化として許容できる場合がほとんどなため、UDP での通信が向いています。
RTMP をサポートするメディアストリーミングサーバ
RTMP でストリーミング配信するには、専用の RTMP サーバーが必要です。ここでは RTMP をサポートする代表的なメディアストリーミングサーバーを3つ紹介します。
Adobe Media Server
Adobe Systems 社が開発しているメディアストリーミングサーバーです。Flash 技術の総本山である Adobe が開発しているだけあり、ここで紹介するメディアサーバーの中で一番知名度が高く、機能も豊富です。そしてその分ライセンス料も高いです。(RTMFP のサポート有無など機能数に応じて複数のエディションに別れています。)
Wowza Media Server
Wowza Media Systems 社によって開発されているメディアストリーミングサーバーです。元 Adobe Systems の社員がスピンアウトして立ち上げたこともあり、Adobe Media Server との互換性が高く、ほぼ同等の機能を持っています。それにも関わらずライセンス価格は Adobe Media Server と比較するとかなり安価なこともあり、AbemaTV でも生放送のストリーミングサーバーには Wowza Media Server を使用しています。
Red5
Java で実装されたオープンソースの RTMP プロトコルをサポートするメディアストリーミングサーバーです。Adobe Media Server にかなり似せて作られていて、単体のサーバーとしては同等の機能を提供してくれますが、クラスタリング構成にあまり対応していないため、大規模な配信サービスを構築する場合には注意が必要です。
RTMP を使用するためのクライアントサイド
Web ブラウザは RTMP をネイティブでは対応していません。ブラウザ上で RTMP を使用するためには、プラグインとして Adobe Flash Player を使用する必要があります。
簡単な RTMP ストリーミング配信を実装してみる
まずは RTMP ストリーミングサーバーを構築します。先述したメディアサーバーを使用したいところですが、今回は単純なストリーミング機能のみを提供できれば良いので、NGINX をメディアストリーミングサーバーとして使うことができる nginx-rtmp-module を使います。
Docker で NGINX を立てる
nginx-rtmp-module を追加してコンパイルした NGINX の Docker イメージを作ります。ベースイメージには Alpine Linux を使います。ここでは RTMP 用に 1935
ポートと Flash アプリケーションを読み込むための HTML を返すために HTTP 80
ポートを開けるようにします。
Dockerfile を作成する
下記の Dockerfile では、コンパイルに必要なパッケージを apk
でインストールして、任意のバージョンの NGINX と nginx-rtmp-module の Tarball をダウンロードし、コンパイルしています。 ./configure
のパラメータがやたら多いですが、必要ないモジュールを除外しているだけです。
NGINX の configuration を設定する
イメージにコピーする nginx.conf
は下記のように設定します。 http
コンテキストに加えて nginx-rtmp-module
で使用できるようになった rtmp
コンテキストに設定を追加しています。
この rtmp
コンテキストの設定により、ローカルに Docker コンテナを立ち上げたとき rtmp://localhost:1935/live
という URL で RTMP サーバに接続が可能になります。
RTMP プレイヤーを実装する
次に RTMP プレイヤーとそれを表示する HTML を作成します。RTMP プレイヤーは Flash アプリケーションとして実装するので ActionScript で書きます。まず新規ファイル Player.as
を作成します。
Player.as
に Player
クラスを作成します。動画を表示するための Video
オブジェクトも追加したいので、 Sprite
クラスを継承しておきます。
次に Stage
の設定します。Flash コンテンツを左上に整列する設定だけします。
NetConnection
クラスを使い、クライアントとサーバー間の双方向の接続を作成するための準備をします。 NetConnection
オブジェクトのステータスが変化したタイミングで、メディアサーバーからのデータを再生できるように、 NetStream
クラスを使ってストリームチャネルを開きます。開いた後ライブストリームを再生するために ns.play()
メソッドを実行します。このときストリーム名として "test"
を渡していますが、これは後程ライブストリームを作成する際にも使う名前になります。
作成したストリームチャネルからの動画を表示するために Video
オブジェクトに取り付けます。
この Video
オブジェクトもコンストラクト時に作成し、ステージに追加してきます。
NetConnection
の準備が整ったので、最後にメディアサーバーに接続します。URL は先程の NGINX の RTMP メディアサーバーの live
application に向けています。
ActionScript のコンパイル
これで RTMP サーバーの実装はできたので、次は ActionScript をコンパイルします。コンパイルには Apache/Adobe Flex SDK の Node.js モジュール版である node-flex-sdk を使用します。まずは NPM でインストールします。
無事インストールできたら、mxmlc
というコマンドを使って、 Player.as
から Player.swf
をコンパイルします。
HTML の作成
作成された Player.swf
を表示する HTML を作成します。
Docker コンテナの起動
Docker コンテナの構成に必要なファイルが揃ったので、これでイメージを作成します。
イメージが作成できたか確認しましょう。
無事に作成できたら、そのイメージから Docker コンテナを起動します。
Web ブラウザで http://localhost/ にアクセスしてみましょう。
黒いボックスが表示されたと思います。このボックスが RTMP プレイヤーなのですが、今は配信するストリームが存在していないため、何も再生できず黒い状態です。ですので、次は再生するストリームを作成します。
ストリームの作成
ここでは、Open Broadcaster Software (OBS)というオープンソースのライブストリーミング用のツールを使用してストリームを作成します。
OBS を起動して、「Settings」ボタンをクリックします。
「Settings」ダイアログが表示されるので、左側のペインから「Stream」を選択します。
すると「URL」と「Stream key」を入力する画面に切り替わりますので、「URL」に NGINX の RTMP サーバーの live
application の URL を入力し、「Stream key」には先程 RTMP プレイヤーを実装したときにストリーム名として指定した test
を入力します。
「Settings」ダイアログで「OK」をクリックしたら、次に「 Sources」の「+」をクリックして適当なメディアソースを追加します。プルダウンメニューが表示されるので「Media Source」を選択して任意の動画ファイルを追加します。
メディアソースが追加されたら、「Start Streaming」ボタンをクリックしてストリーミングを開始します。
ストリーミングが開始されたら、Web ブラウザに戻ります。すると OBS でストリームしている動画がブラウザの方でも再生されていることが確認できます。
RTMP でメディアサーバーのメソッドを呼ぶ
ここまでで RTMP でサーバー側からプッシュされたデータをクライアントで再生する実装をしてきました。しかし、RTMP は双方向のデータ通信が可能なので、クライアント側からサーバー側のメソッドを呼ぶことも可能です。 nginx-rtmp-module
では難しいですが、メディアサーバーに Adobe Media Server や Wowza Media Server を利用して開発をした場合、クライアント側からサーバー側のメソッドを呼ぶことが可能です。
ActionScript の場合、クライアントとサーバー間の双方向の接続が作成した後( NetConnection
オブジェクトが nc.connect()
して、 NetStatusEvent
が "NetConnection.Connect.Success"
になった後)であれば、 nc.call()
でサーバー側のメソッドを呼ぶことができます。 nc.call()
の第1引数がメソッド名なので、 nc.call("doSomething")
のようにクライアントから実行した場合、メディアサーバーに実装した該当のメソッドが実行されます。
たとえば Wowza Media Server の場合であれば、実装は Java なので下記のようなメソッドを実装することで、クライアントから Wowza Media Server のコンソールに doSomething is called
と表示させることが可能です。
AbemaTV の生放送番組では、RTMP の双方向通信を利用して、Web ブラウザから Wowza Media Server のメソッドを呼ぶことで、番組の進行具合に合わせて CM 入りのタイミングや視聴者参加型のインタラクションコンテンツのトリガーを最小限の遅延で放送に挿し込んでいます。
RTMP で受け取った動画を HLS でもストリーミング
メディアサーバーはエンコーダーから RTMP 通信で送られた動画をそのまま RTMP でクライアントにプッシュ送信する以外に HLS や MPEG-DASH でストリーミングできるように変換することも可能です。たとえが nginx-rtmp-module の場合は先程作成した nginx.conf
を編集して、 application live
コンテキストに HLS に関する以下のディレクティブを追加します。
すると、Live セッションの m3u8 プレイリストと 1 秒感覚のセグメントファイルを /usr/local/nginx/html/hls/live
に出力してくれます。この nginx.conf
を反映した Docker コンテナが起動している状態で、http://localhost/hls/test.m3u8 に Safari でアクセスすると HLS でストリーミング再生ができます。(Safari でアクセスする理由は前回書いた通り、HLS をネイティブサポートしている Web ブラウザが Safari だけだからです。)
まとめ
普段の Web フロントエンドの開発では、RTMP や ActionScript を扱う必要があることはあまりありません。しかし、こと動画やストリーミング領域となるとまだ Flash テクノロジーの安定性にお世話になることも多いように思います。WebRTC や WebSocket などの技術の組み合わせでこのあたりの事情もどんどん変化していきそうです。