HLS編のついでとして、MPEG-DASHによる配信も試してみました。

今回の目標

  • 自宅などの様子を外出先から見れるライブカメラをRaspberry Pi 4で作る
    • DASHによるライブ配信を行う
    • 動画はRaspberry Pi 4のハードウェアエンコーダを用いてH.264で圧縮する

MPEG-DASHとは?

HTTPを用いて動画をライブ配信するしくみ。やることはHLSとほとんど同じ。

ffmpegによるDASHファイル生成

DASHで配信を行う際は、H264でエンコード済みの動画をDASH用チャンクファイル(m4s)に分割し、連番を付与しつつmpdプレイリスト(manifest.mpd)を作る必要がある。 これを一発で解決するために、毎度おなじみffmpegを用いる。

今回はubuntu 21.10標準パッケージのffmpegをインストールする。

1
2
3
4
5
6
7
8
9
10
11
12
13
% sudo apt install ffmpeg
% ffmpeg -version
ffmpeg version 4.4-6ubuntu5 Copyright (c) 2000-2021 the FFmpeg developers
built with gcc 11 (Ubuntu 11.2.0-7ubuntu1)
configuration: --prefix=/usr --extra-version=6ubuntu5 --toolchain=hardened --libdir=/usr/lib/aarch64-linux-gnu --incdir=/usr/include/aarch64-linux-gnu --arch=arm64 --enable-gpl --disable-stripping --enable-gnutls --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libdav1d --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librabbitmq --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libsrt --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzimg --enable-libzmq --enable-libzvbi --enable-lv2 --enable-omx --enable-openal --enable-opencl --enable-opengl --enable-sdl2 --enable-pocketsphinx --enable-librsvg --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-chromaprint --enable-frei0r --enable-libx264 --enable-shared
libavutil      56. 70.100 / 56. 70.100
libavcodec     58.134.100 / 58.134.100
libavformat    58. 76.100 / 58. 76.100
libavdevice    58. 13.100 / 58. 13.100
libavfilter     7.110.100 /  7.110.100
libswscale      5.  9.100 /  5.  9.100
libswresample   3.  9.100 /  3.  9.100
libpostproc    55.  9.100 / 55.  9.100

ffmpeg -formatsでDASHがサポートされていることが確認できればOK。

1
2
3
% ffmpeg -formats 2>/dev/null | grep dash
 DE dash            DASH Muxer
 DE webm_dash_manifest WebM DASH Manifest

早速実行してみる。 今回設定するオプションは下記の通り。

  • -f video4linux2: 入力にv4l2を利用
  • -input_format h264: 入力フォーマットにH264を指定
  • -video_size 1920x1080: 入力動画の解像度を1920x1080に指定
  • -framerate 30: 入力動画のフレームレートを30fpsに指定
  • -i /dev/video0: 入力デバイスを/dev/video0に指定
  • -f dash: 出力フォーマットをDASHに指定
  • -use_timeline 1
  • -use_template 1
  • -window_size 5
  • -adaptation_sets "id=0,streams=v"
  • -streaming 1
  • -seg_duration 1
  • -remove_at_exit 1
  • stream/manifest.mpd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
