ESP32 でビットマップ画像を生成し、Motion JPEG でブラウザに動画ストリーミングさせてみた。

ESP32でビットマップ画像ファイルを生成し、ブラウザに連続送信してMotion JPEGならぬMotion BMP動画ストリーミングする実験

記事公開日:2020年3月13日

こんばんは。

約2か月ぶりの記事更新です。
今回はちょっとスゴイですよ!
ESP32開発ボードM5StacでArduinoプログラミングしてビットマップ(BMP)ファイルを生成し、Google ChromeやFireFoxおよびSafari等のブラウザに連続送信して、動画ストリーミングを実現してみる実験です。
今回は初の試みで Motion JPEG (MJPEG) という手法を使いました。
しかも、動画ストリーミングしながら、ブラウザからボタン操作ができる双方向通信が実現できました。

スポンサーリンク

今まで私が過去に扱ってきたストリーミング通信の WebSocketServer-Sent Events あるいは UDP などの、どれとも異なります。
自分的にはめちゃめちゃ新鮮な手法です。
(といっても、MJPEG自体はかなり昔からある手法だったみたいです)

ちょっと注意ですが、MJPEGはMPEGとは異なりますよ~~!
以前、私は同じだと思っていましたが、全然違っていました。
今さら知った次第です。

Motion JPEG方式はTwitterで、@s1tnkさんから参考サイトを教えて頂きました。
教えて頂かなかったら、たぶん延々と分からなかったかも知れません。
感謝感謝でございます。
ありがとうございました~!!!
m(_ _)m

今回は、MJPEGを使ったと言っても、JPEG画像ではなく、BMP画像を使いました。
ネット上ではビットマップ(BMP)画像を使った Motion JPEGの例が殆どありませんでしたが、やってみたら難無く動作しました。
難無く動作したとは言っても、既存のHTTPClientやWebServer、httpdライブラリは使わず、基本的なWiFiやWiFiClientライブラリだけを使って動作させました。
これにはほんと苦労しました。

とにかく、まずは四角形を表示させるだけのシンプルなMotion JPEGストリーミングの以下の動画を見てください。

これは、200 x 148 pixel のビットマップ画像をESP32で生成して、WiFiで連続してスマホに送信しています。
外部WiFiルーターを介す、STAモードです。

これでも一応パラパラ漫画風アニメーションなんです。
ストリーミング中でもボタンコントロールができていて、双方向通信できていますね。
パッと見、面白くない動作ですが、これを実現するまでにほんと苦労しましたよ。

では、続いて、もうちょっと凝ったアニメーションを作ってみた以下の動画をご覧ください。

どうでしょうか。
まるで、スマホのブラウザにマイコン直結の液晶ディスプレイのような感覚で表示させることができました。
これはパッと見ではHTMLのCanvasに描いたように見えますが、ビットマップ画像のアニメーションなんですヨ!!!

メッシュ状の斜線と四角形ムービーのところでは、スマホのボタンスイッチで色をリアルタイムで変えられています。
このスマホは旧型Androidなので、フレームレートは 5fps 程度しか出ていません。
ビットマップ画像の容量が大きいので、仕方ありません。
なので、ボタン操作もちょっとタイムラグがあります。

では、続いてWindows 10パソコンのGoogle Chromeで動作させた動画をご覧ください。

この場合は、フレームレートが 7fps 出ました。
やはり、受信側機器の処理能力に左右されますね。

でも、UDPデータグラムに比べてパケットロスがありません。
受信側のパソコンやスマホの方がESP32より格段に処理能力が高いので当たり前と言えば当たり前ですが、やはりこれはTCP/IP通信の優れたところですね。
らびやんさんのおっしゃる通りでした。
しかも、WiFiルーターの能力によって、かなり距離が離れていてもストリーミングできて、素晴らしいと思いました。

ということで、アマチュア素人の自分なりの目線でMotion JPEGならぬ Motion BMP を紹介していこうと思います。

このブログの維持運営にご支援いただけると助かります。
支援方法はこちらの記事をご覧ください。
(管理人:mgo-tec)

 

    【目次】

  1. なぜMotion JPEG (MJPEG)を使うことにしたのか
  2. 各ブラウザの動作状況
  3. 他のストリーミング(およびデータグラム)通信との違い
  4. ビットマップ(BMP)ファイルの作り方
  5. Arduino core ESP32 で Motion JPEG ストリーミングを実現するザッとした流れ
  6. 双方向通信の方法(Motion JPEGで動画ストリーミング送信しながらブラウザからのリクエストを受信する)
  7. 使ったもの
  8. Arduino core for the ESP32 のインストール
  9. スケッチ例(四角形を表示するだけのシンプルなMotion JPEGストリーミング用)
  10. HTMLおよびJavaScript ソースコード
  11. コンパイル書き込み実行
  12. スケッチ例(ちょっと凝ったMotion JPEGアニメーション)
  13. フレームレートを上げるには
  14. 通信のトラブル解決に便利なツール、Wireshark
  15. HTTPコネクションの問題点
  16. 実は httpdライブラリを使うと、もう少しフレームレートが上がる
  17. 編集後記

