4
4

More than 1 year has passed since last update.

AWS CloudFormationで動画配信環境を構築した!

Last updated at Posted at 2022-11-18

概要

AWS CloudFormationを用いて簡単なHLS形式の動画配信環境を構築します。構成は次の通りです。
live-streaming-demo.png

注意事項

  • AWS CloudFormationの使い方に慣れていて、AWS Media Servicesについてもある程度知見をお持ちの方を対象にしていますが、初めての方もチャレンジしてみて下さい!
  • PoCのため、各サービスのパラメーターはライブ配信に最適なチューニングはされず、ほぼデフォルトの値になっております。
  • AWS CloudFormation テンプレートはYAML形式で記述しています。
  • Tokyoリージョン (ap-northeast-1)で構築しています。
  • 料金が発生しますので検証が終わりましたらCloudFormationスタックの削除を忘れないで下さい。

利用するAWSサービス

手順

CloudFormation

今回は下記セッションに分けて記述していきます。リソースセッションに関しては必須なのでテンプレートに含める必要があります。

  • 形式バージョン
  • Description
  • リソース
  • 出力

他に記述可能なセッションについては下記をご参照下さい。
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-anatomy.html

一気に全てのサービスをテンプレートファイルに書くよりも、まずはCloudFormationのテンプレートに追加する各サービスを分けて設定を見ていきます。

1. MediaStore

MediaLiveのDestinationにMediaStoreのEndpointを指定する必要があるため、先にMediaStoreのリソースを作成します。

  • MediaStoreで設定するリソース
    • Container

Container

■YAML構文

Type: AWS::MediaStore::Container
Properties: 
  AccessLoggingEnabled: Boolean
  ContainerName: String
  CorsPolicy: 
    - CorsRule
  LifecyclePolicy: String
  MetricPolicy: 
    MetricPolicy
  Policy: String
  Tags: 
    - Tag

■設定例

  MediaStoreContainer:
    Type: AWS::MediaStore::Container
    Properties:
      AccessLoggingEnabled: false
      ContainerName: !Ref AWS::StackName
      CorsPolicy:
        - AllowedOrigins:
            - '*'
          AllowedMethods:
            - GET
            - HEAD
          AllowedHeaders:
            - '*'
          MaxAgeSeconds: 3000
      LifecyclePolicy:
        "{\n  \"rules\": [\n    {\n      \"definition\": {\n        \"path\": [\n          {\n            \"prefix\": \"\"\n          }\n        ],\n        \"days_since_create\": [\n          {\n            \"numeric\": [\n              \">\",\n              1\n            ]\n          }\n        ]\n      },\n      \"action\": \"EXPIRE\"\n    }\n  ]\n}"
      MetricPolicy:
        ContainerLevelMetrics: DISABLED
      Policy:
        !Sub "{\n  \"Version\" : \"2012-10-17\",\n  \"Statement\" : [{\n    \"Sid\" : \"PublicReadOverHttps\",\n    \"Effect\" : \"Allow\",\n    \"Principal\" : \"*\",\n    \"Action\" : [\n        \"mediastore:GetObject\",\n        \"mediastore:DescribeObject\"\n    ],\n    \"Resource\" : \"arn:aws:mediastore:${AWS::Region}:${AWS::AccountId}:container/${AWS::StackName}/*\",\n    \"Condition\" : {\"Bool\" : {\n        \"aws:SecureTransport\" : \"true\"\n      }\n    }\n  }]\n}\n"
      Tags:
        - Key: "environment"
          Value: "demo"

2.MediaLive

  • MediaLiveで設定するリソース
    • IAM role
    • Input security group
    • Input
    • Channel

IAM role

■YAML構文

Type: AWS::IAM::Role
Properties: 
  AssumeRolePolicyDocument: Json
  Description: String
  ManagedPolicyArns: 
    - String
  MaxSessionDuration: Integer
  Path: String
  PermissionsBoundary: String
  Policies: 
    - Policy
  RoleName: String
  Tags: 
    - Tag

■設定例

検証のため、PolicyはMediaStoreとCloudWatch Logsにのみ限定しています。

  MediaLiveRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Join ['-', [ Ref: "AWS::StackName", "MediaLive-role", !Select [4, !Split ['-', !Select [2, !Split ['/', Ref: "AWS::StackId"]]]]]]
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          -
            Effect: Allow
            Principal:
              Service:
                - "medialive.amazonaws.com"
            Action:
              - sts:AssumeRole
      Policies:
        -
          PolicyName: !Sub "${AWS::StackName}-medialive-policy"
          PolicyDocument:
            Statement:
              - Effect: Allow
                Action:
                  - mediastore:ListContainers
                  - mediastore:PutObject
                  - mediastore:GetObject
                  - mediastore:DeleteObject
                  - mediastore:DescribeObject
                Resource: !Join ["", ["arn:aws:mediastore:", Ref: "AWS::Region", ":", Ref: "AWS::AccountId", ":*"]]
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                  - logs:DescribeLogStreams
                  - logs:DescribeLogGroups
                Resource: arn:aws:logs:*:*:*
      Tags:
        - Key: "environment"
          Value: "demo"

Input security group

■YAML構文

Type: AWS::MediaLive::InputSecurityGroup
Properties: 
  Tags: Json
  WhitelistRules: 
    - InputWhitelistRuleCidr

■設定例

  MediaLiveInputSecurityGroup:
    Type: AWS::MediaLive::InputSecurityGroup
    Properties:
      WhitelistRules:
        - Cidr: 0.0.0.0/0

Input

■YAML構文

Type: AWS::MediaLive::Input
Properties: 
  Destinations: 
    - InputDestinationRequest
  InputDevices: 
    - InputDeviceSettings
  InputSecurityGroups: 
    - String
  MediaConnectFlows: 
    - MediaConnectFlowRequest
  Name: String
  RoleArn: String
  Sources: 
    - InputSourceRequest
  Tags: Json
  Type: String
  Vpc: 
    InputVpcRequest

■設定例

  MediaLiveInput:
    Type: AWS::MediaLive::Input
    Properties:
      Destinations:
        - StreamName: live/stream
      InputSecurityGroups:
        - !Ref MediaLiveInputSecurityGroup
      Name: !Join ["-", [ Ref: "AWS::StackName", "ml-input"]]
      Tags:
        environment: demo
      Type: RTMP_PUSH

Channel

■YAML構文

Type: AWS::MediaLive::Channel
Properties: 
  CdiInputSpecification: 
    CdiInputSpecification
  ChannelClass: String
  Destinations: 
    - OutputDestination
  EncoderSettings: 
    EncoderSettings
  InputAttachments: 
    - InputAttachment
  InputSpecification: 
    InputSpecification
  LogLevel: String
  Name: String
  RoleArn: String
  Tags: Json
  Vpc: 
    VpcOutputSettings

■設定例

MediaLive Channelの設定プロパティーはとても豊富なので慣れてきたら色々チューニングするのもいいかもしれません!

調整するパラメータ値

  • MediaLiveの一般設定
    • Channel class : SINGLE_PIPELINE
    • Maximum input bitrate : MAX_10_MBPS
    • Log level : Error
  • Video
    • 解像度 : 1280x720
    • Codec Settings : H264
    • Rate Control Mode : QVBR
    • Bitrate : 2000000 bps
    • Max Bitrate : 2000000 bps
    • Framerate : 30 fps
    • ABR : なし
  • Audio
    • Codec Settings : AAC
    • Bitrate : 128000 bps
  MediaLiveChannel:
    Type: AWS::MediaLive::Channel
    Properties:
      ChannelClass: "SINGLE_PIPELINE"
      Destinations:
        - Id: msdest
          MediaPackageSettings: []
          Settings:
            - Url: !Join [ "",  [ "mediastoressl://", !Select [1, !Split [ "://", !GetAtt MediaStoreContainer.Endpoint ] ], "/live" ] ]
      EncoderSettings:
        AudioDescriptions:
          - AudioSelectorName: Audio1
            AudioTypeControl: FOLLOW_INPUT
            CodecSettings:
              AacSettings:
                Bitrate: 128000
                CodingMode: CODING_MODE_2_0
                InputType: NORMAL
                Profile: LC
                RateControlMode: CBR
                RawFormat: NONE
                SampleRate: 48000
                Spec: MPEG4
            LanguageCodeControl: FOLLOW_INPUT
            Name: audio_720p128k
        CaptionDescriptions: []
        OutputGroups:
          - Name: MediaStore Group
            OutputGroupSettings:
              HlsGroupSettings:
                AdMarkers: []
                CaptionLanguageMappings: []
                CaptionLanguageSetting: OMIT
                ClientCache: ENABLED
                CodecSpecification: RFC_4281
                Destination:
                  DestinationRefId: msdest
                DirectoryStructure: SINGLE_DIRECTORY
                DiscontinuityTags: INSERT
                HlsCdnSettings:
                  HlsMediaStoreSettings:
                    ConnectionRetryInterval: 1
                    FilecacheDuration: 300
                    MediaStoreStorageClass: TEMPORAL
                    NumRetries: 10
                    RestartDelay: 15
                HlsId3SegmentTagging: DISABLED
                IFrameOnlyPlaylists: DISABLED
                IncompleteSegmentBehavior: AUTO
                IndexNSegments: 10
                InputLossAction: EMIT_OUTPUT
                IvInManifest: INCLUDE
                IvSource: FOLLOWS_SEGMENT_NUMBER
                KeepSegments: 21
                ManifestCompression: NONE
                ManifestDurationFormat: FLOATING_POINT
                Mode: LIVE
                OutputSelection: MANIFESTS_AND_SEGMENTS
                ProgramDateTime: EXCLUDE
                ProgramDateTimePeriod: 600
                RedundantManifest: DISABLED
                SegmentLength: 10
                SegmentationMode: USE_SEGMENT_DURATION
                SegmentsPerSubdirectory: 10000
                StreamInfResolution: INCLUDE
                TimedMetadataId3Frame: PRIV
                TimedMetadataId3Period: 10
                TsFileMode: SEGMENTED_FILES
            Outputs:
              - AudioDescriptionNames:
                  - audio_720p128k
                CaptionDescriptionNames: []
                OutputName: 720p2m
                OutputSettings:
                  HlsOutputSettings:
                    H265PackagingType: HVC1
                    HlsSettings:
                      StandardHlsSettings:
                        AudioRenditionSets: program_audio
                        M3u8Settings:
                          AudioFramesPerPes: 4
                          AudioPids: 492-498
                          NielsenId3Behavior: NO_PASSTHROUGH
                          PcrControl: PCR_EVERY_PES_PACKET
                          PmtPid: '480'
                          ProgramNum: 1
                          Scte35Behavior: NO_PASSTHROUGH
                          Scte35Pid: '500'
                          TimedMetadataBehavior: NO_PASSTHROUGH
                          TimedMetadataPid: '502'
                          VideoPid: '481'
                    NameModifier: "_2m"
                VideoDescriptionName: video_720p2m
        TimecodeConfig:
          Source: EMBEDDED
        VideoDescriptions:
          - CodecSettings:
              H264Settings:
                AdaptiveQuantization: AUTO
                AfdSignaling: NONE
                Bitrate: 2000000
                ColorMetadata: INSERT
                EntropyEncoding: CABAC
                FlickerAq: ENABLED
                ForceFieldPictures: DISABLED
                FramerateControl: SPECIFIED
                FramerateDenominator: 996
                FramerateNumerator: 30000
                GopBReference: DISABLED
                GopClosedCadence: 1
                GopSize: 90
                GopSizeUnits: FRAMES
                Level: H264_LEVEL_AUTO
                LookAheadRateControl: MEDIUM
                MaxBitrate: 2000000
                NumRefFrames: 1
                ParControl: INITIALIZE_FROM_SOURCE
                Profile: MAIN
                RateControlMode: QVBR
                ScanType: PROGRESSIVE
                SceneChangeDetect: ENABLED
                SpatialAq: ENABLED
                SubgopLength: FIXED
                Syntax: DEFAULT
                TemporalAq: ENABLED
                TimecodeInsertion: DISABLED
            Height: 720
            Name: video_720p2m
            RespondToAfd: NONE
            ScalingBehavior: DEFAULT
            Sharpness: 100
            Width: 1280
      InputAttachments:
        - InputAttachmentName: !Join ["-", [ Ref: "AWS::StackName", "ml-input"]]
          InputId: !Ref MediaLiveInput
          InputSettings:
            AudioSelectors:
              - Name: Audio1
            CaptionSelectors: []
            DeblockFilter: DISABLED
            DenoiseFilter: DISABLED
            FilterStrength: 1
            InputFilter: AUTO
            Smpte2038DataPreference: IGNORE
            SourceEndBehavior: CONTINUE
      InputSpecification:
        Codec: AVC
        MaximumBitrate: MAX_10_MBPS
        Resolution: HD
      LogLevel: ERROR
      Name: !Join ["-", [ Ref: "AWS::StackName", "ml-ch"]]
      RoleArn: !GetAtt MediaLiveRole.Arn
      Tags:
        environment: demo

4.CloudFront

  • CloudFrontで設定するリソース
    • Distribution

調整するパラメータ値

  • Protocol : HTTPS only
  • Viewer protocol policy : Redirect HTTP to HTTPS
  • Allowed HTTP methods : GET, HEAD, OPTIONS
  • Headers : Access-Control-Request-Method, Access-Control-Request-Headers, Origin
  • Object caching : Minimum TTL, Maximum TTL, Default TTL = 1s

■YAML構文

{
  "Type" : "AWS::CloudFront::Distribution",
  "Properties" : {
      "DistributionConfig" : DistributionConfig,
      "Tags" : [ Tag, ... ]
    }
}

■設定例

ここでは設定を省きますが、m3u8ファイルとtsファイルをそれぞれ適切なTTLにすることもできます。

  CloudFront:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Origins:
          - Id: !Join ["-", [ Ref: "AWS::StackName", "ms-ctn"]]
            DomainName: !Sub
             - ${ENDPOINT}
             - { ENDPOINT: !Select [1, !Split [ "://", !GetAtt MediaStoreContainer.Endpoint ]]}
            CustomOriginConfig:
              OriginProtocolPolicy: https-only
        Enabled: true
        Comment: "CloudFront for demo livestreaming"
        Logging:
          IncludeCookies: false
          Bucket: ''
          Prefix: ''
        DefaultCacheBehavior:
          AllowedMethods:
          - GET
          - HEAD
          - OPTIONS
          Compress: true
          MaxTTL: 1
          MinTTL: 1
          ViewerProtocolPolicy: "redirect-to-https"
          DefaultTTL: 1
          TargetOriginId: !Join ["-", [ Ref: "AWS::StackName", "ms-ctn"]]
          ForwardedValues:
            QueryString: false
            Cookies:
              Forward: none
            Headers:
              - Origin
              - Access-Control-Request-Method
              - Access-Control-Request-Headers
        PriceClass: PriceClass_All
        ViewerCertificate:
          CloudFrontDefaultCertificate: true
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: W70
            reason: "CloudFront automatically sets the security policy to TLSv1 when the distribution uses the CloudFront domain name (CloudFrontDefaultCertificate=true)"

最終形態

先程設定した各サービスのリソースセッションをCloudFormationテンプレートに落とし込みます。記述が長いので設定例をクリックしてご確認下さい。

設定例
Description: "Cloudformation for MediaServices - MediaLive, MediaStore, CloudFront"
AWSTemplateFormatVersion: '2010-09-09'
Resources:
  MediaStoreContainer:
    Type: AWS::MediaStore::Container
    Properties:
      AccessLoggingEnabled: false
      ContainerName: !Ref AWS::StackName
      CorsPolicy:
        - AllowedOrigins:
            - '*'
          AllowedMethods:
            - GET
            - HEAD
          AllowedHeaders:
            - '*'
          MaxAgeSeconds: 3000
      LifecyclePolicy:
        "{\n  \"rules\": [\n    {\n      \"definition\": {\n        \"path\": [\n          {\n            \"prefix\": \"\"\n          }\n        ],\n        \"days_since_create\": [\n          {\n            \"numeric\": [\n              \">\",\n              1\n            ]\n          }\n        ]\n      },\n      \"action\": \"EXPIRE\"\n    }\n  ]\n}"
      MetricPolicy:
        ContainerLevelMetrics: DISABLED
      Policy:
        !Sub "{\n  \"Version\" : \"2012-10-17\",\n  \"Statement\" : [{\n    \"Sid\" : \"PublicReadOverHttps\",\n    \"Effect\" : \"Allow\",\n    \"Principal\" : \"*\",\n    \"Action\" : [\n        \"mediastore:GetObject\",\n        \"mediastore:DescribeObject\"\n    ],\n    \"Resource\" : \"arn:aws:mediastore:${AWS::Region}:${AWS::AccountId}:container/${AWS::StackName}/*\",\n    \"Condition\" : {\"Bool\" : {\n        \"aws:SecureTransport\" : \"true\"\n      }\n    }\n  }]\n}\n"
      Tags:
        - Key: "environment"
          Value: "demo"

  MediaLiveRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Join ['-', [ Ref: "AWS::StackName", "MediaLive-role", !Select [4, !Split ['-', !Select [2, !Split ['/', Ref: "AWS::StackId"]]]]]]
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          -
            Effect: Allow
            Principal:
              Service:
                - "medialive.amazonaws.com"
            Action:
              - sts:AssumeRole
      Policies:
        -
          PolicyName: !Sub "${AWS::StackName}-medialive-policy"
          PolicyDocument:
            Statement:
              - Effect: Allow
                Action:
                  - mediastore:ListContainers
                  - mediastore:PutObject
                  - mediastore:GetObject
                  - mediastore:DeleteObject
                  - mediastore:DescribeObject
                Resource: !Join ["", ["arn:aws:mediastore:", Ref: "AWS::Region", ":", Ref: "AWS::AccountId", ":*"]]
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                  - logs:DescribeLogStreams
                  - logs:DescribeLogGroups
                Resource: arn:aws:logs:*:*:*
      Tags:
        - Key: "environment"
          Value: "demo"

  MediaLiveInputSecurityGroup:
    Type: AWS::MediaLive::InputSecurityGroup
    Properties:
      WhitelistRules:
        - Cidr: 0.0.0.0/0

  MediaLiveInput:
    Type: AWS::MediaLive::Input
    Properties:
      Destinations:
        - StreamName: live/stream
      InputSecurityGroups:
        - !Ref MediaLiveInputSecurityGroup
      Name: !Join ["-", [ Ref: "AWS::StackName", "ml-input"]]
      Tags:
        environment: demo
      Type: RTMP_PUSH

  MediaLiveChannel:
    Type: AWS::MediaLive::Channel
    Properties:
      ChannelClass: "SINGLE_PIPELINE"
      Destinations:
        - Id: msdest
          MediaPackageSettings: []
          Settings:
            - Url: !Join [ "",  [ "mediastoressl://", !Select [1, !Split [ "://", !GetAtt MediaStoreContainer.Endpoint ] ], "/live" ] ]
      EncoderSettings:
        AudioDescriptions:
          - AudioSelectorName: Audio1
            AudioTypeControl: FOLLOW_INPUT
            CodecSettings:
              AacSettings:
                Bitrate: 128000
                CodingMode: CODING_MODE_2_0
                InputType: NORMAL
                Profile: LC
                RateControlMode: CBR
                RawFormat: NONE
                SampleRate: 48000
                Spec: MPEG4
            LanguageCodeControl: FOLLOW_INPUT
            Name: audio_720p128k
        CaptionDescriptions: []
        OutputGroups:
          - Name: MediaStore Group
            OutputGroupSettings:
              HlsGroupSettings:
                AdMarkers: []
                CaptionLanguageMappings: []
                CaptionLanguageSetting: OMIT
                ClientCache: ENABLED
                CodecSpecification: RFC_4281
                Destination:
                  DestinationRefId: msdest
                DirectoryStructure: SINGLE_DIRECTORY
                DiscontinuityTags: INSERT
                HlsCdnSettings:
                  HlsMediaStoreSettings:
                    ConnectionRetryInterval: 1
                    FilecacheDuration: 300
                    MediaStoreStorageClass: TEMPORAL
                    NumRetries: 10
                    RestartDelay: 15
                HlsId3SegmentTagging: DISABLED
                IFrameOnlyPlaylists: DISABLED
                IncompleteSegmentBehavior: AUTO
                IndexNSegments: 10
                InputLossAction: EMIT_OUTPUT
                IvInManifest: INCLUDE
                IvSource: FOLLOWS_SEGMENT_NUMBER
                KeepSegments: 21
                ManifestCompression: NONE
                ManifestDurationFormat: FLOATING_POINT
                Mode: LIVE
                OutputSelection: MANIFESTS_AND_SEGMENTS
                ProgramDateTime: EXCLUDE
                ProgramDateTimePeriod: 600
                RedundantManifest: DISABLED
                SegmentLength: 10
                SegmentationMode: USE_SEGMENT_DURATION
                SegmentsPerSubdirectory: 10000
                StreamInfResolution: INCLUDE
                TimedMetadataId3Frame: PRIV
                TimedMetadataId3Period: 10
                TsFileMode: SEGMENTED_FILES
            Outputs:
              - AudioDescriptionNames:
                  - audio_720p128k
                CaptionDescriptionNames: []
                OutputName: 720p2m
                OutputSettings:
                  HlsOutputSettings:
                    H265PackagingType: HVC1
                    HlsSettings:
                      StandardHlsSettings:
                        AudioRenditionSets: program_audio
                        M3u8Settings:
                          AudioFramesPerPes: 4
                          AudioPids: 492-498
                          NielsenId3Behavior: NO_PASSTHROUGH
                          PcrControl: PCR_EVERY_PES_PACKET
                          PmtPid: '480'
                          ProgramNum: 1
                          Scte35Behavior: NO_PASSTHROUGH
                          Scte35Pid: '500'
                          TimedMetadataBehavior: NO_PASSTHROUGH
                          TimedMetadataPid: '502'
                          VideoPid: '481'
                    NameModifier: "_2m"
                VideoDescriptionName: video_720p2m
        TimecodeConfig:
          Source: EMBEDDED
        VideoDescriptions:
          - CodecSettings:
              H264Settings:
                AdaptiveQuantization: AUTO
                AfdSignaling: NONE
                Bitrate: 2000000
                ColorMetadata: INSERT
                EntropyEncoding: CABAC
                FlickerAq: ENABLED
                ForceFieldPictures: DISABLED
                FramerateControl: SPECIFIED
                FramerateDenominator: 996
                FramerateNumerator: 30000
                GopBReference: DISABLED
                GopClosedCadence: 1
                GopSize: 90
                GopSizeUnits: FRAMES
                Level: H264_LEVEL_AUTO
                LookAheadRateControl: MEDIUM
                MaxBitrate: 2000000
                NumRefFrames: 1
                ParControl: INITIALIZE_FROM_SOURCE
                Profile: MAIN
                RateControlMode: QVBR
                ScanType: PROGRESSIVE
                SceneChangeDetect: ENABLED
                SpatialAq: ENABLED
                SubgopLength: FIXED
                Syntax: DEFAULT
                TemporalAq: ENABLED
                TimecodeInsertion: DISABLED
            Height: 720
            Name: video_720p2m
            RespondToAfd: NONE
            ScalingBehavior: DEFAULT
            Sharpness: 100
            Width: 1280
      InputAttachments:
        - InputAttachmentName: !Join ["-", [ Ref: "AWS::StackName", "ml-input"]]
          InputId: !Ref MediaLiveInput
          InputSettings:
            AudioSelectors:
              - Name: Audio1
            CaptionSelectors: []
            DeblockFilter: DISABLED
            DenoiseFilter: DISABLED
            FilterStrength: 1
            InputFilter: AUTO
            Smpte2038DataPreference: IGNORE
            SourceEndBehavior: CONTINUE
      InputSpecification:
        Codec: AVC
        MaximumBitrate: MAX_10_MBPS
        Resolution: HD
      LogLevel: ERROR
      Name: !Join ["-", [ Ref: "AWS::StackName", "ml-ch"]]
      RoleArn: !GetAtt MediaLiveRole.Arn
      Tags:
        environment: demo

  CloudFront:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Origins:
          - Id: !Join ["-", [ Ref: "AWS::StackName", "ms-ctn"]]
            DomainName: !Sub
             - ${ENDPOINT}
             - { ENDPOINT: !Select [1, !Split [ "://", !GetAtt MediaStoreContainer.Endpoint ]]}
            CustomOriginConfig:
              OriginProtocolPolicy: https-only
        Enabled: true
        Comment: "CloudFront for demo livestreaming"
        Logging:
          IncludeCookies: false
          Bucket: ''
          Prefix: ''
        DefaultCacheBehavior:
          AllowedMethods:
          - GET
          - HEAD
          - OPTIONS
          Compress: true
          MaxTTL: 1
          MinTTL: 1
          ViewerProtocolPolicy: "redirect-to-https"
          DefaultTTL: 1
          TargetOriginId: !Join ["-", [ Ref: "AWS::StackName", "ms-ctn"]]
          ForwardedValues:
            QueryString: false
            Cookies:
              Forward: none
            Headers:
              - Origin
              - Access-Control-Request-Method
              - Access-Control-Request-Headers
        PriceClass: PriceClass_All
        ViewerCertificate:
          CloudFrontDefaultCertificate: true
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: W70
            reason: "CloudFront automatically sets the security policy to TLSv1 when the distribution uses the CloudFront domain name (CloudFrontDefaultCertificate=true)"

Outputs:
  LiveCloudFront:
    Description: CloudFront Live Playback URL
    Value: !Sub "https://${CloudFront.DomainName}/live.m3u8"

  MediaLiveChannelConsole:
    Description: This is the MediaLive Console. Click the link, then start the new channel.
    Value: !Sub "https://console.aws.amazon.com/medialive/home?region=${AWS::Region}#!/channels:"

  MediaLiveRTMPDest:
    Description: RTMP Destination for MediaLive Input
    Value: !Select [ 0, !GetAtt MediaLiveInput.Destinations]

  MediaStoreLiveStreamUrl:
    Description: MediaStore Playback URL
    Value: !Sub "${MediaStoreContainer.Endpoint}/live.m3u8"

スタック作成

1. CloudFormationスタックを作成します。

最初はChange setsで作成予定のリソースを確認した方がいいかもしれません。

スクリーンショット 2022-11-18 13.14.00.png

スクリーンショット 2022-11-18 13.15.23.png

スクリーンショット 2022-11-18 13.15.56.png

2. 作成成功。Resourceタブから作成済みのリソースを確認できました。

  • Resourcesタブ
    スクリーンショット 2022-11-18 13.23.08.png

  • Outputsタブ
    CLoudFormationテンプレートにOutputセッションも追加してあったのでこちらも確認できました。配信と視聴情報が一目で分かるので楽です!
    スクリーンショット 2022-11-18 13.23.31.png

配信と視聴テスト

1. MediaLive Channelを起動します。

OutputsのMediaLiveChannelConsoleから移動できます。

スクリーンショット 2022-11-18 14.10.25.png

2. OBSやffmpegを使ってRTMP PushでMediaLive Inputの送信先にストリームを送ります。

RTMPの送信先はOutputsのMediaLiveRTMPDestで確認できます。

MP4ファイルでテスト配信をする場合のffmpegの実行例 :

ffmpeg -re -stream_loop -1 -i {INPUTFILE} -acodec copy -vcodec copy -f flv {MediaLiveRTMPDest}

3. 配信が出てきていることを確認します。

MediaLiveコンソール -> Alertsでアラートが消えていればMediaLiveにストリームが無事入ってきていることを意味します。

■ストリームが入ってくる前
スクリーンショット 2022-11-18 14.25.23.png

■ストリームが入ってきた後
スクリーンショット 2022-11-18 14.25.42.png

3. CloudFrontの視聴URLでSafariブラウザーかVLC Media Playerから視聴します。

視聴URLはOutputsのLiveCloudFrontから取得できます。

HLS(m3u8)形式での視聴はSafariブラウザーやVLC Media Playerといったメディアプレイヤーが必要です。

■Mac Safari
スクリーンショット 2022-11-18 16.27.27.png

■VLC Media Player
スクリーンショット 2022-11-18 16.29.28.png

スタックの削除

不要な料金が発生するのを防ぐために、動作確認が終わったらスタックを削除することをお勧めします。

  • 削除の流れとしては

1. MediaLive Channelを停止
2. MediaStore Containerのオブジェクトを削除
3. CloudFormationのスタックを削除

Channelが起動中、もしくはContainerの中身が空ではない状態でスタックの削除を試みるとエラーが出ます。

まとめ

上記CloudFormationを使ってライブ配信一連のリソースを作成してみました。DVR機能が必要な場合はMediaStoreの代わりにAWS Elemental MediaPackageを利用することも可能です。尚、今回は作成する各リソースは一つのみですが、強力なTransform セクションを使えば複数の同じリソースを同時に構築が出来ます。少しでも参考になれば幸いです。

参考

CloudFormation

4
4
1

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
4
4