LoginSignup
1
0

More than 1 year has passed since last update.

Linode と FFmpeg を使って Akamai での HLS ライブストリーミングをテストしてみる

Last updated at Posted at 2022-11-30

はじめに

Akamai ではライブストリーミングで利用できるライブオリジンと CDN を提供しています。
Akamai の提供するライブオリジンである MSL4 (Media Service Live 4) では、ストリーミング形式は HLS/DASH/CMAF をサポートしていますが、どうやって CDN によるライブストリーミングのテストを行うかがよく課題にあがります。
なぜなら、HLS/DASH/CMAF でのインジェストをサポートしているエンコーダーはハードウェア・ソフトウェア含め有償のものしか無いからです。(無償のエンコーダーをご存知でしたらコメントいただけると嬉しいです!)
そのため、テストを行う環境を用意すること自体のハードルが高くなっています。
そこで、今回はエンコーダーとして FFmpeg を利用することで、誰でも簡単にテストできる環境を構築します。

本稿の内容には、Akamai の環境構築も含まれているので、FFmpeg の利用方法だけを見たい方は こちら をクリックしてください。

MSL4 を利用せず、他のライブオリジンと Akamai の CDN でライブストリーミングをされる場合、上記のエンコーダの制限は当てはまらず、利用するライブオリジンの仕様に従います。

テスト環境作成手順

最終的には以下の構成でテストを行います。

AMD (Adaptive Media Delivery) とはストリーミング配信に特化した CDN です。
詳細は以下で確認できます。
Adaptive Media Delivery

Akamai の設定は API 経由で作成するので、事前設定をしていない場合、以下の手順を参考にしてみてください。
API が利用できない場合などは、もちろん GUI で作成しても構いません。

MSL4 (ライブオリジン) の設定

この API ガイドをもとに設定を進めます。

設定に必要なパラメーターを取得

設定の中で Akamai の契約 ID と CP コードが必要なため、それぞれ API 経由で情報を取得します。
後ほど利用するのでどこかにメモしておいてください。

契約 ID

以下のエンドポイントから GET で契約 ID を取得します。
https://{hostname}/config-media-live/v2/msl-origin/contracts

[
    {
        "contractId": "Contract-123",
        "contractName": "Example Company",
        
    }
]
CP コード

以下のエンドポイントから GET で CP コードを取得します。
https://{hostname}/config-media-live/v2/msl-origin/cpcodes?type=INGEST

[
    {
        "id": 1234567,
        "name": "msl_example_cpcode",
        "contracts": [
            {
                "contractId": "Contract-123"
            }
        ]
    }
]

オリジンを新規作成

以下のエンドポイントに POST でリクエストを送ります。

https://{hostname}/config-media-live/v2/msl-origin/origins

Body に必要なパラメータを設定します。
contractId はさきほど取得したパラメータを入力します。
hostName は16文字以内で任意の文字列(英数字のみ)を入力します。
cpcode はさきほど取得したパラメータを入力します。

{
     "emailIds": [
          "admin@example.com"
     ],
     "encoderZone": "JAPAN",
     "contractId": "Contract-123",
     "hostName": "mslexampleorigin",
     "cpcode": 1234567
}

Status Code が 202Accepted であれば正常にリクエストが受け付けられています。
レスポンスボディは空ですが、これは正常な動作です。

オリジン作成の進捗を確認するために、以下のエンドポイントから GET で情報を取得します。

https://{hostname}/config-media-live/v2/msl-origin/origins

[
    {
        "hostName": "014-dn001-mslexampleorigin.akamaiorigin.net",
        "hostNameIdentifier": "mslexampleorigin",
        "cpcode": 1234567,
        "id": 2345678,
        
        "status": "PROVISIONED"
    }
]

statusPROVISIONED になっていれば作成完了です。
完了まで約40-50分程度かかります。

ストリームを新規作成

