脱 ElasticTranscoder ~ AWS MediaConvert への導入プロセス


こんにちは streampack の Tana　です。
## 概要

streampack での動画変換基盤は Elastic Transcoder を使ってますが、
新たなオプションとして MediaConvert の導入の検討も行なっております。
MediaConvert のドキュメントや下記のワークフローなども参考にしつつ、
我らの要件としては満たせそうだったので、組み込みに着手しました。
https://aws.amazon.com/answers/media-entertainment/video-on-demand-on-aws/

構成としてはこんな感じです。

<img width="932" alt="architecture.png" src="https://qiita-image-store.s3.amazonaws.com/0/232803/ddafac28-4481-6894-1cfd-64ba957b0e23.png">

Lambda や Step Functions などは運用が面倒なのと、他に重い処理をやるケースもあるので、使ってません。

## Roleの作成

さあ、早速、動画変換させようと思っても、Job作成には Role が必要です。
なので、MediaConvert用の Role を作成し、Job を投げる際はそれを指定します。

> arn:aws:iam::xxxxxxxxxxxx:role/MediaConvert

## Jobを作成する前に、テンプレート作成

Jobを作成するには、事前に Job templates を一度作成します。
なぜなら 複数のPresets 情報や Job Settings のキュー情報がグルーピングされ再利用しやすくなります。
今回は下記の Templates を作成します。

- HLS Group Outputx3
- MP4 Group Outputx1

また、Job作成の Option は多すぎて一つずつ設定している途方にくれるので、CLIでベースから作成するのがオススメです。

> $ aws mediaconvert create-job --generate-cli-skeleton

``` json
{
    "JobTemplate": "",
    "Queue": "arn:aws:mediaconvert:ap-northeast-1:xxxx:queues/Default",  # <- キュー指定
    "Role": "arn:aws:iam::xxxx:role/MediaConvert",   # <- ロール指定
    "Settings": {
        "AdAvailOffset": 0,
        "AvailBlanking": {
            "AvailBlankingImage": ""
        },
        "Inputs": [
            {
                "AudioSelectorGroups": {
                    "KeyName": {
                        "AudioSelectorNames": [
                            ""
                        ]
                    }
                },
  ・・・ 省略 ・・・

```

CLI または SDK で MediaConvert を使う際の注意点は --endpoint-url の指定が必要です。
> aws mediaconvert get-job --id xxxx-xx --endpoint-url https://xxxxxx.mediaconvert.ap-northeast-1.amazonaws.com

endpoint url は MediaConvert の Account にて取得が可能です。または CLI でも可能です。

## TooManyRequestsException

実際に組み込んでいる際に下記のエラーに悩まされました。

> Aws::MediaConvert::Errors::TooManyRequestsException - Too Many Requests:

原因は下記の endpoint-url の取得をジョブ作成の際に叩いてました。数秒で数回叩いただけでエラーになります。
endpoint-url は変わらなさそうなので、config に定義することにしました。

``` ruby
  def get_endpoint
    client = Aws::MediaConvert::Client.new(
      region: 'ap-northeast-1',
      profile: 'default'
    )
    resp = client.describe_endpoints(max_results: 1)
    resp.endpoints[0].url
  end
```

## HLS AES-128 Encryption

ElasticTranscoder では KMS 使って HLS AES-128 Encryption を行ってましたが、
MediaConvertでは KMS をサポートしていないようです。 よって自前で key を作成して、static value とそのキーの場所を create_job の際に指定します。

``` ruby
encryption: {
  encryption_method: 'AES128',
  initialization_vector_in_manifest: 'INCLUDE',
  type: 'STATIC_KEY',
  static_key_provider: {
    static_key_value: 'xxxxxxxxxxxxxxx', # 下記のstatic_key
    url: 'キーの置き場所URL'
  }
}
```


```
pry(main)> static_key = OpenSSL::Random.random_bytes(16).unpack('H*').first
=> "319f6e14fef207fa8ecf626259229d8e"
```

もし s3 にキーを置く場合は下記の処理が便利です。

```
key = OpenSSL::Random.random_bytes(16)
Aws::S3::Resource.new.bucket(bucket).object(path).put(body: key)
```

## MediaConvert -> CloudWatch Event -> SQS連携

