AWS Elastic TranscoderとKey Management Serviceを使って素敵にHTTP Live Streaming

HTTP Live Streamingとは

HTTP Live Streaming(HLS)というのがあります。
https://developer.apple.com/streaming/

Apple神が作った映像や音声のストリーミングプロトコルです。
ざっくり言うと、音声ファイルを短く分割したリソースファイル(.ts)と、分割したファイルを管理するプレイリストファイル(.m3u8)の2つを使って、HTTPプロトコルにのっとってダウンロードしつつ再生すればいいじゃん、的なやつです。
既存のプロトコルベースだし実装が単純なので色んな所で使われています。
最近だとAbemaTVとか。

これが便利なのは、リソースの暗号化と、回線に応じたリソースファイルの出し分けが規定されているのです。
https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-4.3.2.4
https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-4.3.4.2

この規定にのっとって、複数のビットレートのリソースファイルと暗号化キーを用意しておけば、あとは何も考えずに対応するプレイヤーにぶち込めが勝手にいい感じに再生してくれる、と。
あら便利。
というわけで、コイツをAWSのElastic Transcoderと、暗号化キーを管理するKey Management Serviceを使って、リソースファイルと暗号化を自動化できないかなー、と思ってやってみたらできたのでメモ。

作り方

  1. Identity and Access Management(IAM)のページのメニューにある、Encryption keys(日本語だと暗号化キー)の項目からKey Management Serviceに飛ぶ。(わかるかこんなの)
  2. KMSでで暗号化キーを作る。キモはTranscoderで使うS3のバケットと同じリージョンで作ることと、キー管理者とキーユーザーに”Elastic_Transcoder_Default_Role”を指定すること。これをやらないと何度やってもエンコードエラーになる。
  3. TranscoderでPipelineを作る。ここではEncryptionの項目に、先程KMSで設定したマスターキーのARNの設定をすること。

  1. TranscoderのJobを作る。出力するビットレート毎にOutput Detailsを作り、Playlistの項目は一つだけでOutputs in Master Playlistの項目に、Output Detailsで設定したOutputをすべて追加する。

  1. おもむろにCreate New Jobを押して出来上がりを待つ

確認

ファイルの確認

今回エンコードされたファイルはこんな感じ。

Test001
├── 160k
│   ├── track.key
│   ├── track.m3u8
│   ├── track00000.ts
│   ├── track00001.ts
│   ├── track00002.ts
│   ├...
│   └── track00064.ts
├── 64k
│   ├── track.key
│   ├── track.m3u8
│   ├── track00000.ts
│   ├── track00001.ts
│   ├── track00002.ts
│   ├...
│   └── track00064.ts
└── Test001.m3u8

マスタープレイリストのTest001.m3u8の中身はこんな。

#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=189000,CODECS="mp4a.40.2"
160k/track.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=89000,CODECS="mp4a.40.5"
64k/track.m3u8

それぞれのリソースのプレイリストはこんな感じ。

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-ALLOW-CACHE:YES
#EXT-X-TARGETDURATION:11
#EXT-X-KEY:METHOD=AES-128,URI="track.key",IV=0x420473ab30beeaabfe1aa878fda4b312
#EXTINF:10.007800,
track00000.ts
#EXTINF:10.007789,
track00001.ts
#EXTINF:9.984567,
track00002.ts
...
#EXTINF:0.116089,
track00064.ts
#EXT-X-ENDLIST

問題なさげ。

再生テスト

Test001ディレクトリー以下を全部ダウンロードしてきて、マスタープレイリストをVLCメディアプレイヤーにつっこんだらちゃんと再生できるか確認。

あと、
http://dev.classmethod.jp/smartphone/iphone/network-link-conditioner/
にあるNetwork Link Conditionerを使って回線速度を制限して、ちゃんと64kbpsのファイルがダウンロードされるかどうかを串刺して確認。

できた。

Read More

Golang or NodeJSで AWS Step Functions のActivityを書く

AWS Step Functionsによるピタゴラスイッチの終点で、「通常のEC2上でStep Functionsを待ち受け、RDS等になんか処理をするためのタスク」を書いている。

{
  TaskToken:xxxxx
  Input:<前段のタスクで処理したresponse>
}

ワーカーを作動させていると、こういう感じのデータがStepFunctionsから渡ってくるので、それを元に好きに処理して、SuccessやFailureリクエストをStep Functions側に返せばいい。
とりあえずDBへの登録処理とか、データの読み取りなしで実装してみる。

NodeJS

単純にNodeJSでやるならこういう感じ。

src/worker.ts

const AWS = require("aws-sdk");

const stepfunctions  : AWS.StepFunctions = new AWS.StepFunctions({
    apiVersion: '2016-11-23',
    region : "ap-northeast-1"
});


(async ()=>{
    while (1) {
        await new Promise(async (resolve) => {
            const stepFunctionParam = {
                activityArn: 'arn:aws:states:ap-northeast-1:xxxxxxx:activity:StepFunction-Called', /* required */
            };

            const data = await stepfunctions.getActivityTask(stepFunctionParam).promise();

            if (data != null && data.taskToken != null){
                console.log(data)

         /* ... Do Something !!!!!!! */

                const sendParams = {
                    output: "true", /* required */
                    taskToken: data.taskToken, /* required */
                };   

                await stepfunctions.sendTaskSuccess(sendParams).promise()

            }
            resolve();
        }).catch(error => console.error(error))
    }
})()

 
走れ。
 

AWS_PROFILE=my_aws_profile NODE_ENV=test node build/worker.js

 

タスク部分並列処理にしたかったらawaitやらresolveの位置いじっておく。
とりあえずNodeJSで動作確認したが、この手のコードはgolangがポ〜タビリティ高くて良さそうなので、次はGo言語の入門がてら書いてみる。

Go

あとgoのパッケージマネージャツール(vendor管理)glideも使ってみた。

glide create
glide get github.com/aws/aws-sdk-go

しておく。

main.go

package main

import (
    "fmt"
    "github.com/aws/aws-sdk-go/service/sfn"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/aws"
    "time"
)

func asyncTask(sfnSession *sfn.SFN , resp *sfn.GetActivityTaskOutput){

        /* ... Do Something !!!!!!! */

    params := &sfn.SendTaskSuccessInput{
        Output:    aws.String("true"),      // Required
        TaskToken: resp.TaskToken, // Required
    }
    sfnSession.SendTaskSuccess(params)

}

func loop() {

    awsConfig :=  &aws.Config{Region: aws.String("ap-northeast-1")}

    sess, err := session.NewSession(awsConfig)
    if err != nil {
        fmt.Println("failed to create session,", err)
        return
    }

    sfnSession := sfn.New(sess)

    params := &sfn.GetActivityTaskInput{
        ActivityArn: aws.String("arn:aws:states:ap-northeast-1:xxxxxxxxx:activity:StepFunction-Called"), // Required
    }
    resp, err := sfnSession.GetActivityTask(params)

    if err != nil {
        fmt.Println(err.Error())
        return
    } else if resp.TaskToken != nil {
        fmt.Println("success")

        // 並列処理
        go asyncTask(sfnSession , resp)
    }

    fmt.Println(resp)

}

func main() {

    for {
        loop()
    }

}

 
走れ。

AWS_PROFILE=my_aws_profile go run main.go

awsのconfigとかloopの外でよかったかな。
Go初めてなので参照周りやセッションとかもうちょっと調べる。

 

Read More