CloudFormation で AWS Elemental MediaLive を作成する時に既存の AWS Elemental MediaPackage に紐付けるテンプレートを作成してみた

CloudFormationでMediaLiveを作成するときに既存のMediaPackageと紐付けたいと考えた事はありませんか?
2020.08.05

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、大前です。

 

AWS Elemental MediaLive(以下 MediaLive) は未使用の状態でも課金が発生してしまう為、使わなくなったチャンネル等は逐次削除する事が多いかと思います。

一方で、設定項目が多い MediaLive を一々手で作り直すのも手間なものです。

そこで以前は、MediaLive 等を AWS CloudFormation(以下 CloudFormation) で作成するブログを投稿しました。

 

今回は、MediaLive を CloudFormation で作成しつつ、既存の AWS Elemental MediaPackage(以下 MediaPackage) に紐付けるテンプレートを作成してみましたので紹介します。

CloudFormation で MediaPackage までまとめて作成できれば良いのですが、現時点では CloudFormation で MediaPackage を作成する事が出来ない為、既存の MediaPackage をうまく紐付けられる様にしてみました。(早く対応してほしいなー。。。)

 

余談ですが、AWS 公式のサンプルデプロイガイド等ではカスタムリソースを使用して MediaLive や MediaPackage を作成しているサンプルが多いです。興味ある方はそちらも見てみてください。

aws-samples / aws-media-services-simple-live-workflow

作成したテンプレート

早速ですが、作成したテンプレートを先に載せてしまいます。

今回は「既存の MediaPackage との紐付け」のみを目指してテンプレートを作成したので、細かいパラメータ調整などは行なっていません。

流用する時にはよしなに調整してご利用ください。


