ffmpegでUSBカメラの映像をHLSでライブストリーミングする

hls player
OctoPrintとUSBカメラを使うと3Dプリンタの動作状況をリアルタイムでストリーミングできます。ですがストリーミングにmjpg-streamerが使われるため音声は送信されません。
mjpg-streamerの代わりにffmpegでHLSを生成し映像と音声をストリーミングしてみます。

ffmpegでストリーミングをする方法を以前にも紹介しました。
関連記事:Raspberry Pi 3とFFmpegでUSBカメラの映像と音声をストリーミングする
この記事では基本的なストリーミングプロトコルを動かしてみました。
関連記事:Raspberry Pi 3とffmpegでYouTubeへライブ配信をする
ラズベリーパイでFacebookにライブ配信をする方法
この記事ではrtmpでSNSへライブストリーミングをしました。
sapでは再生側に専用のプレーヤーが必要なのが面倒です。rtmpではmjpg-streamerの代わりとしてはタイムラグが大きいのが問題です。

そこで今回はHLS形式でストリーミングをしてみます。

HLSをストリーミングしてみる

HLSを使うと最近のブラウザで簡単にストリーミングビデオを再生できます。HTTP Live Streaming(HLS)の仕組みなどは他のサイト様にお任せです。
簡単な説明だけしておきます。
静的なビデオストリーミングの仕組みを活かしてライブストリーミングに対応した形式です。playlistをブラウザで読み込むとplaylistに書かれているビデオファイルを再生します。このplaylistとビデオファイルをリアルタイムで更新する事でライブストリーミングに対応している感じです。

ffmpegではwebカメラの映像と音声からplaylistとビデオファイルを作ります。ファイルを作っただけではブラウザに届きません。別にWEBサーバーが必要です。
WEBサーバーを動かすのは面倒に思うかもしれませんが、最低限の機能ならPythonを使うとあっという間に動いてしまいます。
WEBサーバーを動かしffmpegの作ったファイルを表示するページを作ります。
それではやって行きましょう。

pythonでWEBサーバーを動かす

WEBサーバーが動くなら何でも良いです。ApacheやIISが既に動いているならそれを使えば良いでしょう。
今回は例としてRaspberry Piで動かします。WANへの公開は考えずLANのみで動かすとします。必要なWEBサーバーは最低限の機能で良いです。
Raspberry PiのOS RaspbianでWEBサーバーを動かすならpythonを使うのが簡単でしょう。あるいはNode-REDで静的なディレクトリを公開しても良いでしょう。
今回は例としてpythonでやってみます。OctoPrintがpythonで動いているというのも理由です。

公開するディレクトリの場所を最初に決めます。ここでは
~/Public/web_server
というディレクトリを公開します。このディレクトリの内容が
http//<Raspberry Piのアドレス>:8000で公開される形です。

~/Publicディレクトリにweb_serverディレクトリを作ってください。

ホームディレクトリにpythonのスクリプトを作ります。ファイル名はstreaming.pyとします。テキストエディタで編集します。中身は次のようにしました。
#!/usr/bin/env python3
import os

import http.server
import socketserver

PORT = 8000

web_dir = os.path.join(os.path.dirname(__file__), '/home/pi/Public/web_server')
os.chdir(web_dir)

Handler = http.server.SimpleHTTPRequestHandler
httpd = socketserver.TCPServer(("", PORT), Handler)
print("serving at port", PORT)
try:
    httpd.serve_forever()
except KeyboardInterrupt:
    pass

httpd.server_close
/home/pi/Public/web_serverへカレントディレクトリを移動しています。表記が絶対パスなので、piユーザーではない場合は修正してください。
このスクリプトに実行権限を与えます。
chmod +x streaming.py
スクリプトを実行します。
python3 streaming.py
これでブラウザでhttp//<Raspberry Piのアドレス>:8000を開けば~/Public/web_serverの内容が表示されるでしょう。
スクリプトはCtrl+Cを押すと止ます。
今回はpython3用スクリプトにしました。python ver.2では動かないでしょう。
今回のpythonのバージョンは3.5.3でした。

ffmpegでUSBカメラの映像と音声からHLS用ファイルを作る

ffmpegでUSBカメラの映像をストリーミングしてみます。
ffmpegのバージョンは3.2.10-1でした。少し古いです。
WEBサーバーを動かしたのとは別にターミナルを開きましょう。
ホームディレクトリでstreamingというスクリプトファイルを作ります。内容は次のようにします。
#! /bin/bash