以下のエンドポイントに POST でリクエストを送ります。

https://{hostname}/config-media-live/v2/msl-origin/streams

Body に必要なパラメータを設定します。
originhostNamecpcode には前項で確認したパラメータをそのまま入力します。
contractId は取得したパラメータを入力します。
encoderZoneJAPAN を入力します。(もちろん JAPAN 以外の地域も指定可能です)
format は今回 HLS を入力します。(DASH、CMAF も指定可能です)
name は任意の文字列を入力します。
cpcode はさきほど取得したパラメータを入力します。

{
     "origin": {
          "hostName": "014-dn001-mslexampleorigin.akamaiorigin.net",
          "cpcode": 1234567
     },
     "contractId": "Contract-123",
     "encoderZone": "JAPAN",
     "format": "HLS",
     "name": "mslexamplestream",
     "cpcode": 1234567
}

Status Code が 202 Accepted であれば正常にリクエストが受け付けられています。
こちらもレスポンスボディは空ですが、正常な動作です。

作成の進捗を確認するために、以下のエンドポイントから GET で情報を取得します。

https://{hostname}/config-media-live/v2/msl-origin/origins

{
    "totalSize": 1,
    "page": 1,
    "pageSize": null,
    "streams": [
        {
            "id": 2345679,
            "name": "mslexamplestream",
            "format": "HLS",
            "type": "MSL4",
            "cpcode": 1234567,
            "originHostName": "014-dn001-mslexampleorigin.akamaiorigin.net",
            "modifiedDate": "2022-01-01T00:00:00.000Z",
            "primaryStorageCpcode": 3456789,
            "backupStorageCpcode": 0,
            "createdBy": "admin",
            "createdDate": "2022-01-01T00:00:00.000Z",
            "streamAuth": false,
            "dvrWindowInMin": 0,
            "provisionDetail": {
                "status": "PROVISIONED"
            },
            "encoderZone": "JAPAN"
        }
    ]
}

provisionDetailstatusPROVISIONED になっていれば作成完了です。
完了まで約2-3時間程度かかります。

これで MSL4 の設定は完了です。

AMD (CDN) の設定

このガイドをもとに設定を進めます。

この設定におけるすべてのリクエストには以下のヘッダを付与してください。
PAPI-Use-Prefixes: true

Edge Hostname の作成

以下のエンドポイントに POST でリクエストを送ります。

https://{hostname}/papi/v1/edgehostnames?contractId=ctr_Contract-123

Body に必要なパラメータを設定します。
domainPrefix にはお好きなサブドメイン名を入力します。
以下の例だと最終的な FQDN は streaming-example.akamaized.net に設定されます。
それ以外のパラメータは例と同様でOKです。

{
     "useCases": [
          {
               "type": "GLOBAL",
               "useCase": "Segmented_Media_Mode",
               "option": "LIVE"
          }
     ],
     "secureNetwork": "SHARED_CERT",
     "productId": "prd_Adaptive_Media_Delivery",
     "domainPrefix": "streaming-example",
     "domainSuffix": "akamaized.net",
     "ipVersionBehavior": "IPV6_COMPLIANCE"
}

成功すると Status Code 201 Created とともに以下の Body が返されます。

{
    "edgeHostnameLink": "/papi/v1/edgehostnames/1234567?contractId=Contract-123"
}

返されたリンク内の数字は edgehostnameId として後ほど利用するのでメモしておいてください。

CP Code 作成

以下のエンドポイントに POST でリクエストを送ります。

https://{hostname}/papi/v1/cpcodes?contractId=ctr_Contract-123

Body に必要なパラメータを設定します。
cpcodeName にはさきほど設定した Edge Hostname を指定するとわかりやすいです。
以下の例だと最終的な FQDN は streaming-example.akamaized.net に設定されます。
それ以外のパラメータは例と同様でOKです。

{
     "productId": "prd_Adaptive_Media_Delivery",
     "cpcodeName": "streaming-example.akamaized.net"
}

