MediaLiveで視聴ストリームの冗長化ぽいことをするには

こんにちは、[streampack](https://cloudpack.jp/service/option/streampack.html)チームのhagiです。

#はじめに

以前の記事([AWS MediaLiveを使って見た](https://qiita.com/khagi/items/afa5dc1ecab5d5fc524b))ではMediaLiveではfailoverがデフォルトで構成されていないと記述しましたが、マニフェストファイル(マスタープレイリスト)を編集することによってfailoverぽいことが可能です。

"failoverぽいこと"とはHLSの特性を利用してMediaLiveでinputを１回だけ切り替え(failover)が可能な構成という意味となります。

**マスタープレイリストとプレイリストを区別するため、記事内ではマスタープレイリストをマニフェストと記述しています。**

##Appleの[HTTP Streaming Overview](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/StreamingMediaGuide/UsingHTTPLiveStreaming/UsingHTTPLiveStreaming.html)より

>Redundant Streams

>If your playlist contains alternate streams, they can not only operate as bandwidth or device alternates, but as failure fallbacks. Starting with iOS 3.1, if the client is unable to reload the index file for a stream (due to a 404 error, for example), the client attempts to switch to an alternate stream.

**意訳**

マスタープレイリストで複数のストリームを指定することでABRとして利用可能ですがフェイルオーバーストリームとしても利用可。例えば404でストリームがアクセス不可だった場合など。


#概要


構成は下記の図をご参照ください。

![MEdiaLive2.png](https://qiita-image-store.s3.amazonaws.com/0/226670/d09485f7-4e30-dfcd-bd3d-c5184ff2f29c.png)

以前の記事([AWS MediaLiveを使って見た](https://qiita.com/khagi/items/afa5dc1ecab5d5fc524b))では”編集済みマニフェストを準備”以外のAWS部分は準備しました。

# HLS構成

すごく軽くですがHLSのファイル構成を説明いたします。
詳しく知りたい方は[RFC8216](https://tools.ietf.org/html/rfc8216)などをご参照ください。

##プレイリストとは

プレイリストとはプレイヤーが再生に必要な情報の場所を記述したファイルとなります。

### 例：プレイリスト

stream1.m3u8

```
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:13
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.051708,
segment-0.ts
#EXTINF:11.302958,
segment-1.ts
#EXTINF:10.135125,
segment-2.ts
#EXTINF:13.388375,
segment-3.ts
#EXTINF:13.096417,
segment-4.ts
#EXTINF:10.010000,
segment-5.ts
#EXTINF:10.510500,
segment-6.ts
```

プレイリストにはデータファイル(*.ts)のファイル名、読み込む順番、各ファイルの再生時間(EXTINF)などが記述されております。
プレイヤーはこのファイルよりどのファイルをどの順番で再生するかの情報が得られます。

## マニフェスト(マスタープレイリスト)とは

マニフェストとはABR(Adaptive Bitrate)利用時に利用され、各プレイリストの情報が含まれています。
プレイヤーはこの情報を元に視聴デバイスで一番適切なbitrate、codec、画角を選択し再生すると共にこの情報を記録し視聴デバイスの環境が(ネットワーク、CPUなど)変わり次第より適切なストリームに切り替わります。

### 例：マスタープレイリスト

stream.m3u8

```
#EXTM3U
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=2420997,BANDWIDTH=2672225,CODECS="mp4a.40.2,avc1.64001F",RESOLUTION=1280x720
stream1.m3u8
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=1215882,BANDWIDTH=1424833,CODECS="mp4a.40.2,avc1.64001F",RESOLUTION=960x540
stream2.m3u8
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=599970,BANDWIDTH=719735,CODECS="mp4a.40.2,avc1.640015",RESOLUTION=480x270
stream3.m3u8
```

上記設定では３ストリーム提供しておりそれぞれのストリームはbitrateと画角が異なります。

```
stream1： bitrate=2672225 画角=1280x720
stream2： bitrate=1424833 画角=960x540
stream1： bitrate=599970 画角=480x270
```

# MediaLive

MediaLiveは割り当てられたinput２個に対して送出しているものとします。
MediaLive input１と2に送出しているデータが同じであるのとMediaPackageのendpoint demoAとdemoBの設定が同じである必要があります。

[ここ](https://qiita.com/khagi/items/afa5dc1ecab5d5fc524b#%E9%80%81%E5%87%BA%E5%85%88%E6%83%85%E5%A0%B1)の情報を利用します。

demoA
URL(MediaLive Channel inputより）：rtmp://XXX.XXX.XXX.XXX:1935/live/
ストリーム名：demoA

demoB
URL(MediaLive Channel inputより）：rtmp://XXX.XXX.XXX.XXX:1935/live/
ストリーム名：demoB

# MediaPackage

[こちら](https://qiita.com/khagi/items/afa5dc1ecab5d5fc524b#%E8%A6%96%E8%81%B4%E6%83%85%E5%A0%B1)の情報を元に利用します。

demoA
MediaPackageで作成したdemoAのEndpoint
https://XXXXXXXXXXXXXXXXX.mediapackage.ap-northeast-1.amazonaws.com/out/v1/AAAAAAAAAAAAAAAAAAAA/index.m3u8

demoB
MediaPackageで作成したdemoBのEndpoint
https://XXXXXXXXXXXXXXXXX.mediapackage.ap-northeast-1.amazonaws.com/out/v1/BBBBBBBBBBBBBBBBBBBB/index.m3u8

*わかりやすいようにディレクトリー部分をAAAAAAAAAAAAAAAAAAAAとBBBBBBBBBBBBBBBBBBBBに変更しました。

## プレイリストの取得方法

プレイリストはMediaPackageのダッシュボードからは取得ができない（と思う）のでマニフェストから引っこ抜きます。
endpointのストリームはお案じである必要がありますので片側のみで十分です。

### 例：DemoA

```
$ curl https://XXXXXXXXXXXXXXXXX.mediapackage.ap-northeast-1.amazonaws.com/out/v1/AAAAAAAAAAAAAAAAAAAA/index.m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=2341629,RESOLUTION=1280x720,CODECS="avc1.64001F,mp4a.40.2"
index_1.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1311615,RESOLUTION=960x540,CODECS="avc1.4D401F,mp4a.40.2"
index_2.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=651615,RESOLUTION=480x270,CODECS="avc1.4D4015,mp4a.40.2"
index_3.m3u8
```

# failoverが実施されるマニフェストの作成

## DemoAのマニフェストを元に編集

新しいindex.m3u8

```
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=2341629,RESOLUTION=1280x720,CODECS="avc1.64001F,mp4a.40.2"
https://XXXXXXXXXXXXXXXXX.mediapackage.ap-northeast-1.amazonaws.com/out/v1/AAAAAAAAAAAAAAAAAAAA/index_1.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2341629,RESOLUTION=1280x720,CODECS="avc1.64001F,mp4a.40.2"
https://XXXXXXXXXXXXXXXXX.mediapackage.ap-northeast-1.amazonaws.com/out/v1/BBBBBBBBBBBBBBBBBBBB/index_1.m3u8

#EXT-X-STREAM-INF:BANDWIDTH=1311615,RESOLUTION=960x540,CODECS="avc1.4D401F,mp4a.40.2"
https://XXXXXXXXXXXXXXXXX.mediapackage.ap-northeast-1.amazonaws.com/out/v1/AAAAAAAAAAAAAAAAAAAA/index_2.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1311615,RESOLUTION=960x540,CODECS="avc1.4D401F,mp4a.40.2"
https://XXXXXXXXXXXXXXXXX.mediapackage.ap-northeast-1.amazonaws.com/out/v1/BBBBBBBBBBBBBBBBBBBB/index_2.m3u8

#EXT-X-STREAM-INF:BANDWIDTH=651615,RESOLUTION=480x270,CODECS="avc1.4D4015,mp4a.40.2"
https://XXXXXXXXXXXXXXXXX.mediapackage.ap-northeast-1.amazonaws.com/out/v1/AAAAAAAAAAAAAAAAAAAA/index_3.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=651615,RESOLUTION=480x270,CODECS="avc1.4D4015,mp4a.40.2"
https://XXXXXXXXXXXXXXXXX.mediapackage.ap-northeast-1.amazonaws.com/out/v1/BBBBBBBBBBBBBBBBBBBB/index_3.m3u8
```

上記例でも記述したように各プレイリストをフルURLで記述いたします。
この編集済みのファイルをwebサーバーに配置し視聴者がアクセスできる場所の配置してください。

プレイヤーは新しいindex.m3u8を確認して各ストリームの最初の方(AAAAAAAAAAAAAAAAAAAA)を再生いたします。

# failoverを発生させる

さて、このままではinput1が停止してもfailoverは発生しません。

理由はinput1への送出が止まってもデフォルトではMediaLiveより黒いストリームが流れ続けるためです。

*下記設定をEMIT_OUTPUT->PAUSE_OUTPUTに変更することにより黒い画面を出さずにすることも可能です。


<img width="812" alt="スクリーンショット 2018-02-09 11.38.56.png" src="https://qiita-image-store.s3.amazonaws.com/0/226670/7dd073df-b306-d9e9-ef1d-f00fab6b95db.png">


黒いストリームが流れ続け、データファイルも作成され、プレイリストも更新され続け、404(not found)にはならないです。
仮にこの黒いストリームを発動しない場合でもMediaPackage内には最後のプレイリストとそのプレイリストに記述されたファイルが残り続けるためこれも404が発生しません。

どうするかというと、

**MediaPackageのendpointを削除するのです。**

endpointを削除することにより404を発生させ、demoBを視聴してもらうことはできたとしてもendpointを削除したら同じendpointを取得することができないと思われます。
またマニフェストは１度しか読み込まれないので再度プレイヤーが読み込むにはプレイヤー側でリロードが必要となります。
そのためdemoAストリームに問題が発生したら**一度だけ**demoBストリームに切り替えることができるが、戻すことはできません。

# 最後に

流れとしては

1. マニフェストを作成し、複数のendpointを記述する
2. ストリームに問題が発生した場合、そのストリームのendpointを削除
3. プレイヤー側でデータファイルをダウンロードしようとした際404エラーが発生し、もう一つのendpointのプレイリストを参照
4. 参照したプレイリストの情報を元にデーターファイルがダウンロードされる。

これが"failoverぽいこと"と説明した理由です。
初めから切り替えることを想定してendpointを複数準備することもできますがそれぞれのinput/outputにお金がかかることを考えるとやたらめったら増やすこともできないですね。

inputが停止した場合CloudWatchイベントも発生するので運用でカバーすることも可能だと思われます。