httpDir=~/Public/web_server
cd $httpDir

ffmpeg -f alsa -thread_queue_size 1024 \
  -i hw:1 \
  -f v4l2 -thread_queue_size 512 -input_format yuyv422 -video_size 800x600 \
  -i /dev/video0 \
  -filter_complex scale=800x600,fps=12 \
  -c:v h264_omx -b:v 764k -g 24 \
  -c:a aac -b:a 64k \
  -flags +cgop+global_header \
  -f hls \
  -hls_time 2 -hls_list_size 3 -hls_allow_cache 0 \
  -hls_segment_filename $httpDir/stream/stream_%d.ts \
  -hls_base_url stream/ \
  -hls_flags delete_segments \
  $httpDir/playlist.m3u8

rm $httpDir/stream/stream_*.ts
rm $httpDir/playlist.m3u8
検索するとよく見かけるのはstream_segment muxerを使った方法でした。ここではhls muxerを使ってみました。ffmpegのバージョンが古いと使えないかもしれません。最新のffmpegを用意しましょう。今回のffmpegでも一部のオプションが使えませんでしたので動くオプションだけにしています。
関連記事:Raspberry Pi 3でx264とハードウエアエンコーダが使えるFFmpegをビルドする

少し説明します。
USBカメラの映像と音声は、音声はalseから映像はv4l2から受け取ります。
ビデオエンコードはハードウエア支援のh.264でエンコードします。Raspberry Piの場合、ハードウエア支援でなくては実用になりません。
"-flags +global_header"を指定しないとブラウザをリロードした時に映像が表示できなくなりました。
一つのセグメントの再生時間は2秒としました。これに合わせてGOP長を設定します。今回は12fpsとしたのでGOP長を24にします。
再生リストに古いセグメントが書かれたままだとストリームを開始した時点までロールバックできてしまいます。今回はリアルタイム配信を目的とするので"-hls_list_size 3"とし直近の3ファイルのみにアクセスできるようにします。
"-hls_segment_filename"でセグメント化されたビデオの保存場所とファイル名を指定します。
"-hls_base_url"で再生リストのファイル名にパスを設定できます。これを書かないとファイル名だけになります。
セグメント化された古いビデオファイルが大量に出来上がります。古いファイルを自動的にデリートするため"-hls_flags delete_segments"を指定します。
ffmpegを停止した後はセグメント化されたファイルとplaylist.m3u8ファイルを削除しています。

作成したstreamingファイルに実行権限を与えます。
chmod +x streaming

実行させてみます。
./streaming
~/Public/web_serverディレクトリに"playlist.m3u8"ファイルができるでしょう。~/Public/web_server/streamディレクトリにセグメント化されたファイルができるでしょう。

index.htmlを用意する

これでライブストリーミングをする準備は整いましたが、肝心のブラウザで表示するページを作っていません。ビデオを表示するindex.htmlを作ります。

ところで、HLS形式のビデオは全てのブラウザが再生できるわけではありません。多くの方が使っているだろうChromeでは再生できないのです。
ですが心配はいりません。HLSを再生するjavascriptプレーヤーがいくつかありますのでそれを使います。
今回は
video-dev/hls.js | GitHub
JavaScript HLS client using Media Source Extension
を使います。
index.htmlファイルはWEBサーバのルートディレクトリに作ります。今回は~/Public/web_serverディレクトリですね。
テキストエディタで開き次のように編集します。
<html>

<head>
  <meta charset="utf-8">
  <title>HTTP Live Streaming</title>
  <script src="https://cdn.jsdelivr.net/hls.js/latest/hls.min.js"></script>
</head>

<body>
  <h1>Now 3D Printing !</h1>
  <div>
    <video id="live" width="640" height="480" crossOrigin="anonymous" autoplay="autoplay" controls="controls">
  </div>
  <script>
    if (Hls.isSupported()) {
      var video = document.getElementById('live');
      var hls = new Hls();
      hls.loadSource('./playlist.m3u8');
      hls.attachMedia(live);
      hls.on(Hls.Events.MANIFEST_PARSED, function () {
        video.play();
      });
    }
    function jumptolatest() {
      document.getElementById("live").currentTime = 99999999;
    }
    setTimeout("jumptolatest()", 2000);

  </script>
</body>

</html>
ビデオを表示するのはvideoタグでできます。簡単です。ですがHLSに対応していない場合はhls.jsを呼び出します。
ロードが終わって2秒後に最新の時刻へジャンプします。どうやってやるのが正攻法なのかわかりませんでした。