成功すると Status Code 201 Created とともに以下の Body が返されます。

{
    "cpcodeLink": "/papi/v1/cpcodes/1234567?contractId=Contract-123"
}

返されたリンク内の数字は CP Code の id として後ほど利用するのでメモしておいてください。

Property の作成

Property では CDN 自体の設定をしていきます。

以下のエンドポイントに POST でリクエストを送ります。

https://{hostname}/papi/v1/properties?contractId=ctr_Contract-123

Body に必要なパラメータを設定します。
propertyName にもさきほど設定した Edge Hostname を指定するとわかりやすいです。
それ以外のパラメータは例と同様でOKです。

{
     "productId": "prd_Adaptive_Media_Delivery",
     "propertyName": "streaming-example.akamaized.net",
     "ruleFormat": "latest"
}

成功すると Status Code 201 Created とともに以下の Body が返されます。

{
    "propertyLink": "/papi/v1/properties/1234567?contractId=Contract-123"
}

Property の編集

Property バージョンの取得

Property を編集するためにバージョン情報を取得します。
Property の作成で返されたリンクのパスに /versions を付与したエンドポイントから GET で情報を取得します。

https://{hostname}/papi/v1/properties/1234567/versions?contractId=ctr_Contract-123

{
    "propertyId": "1234567",
    "propertyName": "streaming-example.akamaized.net",
    "accountId": "Account-123",
    "contractId": "Contract-123",
    "groupId": "1234567",
    "assetId": "1234567",
    "versions": {
        "items": [
            {
                "propertyVersion": 1,
                "updatedByUser": "admin",
                "updatedDate": "2022-01-01T00:00:00.000Z",
                "productionStatus": "INACTIVE",
                "stagingStatus": "INACTIVE",
                "etag": "d8ccb79ff18c8de1352c8ae2e56aadafabec1794",
                "productId": "Adaptive_Media_Delivery",
                "ruleFormat": "latest"
            }
        ]
    }
}

上記の出力の propertyIdpropertyVersionetag を次の設定で利用します。

Property に作成した Edge Hostname を追加する

以下のエンドポイントに PUT でリクエストを送ります。

https://{hostname}/papi/v1/properties/prp_1234567/versions/1/hostnames?contractId=ctr_Contract-123&validateHostnames=false&includeCertStatus=false

Body に必要なパラメータを設定します。
cnameFromcnameTo にはさきほど設定した Edge Hostname を指定します。
edgeHostnameId の数字部分にはメモしておいた値を指定します。
それ以外のパラメータは例と同様でOKです。

また、この作業ではリクエストヘッダに If-Match を追加し、さきほど取得した etag の値を設定してリクエストする必要があるのでご注意ください。

If-Match: "d8ccb79ff18c8de1352c8ae2e56aadafabec1794"

[
    {
        "cnameType": "EDGE_HOSTNAME",
        "cnameFrom": "streaming-example.akamaized.net",
        "cnameTo": "streaming-example.akamaized.net",
        "edgeHostnameId": "ehn_1234567"
    }
]

本稿執筆時の2022年11月30日において API 経由では Akamai の共有証明書が割り当てられません。
そのため、テストでは HTTPS は利用せず、HTTP のみで行います。

Property の Rule を変更する

以下のエンドポイントに POST でリクエストを送ります。

https://{hostname}/papi/v1/properties/prp_1234567/versions/1/rules?contractId=ctr_Contract-123&validateMode=full&validateRules=true&dryRun=false

Body に必要なパラメータを設定します。
以下の例の中でいくつか変更が必要な部分があるので、例のあとに解説します。

