読者です 読者をやめる 読者になる 読者になる

Theta Sやウェブカメラをラズパイ上のgstreamer+HLSでストリーミング配信してGearVRとかで再生

Raspberry Pi Gear VR Hypriot Theta S Unity

概要

前回mjpegをmjpegストリーマーで配信する方法を紹介しましたが、少なくともUnity上ではfpsがあまり出ない欠点がありました。

hammmm.hatenablog.com

今回は、Raspberry Pi 2でWebCamやTheta S(live view)からの動画を、gstreamerというソフトウェアを使ってHLS(HTTP Live Streaming)方式でストリーミング配信する方法です。

  • メリット
    • http通信のみで実現できるので、再生環境が整いやすい
    • ラズパイ搭載h.264のハードウェアエンコーディングを使うため、フレームレートがそこそこ出る
    • 複数人への同時配信が可能
  • デメリット
    • 現状では録画時間と再生時間の間に30~60秒前後の大きなラグが生じる

前回と同じような構成で配信できます。 f:id:hammm:20160214191153j:plain:w400

HLSとは?

HTTP Live Streamingの略で、Appleが定めた、httpだけでストリーミングを行うための仕様です。> HTTP Live Streaming (HLS) - Apple Developer

以下は自分の今の認識ですが、仕様からするとおかしいところがあるかもしれません。

Apacheやnginxといった一般的なウェブサーバーで以下のファイルを配信することで、ストリーミング配信を実現できます。

  • .m3u8という拡張子のプレイリストファイル(1つ)

    • 中に実際のストリームファイルへのリンクURLが書いてある。
  • 連番のストリームファイル(stream00001.ts, stream00002.tsのような連番ファイル)

    • こちらに動画が数秒分ずつ入っている。

gstreamer等を使って、新しい連番ストリームファイルを生成して、ファイルを置くたびに.m3u8ファイルを書き換えていくことで、プレイヤーが最新の情報を取得できるようにしておきます。

これを再生する動画プレイヤー側は、まず.m3u8ファイルを読み込んで、ストリームファイルへのリンクを取得し、さらにリンクをたどってストリームを取得します。m3u8には複数のストリームファイルが書かれているので、順番に読んでいくことができます。定期的にm3u8ファイルを読み込み直すと、更新された情報を得ることができ、さらに新しい連番ストリームファイルを得ることができます。

対応プレイヤー

  • ブラウザ系 (htmlファイル)

    ブラウザ上で再生するためにはvideoタグが書かれたhtmlファイルも一緒においておく必要があります。

    • MacOS XではSafariが対応していそうですが、未検証。
    • Androidでは標準ブラウザで再生することができます。 (Android 5のGalaxy S6 edgeで確認)
    • iOSのMobile Safariでは成功していません。
    • WindowsChrome, Firefoxでは成功していません。
  • 直接起動

    • UnityではAndroid向けのEasy Movie Textureという有料AssetがHLSに対応しています。m3u8ファイルへのURLを指定することで再生できます。
    • iOSVLCアプリでm3u8ファイルへのURLを指定することで再生できました。
    • WindowsVLCアプリでも同様に再生できますが、しばらくすると勝手に停止する傾向がありました。

HLS配信の難しいところ

m3u8をネットから読めるようにウェブサーバーに置くまでは良いですが、.m3u8の中に書かれたストリームファイルのURLもちゃんと読める場所(ホスト、アドレス)を示している必要があります。 たとえばですが、Bonjourが有効な環境で読める なんとか.local といったアドレスを.m3u8 の中のURLに入れてしまうと、Bonjourが無効な環境では再生できなくなってしまいます。 なお、EasyMovieTextureでは、m3u8ファイル内のストリームURIについて、 /stream/stream00001.ts のようにホスト名を省略することが可能でした。他のプレイヤーでは対応していない可能性がありますが、一応仕様的にはアリっぽいです。(※後述「手法A」)

実装

今回の手法

  • gstreamerのhlssinkを利用して.m3u8ファイルと.tsファイル群を生成します。
  • 重い処理であるh.264へのエンコードに、OpenMaxアクセラレーションを利用するomxh264encを利用します。
  • nginxをdockerコンテナで起動し、HTTP配信を担わせます。

前提

以前の記事で書いたような、HypriotOSを入れた状態を想定していますが、通常のRaspbianからでも実行可能と思います。(dockerまわりは自分でなんとかする方向ということで)

gstreamerをインストール

sudo apt-get update
sudo apt-get install gstreamer1.0-*

ストリーミング実行