2020/11/2 修正 本稿投稿当時に動いていたテンプレートが動かなくなっていたため、修正版に差し替えました。
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Parameters": {
    "ProjectName": {
      "Description": "Name of MediaLiveInput",
      "Default": "sample-prj",
      "Type": "String"
    },
    "StreamKey": {
      "Description": "StreamKey",
      "Type": "String",
      "Default": "streamkey"
    },
    "ChannelClass": {
      "Description": "ChannelClass",
      "Type": "String",
      "Default": "STANDARD",
      "AllowedValues": [
        "STANDARD",
        "SINGLE_PIPELINE"
      ]
    },
    "MediaPackageChannelId": {
      "Description": "MediaPackage ChannelId",
      "Type": "String"
    }
  },
  "Resources": {
    "MediaLiveInput": {
      "Type": "AWS::MediaLive::Input",
      "Properties": {
        "Destinations": [
          {
            "StreamName": { "Fn::Sub" : "${ProjectName}/${StreamKey}-A" }
          },
          {
            "StreamName": { "Fn::Sub" : "${ProjectName}/${StreamKey}-B" }
          }
        ],
        "InputSecurityGroups": [
          {"Ref" :  "MediaLiveInputSecurityGroup"}
        ],
        "Name": { "Fn::Sub" : "${ProjectName}-input" },
        "Tags": {
          "Name": { "Fn::Sub" : "${ProjectName}-input" }
        },
        "Type": "RTMP_PUSH"
      }
    },
    "MediaLiveInputSecurityGroup": {
      "Type": "AWS::MediaLive::InputSecurityGroup",
      "Properties": {
        "Tags": {
          "Name": { "Fn::Sub" : "${ProjectName}-inputsg" }
        },
        "WhitelistRules": [
          {
            "Cidr": "0.0.0.0/0"
          }
        ]
      }
    },
    "MediaLiveChannel": {
      "Type": "AWS::MediaLive::Channel",
      "Properties": {
        "ChannelClass": "STANDARD",
        "Destinations": [
          {
            "Id": {"Ref" :  "MediaPackageChannelId"},
            "MediaPackageSettings": [
              {
                "ChannelId": {"Ref" :  "MediaPackageChannelId"}
              }
            ]
          }
        ],
        "EncoderSettings": {
          "AudioDescriptions": [
            {
              "AudioSelectorName": "default",
              "CodecSettings": {
                "AacSettings": {
                  "Bitrate": 96000,
                  "RawFormat": "NONE",
                  "Spec": "MPEG4"
                }
              },
              "AudioTypeControl": "FOLLOW_INPUT",
              "LanguageCodeControl": "FOLLOW_INPUT",
              "Name": "audio_3_aac96"
            }
          ],
          "OutputGroups": [
            {
              "OutputGroupSettings": {
                "MediaPackageGroupSettings": {
                  "Destination": {
                    "DestinationRefId": {"Ref" :  "MediaPackageChannelId"}
                  }
                }
              },
              "Outputs": [
                {
                  "OutputSettings": {
                    "MediaPackageOutputSettings": {
                    }
                  },
                  "OutputName": "1280_720_1",
                  "VideoDescriptionName": "video_1280_720_1",
                  "AudioDescriptionNames": [
                    "audio_3_aac96"
                  ]
                }
              ]
            }
          ],
          "TimecodeConfig": {
            "Source": "SYSTEMCLOCK"
          },
          "VideoDescriptions": [
            {
              "CodecSettings": {
                "H264Settings": {
                  "AfdSignaling": "NONE",
                  "ColorMetadata": "INSERT",
                  "AdaptiveQuantization": "HIGH",
                  "Bitrate": 1000000,
                  "EntropyEncoding": "CABAC",
                  "FlickerAq": "ENABLED",
                  "FramerateControl": "SPECIFIED",
                  "FramerateNumerator": 30000,
                  "FramerateDenominator": 1001,
                  "GopBReference": "ENABLED",
                  "GopClosedCadence": 1,
                  "GopNumBFrames": 3,
                  "GopSize": 60,
                  "GopSizeUnits": "FRAMES",
                  "SubgopLength": "FIXED",
                  "ScanType": "PROGRESSIVE",
                  "Level": "H264_LEVEL_4_1",
                  "LookAheadRateControl": "HIGH",
                  "NumRefFrames": 1,
                  "ParControl": "SPECIFIED",
                  "Profile": "HIGH",
                  "RateControlMode": "CBR",
                  "Syntax": "DEFAULT",
                  "SceneChangeDetect": "ENABLED",
                  "SpatialAq": "ENABLED",
                  "TemporalAq": "ENABLED",
                  "TimecodeInsertion": "DISABLED"
                }
              },
              "Height": 720,
              "Name": "video_1280_720_1",
              "RespondToAfd": "NONE",
              "Sharpness": 50,
              "ScalingBehavior": "DEFAULT",
              "Width": 1280
            }
          ]
        },
        "InputAttachments": [
          {
            "InputAttachmentName": { "Fn::Sub" : "${ProjectName}-channelinput" },
            "InputId": {"Ref" :  "MediaLiveInput"},
            "InputSettings": {
              "DeblockFilter": "DISABLED",
              "DenoiseFilter": "DISABLED",
              "FilterStrength": 1,
              "InputFilter": "AUTO",
              "SourceEndBehavior": "CONTINUE"
            }
          }
        ],
        "InputSpecification": {
          "Codec": "AVC",
          "MaximumBitrate": "MAX_20_MBPS",
          "Resolution": "HD"
        },
        "Name": { "Fn::Sub" : "${ProjectName}-channel" },
        "RoleArn": { "Fn::GetAtt" : [ "MediaLiveAccessRole", "Arn" ] },
        "Tags": {
          "Name": { "Fn::Sub" : "${ProjectName}-channel" }
        }
      }
    },
    "MediaLiveAccessRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "medialive.amazonaws.com"
                ]
              },
              "Action": [
                "sts:AssumeRole"
              ]
            }
          ]
        },
        "ManagedPolicyArns": [
          "arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess"
        ],
        "Path": "/",
        "Policies": [
          {
            "PolicyName": { "Fn::Sub" : "${ProjectName}-MediaLiveCustomPolicy" },
            "PolicyDocument": {
              "Statement": [
                {
                  "Effect": "Allow",
                  "Action": [
                    "s3:ListBucket",
                    "s3:PutObject",
                    "s3:GetObject",
                    "s3:DeleteObject"
                  ],
                  "Resource": "*"
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "mediastore:ListContainers",
                    "mediastore:PutObject",
                    "mediastore:GetObject",
                    "mediastore:DeleteObject",
                    "mediastore:DescribeObject"
                  ],
                  "Resource": "*"
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "logs:CreateLogGroup",
                    "logs:CreateLogStream",
                    "logs:PutLogEvents",
                    "logs:DescribeLogStreams",
                    "logs:DescribeLogGroups"
                  ],
                  "Resource": "arn:aws:logs:*:*:*"
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "mediaconnect:ManagedDescribeFlow",
                    "mediaconnect:ManagedAddOutput",
                    "mediaconnect:ManagedRemoveOutput"
                  ],
                  "Resource": "*"
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "ec2:describeSubnets",
                    "ec2:describeNetworkInterfaces",
                    "ec2:createNetworkInterface",
                    "ec2:createNetworkInterfacePermission",
                    "ec2:deleteNetworkInterface",
                    "ec2:deleteNetworkInterfacePermission",
                    "ec2:describeSecurityGroups"
                  ],
                  "Resource": "*"
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "mediapackage:DescribeChannel"
                  ],
                  "Resource": "*"
                }
              ]
            }
          }
        ],
        "RoleName": { "Fn::Sub" : "${ProjectName}-MediaLiveAccessRole" }
      }
    }
  }
}

