先の記事で
- 動画自体のタイムコードを書き込んだものを作った
- Shaka Streamer, Shaka Packagerを使ってHLS Liveのストリーミングをローカル環境で構築できた
なので、次は「現在時刻(FFmpegで動画を作っている時の時刻)を書き込んだ動画を作成しながらローカル環境でHLS Liveのストリーミング」をしてみる。
使用する環境
- macOS 10.14.6(18G3020)
- FFmpeg version 4.1.4
- Shaka Packager v2.4.1-c731217-release
Shaka Streamerは使わなくてもよかった。FFmpegからのUDPの入力をするためのShaka Streamerのconfigがわからなかったので、Shaka Streamerを使うのは断念した。
現在時刻を書き込んだ動画を作成
先に書いた記事のコマンドを一部変更して、現在時刻を書き込んだ動画をローカルに保存する。
ffmpeg -re -i 03_caminandes_llamigos_1080p.mp4 -vcodec h264 \
-profile:v main -b:v 1000k -minrate:v 1000k -maxrate:v 1000k \
-bufsize:v 2000k -crf 23 -sc_threshold 0 -keyint_min 24 -r 24 -g 48 \
-vf "scale=640:-1, \
drawtext=fontfile=/Library/Fonts/Courier\ New\ Bold.ttf: \
text='%{localtime\:%X}': r=24: fontsize=60: fontcolor=white: \
x=(w-tw)/2: y=h-(2*lh): box=1: boxcolor=0x00000000@1" \
-acodec aac -ac 2 -ab 64k -ar 44.1k -y output2.mp4
変更した点
- ffmpegのオプションで
-re
を加える。 - drawtextのオプションの
timecode='00\;00\:00\:00'
をtext='%{localtime\:%X}'
に変更する。
localtimeの説明については https://ffmpeg.org/ffmpeg-filters.html#Text-expansion を参考に。
ffmpeg drawtime localtime
でググるといろんな人のページも出てくるので参考になると思います。
HLSのストリーミングをしてみる
httpdを起動し、Shaka Packagerを実行し、UDPで入力を待ち受け、UDPにFFmpegで入力をして、HLSを出力するという風なことをしてみます。
httpd起動
sudo brew services start httpd
Shaka Packagerの実行
以下のスクリプトではこんなことをしてます。
-
udp://127.0.0.1:40000
で入力を待つ -
./www/packager/hls.m3u8
にHLSを出力する
#!/bin/bash
INPUT="udp://127.0.0.1:40000"
OUT_DIR="./www/packager"
OUT_AUDIO_SEGMENT="${OUT_DIR}/audio_\$Number\$.ts"
OUT_AUDIO_PLAYLIST="${OUT_DIR}/audio.m3u8"
OUT_VIDEO_SEGMENT="${OUT_DIR}/video_\$Number\$.ts"
OUT_VIDEO_PLAYLIST="${OUT_DIR}/video.m3u8"
OUT_MASTER_PLAYLIST="${OUT_DIR}/hls.m3u8"
rm -rf $OUT_DIR
mkdir -p $OUT_DIR
packager \
"in=${INPUT},stream=audio,segment_template=${OUT_AUDIO_SEGMENT},playlist_name=${OUT_AUDIO_PLAYLIST},hls_group_id=audio" \
"in=${INPUT},stream=video,segment_template=${OUT_VIDEO_SEGMENT},playlist_name=${OUT_VIDEO_PLAYLIST}" \
--hls_master_playlist_output ${OUT_MASTER_PLAYLIST} \
--hls_playlist_type LIVE
FFmpegでの入力
udp://127.0.0.1:40000 でFFmpegから入力をする。
これまでのffmpegのコマンドだとストリーミングをするにはCPUを使用しすぎるので、オプションを変えて試す。
ffmpeg -re -stream_loop -1 -i 03_caminandes_llamigos_1080p.mp4 \
-vcodec h264 -profile:v main -preset ultrafast -tune zerolatency \
-b 500k -sc_threshold 0 -keyint_min 24 -r 24 -g 48 \
-vf "scale=640:-1, \
drawtext=fontfile=/Library/Fonts/Courier\ New\ Bold.ttf: \
text='%{localtime\:%X}': r=24: fontsize=60: fontcolor=white: \
x=(w-tw)/2: y=h-(2*lh): box=1: boxcolor=0x00000000@1" \
-acodec aac -ac 2 -ab 64k -ar 44.1k -f mpegts udp://127.0.0.1:40000
結果
遅延
ffplayで再生をして確認してみたところ、遅延が23秒前後〜28秒程度となった。
出来上がったHLSの確認
出来上がったm3u8の中身を見ると以下の通りだった。
- m3u8を見る限り、1つのtsファイルの長さが6秒だった
- m3u8に含まれるtsファイルの数が特定の数ではなかった(5、6個を想定していたが、生成されたtsが全部含まれていた)
video_47.ts
#EXTINF:6.000,
video_48.ts
#EXTINF:6.000,
video_49.ts
#EXTINF:6.000,
video_50.ts
#EXTINF:6.000,
video_51.ts
#EXTINF:6.000,
video_52.ts
また、videoのtsファイルをffprobeで確認し、以下のことは確認できたので、FFmpegでの入力したストリームにはおよそ問題がなさそうだった。
- 長さが6秒であるのは確かだった
- 2秒ごとにI-frameが含まれていた
次の調査
調子のいい時は遅延が20秒未満になるのだけれども、どうも安定しない。
FFmpegのコマンドのチューニング、Shaka Packagerのコマンドのオプションの理解が必要そうだ。
(追記)チューニング後
Shaka Packagerのオプションを見直したら安定した。
- チャンクファイルの長さが2秒になるように。
- m3u8のチャンクファイルの数が3つになるように。
遅延は8〜9秒ほど。
前のスクリプトからの変更点のみ記述。
# 以下は追加
OPTIONS="${OPTIONS} --segment_duration 2"
OPTIONS="${OPTIONS} --time_shift_buffer_depth 4"
OPTIONS="${OPTIONS} --hls_master_playlist_output ${OUT_MASTER_PLAYLIST}"
OPTIONS="${OPTIONS} --hls_playlist_type LIVE"
# 以下packagerのコマンドに上記のOPTIONSを追加
packager \
"in=${INPUT},stream=audio,segment_template=${OUT_AUDIO_SEGMENT},playlist_name=${OUT_AUDIO_PLAYLIST},hls_group_id=audio" \
"in=${INPUT},stream=video,segment_template=${OUT_VIDEO_SEGMENT},playlist_name=${OUT_VIDEO_PLAYLIST}" \
${OPTIONS}
CPUのStats等調査はしていなかったけども、Shaka Packagerの動作が安定したのか、HLSの再生も止まることがなくなった。
単純に6秒のチャンクを作るとしたら6秒分のリソースを最低確保するわけだから当たり前だよな。。。
FFmpegで入力するVideoを 720p/1.5Mbps/MP にしても問題なく再生ができるようになった。
(追記2)MP4のHLSにして試してみる
先のShaka Packagerのスクリプトを変更し、
- 出力をTSからMP4(m4s)に変更
- Fragmentも指定(1秒)
のHLSに変更してみる。
# SegmentとFragmentを指定
OPTIONS="${OPTIONS} --segment_duration 2"
OPTIONS="${OPTIONS} --fragment_duration 1"
OPTIONS="${OPTIONS} --time_shift_buffer_depth 4"
OPTIONS="${OPTIONS} --hls_master_playlist_output ${OUT_MASTER_PLAYLIST}"
OPTIONS="${OPTIONS} --hls_playlist_type LIVE"
packager \
"in=${INPUT},stream=audio,init_segment=${OUT_AUDIO_INIT},segment_template=${OUT_AUDIO_SEGMENT},playlist_name=${OUT_AUDIO_PLAYLIST},hls_group_id=audio" \
"in=${INPUT},stream=video,init_segment=${OUT_VIDEO_INIT},segment_template=${OUT_VIDEO_SEGMENT},playlist_name=${OUT_VIDEO_PLAYLIST}" \
${OPTIONS}
結果は、最短で6秒以上〜7秒未満となった。
理由は調べていないが、Fragment化していないとTSで出力した時と同じ程度の遅延であったので・・・
『Fragment化されているので、Shaka Playerはその分m3u8を早く作り、プレイヤー側はそれを読み込んでm4sをダウンロードしながら再生しているんじゃなかろうか』
と思った。適当な見解なので間違っていると思ってください。
本記事はこれで終わり。
実際にサーバーに配置して試してみたくなった。
けどサーバー知識皆無で面倒だしお金かかるの怖い。
(追記3)AndroidとiOSで再生してみた
追記2で作ったストリームをAndroidとiOSで再生してみた。興味深い結果だった。
環境は以下の通り。
- Android
- SOV33 (Android 8.0.0)
- ExoPlayer r2.11.1
- iOS
- iOS Simulator (iOS 13.2.2)
- AVPlayer
結果としては以下の通り。
- Androidは実機にも関わらず、ffplayの遅延+2秒程度(=8〜10秒)
- iOSはffplayの遅延+1秒程度(=7〜9秒)、Simulatorなのに頑張っている
Androidで使用しているExoPlayerは以前のバージョンだと再生位置をチューニングすることができた。最新のバージョンでもチューニングができれば遅延を小さくできるかもしれない。
実際、ExoPlayerのデモアプリではシークバーが表示され、シークバーを操作するとffplayと同程度の遅延になることもあったので、可能性は十分にありそう。