ラズパイ4とGStreamerでストリーミングサーバーを作ろう

## やること

ラズパイ4(`Raspberry Pi 4 メモリ 4GB`)にカメラをつけてストリーミング配信の環境を作ります。 ラズパイに付けられるマイクがなかったので、音声なし映像のみです。
※ラズパイ3 (`Raspberry Pi 3 model B+`でもインストールできることを確認しました）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/74b06c4a-ee1d-e7fa-b714-23f1fd5de6cb.png" width="400">

## とりあえずファイルが欲しい場合

ストリーミングサーバをたてず、`.m3u8`,`.ts`ファイルが欲しい場合は、こちらからダウンロードください。

→ [テスト用にHLSライブストリーミング配信データおいときます](https://note.com/agw/n/nbaa8df4e221e)

## 参考

- [ラズパイを映像展示用デバイスにした話](https://qiita.com/zgw426/items/61949d7802bebaaee941)
    - ラズパイにNodejsインストール
- [[メモ]video.jsでHLS配信をやってみた](https://qiita.com/t114/items/c7fdffaa2e7fdf406a7b)
    - Video.jsの使い方
- [Raspberry Pi + Node.jsでSkyWayを動かしてみる](https://qiita.com/n0bisuke/items/7b8907fbcb7c9bd89bcb)
    - GStreamerのインストール


## 構成図

カメラを付けたラズパイを、GStreamerとNodejsでストリーミングサーバにします。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/23aae3b6-acd1-3802-5925-52d545a2f250.png" width="400">

更に、CORSを設定し別のWEBサーバでもストリーミングデータが利用できるようにします。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/b7059a6c-5e0b-d644-b35f-1f3efbbc5761.png" width="400">



## 環境

- ラズパイ
    - Raspberry Pi 4 Model B 4GB

```
$ lsb_release -a
No LSB modules are available.
Distributor ID: Raspbian
Description:    Raspbian GNU/Linux 10 (buster)
Release:        10
Codename:       buster
```


- カメラ

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/4e54274e-3b57-0bde-3d2f-eb30a695b2d8.png" width="400">

[参照元](https://www.amazon.co.jp/dp/B00FGKYHXA/ref=asc_df_B00FGKYHXA2491448/?tag=jpgo-22&creative=9339&creativeASIN=B00FGKYHXA&linkCode=df0&hvadid=217932859331&hvpos=1o3&hvnetw=g&hvrand=11332564013545782284&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=1009309&hvtargid=pla-440151129080)


カメラがサポートするフォーマット

```
$ v4l2-ctl --list-formats-ext
ioctl: VIDIOC_ENUM_FMT
        Type: Video Capture

        [0]: 'YU12' (Planar YUV 4:2:0)
                Size: Stepwise 32x32 - 2592x1944 with step 2/2
        [1]: 'YUYV' (YUYV 4:2:2)
                Size: Stepwise 32x32 - 2592x1944 with step 2/2
        [2]: 'RGB3' (24-bit RGB 8-8-8)
                Size: Stepwise 32x32 - 2592x1944 with step 2/2
        [3]: 'JPEG' (JFIF JPEG, compressed)
                Size: Stepwise 32x32 - 2592x1944 with step 2/2
        [4]: 'H264' (H.264, compressed)
                Size: Stepwise 32x32 - 2592x1944 with step 2/2
        [5]: 'MJPG' (Motion-JPEG, compressed)
                Size: Stepwise 32x32 - 2592x1944 with step 2/2
        [6]: 'YVYU' (YVYU 4:2:2)
                Size: Stepwise 32x32 - 2592x1944 with step 2/2
        [7]: 'VYUY' (VYUY 4:2:2)
                Size: Stepwise 32x32 - 2592x1944 with step 2/2
        [8]: 'UYVY' (UYVY 4:2:2)
                Size: Stepwise 32x32 - 2592x1944 with step 2/2
        [9]: 'NV12' (Y/CbCr 4:2:0)
                Size: Stepwise 32x32 - 2592x1944 with step 2/2
        [10]: 'BGR3' (24-bit BGR 8-8-8)
                Size: Stepwise 32x32 - 2592x1944 with step 2/2
        [11]: 'YV12' (Planar YVU 4:2:0)
                Size: Stepwise 32x32 - 2592x1944 with step 2/2
        [12]: 'NV21' (Y/CrCb 4:2:0)
                Size: Stepwise 32x32 - 2592x1944 with step 2/2
        [13]: 'BGR4' (32-bit BGRA/X 8-8-8-8)
                Size: Stepwise 32x32 - 2592x1944 with step 2/2
```

このようにラズパイに接続します。（写真はイメージです。基板はラズパイ4ではありません）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/68390b16-b364-9cf2-8433-a8401236d53c.jpeg" width="400">

## GStreamerをインストールしよう

ストリーミング配信に必要なアプリGStreamerをインストールします。以下2つのコマンドを実行すればインストール完了です。（ということがわかるまで苦労しました）

```
$ sudo apt install autoconf automake libtool
$ sudo apt install gstreamer1.0-tools gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
```

## GStreamerの動作確認しよう

早速ストリーミングデータを作成してみます。

```
$ mkdir test
$ cd test
$ 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=./segment%05d.ts \
 playlist-location=output.m3u8 \
 playlist-root=./
```

実行時はこんな感じです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/12effdfd-0155-59a9-617d-ab81aa0e05d8.png" width="400">

実行すると、このように`.m3u8`,`.ts`ファイルが作成されます。

```
$ ls
output.m3u8  segment00000.ts  segment00001.ts  segment00002.ts
```


## GStreamerの出力ファイルを確認しよう

`.m3u8`の中は、`.ts`ファイルとの相対パスが書かれてました。実際の相対パスとずれていないか確認します。（これに気が付かず苦労しました）

```
$ cat output.m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-ALLOW-CACHE:NO
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-TARGETDURATION:9

#EXTINF:8.7807645797729492,
./segment00000.ts
#EXTINF:7.9985880851745605,
./segment00001.ts
#EXTINF:6.8206477165222168,
./segment00002.ts
```

## `.ts`ファイルを確認しよう

`.ts`ファイルをWindowsにコピーしてダブルクリックで実行します。カメラ映像が再生されれば成功です。（`.ts`ファイルは動画データなんですね、へぇー）


## Nodejsをインストールしよう

GStreamerでストリーミングデータが作れるようになりました。そのデータをWebブラウザで開くことができるように、NodejsをインストールしてラズパイをWebサーバにします。

```
# npmを更新
sudo npm install npm@latest -g
# npmのバージョン確認
$ npm -v
# -> 6.13.4

$ sudo apt update
$ sudo apt install curl
$ curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
$ sudo apt install nodejs -y

# nodejsのバージョン確認
$ node -v
# -> v12.14.0
```

Nodejsがインストールできたので、プロジェクトを作成します。

```
$ mkdir nodejs
$ cd nodejs
$ npm init
$ npm install --save express
$ touch app.js
$ mkdir wwwroot
$ touch wwwroot/test.html
```

`app.js`ファイルを以下のようにします。

```javascript:app.js
var express = require('express');
var app = express();

app.use(express.static('wwwroot'));

var port = 3000;
app.listen(port,function(){
	console.log("サーバがポート%dで起動しました。モード:%s",port,app.settings.env)
});
```

`test.html`ファイルを以下のようにします。

```html:wwwroot/test.html
<!DOCTYPE html>
<html><head></head><body>
TEST
</body></html>
```

Webサーバとして動作するか試運転します。

```
$ pwd
/home/pi/nodejs
 $ node app.js
サーバがポート3000で起動しました。モード:development
```

ブラウザで`http://＜ラズパイのIPアドレス＞:3000/test.html`を開き、Webサイトが開けば成功です。

## ストリーミングサーバにしよう！

ストリーミングデータの作成とWebサーバを用意できました。これら2つを使ってストリーミングサーバを作ります。

具体的には、ストリーミングデータをhtmlの`<video> </video>`タグで表示できるようにします。そのためには、`video.js`というモジュールを使うことでChromeでも表示できるようになります。

必要なファイル（`.m3u8`, `.ts`以外）は、githubに置きました。これを`wwwroot`配下に格納します。
https://github.com/zgw426/GStreamerSample/tree/master/sample01

ストリーミングデータ含め、各ファイルがこのように配置されるようにします。

```bash
$ pwd
/home/pi/nodejs/wwwroot
$ tree
.
├── output.m3u8
├── segment00056.ts
├── segment00057.ts
├── segment00058.ts
├── segment00059.ts
├── segment00060.ts
├── segment00061.ts
├── segment00062.ts
├── segment00063.ts
├── static
│   ├── css
│   │   └── video-js.min.css
│   └── js
│       ├── video.min.js
│       ├── videojs-contrib-hls.min.js
│       └── videojs-contrib-media-sources.min.js
└── streamtest.html
```

## ストリーミングサイトを開いてみよう

`http://＜ラズパイのIPアドレス＞:3000/streamtest.html`を開くとこのような画面が表示されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/0ffb4296-586d-a131-4f97-e4e2ceccab60.png" width="400">

動画を再生して、ラズパイに付けたカメラ映像が動画として表示されれば成功です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/567e3673-c9ff-3633-7f32-3bc6c2742b2f.png" width="400">

これで、ストリーミングサーバが完成しました。

## CORSを有効にしよう

冒頭に紹介した2つ目の構成にします。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/b7059a6c-5e0b-d644-b35f-1f3efbbc5761.png" width="400">

別にWebサーバーを用意して、そちらのサイトでストリーミング配信ができるようにします。これには、CORS(オリジン間リソース共有)を有効にする必要があります。

CORSが有効でないと、`Access to XMLHttpRequest at 'http://xxx' from origin 'http://yyy' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.`というエラーが発生し再生できません。 

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">CORSできてないとこうなる <a href="https://t.co/bEVYQpEojs">pic.twitter.com/bEVYQpEojs</a></p>&mdash; j4amountain (@zsipparu) <a href="https://twitter.com/zsipparu/status/1208176641005932544?ref_src=twsrc%5Etfw">December 21, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


## NodejsにCORSモジュールをインストールしよう

ラズパイのNodejsにCORSを有効にするため、モジュールをインストールします。

```
$ npm install cors
```

`app.js`を以下のようにします。これで、CORS設定が完了です。

**※注意※**
この設定では、どのサーバからもリソースが利用できる状態で、セキュリティ的には危険な状態です。本来なら、特定のサーバからのみアクセス許可するなど、許可する範囲を最小にします。

```javascript:app.js
var express = require('express');
var cors = require('cors');
var app = express();

app.use(cors());
app.use(express.static('wwwroot'));

var port = 3000;
app.listen(port,function(){
        console.log("サーバがポート%dで起動しました。モード:%s",port,app.settings.env)
});
```

CORSを有効にすると、このように別PCのWebサーバでもストリーミング配信ができるようになります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/74b06c4a-ee1d-e7fa-b714-23f1fd5de6cb.png" width="400">


stream.html は、streamtest.htmlをコピーして、以下のように編集すると作れます。

[変更前] `<source src="./output.m3u8" type="application/x-mpegURL">`
[変更後] `<source src="http://{ラズパイIP or ホスト名}:{ポート番号}/output.m3u8" type="application/x-mpegURL">`




## おまけ：遅延テスト

ストリーミング配信にはいくらか遅延があります。配信時間が長くなった場合、遅延がどうなるか検証してみました。16時間連続稼働でもそれほど遅くならず、個人的には満足な値でした。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/c0cf225d-74ed-8a86-8b8b-6c7b6b0d03d5.png" width="300">

結果

||遅延|
|---|---|
|開始時|18秒|
|1.5時間後|42秒|
|16時間後|27秒|