l3tnun / EPGStation

Mirakurun を使用した録画管理ソフト
https://twitter.com/l3tnun
MIT License
561 stars 150 forks source link

【提案】mpegts.js による web での低遅延ライブ視聴 #433

Closed xqq closed 3 years ago

xqq commented 3 years ago

概要

flv.js を基づいて mpegts.js を作ってきました。 H.264+AAC に限り、MPEG2-TS のストリームを HTML5 で直接再生することができました。

http://xqq.github.io/mpegts.js/demo/ にてテストできます。

0x00 低遅延

obs から Simple Realtime Server にプッシュし、mpegts.js を用いてローカルネットで再生する際に、最速 1 秒程度の遅延が達成できましたが、放送中番組のライブ視聴の場合は、FFmpeg による re-encode が遅延を生じるため、mpegts.js で番組のライブは同時再生の TVTest と比べて概ね 3 秒くらいの遅延が生じます。

0x01 再生起動時間の短縮

hls.js は少なくとも2つの TS Segment を待ってから再生が可能になるため起動時間が長くなります。mpegts.js は FFmpeg が一番最初の H264 IDR Frame が出力されたら、直ちに画面を出すことができています。

チューナーの起動や FFmpeg の probe なども時間かかるため、mpegts.js の場合はリクエストしてから画面が出るまで、概ね 4\~5 秒かかります。

一方、FFmpeg が既に動作していて、途中から視聴し始める場合は、mpegts.js がサーバーに接続した瞬間に、受け取った video frame が IDR Frame でない可能性も高いため、一番最初の IDR Frame を待たなければ再生が始められません。そのため、途中から視聴すれば多くの場合に 1\~2 秒で起動できますが、H264 の gop size に依存します。

なお、mpegts.js の起動が遅くても、一番最初の IDR Frame を待ってから処理するので、遅延は累積しません。

0x02 FFmpeg 起動時間の短縮

(vaapi 使用していますので x264 の場合は調整してください)

"name": "1920x1080(mpegts,h264,vaapi)",
"cmd": "%FFMPEG% -re -dual_mono_mode main -vaapi_device /dev/dri/renderD128 -hwaccel vaapi -hwaccel_output_format vaapi -i pipe:0 -analyzeduration 500000 -fflags nobuffer -flags low_delay -max_delay 250000 -max_interleave_delta 1 -threads 2 -map 0 -ignore_unknown -vf format=nv12|vaapi,hwupload,deinterlace_vaapi,scale_vaapi=w=1920:h=1080 -c:v h264_vaapi -profile:v high -level 51 -aspect 16:9 -qp:v 18 -b:v 5M -maxrate:v 10M -bufsize:v 5M -c:a aac -ar 48000 -ab 160k -ac 2 -f mpegts pipe:1"

-analyzeduration 500000 -fflags nobuffer -flags low_delay -max_delay 250000 -max_interleave_delta 1 というパラメータで FFmpeg の起動時間をある程度短縮することができます。-probesize というパラメータもありますが試していません。

-g 30 というパラメータで gop size を 30 frame に調整することができます。gop size を調整することで途中からの視聴の起動時間を短縮することができますが、mpegts.js の遅延には影響ありません。

0x03 プレーヤー側の遅延追いかけ機能

image

例えば Chrome の場合は、HTMLMediaElement の内部バッファーは常に 1 秒ほどのバッファーを持つことがあります。そのバッファーが 1 秒よりも長くなった場合に、自動的にバッファー内の遅延を 0.5 秒に追いかける機能を付けています。

以下のように設定する必要があります:

player = mpegts.createPlayer(mediaDataSource, {
    enableWorker: true,
    isLive: true,
    liveBufferLatencyChasing: true,
    liveBufferLatencyMaxLatency: 1,
    liveBufferLatencyMinRemain: 0.5
});

0x04 ARIB B24 字幕の抽出

player.on(mpegts.Events.PES_PRIVATE_DATA_ARRIVED, function(data) {
    b24Renderer.pushData(data.pid, data.data, data.pts);
});

少し申し訳ないと思いますが、こちらでは FFmpeg 4.1.4 を使っているため mpegts にリマックス際に ARIB 字幕が正しくコピーできていますが、最新バージョンの動作を確認していませんので、お願いたいと思います。

これにより、hls.js の改造や FFmpeg の hlsenc.c を改造する必要もなくなるかと思います。

0x05 ブラウザ互換性

Chrome, FireFox, Safari(macOS, iPadOS), Edge(classic/Chromium) にて動作確認できています。 なお iOS が使用不可ですが、iPadOS にて動作確認できています。