% mkdir -p stream
% rm -f stream/*.*
ffmpeg  -f video4linux2 \
        -input_format h264 \
        -video_size 1920x1080 \
        -framerate 30 \
        -i /dev/video0 \
        -f dash -c:v copy \
        -use_timeline 1 \
        -use_template 1 \
        -window_size 5 \
        -adaptation_sets "id=0,streams=v" \
        -streaming 1 \
        -seg_duration 1 \
        -remove_at_exit 1 \
        stream/manifest.mpd

カメラの接続・設定やアクセス権限の設定など、全てがうまくいっていれば、 下記のように数秒おきに動画ファイルが書き出され、プレイリストが更新されていく。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
ffmpeg version 4.4-6ubuntu5 Copyright (c) 2000-2021 the FFmpeg developers
  built with gcc 11 (Ubuntu 11.2.0-7ubuntu1)
  configuration: --prefix=/usr --extra-version=6ubuntu5 --toolchain=hardened --libdir=/usr/lib/aarch64-linux-gnu --incdir=/usr/include/aarch64-linux-gnu --arch=arm64 --enable-gpl --disable-stripping --enable-gnutls --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libdav1d --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librabbitmq --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libsrt --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzimg --enable-libzmq --enable-libzvbi --enable-lv2 --enable-omx --enable-openal --enable-opencl --enable-opengl --enable-sdl2 --enable-pocketsphinx --enable-librsvg --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-chromaprint --enable-frei0r --enable-libx264 --enable-shared
  libavutil      56. 70.100 / 56. 70.100
  libavcodec     58.134.100 / 58.134.100
  libavformat    58. 76.100 / 58. 76.100
  libavdevice    58. 13.100 / 58. 13.100
  libavfilter     7.110.100 /  7.110.100
  libswscale      5.  9.100 /  5.  9.100
  libswresample   3.  9.100 /  3.  9.100
  libpostproc    55.  9.100 / 55.  9.100
Input #0, video4linux2,v4l2, from '/dev/video0':
  Duration: N/A, start: 238172.345795, bitrate: N/A
  Stream #0:0: Video: h264 (High), yuv420p(progressive), 1920x1080, 30 fps, 30 tbr, 1000k tbn, 2000k tbc
[dash @ 0xaaab099a7520] No bit rate set for stream 0
[dash @ 0xaaab099a7520] Opening 'stream/init-stream0.m4s' for writing
Output #0, dash, to 'stream/manifest.mpd':
  Metadata:
    encoder         : Lavf58.76.100
  Stream #0:0: Video: h264 (High), yuv420p(progressive), 1920x1080, q=2-31, 30 fps, 30 tbr, 1000k tbn, 1000k tbc
Stream mapping:
  Stream #0:0 -> #0:0 (copy)
Press [q] to stop, [?] for help
[dash @ 0xaaab099a7520] Timestamps are unset in a packet for stream 0. This is deprecated and will stop working in the future. Fix your code to set the timestamps properly
[dash @ 0xaaab099a7520] Opening 'stream/chunk-stream0-00001.m4s.tmp' for writing
[dash @ 0xaaab099a7520] Non-monotonous DTS in output stream 0:0; previous: 0, current: 0; changing to 1. This may result in incorrect timestamps in the output file.
[dash @ 0xaaab099a7520] Opening 'stream/manifest.mpd.tmp' for writingd= 1.1x
[mp4 @ 0xaaab09a49770] Application provided duration: -22 / timestamp: 1965362 is out of range for mov/mp4 format
[mp4 @ 0xaaab09a49770] pts has no value
[dash @ 0xaaab099a7520] Opening 'stream/chunk-stream0-00002.m4s.tmp' for writing
[dash @ 0xaaab099a7520] Opening 'stream/manifest.mpd.tmp' for writingd=1.05x
[mp4 @ 0xaaab09a49770] Application provided duration: -22 / timestamp: 3964035 is out of range for mov/mp4 format
[mp4 @ 0xaaab09a49770] pts has no value
[dash @ 0xaaab099a7520] Opening 'stream/chunk-stream0-00003.m4s.tmp' for writing
[dash @ 0xaaab099a7520] Opening 'stream/manifest.mpd.tmp' for writingd=1.03x
[mp4 @ 0xaaab09a49770] Application provided duration: -21 / timestamp: 5962708 is out of range for mov/mp4 format
[mp4 @ 0xaaab09a49770] pts has no value
[dash @ 0xaaab099a7520] Opening 'stream/chunk-stream0-00004.m4s.tmp' for writing
[dash @ 0xaaab099a7520] Opening 'stream/manifest.mpd.tmp' for writingd=1.02x
frame=  228 fps= 31 q=-1.0 Lsize=N/A time=00:00:07.52 bitrate=N/A speed=1.02x
video:1378kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown

カメラの読み取りとDASHフォーマットでの出力がうまくできると、 streamディレクトリ以下に下記のようなファイルが出力される。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
% ls -l
total 4424
-rw------- 1 gloria trainers 345782 Feb 24 23:46 chunk-stream0-00026.m4s
-rw------- 1 gloria trainers 468022 Feb 24 23:46 chunk-stream0-00027.m4s
-rw------- 1 gloria trainers 406411 Feb 24 23:46 chunk-stream0-00028.m4s
-rw------- 1 gloria trainers 453560 Feb 24 23:46 chunk-stream0-00029.m4s
-rw------- 1 gloria trainers 374345 Feb 24 23:46 chunk-stream0-00030.m4s
-rw------- 1 gloria trainers 346165 Feb 24 23:46 chunk-stream0-00031.m4s
-rw------- 1 gloria trainers 383838 Feb 24 23:46 chunk-stream0-00032.m4s
-rw------- 1 gloria trainers 391931 Feb 24 23:46 chunk-stream0-00033.m4s
-rw------- 1 gloria trainers 371007 Feb 24 23:46 chunk-stream0-00034.m4s
-rw------- 1 gloria trainers 449495 Feb 24 23:46 chunk-stream0-00035.m4s
-rw------- 1 gloria trainers 500859 Feb 24 23:46 chunk-stream0-00036.m4s.tmp
-rw------- 1 gloria trainers    790 Feb 24 23:45 init-stream0.m4s
-rw------- 1 gloria trainers   1518 Feb 24 23:46 manifest.mpd
-rw------- 1 gloria trainers    588 Feb 24 23:46 media_0.m3u8
-rw------- 1 gloria trainers    117 Feb 24 23:45 out.m3u8

chunk-stream0-00000.m4sからの連番になっているが、-window_size 5を設定しているため、 古いものから順に削除されている。

manifest.mpdの中身は下記のようになる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?xml version="1.0" encoding="utf-8"?>
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="urn:mpeg:dash:schema:mpd:2011"
        xmlns:xlink="http://www.w3.org/1999/xlink"
        xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd"
        profiles="urn:mpeg:dash:profile:isoff-live:2011"
        type="dynamic"
        minimumUpdatePeriod="PT1S"
        suggestedPresentationDelay="PT1S"
        availabilityStartTime="2022-02-24T14:47:53.221Z"
        publishTime="2022-02-24T14:48:04.994Z"
        timeShiftBufferDepth="PT9.9S"
        maxSegmentDuration="PT1.0S"
        minBufferTime="PT3.9S">
        <ProgramInformation>
        </ProgramInformation>
        <ServiceDescription id="0">
        </ServiceDescription>
        <Period id="0" start="PT0.0S">
                <AdaptationSet id="0" contentType="video" startWithSAP="1" segmentAlignment="true" bitstreamSwitching="true" frameRate="30/1" maxWidth="1920" maxHeight="1080" par="16:9">
                        <Representation id="0" mimeType="video/mp4" codecs="avc1.640028" bandwidth="1339936" width="1920" height="1080" sar="1:1">
                                <SegmentTemplate timescale="1000000" availabilityTimeComplete="false" initialization="init-stream$RepresentationID$.m4s" media="chunk-stream$RepresentationID$-$Number%05d$.m4s" startNumber="2">
                                        <SegmentTimeline>
                                                <S t="1965382" d="1998672" r="1" />
                                                <S d="1998671" />
                                                <S d="1998673" />
                                                <S d="1998671" />
                                        </SegmentTimeline>
                                </SegmentTemplate>
                        </Representation>
                </AdaptationSet>
        </Period>
</MPD>

次はこれらのファイルをブラウザから取得できるように、WebサーバとWebページを作成する。

動画閲覧用Webページの作成

Webブラウザ上でDASH動画を再生したい時は、dash.jsを用いる。

使い方はそれほど難しくなく、video要素に対して読み取った動画ソースをattachするだけでよい。 video要素にcontrols属性を加えておくと、再生・停止や音量調整、一定の範囲内でのシークなどもできるようになる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html>
  <head>
    <title>DASHのサンプル</title>
    <meta charset="UTF-8">
    <script src="http://cdn.dashjs.org/latest/dash.all.min.js"></script>
    <script src="/static/video-dash.js"></script>
  </head>

  <body id="mainBox">
    <h1>DASHのサンプル</h1>
    <video id="sampleVideo" controls style="max-width: 95vw; max-height: 80vh;"></video>
    <script type="text/javascript">
      let url = '/stream/manifest.mpd';
      let player = dashjs.MediaPlayer().create();
      player.initialize(document.querySelector('#sampleVideo'), url, true);
    </script>
  </body>
</html>

ここまでのファイルが下記のように展開されている状態にし、このディレクトリをWebサーバで公開する。

1
2
3
4
5
6
7
8
9
10
11
12
13
% find .
.
./index.html
./stream
./stream/chunk-stream0-00007.m4s.tmp
./stream/manifest.mpd
./stream/chunk-stream0-00006.m4s
./stream/chunk-stream0-00005.m4s
./stream/chunk-stream0-00004.m4s
./stream/chunk-stream0-00003.m4s
./stream/chunk-stream0-00002.m4s
./stream/chunk-stream0-00001.m4s
./stream/init-stream0.m4s

Webサーバは単純に上記のHTMLファイルと動画ファイルを転送するだけなのでなんでもよい。 Python3が入っているのであれば、Python標準添付のhttp.serverモジュールを用いてWebサーバが起動できる。

1
% python3 -m http.server 8000 --bind 0.0.0.0 --directory .
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
%  python3 -m http.server 8000 --bind 0.0.0.0 --directory .
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.39.1.1 - - [25/Feb/2022 00:04:34] "GET / HTTP/1.1" 200 -
10.39.1.1 - - [25/Feb/2022 00:04:34] "GET /favicon.ico HTTP/1.1" 404 -
10.39.1.1 - - [25/Feb/2022 00:04:34] code 404, message File not found
10.39.1.1 - - [25/Feb/2022 00:04:43] "GET /stream/manifest.mpd HTTP/1.1" 200 -
10.39.1.1 - - [25/Feb/2022 00:04:44] "GET /stream/init-stream0.m4s HTTP/1.1" 200 -
10.39.1.1 - - [25/Feb/2022 00:04:47] "GET /stream/manifest.mpd HTTP/1.1" 200 -
10.39.1.1 - - [25/Feb/2022 00:04:48] "GET /stream/chunk-stream0-00238.m4s HTTP/1.1" 200 -
10.39.1.1 - - [25/Feb/2022 00:04:48] "GET /stream/chunk-stream0-00239.m4s HTTP/1.1" 200 -
10.39.1.1 - - [25/Feb/2022 00:04:49] "GET /stream/manifest.mpd HTTP/1.1" 304 -
10.39.1.1 - - [25/Feb/2022 00:04:49] "GET /stream/chunk-stream0-00240.m4s HTTP/1.1" 200 -
10.39.1.1 - - [25/Feb/2022 00:04:50] "GET /stream/manifest.mpd HTTP/1.1" 200 -
10.39.1.1 - - [25/Feb/2022 00:04:50] "GET /stream/chunk-stream0-00241.m4s HTTP/1.1" 200 -
10.39.1.1 - - [25/Feb/2022 00:04:51] "GET /stream/manifest.mpd HTTP/1.1" 304 -
10.39.1.1 - - [25/Feb/2022 00:04:52] "GET /stream/manifest.mpd HTTP/1.1" 200 -
10.39.1.1 - - [25/Feb/2022 00:04:52] "GET /stream/chunk-stream0-00242.m4s HTTP/1.1" 200 -

ここまでの作業がうまくいっていれば、http://Raspberry Pi 4のIPアドレス:8000/にブラウザからアクセスすると、動画が閲覧できる…はず。

DASHとHLSの同時配信

先ほどのDASH用コマンドに-hls_playlistオプションと-hls_master_nameオプションを付けると、 HLS形式のプレイリストも同時に生成してくれるようになる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
% mkdir -p stream
% rm -f stream/*.*
ffmpeg  -f video4linux2 \
        -input_format h264 \
        -video_size 1920x1080 \
        -framerate 30 \
        -i /dev/video0 \
        -f dash -c:v copy \
        -use_timeline 1 \
        -use_template 1 \
        -window_size 5 \
        -adaptation_sets "id=0,streams=v" \
        -hls_playlist 1 \
        -hls_master_name "out.m3u8" \
        -streaming 1 \
        -seg_duration 1 \
        -remove_at_exit 1 \
        stream/manifest.mpd

これで一番最初に参照されるout.m3u8と、out.m3u8経由で参照されるmedia_0.m3u8が生成される。 後はout.m3u8をhls.jsを用いて参照すれば、HLS単独版と同様に動画が再生できる。

1
2
3
4
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-STREAM-INF:BANDWIDTH=2007405,RESOLUTION=1920x1080,CODECS="avc1.640028"
media_0.m3u8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#EXTM3U
#EXT-X-VERSION:6
#EXT-X-TARGETDURATION:2
#EXT-X-MEDIA-SEQUENCE:27
#EXT-X-MAP:URI="init-stream0.m4s"
#EXTINF:1.998673,
#EXT-X-PROGRAM-DATE-TIME:2022-02-25T00:13:20.932+0900
chunk-stream0-00027.m4s
#EXTINF:1.998673,
#EXT-X-PROGRAM-DATE-TIME:2022-02-25T00:13:22.931+0900
chunk-stream0-00028.m4s
#EXTINF:1.998673,
#EXT-X-PROGRAM-DATE-TIME:2022-02-25T00:13:24.930+0900
chunk-stream0-00029.m4s
#EXTINF:1.998673,
#EXT-X-PROGRAM-DATE-TIME:2022-02-25T00:13:26.928+0900
chunk-stream0-00030.m4s
#EXTINF:1.998673,
#EXT-X-PROGRAM-DATE-TIME:2022-02-25T00:13:28.927+0900
chunk-stream0-00031.m4s