Closed23

動画配信と戯れる

だいだいだいだい

動画配信と戯れたい

免責事項:

  • 勉強しながら書いているので情報が正確とは限らないです
  • 所属する会社や組織とは全く関係ない活動です
だいだいだいだい

MEMO: コーデック

  • 動画や音声データをエンコード/デコードするプログラム
  • 例: 映像では「H.264」「H.265」「MPEG-2」「WMV」など、音声では「MP3」「AAC」「WMA」など
だいだいだいだい

とりあえずオプションつけずにやってみる

ffmpeg -i assets/original/sample-30s.mp4 assets/hls/sample-30s_noopts.m3u8
だいだいだいだい

できたファイルたち

.m3u8: プレイリスト
.ts: セグメントファイル(細切れの動画ファイル)

ls assets/hls/sample-30s_noopts*
assets/hls/sample-30s_noopts.m3u8 assets/hls/sample-30s_noopts2.ts
assets/hls/sample-30s_noopts0.ts  assets/hls/sample-30s_noopts3.ts
assets/hls/sample-30s_noopts1.ts
だいだいだいだい

生成されたm3u8(プレイリストファイル)の中身(と私のコメント、コメントの内容はRFC8216を参照している)

#EXTM3U  -> m3u8(m3u)のプレイリストファイルであることを示す
#EXT-X-VERSION:3 -> バージョン
#EXT-X-TARGETDURATION:8 -> 最大セグメント長(8秒)
#EXT-X-MEDIA-SEQUENCE:0 -> 最初のセグメントのシーケンス番号
#EXTINF:8.333333, -> セグメントの期間(8sec)
sample-30s_noopts0.ts -> セグメントファイルの在処
#EXTINF:8.333333,
sample-30s_noopts1.ts
#EXTINF:8.333333,
sample-30s_noopts2.ts
#EXTINF:5.366667,
sample-30s_noopts3.ts
#EXT-X-ENDLIST -> もうセグメントがないことを示す
だいだいだいだい

EXT-X-TARGETDURATION(8) < EXTINF(8.333333)はおかしい気がしたが、

The EXTINF duration of each Media Segment in the Playlist file, when rounded to the nearest integer, MUST be less than or equal to the target duration;

要は整数に丸めたときにEXT-X-TARGETDURATION以下であればいいらしい

だいだいだいだい

各セグメント長を足すと元のmp4と大体同じ長さになる。大体なのはそういうものなのか

EXTINFの合計

 ❯ echo '8.333333*3+5.366667' | bc
30.366666

元動画

 ❯ ffprobe assets/original/sample-30s.mp4 2>&1 | grep Duration
  Duration: 00:00:30.44, start: 0.000000, bitrate: 5691 kb/s
だいだいだいだい

試しにセグメントの順番をバラバラにしたら、再生時もちゃんとバラバラになった

 ❯ diff -u assets/hls/sample-30s_noopts.m3u8 assets/hls/sample-30s_noopts_change_seq.m3u8
--- assets/hls/sample-30s_noopts.m3u8	2023-05-13 18:29:43
+++ assets/hls/sample-30s_noopts_change_seq.m3u8	2023-05-13 19:19:04
@@ -3,11 +3,11 @@
 #EXT-X-TARGETDURATION:8
 #EXT-X-MEDIA-SEQUENCE:0
 #EXTINF:8.333333,
-sample-30s_noopts0.ts
+sample-30s_noopts2.ts
 #EXTINF:8.333333,
 sample-30s_noopts1.ts
-#EXTINF:8.333333,
-sample-30s_noopts2.ts
 #EXTINF:5.366667,
 sample-30s_noopts3.ts
+#EXTINF:8.333333,
+sample-30s_noopts0.ts
 #EXT-X-ENDLIST
だいだいだいだい

http経由でも配信できる(safariで開く)

❯ npx http-server

http://127.0.0.1:8080/assets/hls/sample-30s_noopts.m3u8

だいだいだいだい

セグメント長20sを指定してみる

ffmpeg -i assets/original/sample-30s.mp4 \
-hls_time 20 \
assets/hls/sample-30s_seg20s.m3u8

結果

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:25
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:25.000000,
sample-30s_seg20s0.ts
#EXTINF:5.366667,
sample-30s_seg20s1.ts
#EXT-X-ENDLIST

セグメント長がそれぞれ、25.000000、5.366667の2セグメントに分割された。
20secではない

だいだいだいだい

逆に短く、2sにしてみる

ffmpeg -i assets/original/sample-30s.mp4 \
-hls_time 2 \
assets/hls/sample-30s_seg2s.m3u8

結果

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:8
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:8.333333,
sample-30s_seg2s0.ts
#EXTINF:8.333333,
sample-30s_seg2s1.ts
#EXTINF:8.333333,
sample-30s_seg2s2.ts
#EXTINF:5.366667,
sample-30s_seg2s3.ts
#EXT-X-ENDLIST

