📷

Jetson NanoからWebカメラの映像をMJPEGでFlutterアプリに表示させる方法

2021/10/20に公開

はじめに

Jetson NanoにつないだWebカメラからMotion JPEG(MJPEG)でストリーミング配信を行い、Flutterのアプリケーションで表示させる方法について述べる。

MJPEGとは下記の通り。

MJPEGとは動画を構成する1枚1枚のJPEG画像をパラパラ漫画のように連続させ動画データとする方式。
特徴として、動きが激しいケースで対応可能で、1フレームを鮮明に取り出すことができる。 - ビデオ圧縮形式とは

Jetson Nano側の設定

こちらの記事を参考にした。その記事を参考にしながら補足説明を行う。

まずMJPEGをHTTPでストリーミングするオープンソース「mjpg-streamer」をJetson Nanoに導入する。

インストール方法はこちらのwikiを参考にする。

# Update & Install Tools
sudo apt-get update -y
sudo apt-get upgrade -y
sudo apt-get install build-essential git imagemagick libv4l-dev libjpeg-dev cmake -y

# Clone Repo in /tmp
cd /tmp
git clone https://github.com/jacksonliam/mjpg-streamer.git
cd mjpg-streamer/mjpg-streamer-experimental

# Make
make
sudo make install

アプリケーションの起動は下記のコマンドを利用する。ただしインストール方法はRaspberry Piを想定したものであることに注意する。

# Run
/usr/local/bin/mjpg_streamer -i "input_uvc.so -r 1280x720 -d /dev/video0 -f 30" -o "output_http.so -p 8080 -w /usr/local/share/mjpg-streamer/www"

上記のwebサーバーの起動部分は下記のようなスクリプトをつくって、それを実行しても良い。例えばファイル名をrun_mjpg-streamer.shとする。

#!/bin/sh

input=/usr/local/lib/mjpg-streamer/input_uvc.so
output=/usr/local/lib/mjpg-streamer/output_http.so
port=8080
device=/dev/video0
webroot=/usr/local/share/mjpg-streamer/www

mjpg_streamer -i "$input -d $device -r 640x480 -f 1" -o "$output -p $port -w $webroot"

下記は「shebang」と呼ばれシェルスクリプトを使う際のおまじない。

スクリプトを読み込むインタープリタを指定する事ができ、実行時に shやbashを指定する必要がなくなる

#!/bin/sh

つまりrun_mjpg-streamer.shを実行するとき、本来は

/bin/sh ./run_mjpg-streamer.sh

とする必要があったが、下記のようにするだけシェルスクリプトを実行することができる。

./run_mjpg-streamer.sh

実行時にアクセス権限がないと言われた場合、そのファイルがあるディレクトリで、下記を実行する。その後、もう一度実行すると良い。

chmod 775 run_mjpg-streamer.sh

実行したあと、下記のアドレスにアクセスするとWebカメラからの動画を確認することができる。ただし、今回の場合指定しているポートは8080である。

http://Jetson NanoのIPアドレス】:【指定したポート】/

IPアドレスはターミナルから、下記を入力してwlan0のinet部分表示されるIPアドレスを利用する。

ifconfig

そしてURLにアクセスすると下記のような画面が現れれば成功。

Flutterのコード

Flutterアプリ上でMJPEGを利用するにはライブラリ「flutter_mjpeg」を使用すると良い。ライブラリはターミナルで使用するアプリのディレクトリに移動し、下記のようにコマンド実行すると適用される。

flutter pub add flutter_mjpeg
flutter pub get

まずは「Example」にあるサンプルコードを動かしてみる。

サンプルコードは下記の通り。

example/lib/main.dart
```dart
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_mjpeg/flutter_mjpeg.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final isRunning = useState(true);
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Demo Home Page'),
      ),
      body: Column(
        children: <Widget>[
          Expanded(
            child: Center(
              child: Mjpeg(
                isLive: isRunning.value,
                error: (context, error, stack) {
                  print(error);
                  print(stack);
                  return Text(error.toString(), style: TextStyle(color: Colors.red));
                },
                stream:
                'http://91.133.85.170:8090/cgi-bin/faststream.jpg?stream=half&fps=15&rand=COUNTER', //'http://192.168.1.37:8081',
              ),
            ),
          ),
          Row(
            children: <Widget>[
              RaisedButton(
                onPressed: () {
                  isRunning.value = !isRunning.value;
                },
                child: Text('Toggle'),
              ),
              RaisedButton(
                onPressed: () {
                  Navigator.of(context).push(MaterialPageRoute(
                      builder: (context) => Scaffold(
                            appBar: AppBar(),
                          )));
                },
                child: Text('Push new route'),
              ),
            ],
          ),
        ],
      ),
    );
  }
}
```

サンプルコードでは下記のようにURLを指定している。

stream: 'http://91.133.85.170:8090/cgi-bin/faststream.jpg?stream=half&fps=15&rand=COUNTER', //'http://192.168.1.37:8081',

こちらをmjpg-streamerで「videoLAN」で表示されているURLに置き換えるとアプリ上で映像が表示される。

シミュレーター上では下記のようになる。

おわりに

割と簡単にJetson NanoにつないだWebカメラの映像をFlutterアプリ上で表示することができた。他のことにも応用できそう。

Discussion