mpegts.getFeatureList().mseLivePlayback を使って、mpegts ライブが再生可能かどうかをチェックすることができます。

0x06 Misc

https://github.com/xqq/mpegts.js/blob/master/docs/api.md ドキュメントが詳しくないので問題があれば直接お聞かせてください。

なお、d.ts が手書きなファイルなので、不具合が生じる場合は教えてください。

既に動作が安定しているかと考えられます。

image image

l3tnun commented 3 years ago

おお、凄いですね。 字幕 + 低遅延がブラウザで実現できるとは... 4~5秒で見れるようになるのであれば webm で視聴するよりも早いんじゃないでしょうか。 素晴らしいですね。

早速 epgstation に組み込みたいと思いますが、この頃土日くらいしか作業できないので少し待ってください

xqq commented 3 years ago

追記:FFmpeg 4.3.2 にて ARIB B24 字幕データを mpegts ストリームに正しくコピーできることが確認できています。

パラメータは微調整が必要ですが、-map 0 を消して -c:s copy を追加する形で動作できるようになります: (-ignore_unknown も要らなくなるようです)

ffmpeg -re -dual_mono_mode main -vaapi_device /dev/dri/renderD128 -hwaccel vaapi -hwaccel_output_format vaapi -i test3.ts  -analyzeduration 500000 -fflags nobuffer -flags low_delay -max_delay 250000 -max_interleave_delta 1 -threads 2 -c:s copy -vf 'format=nv12|vaapi,hwupload,deinterlace_vaapi,scale_vaapi=w=1920:h=1080' -c:v h264_vaapi -profile:v high -level 51 -aspect 16:9 -qp:v 18 -b:v 5M -maxrate:v 10M -bufsize:v 5M -c:a aac -ar 48000 -ab 160k -ac 2 -f mpegts  test3.copy.ts
[2021-03-23T12:35:26.114] [DEBUG] stream - Input #0, mpegts, from 'pipe:0':
  Duration: N/A, start: 13202.613244, bitrate: N/A
  Program 1024
    Metadata:
      service_name    : ?NHK?Am9g?1�?El5~
      service_provider:
    Stream #0:1[0x100]: Video: mpeg2video (Main) ([2][0][0][0] / 0x0002), yuv420p(tv, bt709, top first), 1440x1080 [SAR 4:3 DAR 16:9], 29.97 fps, 29.97 tbr, 90k tbn, 59.94 tbc
[2021-03-23T12:35:26.114] [DEBUG] stream -
    Side data:
      cpb: bitrate max/min/avg: 20000000/0/0 buffer size: 9781248 vbv_delay: N/A
    Stream #0:2[0x110]: Audio: aac (LC) ([15][0][0][0] / 0x000F), 48000 Hz, stereo, fltp, 304 kb/s
    Stream #0:3[0x130]: Subtitle: arib_caption (Profile A) ([6][0][0][0] / 0x0006)
    Stream #0:4[0x138]: Data: bin_data ([6][0][0][0] / 0x0006)
    Stream #0:5[0x140]: Unknown: none ([13][0][0][0] / 0x000D)
    Stream #0:6[0x160]: Unknown: none ([13][0][0][0] / 0x000D)
    Stream #0:7[0x161]: Unknown: none ([13][0][0][0] / 0x000D)
    Stream #0:8[0x162]: Unknown: none ([13][0][0][0] / 0x000D)
[2021-03-23T12:35:26.114] [DEBUG] stream -
    Stream #0:9[0x170]: Unknown: none ([13][0][0][0] / 0x000D)
    Stream #0:10[0x171]: Unknown: none ([13][0][0][0] / 0x000D)
    Stream #0:11[0x172]: Unknown: none ([13][0][0][0] / 0x000D)
  Program 1025
    Metadata:
      service_name    : ?NHK?Am9g?2�?El5~
      service_provider:
  Program 1408
    Metadata:
      service_name    : ?NHK?7HBS?G�?El5~
      service_provider:
  No Program
    Stream #0:0[0x12]: Data: epg

[2021-03-23T12:35:26.116] [DEBUG] stream - Stream mapping:
  Stream #0:1 -> #0:0 (mpeg2video (native) -> h264 (h264_vaapi))
  Stream #0:2 -> #0:1 (aac (native) -> aac (native))
  Stream #0:3 -> #0:2
[2021-03-23T12:35:26.116] [DEBUG] stream -  (copy)