デフォルトの状態と変わらず。

だいだいだいだい

理由はffmpegのドキュメントに書いてある

hls_time duration
Set the target segment length. Default value is 2.

duration must be a time duration specification, see (ffmpeg-utils)the Time duration section in the ffmpeg-utils(1) manual. Segment will be cut on the next key frame after this time has passed.

http://ffmpeg.org/ffmpeg-formats.html#hls-2

要は、指定したセグメント長を超えた後の次の「キーフレーム」で分割されると。

だいだいだいだい

キーフレーム??

そもそも、H.264などのコーデックでは「GOP(Group Of Picture)」という単位で動画を扱うらしい。

データ圧縮のために、オリジナルのフレームを含むのは一部のフレーム(これがキーフレーム)のみで、あとは前後のフレームのデータを元に再現(フレーム間予測)するフレーム(Pフレーム・Bフレーム)らしい。

GOPはキーフレームとPフレーム、Bフレームを含む単位と。キーフレームがないとPフレーム、Bフレームは再現できないから、GOP単位で扱うしかない。なるほど。

参考

https://note.com/makotokaga/n/n5e94ab8a48aa
https://aviutl.info/keyframe/

だいだいだいだい

というわけで元動画のGOP長がおそらく8.33333なのだろうと予測。確認方法はわからん。

セグメント長20sec指定 → 実際のセグメント長 8.33333x3 = 25sec(20以上の最小)
セグメント長2sec → 8.33333x3 = 8.33333sec(2以上の最小)

となるわけだななるほど

だいだいだいだい

キーフレームの位置はffprobeで確認できるっぽい。実際8.333333区切りの位置でキーフレームがあることがわかる

ffprobe -show_frames -select_streams v -pretty assets/original/sample-30s.mp4 | grep "key_frame=1" -A 2
(中略)
key_frame=1
pts=0
pts_time=0:00:00.000000
--
key_frame=1
pts=128000
pts_time=0:00:08.333333
--
key_frame=1
pts=256000
pts_time=0:00:16.666667
--
key_frame=1
pts=384000
pts_time=0:00:25.00000

参考 http://santa.ldblog.jp/archives/41750596.html

だいだいだいだい

GOPを1sに変更して、セグメント長2sでhls化してみたけど上手くいかなかった🤔

元動画は30fps(1秒に30フレーム)

ffprobe assets/original/sample-30s.mp4 2>&1 | grep fps
  Stream #0:0[0x1](und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(progressive), 1920x1080 [SAR 1:1 DAR 16:9], 5569 kb/s, 30 fps, 30 tbr, 15360 tbn (default)

gオプションでGOP内のフレーム数を設定できるらしい。ということは30フレームとすればGOP長が1秒になるはず。

ffmpeg -i assets/original/sample-30s.mp4 -g 30 assets/mp4/sample-30s_gop1s.mp4

1sになってるっぽい

ffprobe -show_frames -select_streams v -pretty assets/mp4/sample-30s_gop1s.mp4 | grep "key_frame=1" -A 2
(中略)
key_frame=1
pts=0
pts_time=0:00:00.000000
--
key_frame=1
pts=15360
pts_time=0:00:01.000000
--
key_frame=1
pts=30720
pts_time=0:00:02.000000

もう一度hls_time 2sを指定してhlsに変換してみる

ffmpeg -i assets/mp4/sample-30s_gop1s.mp4 \
-hls_time 2 \
assets/hls/sample-30s_seg2s.m3u8

結果うまくいかず8.333333のまま(なにもわからない)

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:8
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:8.333333,
sample-30s_seg2s0.ts
#EXTINF:8.333333,
sample-30s_seg2s1.ts
#EXTINF:8.333333,
sample-30s_seg2s2.ts
#EXTINF:5.366667,
sample-30s_seg2s3.ts
#EXT-X-ENDLIST

動画難しいな...
まあ大した問題じゃないので次

だいだいだいだい

hlsでライブ配信(オンデマンドではないやつやってみたい)ができるサーバを書いてみたい
いい資料がなかったので自己流で

素材ははBig Buck Bunny ここから: http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4

ダウンロード

curl http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4 -o assets/original/BigBuckBunny.mp4

hlsにトランスコード
hls_list_sizeを指定しているのは、指定しないと、セグメントが最大5つしか含まれないため(Doc

ffmpeg -i assets/original/BigBuckBunny.mp4 \
-hls_time 2 \
-hls_list_size 0 \
assets/hls/BigBuckBunny.m3u8

できたm3u8ファイル

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.416667,
BigBuckBunny0.ts
#EXTINF:1.500000,
BigBuckBunny1.ts
#EXTINF:3.875000,
(中略)
#EXTINF:5.208333,
BigBuckBunny140.ts
#EXT-X-ENDLIST
このスクラップは2023/05/13にクローズされました