{
   "rules": {
        "name": "default",
        "children": [
            {
                "name": "Default CORS Policy",
                "children": [],
                "behaviors": [
                    {
                        "name": "modifyOutgoingResponseHeader",
                        "options": {
                            "action": "MODIFY",
                            "avoidDuplicateHeaders": false,
                            "newHeaderValue": "*",
                            "standardModifyHeaderName": "ACCESS_CONTROL_ALLOW_ORIGIN"
                        }
                    },
                    {
                        "name": "modifyOutgoingResponseHeader",
                        "options": {
                            "action": "MODIFY",
                            "avoidDuplicateHeaders": false,
                            "newHeaderValue": "GET,POST,OPTIONS",
                            "standardModifyHeaderName": "ACCESS_CONTROL_ALLOW_METHODS"
                        }
                    },
                    {
                        "name": "modifyOutgoingResponseHeader",
                        "options": {
                            "action": "MODIFY",
                            "avoidDuplicateHeaders": false,
                            "newHeaderValue": "origin,range,hdntl,hdnts,CMCD-Request,CMCD-Object,CMCD-Status,CMCD-Session",
                            "standardModifyHeaderName": "ACCESS_CONTROL_ALLOW_HEADERS"
                        }
                    },
                    {
                        "name": "modifyOutgoingResponseHeader",
                        "options": {
                            "action": "MODIFY",
                            "avoidDuplicateHeaders": false,
                            "newHeaderValue": "Server,range,hdntl,hdnts,Akamai-Mon-Iucid-Ing,Akamai-Mon-Iucid-Del,Akamai-Request-BC",
                            "standardModifyHeaderName": "ACCESS_CONTROL_EXPOSE_HEADERS"
                        }
                    },
                    {
                        "name": "modifyOutgoingResponseHeader",
                        "options": {
                            "action": "MODIFY",
                            "avoidDuplicateHeaders": false,
                            "newHeaderValue": "true",
                            "standardModifyHeaderName": "ACCESS_CONTROL_ALLOW_CREDENTIALS"
                        }
                    },
                    {
                        "name": "modifyOutgoingResponseHeader",
                        "options": {
                            "action": "MODIFY",
                            "avoidDuplicateHeaders": false,
                            "newHeaderValue": "86400",
                            "standardModifyHeaderName": "ACCESS_CONTROL_MAX_AGE"
                        }
                    }
                ],
                "criteria": [],
                "criteriaMustSatisfy": "all",
                "comments": ""
            }
        ],
		"behaviors": [
            {
                "name": "origin",
                "options": {
                    "originType": "MEDIA_SERVICE_LIVE",
                    "mslorigin": "014-dn001-mslexampleorigin.akamaiorigin.net"
                }
            },
            {
                "name": "cpCode",
                "options": {
                    "value": {
                        "id": 1234567,
                        "description": "streaming-example.akamaized.net",
                        "products": [
                            "Adaptive_Media_Delivery"
                        ],
                        "name": "streaming-example.akamaized.net"
                    }
                }
            },
            {
                "name": "segmentedMediaOptimization",
                "options": {
                    "behavior": "LIVE",
                    "showAdvanced": false,
                    "enableUllStreaming": false
                }
            },
            {
                "name": "originCharacteristics",
                "options": {
                    "authenticationMethod": "AUTOMATIC",
                    "country": "JAPAN",
                    "authenticationMethodTitle": ""
                }
            },
            {
                "name": "contentCharacteristicsAMD",
                "options": {
                    "catalogSize": "UNKNOWN",
                    "contentType": "HD",
                    "dash": false,
                    "hds": false,
                    "hls": true,
                    "popularityDistribution": "UNKNOWN",
                    "segmentDurationHLS": "SEGMENT_DURATION_6S",
                    "segmentSizeHLS": "UNKNOWN",
                    "smooth": false
                }
            },
            {
                "name": "clientCharacteristics",
                "options": {
                    "country": "JAPAN"
                }
            },
            {
                "name": "cacheKeyQueryParams",
                "options": {
                    "behavior": "IGNORE_ALL"
                }
            },
            {
                "name": "segmentedContentProtection",
                "options": {
                    "enabled": false,
                    "hlsMediaEncryption": false,
                    "tokenAuthenticationTitle": "",
                    "mediaEncryptionTitle": "",
                    "dashMediaEncryption": false
                }
            },
            {
                "name": "dynamicThroughtputOptimization",
                "options": {
                    "enabled": true
                }
            }
        ],
        "options": {
            "is_secure": true
        } 
    }
}

