HLSを使ったライブストリーミングを試してみます
あらすじ
前々回はPythonからWin32APIをバシバシ叩いてきりたん好きなコトを喋らせることができるようになったのでした。
前回はAzureのWindowsServerにHTTPリクエストを送ってきりたん好きなコトを喋らせるサーバができたのでした。
今回は、HTTP Live Streaming(HLS)を用いてきりたんボイスをライブ配信してみようと思います!
HTTP Live Streaming
HTTP Live Streamingとは、Appleが開発したHTTPベースのストリーミング配信プロトコルです。 静的な動画ファイルのストリーミング配信はもちろん、ライブ配信(生放送)もできたり、 アダプティブストリーミングと呼ばれる回線速度に応じて配信するビットレートを変更する技術も利用可能です。
最近話題のAbemaTVなんかでも、HLSで配信を行っています。 ちなみに、Twitterにアップされた動画もHLSで配信されています。
ストリーミング配信プロトコルと聞くと、複雑そうな気がしてきますが、HLSはHTTPベースで非常に単純です。 ザックリと説明を書いてみます。
HLSのしくみ
HLSでの配信は、.ts
ファイルと.m3u8
ファイルによって行われます。
ts
.ts
ファイルは、MPEG-2 TSと呼ばれる形式で、配信される映像・音声そのものが格納されます。
配信されるデータは一定の秒数ごとに分割し、このMPEG-2 TS形式で保存しておきます。 分割された.ts
ファイルは、HTTPでダウンロードできるようにしておきます。
ちなみに、日本のデジタルテレビ放送もこのMPEG-2 TSで配信されています。
m3u8
.m3u8
ファイルは、配信ファイルのインデックスです。 先述した.ts
に分割された映像・音声データのURLが列記されています。
AbemaTVから配信されている.m3u8
の例
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=300000
240/playlist.m3u8?t=3i87VhR5nuXMsjxJRGBiEYSNPdfggGQtr9LjXNx1fr5Dufac7cEaEKMyo2UAv77B63hAvVewach5eaPjFGK3EU22fcpcFD4RAeNAE7nisDwZguUqvp&mq=720&lanceId=c99528aa-0c3c-4987-ab6c-ce5cd1430223
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=900000
360/playlist.m3u8?t=3i87VhR5nuXMsjxJRGBiEYSNPdfggGQtr9LjXNx1fr5Dufac7cEaEKMyo2UAv77B63hAvVewach5eaPjFGK3EU22fcpcFD4RAeNAE7nisDwZguUqvp&mq=720&lanceId=c99528aa-0c3c-4987-ab6c-ce5cd1430223
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1400000
480/playlist.m3u8?t=3i87VhR5nuXMsjxJRGBiEYSNPdfggGQtr9LjXNx1fr5Dufac7cEaEKMyo2UAv77B63hAvVewach5eaPjFGK3EU22fcpcFD4RAeNAE7nisDwZguUqvp&mq=720&lanceId=c99528aa-0c3c-4987-ab6c-ce5cd1430223
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2200000
720/playlist.m3u8?t=3i87VhR5nuXMsjxJRGBiEYSNPdfggGQtr9LjXNx1fr5Dufac7cEaEKMyo2UAv77B63hAvVewach5eaPjFGK3EU22fcpcFD4RAeNAE7nisDwZguUqvp&mq=720&lanceId=c99528aa-0c3c-4987-ab6c-ce5cd1430223
これはMaster Playlistと呼ばれるデータで、 回線速度によって異なるビットレートでの配信を行うアダプティブストリーミングのためのファイルです。 次に示すMedia PlaylistのURLと想定する回線速度が列記されています。
AbemaTVから配信されている.m3u8
の例
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:4
#EXT-X-DISCONTINUITY-SEQUENCE:1
#EXT-X-KEY:METHOD=AES-128,URI="abematv://v2/abema-news/abema-news/DUjoiyL1pJGkADZotyiXDn5",IV=0xaccca4b41de3d9afb029070eb564be40
#EXTINF:5.005000,
https://abematv.akamaized.net/tsnews/abema-news/h264/720/5BPWe1D8Hu9yCC8HaA3oHS.ts
#EXTINF:5.005000,
https://abematv.akamaized.net/tsnews/abema-news/h264/720/5SphyMY1TTLvYkFo7B5JuM.ts
#EXTINF:5.005000,
https://abematv.akamaized.net/tsnews/abema-news/h264/720/2kxyGFo9sH9zUUfKj5USUk.ts
#EXTINF:5.005000,
https://abematv.akamaized.net/tsnews/abema-news/h264/720/Cz43TVWLgUgqskzvWBBnjA.ts
これはMedia Playlistと呼ばれるデータで、 配信されている映像・音声が格納された.ts
ファイルのURLが列記されています。
再生の方法
クライアントは、まず.m3u8
ファイルを取得します。 それがMaster Playlistであれば、回線速度によって適切な.m3u8
を読みに行きます。 それがMedia Playlistであれば、.ts
ファイルを取得して再生します。
クライアントは、.m3u8
内のタグと呼ばれるデータ(#EXT
で始まる行)に従って、.m3u8
を再読込します。 ライブ配信を行う場合は、クライアントが再読込した際に新しい配信データが追加されていれば良いわけです。
以下に、主要なタグの説明を示します。
EXT-X-TARGETDURATION
分割された.ts
の中で最大の長さに最も近い整数値を指定します。 クライアントは、およそこの秒数ごとに.m3u8
を再読込します。
EXT-X-MEDIA-SEQUENCE
その.m3u8
にかかれている一番最初の.ts
が、放送全体で何番目の.ts
であるかの値を指定します。 クライアントが分割された.ts
を正しく連続再生する上で必要になります。
EXTINF
分割された.ts
1つの秒数。小数で指定できる。
HLSを再生したい
HLSはブラウザ上で再生できるのが強いです。 https://caniuse.com/#search=HLS
ん?????なんか赤いな……
FirefoxとChromeが対応してないやんけ!!!!!!!!! 珍しくEdgeが優秀だ……
悲しいですね。 でもMesia Source Extensions(MSE)という機能を使うとそれっぽくHLSを再生できるので安心です。 https://caniuse.com/#search=MSE
MSEを使ったHLS再生は、Video.jsとかhls.jsとかのライブラリを使うと簡単です。
ちなみに、AbemaTVはTHEOplayerという有償のプレーヤーを使ってるみたい。
HLSで生配信
HLSをなんとな~くわかった気になったので、ライブ配信をやってみます。
HLSで生配信をするにはどうすればよいのかというと、つまり
- データをMPEG-2 TSにエンコードする
.m3u8
に.ts
へのリンクを追加する
を繰り返すだけです。
.ts
へのを追加していくだけだとドンドン.m3u8
がでっかくなってしまうので、 過去の.ts
へのリンクはある程度時間が立ったら消してしまいましょう。 .ts
へのリンクを消したら、#EXT-X-MEDIA-SEQUENCE
を増やさないとクライアントが困ってしまうので注意です。
とっても単純ですね! さて、先述したことをやるだけでライブ配信サーバが書けてしまいます。
今回は、Twitterからタイムラインを取得して、ツイートをいい感じにきりたんに読んでもらい、 HLSを用いてリアルタイムでその音声データを配信してみます。
音声ファイルを分割してMPEG-2 TSにするのを自分で書くのは流石にしんどいので、 FFMPEGさんにお願いしました。 https://www.ffmpeg.org/ffmpeg-formats.html#hls-1
やること
twitter.listen()
- UserStreamでツイート取得
- kiritan.pyにジョブを投げる
- encoder.pyのキューに読み上げたWAVファイルを蓄積
encoder.livestreaming()
- キューにファイルがなければ無音データをプレイリストに追加
- キューにファイルがあればTSに分割してプレイリストに追加
- プレイリストの先頭のTSの再生時間分だけ待って、プレイリストから削除
やりました
方針が定まったら書くだけ……
コード
全コード
HLS関係の処理はたったコレだけです!
# FFMPEGでファイルをMPEG-TSにエンコード(中身はMP3)
def ts(file):
logging.info("Encoding WAV to MPEG-TS")
data = subprocess.run(
[
"ffmpeg",
"-i", file, "-vn",
"-acodec", "libmp3lame",
"-ab", "128k",
"-ac", "2",
"-ar", "44100",
"-f", "hls",
"-hls_time", "2",
"-hls_list_size", "0",
"-start_number", str(int(time.time() * 1000)),
"-hls_segment_filename", "static/live%d.ts",
"pipe:1.m3u8"
],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL
)
# 出力されたプレイリストをパースして返す
playlist = data.stdout.decode("utf-8")
playlist = playlist[playlist.rfind("#EXTM3U"):]
# Tuple (再生時間, ファイルパス)
return re.findall(r"#EXTINF:([\d.]+),\s+(\S+)", playlist)
# ライブストリーミングキューに追加
que = []
def enqueue(f):
que.append(f)
# ライブプレイリストを更新
tsl = []
seq = 0
def __livecasting():
global seq
while True:
try:
if len(que) != 0:
# キューにデータがあればプレイリストに追加
tsl.extend(ts(que.pop(0)))
else:
# キューが空なら無音ファイルを配信
while len(tsl) < 3:
tsl.append(("2.04", "silent.ts"))
# TS 1つ分だけ休憩する
time.sleep(float(tsl[0][0]))
tsl.pop(0)
seq += 1
except:
logging.error(traceback.format_exc())
# サーバ起動
def livecasting():
# 古い配信データを削除
for f in glob.glob("static/live*"):
os.remove(f)
threading.Thread(target=__livecasting).start()
# ライブプレイリストを生成
def playlist():
pl = [
"#EXTM3U",
"#EXT-X-VERSION:3",
"#EXT-X-TARGETDURATION:3",
"#EXT-X-MEDIA-SEQUENCE:%d" % seq
]
for ts in tsl[:5]:
pl.append("#EXTINF:%s," % ts[0])
pl.append("#EXT-X-DISCONTINUITY")
pl.append("/static/%s" % ts[1])
return "\n".join(pl)
ffmpegを使っているので、別途用意が必要です。 必要なPythonのライブラリはpypiwin32
とflask
とtweepy
です
pip install pypiwin32 flask tweepy
動作検証
大体のブラウザでhls.jsを介した再生ができました。
ネイティブでHLSに対応しているブラウザ(Safari, Edge, iOS Safari, Android Chrome)は、 .m3u8
に直接アクセスしても再生できました。
なんかAndroidだとちょっとプツプツしちゃってるかも???
ハマりそうなポイント
- TS1つの長さ、プレイリスト全体の長さ、
#EXT-X-TARGETDURATION
をうまく調整しないと再生されなかったりプツプツなったりする- このへんどうするのが最適なのかがわからないので今回は試行錯誤した
- TSが切り替わる(別のメディアから生成したものになる)時に
#EXT-X-DISCONTINUITY
を付けないと再生が止まる- Appleのソフトウェアはうまくやってくれるけど、その他は上手く行かない
- TwitterのUserStreamはPCの時計かズレてると認証失敗する
おしまい
ということで、AzureのWindowsServerでWin32APIを使ってVOICEROIDを操作してTwitterのTLを読み上げた音声をHLSでライブ配信できました!
Win32APIとかHLSとか、まだわからないことがたくさんなので、それはおかしいだろ!って思ったら鉞おねがいします><
それにしても、きりたんはかわいいですね!
おしまい