ブラウザで表示する

WEBサーバー、ffmpegのスクリプトを動かしてください。
それではブラウザで表示してみます。PCのブラウザなどで次のURLを開きます。
http//<Raspberry Piのアドレス>:8000
HLS streaming
映像はもちろん音声もストリーミングできているはずです。
概ね5秒ほどのディレイでストリーミングできています。

まとめ

USBカメラの映像と音声をffmpegでHLS形式し、python3でhttpサーバーを動かし配信しました。多くのブラウザで再生できるようhls.jsプレーヤーを使って再生しました。

この記事公開時で最新のRaspbian Version:March 2018で実行しました。pythonもffmpegも既に入っているので簡単に動かせるはずです。

phthonはほとんど知らないのですが多くのライブラリがありお手軽にいろいろな事ができるのがわかります。ですがpythonのバージョンによって言語仕様が異なったり、ライブラリ名が違ったりして慣れないと検索した情報を読み解けません。便利ですが初心者には厄介な言語になりつつあります。

OctoPrintはpythonでできています。pluginで機能を追加する事もできます。
httpサーバーはTornadeを使っているようです。
Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed.
今回のストリーミング方法のplugin化をしてみたいです。
mjpg-streamerを表示しているコンテナの中身を入れ替えればいいはずです。仕組み的には単純なので勉強してみようかと思っていますが、3Dプリンタをあまり使わなくなった昨今・・・やる気が出ない。あるいはどなたかお願いします!


コメント

  1. 初めまして
    Raspberry Pi 3のOctoPrintで凄く参考にさせて頂き、インストール及びUSBカメラもコマンドライン(pi@**:~$)から~/Downloads/OctoPrint/venv/bin/octoprint serveと入力すれば順調に動き、USBカメラも同じく./mjpg_streamer -i "./input_uvc.so" -o "./output_http.so"と入力すれば動くのですが、どうしても自動実行(裏で)が出来ません。スクリプトも色々試したり自動起動を調べたりしたのですが上手く行きません。
    デスクトップが立ち上がって裏でOctoPrintとUSBカメラを起動させたいのですがどうすれば良いでしょうか。
    2日間色々調べて試したのですがどうしても上手く行かないので質問させて頂きました。宜しくお願いします。

    返信削除
    返信
    1. こんにちは。
      "cron"か"systemd"を使うのが良いでしょう。ググってください。"systemd"は少し難しいです。
      OctoPrintを動かすだけならOctoPiを使うのが良いでしょう。でもデスクトップや自前のプログラムと連携するのは難しくなります。

      削除
    2. 早速の返信ありがとうございます。"cron"か"systemd"ですが私の能力では何度も試しましたが無理でしたし元々cronもyumも無くダウンロードすれども使えず、systemdは難解でエラーばかり。
      どうしてもデスクトップを出して他の事もしたいのですが自動起動が出来なければどうにもなりません。
      差し支えなければ詳しく教えて頂きたいのですが、無理でしたら諦めます。
      とても上手く行ったのに残念です。

      削除
    3. デスクトップ前提なら"autostart"がいいですね。
      "/home/pi/.config/lxsession/LXDE-pi/autostart"にコマンドを書きます。

      削除
    4. ご指南ありがとうございます。
      私のラズベリーパイには"/home/pi/.config/lxsession/LXDE-pi/autostart"が有りませんでした。
      その部分は、"/home/pi/.config/lxpanel/LXDE-pi"でautostartファイルは無かったので作りました。
      そして@python /home/pi/OctoPrint/venv/bin/octoprint serve
      と記述しましたが実行出来ませんでした。
      教えて頂いた通りに打ち込むとファイルエラーです。
      どうすれば良いでしょうか。
      なので未だにOctoPrintの自動実行が出来ないのでカメラテストは終わっていますがwebcam startのテストが出来ない状態です。
      宜しくお願いします。

      削除
    5. これ以上はエスパー能力が必要になります。
      autostart自体が動いていない、autostartへのコマンドが解釈されない、という問題の切り分けが第一です。
      コマンドの引数に気を付けましょう。例えば"serve"は"python"の引数ですか?"octoprint"の引数ですか? あいまいな表現で人の思い込み通りに実行されたとしてもただのラッキーにすぎません。
      あいまいな引数を与える場合はできるだけ個別のスクリプトファイルを作り引数を分離しましょう。

      削除

コメントを投稿

最近のコメント

Threaded Recent Comments will be here.