オリジンサーバーのパラメータを変更します。
mslorigin に作成した MSL4 のオリジンホスト名を入力します。
それ以外のパラメータは下記の例のままでOKです。

            {
                "name": "origin",
                "options": {
                    "originType": "MEDIA_SERVICE_LIVE",
                    "mslorigin": "014-dn001-mslexampleorigin.akamaiorigin.net"
                }
            }

CP Code のパラメータを変更します。
id はさきほどメモした値を指定します。
descriptionname には作成した CP Code の名前を指定します。

            {
                "name": "cpCode",
                "options": {
                    "value": {
                        "id": 1234567,
                        "description": "streaming-example.akamaized.net",
                        "products": [
                            "Adaptive_Media_Delivery"
                        ],
                        "name": "streaming-example.akamaized.net"
                    }
                }
            }

上記を変更した内容を Body としてリクエストします。

成功すると Status Code 200 OK と Property の内容が返されます。

{
    "accountId": "act_Account-123",
    "contractId": "ctr_Contract-123",
    "groupId": "grp_12345",
    "propertyId": "prp_1234567",
    "propertyVersion": 1,
    

Property を展開する

以下のエンドポイントに POST でリクエストを送ります。

https://{hostname}/papi/v1/properties/prp_1234567/activations?contractId=ctr_Contract-123

Body に必要なパラメータを設定します。
notifyEmailsには管理者のアドレスを指定します。
それ以外のパラメータは例と同様でOKです。

{
     "acknowledgeAllWarnings": true,
     "activationType": "ACTIVATE",
     "fastPush": true,
     "ignoreHttpErrors": true,
     "notifyEmails": [
          "admin@example.com"
     ],
     "useFastFallback": false,
     "network": "PRODUCTION",
     "note": "First activation",
     "propertyVersion": 1
}

成功すると Status Code 201 Created とともに以下の Body が返されます。

{
    "activationLink": "/papi/v1/properties/1234567/activations/1234567?contractId=Contract-123"
}

これで MSL4 と AMD の設定は完了です。

ライブストリーミングをテストする

今回は Linode 上から MSL4 に Ingest します。
無料のソフトウェアエンコーダーで HLS での Ingest をサポートしているものがないと思われるため、FFmpeg を利用します。

仮想マシンを作成する

まずは Linode の仮想マシンを作成します。
以下の StackScripts を利用すると、私と同じ環境でお試しいただけます。

もちろんご自身のお好きな環境にFFmpeg をインストールしても構いません。

テスト用映像素材の準備

ここではテスト用の動画として有名な「Big Buck Bunny」を利用します。
以下のリンクからご自身の環境にダウンロードしてください。

$ curl -O https://download.blender.org/demo/movies/BBB/bbb_sunflower_1080p_30fps_normal.mp4

FFmpeg のパラメータを作成する

FFmpeg のパラメータ作成は非常に難解です。
そこで、Akamai 社員の有志が作成した便利なコマンド生成ツールを利用します。(とても便利です!!)

ffmpeg_command_line_builder_default.png

設定できるところはたくさんありますが、最低限必要な部分だけ設定します。

Input は Loop file のまま、さきほどダウンロードしたファイル名を指定してください。(必要に応じてファイルが保管されている場所のパスも)
OptimizeSegment size6s を指定します。AMDで設定した値と同じです。
Outputs では HLS live stream を指定します。

すると、追加入力項目が出てくるので、以下のように設定します。
Packaging: HLS (ts) - discrete
EP type: Akamai
Stream ID: Stream の ID
Event name: 任意の文字列

Stream ID は MSL4 の Stream の ID です。

以下のエンドポイントから GET で情報を取ると、id というキーがあるのでこの値を入力してください。
https://{hostname}/config-media-live/v2/msl-origin/origins

設定はこんな感じ。

ffmpeg_command_builder_parameters_filled.jpg

各パラメータを入力すると、以下のように Shell Script が出力されます。

#!/bin/bash

#Generated by command builder r0.3h

timestamp="$(date +%s)"

ffmpeg \
\
-re -fflags +genpts -stream_loop -1 -i testclip.mp4 \
\
-flags +global_header -r 30000/1001 \
\
-filter_complex "scale=1280x720" \
\
-pix_fmt yuv420p \
-c:v libx264 \
\
-b:v:0 3000K -maxrate:v:0 3000K -bufsize:v:0 3000K/2 \
\
-g:v 30 -keyint_min:v 30 -sc_threshold:v 0 \
\
-color_primaries bt709 -color_trc bt709 -colorspace bt709 \
\
-c:a aac -ar 48000 -b:a 96k \
\
-map 0:v:0 \
-map 0:a:0 \
\
-preset veryfast \
-tune zerolatency \
\
-hls_init_time 6.006 \
-hls_time 6.006 \
-hls_list_size 20 \
-hls_flags delete_segments \
-hls_base_url $timestamp/ \
-var_stream_map 'a:0,agroup:a0,default:0 v:0,agroup:a0' \
-hls_segment_filename 'http://p-ep2345678.i.akamaientrypoint.net/2345678/streamtest/'$timestamp/stream%v_%05d.ts \
-master_pl_name master.m3u8 \
-http_user_agent Akamai_Broadcaster_v1.0 \
-http_persistent 1 \
-f hls \
http://p-ep2345678.i.akamaientrypoint.net/2345678/streamtest/level_%v.m3u8

これを Linode の仮想マシン上で実行すると、Big Bug Bunny が延々とループ配信されます。

Shell Script を配置する

生成されたスクリプトを仮想マシンに配置します。
ここでは bbb_stream.sh というファイル名で保存しています。

このままでは実行できないので、実行権限を付与します。

$ chmod +x bbb_stream.sh

実行権限が付与されているかどうか確認します。

$ ls -l bbb_stream.sh
-rwxrwxr-x 1 admini admin      986 Jan 01 00:00 bbb_stream.sh

x がついているのでOKです。

ライブストリーミングを開始する

Shell Script の準備ができているので、あとは実行するだけです。

$ ./bbb_stream.sh
ffmpeg version 4.4.2-0ubuntu0.22.04.1 Copyright (c) 2000-2021 the FFmpeg developers
  built with gcc 11 (Ubuntu 11.2.0-19ubuntu1)
 ︙

エラーが発生しなければOKです。

ライブを視聴する

Akamai のデモプレイヤーを利用してライブ配信を視聴します。

ライブは URL は以下のフォーマットで配信されます。

http://[hostname]/hls/live/[stream_id]/[event_name]/[filename].m3u8

今回の構成だと以下のようになります。

http://streaming-example.akamaized.net/hls/live/2345678/streamtest/master.m3u8

こちらをプレイヤーに指定して再生します。

bbb_on_akamai_player.jpg

問題なく再生されていればOKです。

まとめ

今回は Akamai のライブ配信設定から FFmpeg を利用してライブストリーミングを行う手順までを解説しました。
最も難解な FFmpeg を簡単に使えるようになるので、テストのハードルは大きく下がると思います。
ただ、利用したスクリプトは例外処理等はまったくないため、あくまでも検証用として使ってください。
AMD(CDN)は API で作成しましたが、とても手間がかかるので 後日別の記事で Terraform での展開についても紹介したいと思います。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0