Theta S向け

  • 準備

    前回と同様、THETA SをLIVE VIEWモードで起動して、ラズパイにUSB接続してください。そこで、/dev/video0 というデバイスが現れているかどうかを確認してください。

  • 手法A : ストリームファイルをホスト名抜きのパス指定する方法

    最後のplaylist= のところがホスト名を除外したパスになっています。AndroidiOSVLCではこの方法でいけます。この時点でホスト名とか意識しなくて良い分、楽といえます。仕様上もPlaylist(m3u8)に対して相対指定ができると書いてあるので良さそうです。

mkdir /var/www/stream
sudo gst-launch-1.0 -v -e v4l2src device=/dev/video0 ! image/jpeg,width=1280,height=720 ! jpegdec ! omxh264enc target-bitrate=2000000 control-rate=variable ! video/x-h264,profile=baseline ! h264parse ! mpegtsmux ! hlssink max-files=8 target-duration=5 location=/var/www/stream/segment%05d.ts playlist-location=/var/www/stream/output.m3u8 playlist-root=/stream/
  • 手法B : ストリームファイルをURL指定する方法

    最後のplaylist= のところがホスト名を含むURLになっています。WindowsVLCではこちらの方法でないと動かないようです。

mkdir /var/www/stream
sudo gst-launch-1.0 -v -e v4l2src device=/dev/video0 ! image/jpeg,width=1280,height=720 ! jpegdec ! omxh264enc target-bitrate=2000000 control-rate=variable ! video/x-h264,profile=baseline ! h264parse ! mpegtsmux ! hlssink max-files=8 target-duration=5 location=/var/www/stream/segment%05d.ts playlist-location=/var/www/stream/output.m3u8 playlist-root=http://(ラズパイのホスト名またはIPアドレス)/stream/

解説

# デバイス(USB,OpenMax)の関係でスーパーユーザー(sudo)で実行します。
sudo gst-launch-1.0 -v -e 

# V4L2というドライバでWebCamのデバイスを読みます。
v4l2src device=/dev/video0 

# motion jpegフォーマットの1280x720のものを取り出します
! image/jpeg,width=1280,height=720  

# motion jpegをデコードします
! jpegdec 

# OpenMaxアクセラレーションを利用してH.264形式にエンコードします
! omxh264enc target-bitrate=2000000 control-rate=variable 

# H.264形式の詳細方式を指定します
! video/x-h264,profile=baseline 

# H.264を適切な形にしてくれる魔法(え
! h264parse 

# MPEGの入れ物(TS)につっこみます
! mpegtsmux 

# HLS配信モジュールでファイルを出力します。5秒ぐらいでファイルを切り分け、8ファイルまで維持します。その他、m3u8ファイルの出力先、tsファイルの出力先、m3u8ファイル内でのtsファイルの位置、などを指定します。
! hlssink max-files=8 target-duration=5 location=/var/www/stream/segment%05d.ts playlist-location=/var/www/stream/output.m3u8 playlist-root=/stream/

h.264対応USBウェブカメラ向け

Logicool C920のような、元からh.264を出力できるWebCamの場合は、以下のようなコマンドラインで、ラズパイにあまり負荷をかけずに配信することができます。ウェブカメラからh.264フォーマットで入力して、それをそのままmpeg-TSでつつんでHLS配信します。

sudo gst-launch-1.0 -v -e v4l2src device=/dev/video0 ! video/x-h264,width=640,height=480,framerate=15/1 ! h264parse ! mpegtsmux ! hlssink max-files=8 target-duration=5 location=/var/www/stream/segment%05d.ts playlist-location=/var/www/stream/output.m3u8 playlist-root=/stream/

その他のUSBウェブカメラ向け

通常のWebCam向けのコマンドラインです。OpenMaxアクセラレーションを利用してh.264エンコードします。

sudo gst-launch-1.0 -v -e v4l2src device=/dev/video0 ! video/x-raw,width=1280,height=720,framerate=15/1 ! omxh264enc ! h264parse ! mpegtsmux ! hlssink max-files=8 target-duration=5 location=/var/www/stream/segment%05d.ts playlist-location=/var/www/stream/output.m3u8 playlist-root=/stream/

gstreamer動作確認

HLS用のgstreamerが動作しているか、確認する方法です。

ls -al /var/www/stream/

を何回か実行して、以下を確認しましょう。

  • .tsファイルのサイズが増えているか?
  • しばらくすると.m3u8 ファイルが更新されているか?
  • .tsファイルがある程度の容量になったら次の.tsファイルが作られているか?

動きが無かったり、1つのファイルのサイズがいつまでも増え続けていたりしたら異常です。