なぜMotion JPEG (MJPEG)を使うことにしたのか

なぜ、今、私がMotion JPEGを使うことにしたのかと言うと、去年からESP32やM5Stackとイメージセンサ OV2640を搭載したM5Cameraとのストリーミング(データグラム)通信の実験をしていて、UDP通信だとパケットロスしたり、なかなかフレームレートが上げられなかったりして、謎が多かったんです。
UDPはパケットロスすることは分っていたのですが、高速通信できるはずなのに、もうちょっと何とかなりそうなのに変だなぁ、、、と思っていました。

そこでTwitterでつぶやいていたところ、いつもお世話になっているらびやんさんが、TCP/IPの方がパケットロスが無くて充分速度が出るよ、とおっしゃっていたので私も試してみることにしました。

そう言えば、以前、このブログのこちらの記事で、Arduino core for the ESP32のサンプルスケッチCameraWebServerを使った実験をしました。
そうしたら、スマホのブラウザに表示されたカメラ画像ストリーミングがメチャメチャ高速で、驚くほどリアルタイム性が高かったんです。
画角を800 x 600 pixelくらいの大画面にしても、20fpsくらい速度が出ていて、スゴかったんです。
ということは、プログラミング次第でここまで高速化が実現できて、パケットロスも無くせるわけですね。

そこで、サンプルスケッチのCameraWebServerを読み解いてみると、ブラウザとの通信はhttpdライブラリ関数を使っていました。
ただ、このhttpdの関数群を見ても、ブラウザとのコールアンドレスポンスが謎すぎて、良く解りませんでした。
それに、ブラウザに出力するHTMLタグは、GZIP圧縮されたバイナリファイルをヘッダに組み込んでいて、
「なんじゃこりゃ!?」
状態でした。
アマチュアの私にとっては敷居が高すぎるっっ!!! と思いました。

そんなこんなでしたが、ブラウザのソースコードビュー画面を見れば、HTMLソースコードが読み取ることができました。
さらに解明を進めていると、今度は<img>タグだけで動画ストリーミングが実現できている謎にぶち当たりました。

動画形式ファイルと言えば、MPEG形式を想像しますよね。
私は動画に関しては全くのド素人で、仕組みも全く分からないのですが、パソコンで動画を楽しむ場合、多くがMPEG形式でした。
<img>タグと言えば、JPEGやBMPなどの静止画だけの表示のはずなのに、なぜ・・・?
動画は<video>タグのはずでは・・・?
謎、謎、謎、、、でした。