ポイント

MediaLive を CloudFormation で作成する事自体そこそこ大変なのですが、今回は MediaPackage の紐付けに関連する箇所のみ説明を入れたいと思います。

Properties.Destinations

Destinations 内には紐付ける MediaPackage のチャンネル ID と、その設定に紐付く一意の ID(任意)を設定する必要があります。

MediaPackage のチャンネル ID を Parameters に設定する事で、スタック作成時に MediaPackage のチャンネル ID を指定する事で紐付けが出来る様になります。

      Destinations:
        - Id: !Ref MediaPackageChannelId
          MediaPackageSettings:
            - ChannelId: !Ref MediaPackageChannelId

Properties.EncoderSettings.OutputGroups

まず OutputGroupSettings には "MediaPackageGroupSettings" を記載し、上記 Destinations で定義した ID を DestinationRefId として記載します。


2020/11/2 修正

以下部分について仕様が変わったらしく、MediaPackageOutputSettings 配下は null を指定する形となった様です。 https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-medialive-channel-mediapackageoutputsettings.html yaml 形式では null を指定すると CloudFormation テンプレート指定時にエラーが出るため、上記テンプレートも json に修正しています。

また、Outputs 内の記載で一番苦戦しました。

ここで何度もエラーが発生してしまいうまく行かなかったのですが、OutputSettings.MediaPackageOutputSettings と Outputs 配下にそれぞれ OutputName を記載する事で作成が可能になりました。(例えば OutputSettings.MediaPackageOutputSettings が空だとエラー)

ここに関してはこれが正しい記法なのか正直わかっていません。。。情報をお持ちの方いれば教えてください。。

        OutputGroups:
          - OutputGroupSettings:
              MediaPackageGroupSettings:
                Destination:
                  DestinationRefId: !Ref MediaPackageChannelId
            Outputs:
              - OutputSettings:
                  MediaPackageOutputSettings:
                    OutputName: "1280_720_1"
                OutputName: "1280_720_1"
                VideoDescriptionName: "video_1280_720_1"
                AudioDescriptionNames:
                  - "audio_3_aac96"

Properties.EncoderSettings.VideoDescriptions.CodecSettings.ParControl

細かい部分ですが、MediaLive から MediaPackage に映像を送出する際はこのパラメータを SPECIFIED にする必要があります。これはコンソール上から作成する際にも同様です。

        VideoDescriptions:
          - CodecSettings:
              H264Settings:
                (中略)
                ParControl: "SPECIFIED"
                (中略)

 

紐付けをやってみた

実際にこの CloudFormation テンプレートを使って既存の MediaPackage との紐付けをしてみたいと思います。

1. MediaPackage チャンネルの作成

まずは MediaPackage チャンネルをコンソールから作成します。

ちなみに、Terraform は MediaPackage を作成できます。一方で MediaLive が非対応なのでなかなか辛いところです。

 

MediaPacakge のチャンネル画面より Create をクリック

 

チャンネル ID を入力して Create をクリックします。今回は "TestChannel" としました。

 

これで MediaPackage の設定は完了です。本来配信を行う場合はエンドポイントの作成なども必要ですが、今回は紐付けだけを確認したいのでここまでで OK とします。

 

2. CloudFormation スタックの作成

続いて、上記で作成した CloudFormation テンプレートからスタックを作成していきます。

 

CloudFormation 画面のスタック作成より、テンプレートファイルをアップロードします。

 

続いて、各パラメータを入力します。

今回は「スタックの名前」と「MediaPackageChannelId」以外はデフォルトで大丈夫です。

MediaPackageChannelId には先ほど作成した MediaPackage のチャンネル ID を入力します。

 

その後のオプション等は特に設定せず、最後に IAM ロールの作成がある事に同意してスタックの作成を実行します。

 

特に問題なければ、スタックの作成が成功するはずです。

 

3. 作成された MediaLive チャンネルを確認

CloudFormation で作成された MediaLive チャンネルを確認しにいきます。

 

新しくチャンネルが作成されていることが確認できます。

 

チャンネルの詳細を開き、「送信先」を確認すると画面下部に指定した MediaPackage が紐づいている事が確認できます。

 

これにて、無事に CloudFormation を使って既存の MediaPackage を紐づける事が出来ました。

おわりに

MediaLive を CloudFormation で作成する際に既存の MediaPackage と紐づけるためのテンプレートを公開してみました。

MediaLive を作成する CloudFormation テンプレートを作成するのはなかなか大変ですが、一度作って置くと便利なのも事実です。うまく活用していきましょう。

 

確認後はリソースの削除をお忘れなく。

 

以上、AWS 事業本部の大前でした。

参考