ウェブサーバー(nginx)設定

上記まででできているのは、「とあるディレクトリにm3u8ファイルと.tsファイルを置く」ということまでです。ですので、これを配信してあげる必要があります。 ここではLAN内での配信とします。

dockerがインストールされた環境を前提とします。

$ docker run -v /var/www:/usr/share/nginx/html/ -p 80:80 -d mhamanaka/rpi-nginx-hls:0.1

実行すると、自動的にイメージのダウンロード、展開からnginxの実行まで自動的に行われます。

docker ps で稼働状況を確認等してください。 docker logs とするとnginxの最新のログを得られます。

  • コマンドラインについて

    -v /var/www:/usr/share/nginx/html/ は、ローカルの/var/wwwを、docker container側の /usr/share/nginx/html/ にマウントするという意味です。コンテナのnginxは /usr/share/nginx/html/ にあるファイルを配信する設定になっているため、こう書いておくと、ローカル側の /var/www の内容を配信することになります。

  • mhamanaka/rpi-nginx-hls:0.1 イメージについて

    mhamanaka/rpi-nginx-hls:0.1 というイメージの内容が謎かと思いますが、これは lalyos/rpi-nginx のmime.types (/etc/nginx/mime.types) に、以下のHLS用設定を追加したものです。

    application/x-mpegURL                 m3u8;
    video/MP2T                            ts;

ブラウザ再生向けhtmlファイル

ブラウザで再生する場合は、以下のように、videoタグを含んだhtmlファイルを /var/www/viewer.html のような場所に置きます。 そうすると、上記の設定であれば http://(ラズパイのホスト名またはIPアドレス)/viewer.html にアクセスするとHLS再生用ページが開きます。再生ボタンを押すと再生できます。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no">
  <title>HTTP Live Streaming Test</title>
</head>
<body>
  <header>
    <h1>HTTP Live Streaming Test</h1>
  </header>
  <div>
    <video width="1280" height="720" src="/stream/output.m3u8" preload="none" onclick="this.play()" controls />
  </div>
</body>
</html>

再生方法

Unity,VLC(ウェブブラウザ以外)

m3u8へのURLを指定して再生します。

http://(ホスト名またはIPアドレス)/stream/output.m3u8

  • Unity Easy Movie Texture (有料Asset. $55)の場合

    Media Player Controlコンポーネントの、Str File Name のところに上記文字列を入れてください。

    • Easy Movie Textureのサンプルシーンでの設定例

      f:id:hammm:20160214171404p:plain:w400

      実行例

      f:id:hammm:20160214171301p:plain:h300

    • GearVR向け設定例

      こちらの記事 のプロジェクトの、Web Cam Drawer のところを Media Player Controlに置き換えます。

      f:id:hammm:20160214190715p:plain

      実行の様子

      f:id:hammm:20160214191153j:plain

  • VLCアプリ(Windows,iOS)の場合

    「ネットワークストリームを開く」から上記アドレスを入力してください。

    • Windows版の場合

      m3u8ファイル内に完全なURLが入る、上記「手法B」が必要のようです。

ウェブブラウザの場合

videoタグを埋め込んだhtmlファイルへのURLを指定して開きます。

http://(ホスト名またはIPアドレス)/viewer.html

ただし、Androidのブラウザ以外は上記設定で成功していません・・・!

Tips

Theta S + gstreamerでのframerate指定がやばい

一般的にTheta Sは15fpsと言われているので、 image/jpeg,width=1280,height=720,framerate=15/1 のような指定をしてしまいがちですが、これは動きません! 正解は15fpsよりわずかに低い、 image/jpeg,width=1280,height=720,framerate=2997/200 です。

ってわかるか!!!

なお、framerateの指定なしであれば、勝手にこの値を選んでくれます。

できなかったこと

  • omxmjpegdecの利用

    • 本来ならMJPEGデコードに、jpegdec ではなく、OpenMaxアクセラレーションの効く omxmjpegdec が使えるはずという気がするのだが、全然動作しなかった。(結果を吐かずにずっと止まってしまう)
  • 音声ストリーミング

    • 音声も一緒にmpeg tsに載せられるはずですが、muxまわりの指定方法がよくわかっていないため、できていません。
  • iOS Mobile SafariWindows Firefoxでの再生

    • 何か設定に問題がある?
  • gstreamerのDockerコンテナ化

    • OpenMax利用部分がどうしても解決しなかった。
  • omxまわりのバージョン更新 (Hypriotの関係か、v1.0.0がインストールされていた。本来はv1.2.0系があるはず)