下記は CloudWatch Eventルールの設定です。ポイントはジョブ作成時に userMetadata にmediaconvert_event　が設定されている時のみ、SQSへジョブが投げられます。
意図しないケースやテストで作成したジョブ際にSQSにメッセージが飛んでしまうのを防ぎます。

``` json
{
  "source": [
    "aws.mediaconvert"
  ],
  "detail": {
    "userMetadata": {
      "workflow": [
        "mediaconvert_event"
      ]
    },
    "status": [
      "COMPLETE",
      "ERROR",
      "PROGRESSING",
      "STATUS_UPDATE",
      "INPUT_INFORMATION"
    ]
  }
}
```

## 変換処理の進捗を取得するには？

変換がどれくらい進んだかはある程度目安としては知りたいものです。
MediaConvert では STATUS_UPDATE にて1分置きにどれくらいのフレームを処理したか教えてくれます。
せめて、進捗率として返してくれた方が実用的な気もしますが。。

> "framesDecoded": 370

全体の何パーセント処理されたかわからないので、トータルフレーム数は ffprobe にて取得して、framesDecoded / nb_frames * 100 で計算することにしました。

> $ ffprobe -select_streams v -show_streams video.mp4 -print_format json | jq '.streams[].nb_frames'

下記はフルで取得した場合です。

``` json
    "streams": [
        {
            "index": 0,
            "codec_name": "h264",
            "codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
            "profile": "High",
            "codec_type": "video",
            "codec_time_base": "1/48",
            "codec_tag_string": "avc1",
            "codec_tag": "0x31637661",
            "width": 1920,
            "height": 1080,
            "coded_width": 1920,
            "coded_height": 1088,
            "has_b_frames": 2,
            "sample_aspect_ratio": "0:1",
            "display_aspect_ratio": "0:1",
            "pix_fmt": "yuv420p",
            "level": 50,
            "chroma_location": "left",
            "refs": 5,
            "is_avc": "true",
            "nal_length_size": "4",
            "r_frame_rate": "24/1",
            "avg_frame_rate": "24/1",
            "time_base": "1/24",
            "start_pts": 0,
            "start_time": "0.000000",
            "duration_ts": 1253,
            "duration": "52.208333",
            "bit_rate": "2108423",
            "bits_per_raw_sample": "8",
            "nb_frames": "1253",
            "disposition": {
                "default": 1,
                "dub": 0,
                "original": 0,
                "comment": 0,
                "lyrics": 0,
                "karaoke": 0,
                "forced": 0,
                "hearing_impaired": 0,
                "visual_impaired": 0,
                "clean_effects": 0,
                "attached_pic": 0
            },
            "tags": {
                "creation_time": "1970-01-01 00:00:00",
                "language": "und",
                "handler_name": "VideoHandler"
            }
        }
    ]
```

## 変換速度は？

試しに下記のファイルで試しました。

- サイズ: 630M
- ビットレート: 10M 
- codec: H.264
- フォーマット: mp4
- 動画の尺: 8分ほど

Output は ETSとMediaconvert同様に HLSx3 と MP4x1 です。

| / | ElasticTranscoder | MediaConvert |
|:-----------------|:------------------:|:------------------:|
| 変換時間             | 00:01:36 | 00:05:30 |

1回しか試してないですが、ETSの方が1/4ぐらいの時間ですみました。
いろんなコンテンツをあげまくってますが、ETSより遅いのは明らかに感じます。

## Apple ProResコーデックサポート

> "encoder": "Apple ProRes 422",

お客さんのコンテンツにたまにあるのですが、iMovie や Final Cut PRO などで編集されて上記のコーデックでアップされるケースがあります。ElasticTranscoderではサポートされていないので、H.264のものであげてもらったり、手動で変換などが必要でしたが、この問題が解決できるのは MediaConvert を使うメリットであると思います。

## Job Templates が便利

一度定義すると使い回せるのが便利です。
もし既存のある Preset に再生の挙動がおかしい場合に、プランBのテンプレートを準備していれば、切り替えが柔軟にきくかもしれません。

##　今後の課題

mp3 の場合はなぜか create_job でエラーがかえってくるのでその詳細原因と解決し、
いろんな動画コンテンツでの検証と再生確認などが残ってますが、これらがクリアになり
かつコスト面でも問題なければ、メインで使うことになるかもしれません。
今後のアップデートにも乞うご期待。