[2021-03-23T12:35:26.616] [DEBUG] stream - frame=    0 fps=0.0 q=0.0 size=       0kB time=-577014:32:22.77 bitrate=  -0.0kbits/s speed=N/A
[2021-03-23T12:35:26.640] [DEBUG] stream - [h264_vaapi @ 0x55c598a3dcc0] Buffering settings are ignored in CQP RC mode.

[2021-03-23T12:35:26.641] [DEBUG] stream - Output #0, mpegts, to 'pipe:1':
  Metadata:
    encoder         : Lavf58.45.100
    Stream #0:0: Video: h264 (h264_vaapi) (High), vaapi_vld, 1920x1080 [SAR 1:1 DAR 16:9], q=-1--1, 5000 kb/s, 29.97 fps, 90k tbn, 29.97 tbc
    Metadata:
      encoder         : Lavc58.91.100 h264_vaapi
    Stream #0:1: Audio: aac (LC), 48000 Hz, stereo, fltp, 160 kb/s
    Metadata:
      encoder         : Lavc58.91.100 aac
    Stream #0:2: Subtitle: arib_caption (Profile A) ([6][0][0][0] / 0x0006)
[mpegts @ 0x55c598a1b180] Stream 2, codec arib_caption, is muxed as a private data stream and may not be recognized upon reading.

[2021-03-23T12:35:27.144] [DEBUG] stream - frame=   13 fps= 13 q=-0.0 size=    1200kB time=00:00:00.96 bitrate=10157.3kbits/s speed=0.942x
[2021-03-23T12:35:27.672] [DEBUG] stream - frame=   22 fps= 14 q=-0.0 size=    1697kB time=00:00:01.40 bitrate=9881.6kbits/s speed=0.904x
[2021-03-23T12:35:28.174] [DEBUG] stream - frame=   41 fps= 20 q=-0.0 size=    2532kB time=00:00:02.37 bitrate=8737.2kbits/s speed=1.15x
[2021-03-23T12:35:28.675] [DEBUG] stream - frame=   51 fps= 20 q=-0.0 size=    2952kB time=00:00:02.37 bitrate=10186.6kbits/s speed=0.928x
[2021-03-23T12:35:29.181] [DEBUG] stream - frame=   70 fps= 23 q=-0.0 size=    3843kB time=00:00:03.34 bitrate=9420.7kbits/s speed=1.09x
[2021-03-23T12:35:29.717] [DEBUG] stream - frame=   85 fps= 24 q=-0.0 size=    4435kB time=00:00:03.37 bitrate=10781.4kbits/s speed=0.945x
[2021-03-23T12:35:30.187] [DEBUG] stream - frame=  100 fps= 25 q=-0.0 size=    5455kB time=00:00:04.30 bitrate=10369.6kbits/s speed=1.06x

FFmpeg にコピーされたら arib_caption の認識が失って、bin_data になってしまったようですが、mpegts.js では字幕パターンの PES private data が来ていることが確認しました。

mpegts.js 使用の場合は unrecognizer なしでも行けそうな感じです。

xqq commented 3 years ago

ffmpeg 4.2.1 においても上記のパラメータで動作を確認できました。

ffmpeg 4.2 に libaribb24 機能が追加された以来のバージョンに、こういう手法で字幕を抽出できると推測しています。

xqq commented 3 years ago

申し訳ないと思いますが、上記のパラメータには不備があると考えられますので再度調査します。

b24.js を開発したときの記憶ですが、一部の放送局は常に2つの字幕ストリームを持ち、番組によって切り替わって使用することがあったということを思い出しました。

特に番組が切り替わる際に、1つの字幕ストリームの使用を終了し、もう1つの字幕ストリームを使うようにすることがあります。

image

TVTest は2つの字幕ストリームをきちんと識別できていますが、 FFmpeg の場合は、active 状態でない字幕ストリームが arib_caption に認識されず、bin_data として扱われます:

image

上記のパラメータ -c:s copy は、字幕と認識される arib_caption ストリームをコピーしますが、 inactive 状態の字幕ストリーム Stream #0:3[0x138]: Data: bin_dataを抽出していないので、視聴途中に使用する字幕ストリームの切り替えが発生したら、字幕が映らなくなります。

字幕と認識されるストリームを抽出以外、データストリームも同時抽出するには以下のパラメータが必要になります:

-map 0 -c:s copy -c:d copy -ignore_unknown

mpegts.js 使用に限り、以上のパラメータは FFmpeg 4.1.4 & 4.2.1 & 4.3.2 すべてにおいて動作確認しました。

なお、FFmpeg 4.3.2 は epg 情報も識別できるようになったため、epg 情報も一緒に抽出してしまいます...