そもそも、OV2640などの旧式イメージセンサは、MPEG動画出力はありません。
JPEGやビットマップ画像、YUVなどのフォーマットです。
JPEGやビットマップ画像設定の場合は、完成された1枚の画像(1フレーム)を順次高速で生成し、パラパラ漫画のようにESP32などのマイコンにDMA(Direct Memory Access)で送って来ます。
(これについては過去の以下の記事を参照してください。
esp32-cameraライブラリを読み解く ~OV2640, SCCB, DMA, I2S 編~

この1枚1枚の画像をブラウザに一方的に送り付けていて、なぜ動画ストリーミングになるのか、不思議でなりませんでした。

そんな中、先にも述べたように、この謎をTwitterでつぶやいていたら、@s1tnkさんからとっても有益な情報をいただきました。
これはMotion MPEGという手法で、以下の記事を参考にすれとよいですよ! ということでした。

Androidのカメラ映像をMotionJPEGで配信する

なんと!
衝撃的!!!

ネットで調べてもなかなか分からなかったのですが、この記事で一発理解できました。

HTTPレスポンスヘッダにほんの少しのテキストファイルを含めるだけで、JPEG動画ストリーミングが実現できてしまうとのことです。
HTTP通信を理解されている人ならば、この記事を見れば一発で理解できると思います。
WebSocketの時のようにハンドシェイクに苦労していたのはいったい何だったのか、、、と思いましたね。
たったこれだけでストリーミングできるなら、早く言ってよ!、、、って思っちゃいました。

そんなこんなでしたが、改めて、@s1tnkさん、およびQiita記事の@Yukio-Ichikawaさん、情報ありがとうございました。
m(_ _)m

というわけで、Moton JPEG ( MJPEG )を使ってみることにしたわけです。
後で方法を詳しく説明しますが、これは便利ですよ!
ブラウザをまるで液晶ディスプレイのように画像出力できるんです。
特に、イメージセンサから受け取った画像をパラパラ漫画のように連続送信して動画ストリーミングする場合、最適な手段だと思います。
MJPEG自体は古い昔の規格ですが、自作IoTや電子工作界隈では、まだまだ現役で使えそうですよ!

各ブラウザの動作状況

私が2020/3月時点で動作確認して分かったのは、こんな感じです。

Windows 10  Google Chrome OK
Android 8.0  Google Chrome OK
Windows 10  FireFox OK
iOS (12.4.5)  Safari OK
Windows 10  Edge ×
Windows 10  Internet Explorer ×

iOSのSafariについては、ブラウザの反応がイマイチでした。
ただ、私の手持ちiPadが古いせいかもしれません。

他のストリーミング(およびデータグラム)通信との違い

私は過去に3種類くらいWiFiでESP32とストリーミング(およびデータグラム)通信を実験してきました。
以下の
WebSocket
Server-Sent Events
UDP

などです。

プロトコル コネクション確立 データ送受信 パケットロス バイナリ送受信 対応ブラウザ
WebSocket 超複雑 双方向 あまり無い OK ほぼOK
Server-Sent Events 比較的簡単 一方向 あまり無い ×(テキストのみ) IE以外OK
UDP 簡単 双方向可 多い OK 無理
Motion JPEG 比較的簡単 ほぼ双方向 あまり無い OK IEとEdge以外OK

Motion JPEG (MJPEG)

Motion JPEG ( MJPEG )は、先ほどもチラッと紹介したとおり、JPEG画像やビットマップ(BMP)画像のパラパラ漫画風の動画ストリーミングです。
後で詳しく紹介しますが、Web上のHTMLの<img>タグや<iframe>タグ内に画像を一方的に送信し続けることで実現します。

ブラウザとのコネクション確立(ハンドシェイク)は通常のHTTP通信ハンドシェイクのヘッダ情報に一行増やすだけで実現できて、JPEGやビットマップ画像の動画ストリーミングをするにはとっても手軽です。

ただ、ストリーミングにはTCP/IP および HTTPの規格に沿うため、一度に送るパケット通信量がデバイスによって決められています。
Arduino core for ESP32 の場合、1460 byteで定義されています。
なので、適度に画像を分割して送信するプログラミングが必要になります。

ビットマップ(BMP)画像でストリーミングする場合、JPEG画像に比べて数倍~数十倍の容量になるので、当然フレームレートも下がります。

そして、ボタン操作をして動画を停止したりするリアルタイム双方向通信は、プログラミングがちょっと難しいです。
後で詳しく紹介しますが、制御コントロール用のテキスト通信のポート番号と、動画ストリーミング用のポート番号は別にするという風にしなければうまくできませんでした。

WebSocket

WebSocketは、完全双方向リアルタイム通信ですが、ブラウザとのコネクション確立はかなり面倒で複雑です。
過去のこちらの記事ではコネクション確立(ハンドシェイク)方法を、こちらの記事ではデータ送受信方法を紹介しています。

ESP32とブラウザとのWebSocket通信はできましたが、ESP32同士やM5Stack同士は実現できていません。
私自身も素人ながらライブラリを作ったりしましたが、どこかのプロが作った既存ライブラリに頼った方が賢明だと思います。

そして、JPEG画像やビットマップ画像を送信する場合はバイナリデータを扱いますので、WebSocketでも可能なことは可能です。
でも、私はまだバイナリデータの送受信は試しておらず、テキストデータのみのままです。

Server-Sent Events

Server-Sent Events については、テキストデータの場合のみで、しかも一方向通信です。
一方向のテキストデータのストリーミングに限って言えば、WebSocketに比べてとても簡単で素晴らしいシステムだと思います。
ビットマップ画像などのバイナリデータを扱うことは工夫すれば出来ないことは無いかもしれませんが、特殊文字の条件分けが複雑で、とても非効率なプログラミングになるので、やめた方が良いですね。

UDP

UDPについては、ストリーミングという用語ではなく、正確にはデータグラムというそうです。
UDPの最大の欠点は、ブラウザとの通信が現時点では出来ないことです。
アプリ経由なら可能ですが、個人的にはやはりブラウザと通信したいですね。
UDPはESP32やM5Stackなどの自作IoT機器間のハンドシェイク(コネクション確立)が簡単で実現しやすいのが利点です。
ただ、パケットロスは避けられません。

パケットロスを防いで、できるだけ高速受信するためには、送信側の能力を圧倒的に上回る受信側の処理速度が必要ですね。
ESP32やM5Stack同士のUDP通信では、パケットロスを防ぐならば送信側の速度を遅くするしか無いようです。
それに、Arduino core ESP32 のWiFiデフォルト設定では、ロスしたパケットが再送されているらしく、そのせいで通信トラフィックを圧迫してしまいます。
これは、UDP規格の下位層で再送設定されているようで、UDPデフォルトの正常な動作のようです。
そのせいで、送信側の電力を多く消耗するのも厄介です。
その場合、トラフィックを監視しながら、送信間隔をコントロールするなどの処理が必要なのかも知れません。

では、次の節ではビットマップファイルの作り方を紹介します。


スポンサーリンク


コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください