MediaLiveでライブ配信システムを構築する

ライブ配信システムの構成

映像データは下記のように流れる。

カメラ、マイク -> ローランドの映像ミキサー -> LiveShell X -> MediaLive -> MediaStore -> CloudFront -> 各プラットフォームのプレーヤー

この手順では、MediaLive、MediaStore、CloudFrontの設定について行う。

このうち、MediaLiveには

  1. インプット( LiveShell X からの RTMP を受け取る )
  2. チャンネル( 映像データをエンコードする ) で構成されている。

またMediaStoreはストレージで、MediaLiveでエンコードされたデータを保存する領域になっている( MediaLive だけでは映像を保存できない、かならず別の保存する領域に転送する必要がある )

CloudFrontは大規模配信で使われるCDNで、 複数ユーザにキャッシュを見せる事で、MediaStoreの負荷を下げるために用いている。つまりCloudFront無しでも配信は可能だが、CloudFront無しだとライブ授業のユーザー数には耐えられないため、導入しておいた方がよい。

MediaStore( 映像チャンク置き場 )を作る

MediaStoreは「MediaLiveでエンコードされた映像のチャンクを置く場所」として必要( MediaLiveにはストレージが無いので、別途つくる必要がある ) MediaLiveを作る際にMediaStoreのアドレスを指定する必要があるので、事前に作成する。

チャンネルを作成する

MediaLiveで映像データをエンコードするチャンネルを作成する

管理画面 からログイン

設定例

入力名: dev 入力タイプ: RTMP (プッシュ) Network mode: Public 入力セキュリティグループ: 既存の使用、0.0.0.0/0 入力の送信先: SINGLE_PIPELINE

  1. [チャンネルの作成] をクリック

  2. ( その環境にMediaLive用のロールが無ければ作る必要があるので )テンプレートからロールを作成する

チャンネルテンプレートは[ Live Action ]をベースに作成する事にした。 ( このテンプレートの内容は時代によって変わるようだ。以前よりテンプレートも増えているし内容も異なる )

[ Channel class ] は [ SINGLE PIPELINE ]を選択する。 標準では[ STANDARD ]になっているが、正しく冗長化を試みるとなると、インターネット回線を2つ用意するような話になるので、シンプルに[ SINGLE PIPELINE ]で設定する。

  1. RTMP入力を作成する

名称、RTMP(プッシュ)、ネットワークモードはPublicを選択。 入力セキュリティグループはプロバイダから得られたIPアドレスが変わるリスクを考えて 0.0.0.0 としているが、 この箇所へのDOS攻撃を考えると、RTMPのパスワードを検討する必要がある。 [入力の送信先]は[ SINGLE PIPELINE ]で、 [ アプリケーション名とインスタンス ] は

  • アプリケーション名: dev
  • アプリケーションインスタンス: 無し で[ 作成 ]をクリック。

この [ アプリケーション名とインスタンス ] はRTMPで接続する際( 例えば LiveShell X で接続先の設定をする際の パスとストリームキーの設定になる

  1. MediaLive -> MediaStore へのファイルアップロード設定を行なう 事前にMediaStoreの設定は済んでいると思うので、下記のような設定を行なう。

MediaStoreのURLは冒頭部分に mediastoressl:// を付ける。 mediastoressl://h3uuhq63cs6j5m.data.mediastore.ap-northeast-1.amazonaws.com/z/live

720p60は削除など簡単な調整を行なう。

  1. [ チャンネルの作成 ]をクリック 割とすぐ作成される。

CLIから作成する

MediaStore領域を作成する

aws –profile AWSアカウント名 mediastore create-container –container-name コンテナ名

実行例)

aws mediastore create-container  --container-name dev1

出力結果

{
    "Container": {
        "CreationTime": "2021-02-12T16:59:20+09:00",
        "ARN": "arn:aws:mediastore:ap-northeast-1:222222222:container/dev1",
        "Name": "dev1",
        "Status": "CREATING",
        "AccessLoggingEnabled": false
    }
}

MediaStoreのコンテナのポリシー( コンテナへのアクセス権限の設定 )

先に下記のようなjsonを用意する

mediastore_container_policy.json

AWSアカウントID、Resourceの箇所は必要に応じて変更する

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "MediaStoreFullAccess",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::1111111111:root"
      },
      "Action": "mediastore:*",
      "Resource": "arn:aws:mediastore:ap-northeast-1:1111111111:container/broadcast-endpoint-1/*",
      "Condition": {
        "Bool": {
          "aws:SecureTransport": "true"
        }
      }
    },
    {
      "Sid": "PublicReadOverHttps",
      "Effect": "Allow",
      "Principal": "*",
      "Action": [
        "mediastore:GetObject",
        "mediastore:DescribeObject"
      ],
      "Resource": "arn:aws:mediastore:ap-northeast-1:1111111111:container/broadcast-endpoint-1/*",
      "Condition": {
        "Bool": {
          "aws:SecureTransport": "true"
        }
      }
    }
  ]
}

下記のように設定

aws mediastore put-container-policy --container-name broadcast-endpoint-1 --policy file://tmp/mediastore_container_policy.json

MediaStoreのCORS設定

CORSの設定を行い、CloudFrontからアクセス可能にする。先に下記のようなjsonを用意する

corsPolicy.json

[
  {
    "AllowedOrigins": ["*"],
    "AllowedMethods": ["GET", "HEAD"],
    "AllowedHeaders": ["*"],
    "MaxAgeSeconds": 3000
  }
]

下記のコマンドで適用する

aws mediastore put-cors-policy \
               --container-name dev-inoue-20210105 \
               --cors-policy file://corsPolicy.json

MediaLiveのインプットエンドポイントを作成

先にjsonファイルを作成する。

  • StreamName: ストリーム名
  • InputSecurityGroups: 各AWSアカウントでセキュリティーグループのIDは異なるので注意
  • Name: AWS管理画面で見える名前

cat medialive_input-dev1.json

{
    "Destinations": [
        {
          "StreamName": "studio-dev1"
        }
    ],
    "InputDevices": [],
    "InputSecurityGroups": [
        "8167665"
    ],
    "MediaConnectFlows": [],
    "Name": "studio-dev1",
    "Sources": [],
    "Tags": {},
    "Type": "RTMP_PUSH"
}

下記コマンドで作成する。 –type RTMP_PUSH  で作成するタイプを指定している。またデフォルトだとシンプル構成で構成される。

aws medialive create-input –type RTMP_PUSH –cli-input-json file://medialive_input-dev1.json

下記のような出力を得られる

  • Input.Destinations.Url このURLでRTMPを受け付ける
{
    "Input": {
        "Arn": "arn:aws:medialive:ap-northeast-1:222222222:input:6794009",
        "AttachedChannels": [],
        "Destinations": [
            {
                "Ip": "52.194.8.54",
                "Port": "1935",
                "Url": "rtmp://52.194.8.54:1935/studio-dev1"
            }
        ],
        "Id": "6794009",
        "InputClass": "SINGLE_PIPELINE",
        "InputDevices": [],
        "InputSourceType": "STATIC",
        "MediaConnectFlows": [],
        "Name": "studio-dev1",
        "SecurityGroups": [
            "8167665"
        ],
        "Sources": [],
        "State": "DETACHED",
        "Tags": {},
        "Type": "RTMP_PUSH"
    }
}

MediaLiveのチャンネルを作成する

割と大きなjsonファイルになるが、下記のようなjsonファイルを用意する。 AWS管理画面から、このjsonの雛形のようなものはダウロード可能だが、出力される内容にバグがあり、jsonのkeyは先頭文字を大文字に変更する必要がある。 また下記の要素は都度、変更する必要がある

  • Name
  • InputId
  • Destinations.Settings.Url
  • RoleArn: AWSアカウントによってIAM の ARNが異なる
{
  "Name": "inoue-20210105",
  "InputAttachments": [
    {
      "InputId": "7549717",
      "InputAttachmentName": "mp4",
      "InputSettings": {
        "SourceEndBehavior": "CONTINUE",
        "InputFilter": "AUTO",
        "FilterStrength": 1,
        "DeblockFilter": "DISABLED",
        "DenoiseFilter": "DISABLED",
        "Smpte2038DataPreference": "IGNORE",
        "AudioSelectors": [],
        "CaptionSelectors": []
      }
    }
  ],
  "Destinations": [
    {
      "Id": "destination1",
      "Settings": [
        {
          "Url": "mediastoressl://cdjdkfgqompc4q.data.mediastore.ap-northeast-1.amazonaws.com/live/test"
        }
      ],
      "MediaPackageSettings": []
    }
  ],
  "EncoderSettings": {
    "AudioDescriptions": [
      {
        "AudioSelectorName": "Default",
        "CodecSettings": {
          "AacSettings": {
            "InputType": "NORMAL",
            "Bitrate": 192000,
            "CodingMode": "CODING_MODE_2_0",
            "RawFormat": "NONE",
            "Spec": "MPEG4",
            "Profile": "LC",
            "RateControlMode": "CBR",
            "SampleRate": 48000
          }
        },
        "AudioTypeControl": "FOLLOW_INPUT",
        "LanguageCodeControl": "FOLLOW_INPUT",
        "Name": "audio_2"
      }
    ],
    "CaptionDescriptions": [],
    "OutputGroups": [
      {
        "OutputGroupSettings": {
          "HlsGroupSettings": {
            "IncompleteSegmentBehavior": "AUTO",
            "DiscontinuityTags": "INSERT",
            "AdMarkers": [],
            "CaptionLanguageSetting": "OMIT",
            "CaptionLanguageMappings": [],
            "HlsCdnSettings": {
              "HlsMediaStoreSettings": {
                "NumRetries": 10,
                "ConnectionRetryInterval": 1,
                "RestartDelay": 15,
                "FilecacheDuration": 300,
                "MediaStoreStorageClass": "TEMPORAL"
              }
            },
            "InputLossAction": "EMIT_OUTPUT",
            "ManifestCompression": "NONE",
            "Destination": {
              "DestinationRefId": "destination1"
            },
            "IvInManifest": "INCLUDE",
            "IvSource": "FOLLOWS_SEGMENT_NUMBER",
            "ClientCache": "ENABLED",
            "TsFileMode": "SEGMENTED_FILES",
            "ManifestDurationFormat": "INTEGER",
            "SegmentationMode": "USE_SEGMENT_DURATION",
            "RedundantManifest": "DISABLED",
            "OutputSelection": "MANIFESTS_AND_SEGMENTS",
            "StreamInfResolution": "INCLUDE",
            "IFrameOnlyPlaylists": "DISABLED",
            "IndexNSegments": 10,
            "ProgramDateTime": "EXCLUDE",
            "ProgramDateTimePeriod": 600,
            "KeepSegments": 21,
            "SegmentLength": 6,
            "TimedMetadataId3Frame": "PRIV",
            "TimedMetadataId3Period": 10,
            "HlsId3SegmentTagging": "DISABLED",
            "CodecSpecification": "RFC_4281",
            "DirectoryStructure": "SINGLE_DIRECTORY",
            "SegmentsPerSubdirectory": 10000,
            "Mode": "LIVE"
          }
        },
        "Name": "HD",
        "Outputs": [
          {
            "OutputSettings": {
              "HlsOutputSettings": {
                "NameModifier": "_720p30",
                "SegmentModifier": "mp4test",
                "HlsSettings": {
                  "StandardHlsSettings": {
                    "M3u8Settings": {
                      "AudioFramesPerPes": 4,
                      "AudioPids": "492-498",
                      "EcmPid": "8182",
                      "NielsenId3Behavior": "NO_PASSTHROUGH",
                      "PcrControl": "PCR_EVERY_PES_PACKET",
                      "PmtPid": "480",
                      "ProgramNum": 1,
                      "Scte35Pid": "500",
                      "Scte35Behavior": "NO_PASSTHROUGH",
                      "TimedMetadataPid": "502",
                      "TimedMetadataBehavior": "NO_PASSTHROUGH",
                      "VideoPid": "481"
                    },
                    "AudioRenditionSets": "program_audio"
                  }
                },
                "H265PackagingType": "HVC1"
              }
            },
            "OutputName": "mp4",
            "VideoDescriptionName": "video_720p30",
            "AudioDescriptionNames": ["audio_2"],
            "CaptionDescriptionNames": []
          }
        ]
      }
    ],
    "TimecodeConfig": {
      "Source": "EMBEDDED"
    },
    "VideoDescriptions": [
      {
        "CodecSettings": {
          "H264Settings": {
            "AfdSignaling": "NONE",
            "ColorMetadata": "INSERT",
            "AdaptiveQuantization": "HIGH",
            "Bitrate": 3000000,
            "EntropyEncoding": "CABAC",
            "FlickerAq": "ENABLED",
            "ForceFieldPictures": "DISABLED",
            "FramerateControl": "SPECIFIED",
            "FramerateNumerator": 30,
            "FramerateDenominator": 1,
            "GopBReference": "DISABLED",
            "GopClosedCadence": 1,
            "GopNumBFrames": 1,
            "GopSize": 60,
            "GopSizeUnits": "FRAMES",
            "SubgopLength": "FIXED",
            "ScanType": "PROGRESSIVE",
            "Level": "H264_LEVEL_AUTO",
            "LookAheadRateControl": "HIGH",
            "NumRefFrames": 3,
            "ParControl": "INITIALIZE_FROM_SOURCE",
            "Profile": "HIGH",
            "RateControlMode": "CBR",
            "Syntax": "DEFAULT",
            "SceneChangeDetect": "ENABLED",
            "Slices": 1,
            "SpatialAq": "ENABLED",
            "TemporalAq": "ENABLED",
            "TimecodeInsertion": "DISABLED"
          }
        },
        "Height": 720,
        "Name": "video_720p30",
        "RespondToAfd": "NONE",
        "Sharpness": 100,
        "ScalingBehavior": "DEFAULT",
        "Width": 1280
      }
    ]
  },
  "RoleArn": "arn:aws:iam::898115053391:role/MediaLiveAccessRole",
  "InputSpecification": {
    "Codec": "AVC",
    "Resolution": "HD",
    "MaximumBitrate": "MAX_20_MBPS"
  },
  "LogLevel": "DISABLED",
  "ChannelClass": "SINGLE_PIPELINE"
}
User
CloudFront
ALB
EC2
RDS