その場合は mpegts.js も epg 情報をコールバックに流れてしまいますが、b24.js / aribb24.js にとっては判断可能と思いますが、検証が必要です。

中途半端の情報に申し訳ないと思います。

xqq commented 3 years ago

若干補足説明します。

0x00 遅延を更に短縮 FFmpeg の mpegts streaming パラメータから -re を除去すれば、 mpegts.js においての視聴遅延が TVTest と比べて、遅延が 1 秒以内(概ね 0.5 秒)に短縮したことを確認しました。 起動時間も概ね 3 秒まで短縮しました。

0x01 Safari において再生不具合の調整 liveBufferLatencyMaxLatency が短すぎて Safari において再生が頻繁にフリーズしたりすることが発生しましたので、 liveBufferLatencyMaxLatency1.5秒までリラックスすれば、Safari の再生フリーズが解消しました。 mpegts.js の master にてディフォルト値を更新しました。

0x02 字幕抽出に関する不具合 hls-b24.js のコールバックでは、pts を秒単位で提供しましたが、mpegts.js では pts をミリ秒で提供する実装をしてしまいました。 b24.js / aribb24.js は秒単位(小数)の pts を受け取ることによって、調整が必要です:

player.on(mpegts.Events.PES_PRIVATE_DATA_ARRIVED, function(data) {
    b24Renderer.pushData(data.pid, data.data, data.pts / 1000);
});

これにより mpegts.js + aribb24.js の低遅延字幕付き再生を動作確認しました。

l3tnun commented 3 years ago

version 2.2.0 で対応されました。

440 がまだできていないので、字幕対応はまだできていません。

xqq commented 3 years ago

再生方式選択の UX について、現状としてユーザーにとって少し分かりづらいではないかと思います。

個人的には、mpegts の再生方式を config で設定するのは少し使いづらいと思います。 また、HLS/MPEGTS/MP4/WebM 方式の中で、それぞれにどういう特徴があるのかもユーザー側に分かりづらいでしょう。

ライブ視聴を始める際に、UI 上にまず Web で再生するか URL Scheme で開くかを選択し、次に配信方式を選択するようにしたらいかがでしょうか。

l3tnun commented 3 years ago

個人的には、mpegts の再生方式を config で設定するのは少し使いづらいと思います。

config.yml のことでしょうか? それとも、ライブ視聴時のダイアログの M2TSやWebM等のプルダウンメニューのことでしょうか?

また、HLS/MPEGTS/MP4/WebM 方式の中で、それぞれにどういう特徴があるのかもユーザー側に分かりづらいでしょう。

これは確かにありそうですね。

ライブ視聴を始める際に、UI 上にまず Web で再生するか URL Scheme で開くかを選択し、次に配信方式を選択するようにしたらいかがでしょうか。

操作手順が増えると煩わしく感じませんかね?

xqq commented 3 years ago

間違いました。すみません。 mpegts 視聴方式の設定は web 上にあるですね。

現時点の UX はユーザーにとって、新たに追加された mpegts.js による低遅延配信機能が感知できません。

また、config の中で、字幕抽出および低遅延最適化に関する必要な ffmpeg パラメータが入れていません。しかも既存のユーザーにとって手作業で修正する必要が生じます。mpegts web 再生専用の config 組を導入すればいいかも?というふうに考えています。

l3tnun commented 3 years ago

ああ、なるほど。 従来の設定と mpegts.js 用の設定では ffmpeg の最適なパラメータが異なるので、設定を分けられるようにしたほうが良いということですね。

epgstation の設計上配信形式(M2TS, WebM, MP4, HLS)でしか区別していなかったので、mpegts.js 用に名称を考えないといけませんね。

xqq commented 3 years ago

ちなみに、aribb24.js による字幕再生機能の追加もユーザーに感知しにくいと思います。 (特に config.yml で手作業の調整が必要になるため)

再生方式に関して個人的には、 Web 上の再生するか、URL Scheme で開くかを UI/UX でもっと明確に分けたらわかりやすいかもしれません。

また、UI 上に hls.js / mpegts.js の再生選択について、それぞれの特徴をユーザーにわかるように工夫する必要があると思います。

さらに、config で ffmpeg パラメータの調整が若干手間がかかるため、 低遅延字幕付き視聴に関する markdown doc も作成したほうがいいかもしれません。

l3tnun commented 3 years ago

当初の予定よりも再生形式や機能も増えましたので、 確かに UI/UX を見直す頃合いなのかもしれませんね。

l3tnun commented 3 years ago

最新の master にて対応完了したので閉じます。