AWS for M&E Blog

Part 2 – anatomy of a live streaming AWS CloudFormation template

In an earlier blog, we shared an AWS CloudFormation template that automated the deployment of a simple live streaming workflow using AWS Elemental MediaLive and AWS Elemental MediaStore. We took apart the template and analyzed the various sections of the template including the defined MediaLive and MediaStore resources.

Since the blog’s publication, AWS Elemental MediaPackage released AWS CloudFormation support for the creation of its channels, VOD packaging groups, and packaging configurations. In this post, we share another live streaming template that uses the newly supported MediaPackage channel and endpoint resource types in CloudFormation. Instead of having a MediaStore container for the MediaLive channel destination, the template has a MediaPackage channel as its destination with an HLS endpoint. And instead of an HLS input, MediaLive uses an MP4 input file. Let’s begin with the deconstruction of the fully deployable template in JavaScript Object Notation (JSON) format, and evaluate the notable parts.

MediaPackage Channel

The first resource defined in the CloudFormation template is the MediaLive channel’s destination, which is a MediaPackage channel.

{
    "Resources": {
        "MediaPackageChannel": {
            "Type": "AWS::MediaPackage::Channel",
            "Description": "MediaPackage Channel",
            "Properties": {
                "Id": {
                    "Fn::Sub": "${AWS::StackName}-Channel"
                }
            }
        }
    }
}

The preceding JSON structure represents the section of the template that defines the MediaPackage channel. It has one required property, the Id. The built-in CloudFormation function Fn::Sub is used to get the value of the StackName at runtime and prepended to the word -Channel. Doing this little trick makes the Channel Id dynamic, creating a unique value every time the template is deployed. The Description property is optional but helpful, especially when your AWS account has multiple MediaPackage channels.

MediaPackage endpoint

The next resource is a MediaPackage endpoint, which has the information needed for MediaPackage to integrate with a player or content distribution network (CDN) such as Amazon CloudFront.

{
    "Resources": {
        "MediaPackageChannel": {
                ...
        },
        "MediaPackageEndpoint": {
            "Type": "AWS::MediaPackage::OriginEndpoint",
            "Properties": {
                "Description": "MediaPackage HLS Endpoint",
                "ChannelId": {
                    "Fn::Sub": "${AWS::StackName}-Channel"
                },
                "Id": "HLSEndpoint",
                "HlsPackage": {},
                "Origination": "ALLOW"
            },
            "DependsOn": "MediaPackageChannel"
        }
     }
 }

The ChannelId is a required property and must match the Id of the MediaPackage channel it’s going to be associated with. This means it has the same value as the MediaPackage channel defined earlier.

MediaPackage supports a variety of endpoint formats like HLS, CMAF, DASH-ISO, and Microsoft Smooth. Our template defines only one, an HLS endpoint as designated by the HlsPackage property. HlsPackage can define a number of parameters, but is left blank here to take the defaults. The Id property is required and must be unique for the channel, since a channel can have multiple endpoints of the same format. Note that when creating a CMAF endpoint, the Id property must be unique for the channel and for your account in the AWS Region. Origination is set to ALLOW so the endpoint can serve content to requesting devices, like an HLS player. Because the endpoint must be associated with a MediaPackage channel, the channel has to exist before CloudFormation creates the endpoint. The DependsOn attribute is set to the MediaPackage channel to ensure this happens.

MediaLive Input

Moving on to the next section, let’s look at the MediaLive HLS input.

{
    "Resources": {
        "MediaPackageChannel": {
                ...
        },
        "MediaPackageEndpoint": {
                ...
        },
        "MediaLiveInput": {
            "Type": "AWS::MediaLive::Input",
            "Properties": {
                "Name": {
                    "Fn::Sub": "${AWS::StackName}-MP4Input"
                },
                "Type": "MP4_FILE",
                "Sources": [
                    {
                        "Url": "s3://rodeolabz-us-west-2/livestreaming-cfn-blog/01_llama_drama_1080p.mp4"
                    }
                ]
            }
        }
     }
 }

The MP4 input has a few defined properties: Name, Sources, and Type. To make the Name dynamic, the variable StackName is prepended to the string -MP4Input. This way, if you deploy the stack more than once, you don’t end up with multiple MP4 inputs with the same name. If you deploy this template with a Stack Name “MyLiveStreaming”, the name of the MP4 input created is “MyLiveStreaming-MP4Input”. The Type corresponds to an MP4 input is MP4_FILE. Finally, the URL of the MP4 source is provided. The Sources property takes a list of URLs, but this template creates a single pipeline channel, so only one source is needed.

MediaLive Channel

And now, we examine the final piece, the MediaLive channel.

{
    "Resources": {
        "MediaPackageChannel": {
                ...
        },
        "MediaPackageEndpoint": {
                ...
        },
        "MediaLiveInput": {
                ...
        },
        "MediaLiveChannel": {
            "Type": "AWS::MediaLive::Channel",
            "Properties": {
                "Name": {
                    "Fn::Sub": "${AWS::StackName}-Channel"
                },
                "ChannelClass": "SINGLE_PIPELINE",
                "RoleArn": {
                    "Ref": "MediaLiveRoleArn"
                },
                "EncoderSettings": {
                    "AudioDescriptions": [
                        {
                            "Name": "audio_1",
                            "LanguageCodeControl": "FOLLOW_INPUT",
                            "AudioTypeControl": "FOLLOW_INPUT"
                        }
                    ],
                    "OutputGroups": [
                        {
                            "OutputGroupSettings": {
                                "MediaPackageGroupSettings": {
                                    "Destination": {
                                        "DestinationRefId": "destination1"
                                    }
                                }
                            },
                            "Outputs": [
                                {
                                    "AudioDescriptionNames": [
                                        "audio_1"
                                    ],
                                    "CaptionDescriptionNames": [],
                                    "OutputName": "EMPOutput1",
                                    "OutputSettings": {
                                        "MediaPackageOutputSettings": {}
                                    },
                                    "VideoDescriptionName": "video_1"
                                }
                            ]
                        }
                    ],
                    "VideoDescriptions": [
                        {
                            "CodecSettings": {
                                "H264Settings": {
                                    "AdaptiveQuantization": "MEDIUM",
                                    "AfdSignaling": "NONE",
                                    "ColorMetadata": "INSERT",
                                    "EntropyEncoding": "CABAC",
                                    "FlickerAq": "ENABLED",
                                    "ForceFieldPictures": "DISABLED",
                                    "FramerateControl": "SPECIFIED",
                                    "FramerateDenominator": 1,
                                    "FramerateNumerator": 30,
                                    "GopBReference": "DISABLED",
                                    "GopClosedCadence": 1,
                                    "GopNumBFrames": 2,
                                    "GopSize": 90,
                                    "GopSizeUnits": "FRAMES",
                                    "Level": "H264_LEVEL_AUTO",
                                    "LookAheadRateControl": "MEDIUM",
                                    "NumRefFrames": 1,
                                    "ParControl": "SPECIFIED",
                                    "ParDenominator": 1,
                                    "ParNumerator": 1,
                                    "Profile": "MAIN",
                                    "RateControlMode": "CBR",
                                    "ScanType": "PROGRESSIVE",
                                    "SceneChangeDetect": "ENABLED",
                                    "SpatialAq": "ENABLED",
                                    "SubgopLength": "FIXED",
                                    "Syntax": "DEFAULT",
                                    "TemporalAq": "ENABLED",
                                    "TimecodeInsertion": "DISABLED"
                                }
                            },
                            "Height": 720,
                            "Name": "video_1",
                            "RespondToAfd": "NONE",
                            "ScalingBehavior": "DEFAULT",
                            "Sharpness": 50,
                            "Width": 1280
                        }
                    ],
                    "TimecodeConfig": {
                        "Source": "EMBEDDED"
                    }
                },
                "Destinations": [
                    {
                        "Id": "destination1",
                        "MediaPackageSettings": [
                            {
                                "ChannelId": {
                                    "Fn::Sub": "${AWS::StackName}-Channel"
                                }
                            }
                        ]
                    }
                ],
                "InputAttachments": [
                    {
                        "InputAttachmentName": "MP4Input",
                        "InputId": {
                            "Ref": "MediaLiveInput"
                        },
                        "InputSettings": {
                            "SourceEndBehavior": "LOOP"
                        }
                    }
                ]
            }
        }
    },
    "Parameters": {
        "MediaLiveRoleArn": {
            "Description": "ARN of the IAM role that MediaLive is allowed to assume in order to perform the operations on the resources specified in the policies.",
            "Type": "String"
        }
    },
    "Outputs": {
        "LiveStreamUrl": {
            "Description": "LiveStream Playback URL",
            "Value": {
                "Fn::Sub": "${MediaPackageEndpoint.Url}"
            }
        }
    }
}

The MediaLive channel defined here is almost identical to the one presented in the earlier blog. So we only note the significant differences.

Under the EncoderSettings property:

  • The OutputGroups defines a MediaPackageGroupSettings since the destination is MediaPackage.
  • The VideoDescriptionName used in the Outputs settings must match the VideoDescriptions section’s Name.
  • The AudioDescriptions name used in the Outputs section must match the AudioDescriptions section’s Name.
  • The DestinationRefId in MediaPackageGroupSettings must match the Id in Destinations.

The Destination defines a MediaPackageSettings configuration, which only requires the MediaPackage channel Id. Thanks to the integration between MediaLive and MediaPackage, you don’t need to specify the individual inputs in MediaPackage.

In the InputAttachments, the Ref function is used to pass along the Input Id that is generated when the MP4 input is created at runtime. The input is looped by setting the SourceEndBehavior to LOOP, otherwise MediaLive stops processing any video as soon as it gets to the end of the file.

The Outputs section is not required, but it allows you to display certain values created by the stack after deployment. This section gives the endpoint URL path needed to watch the video stream. This is the same HLSEndpoint URL you see in the MediaPackage channel on the AWS Management Console once the channel and endpoint have been created.

At this point, if you want to see this simple live streaming workflow in action, deploy the sample CloudFormation template in your own account. Once deployed, you should see the following resources created in your account:

 

 

MediaPackage channel created by template

MediaPackage channel created by template

 

MediaPackage endpoint created by template

MediaPackage endpoint created by template

 

MediaLive input created by template

MediaLive input created by template

 

Verify that all the settings are as expected, and when you’re ready, start the MediaLive channel. To play back the stream, go to the Outputs section of your CloudFormation stack, copy the stream URL, and paste that URL on your Safari browser, or any other HLS player of your choice. You may also go to the MediaPackage on the AWS Management Console to preview the endpoint’s playback.

Outputs tab of the deployed CloudFormation template

Outputs tab of the deployed CloudFormation template

In production, you don’t access or share that MediaPackage endpoint URL directly; instead use a content distribution network (CDN) like Amazon CloudFront. CloudFormation has support for CloudFront, so the template provided here can be extended to accommodate that if needed.

When you finish testing the template, stop the MediaLive Channel, then delete the stack from the CloudFormation console. This removes all resources created by CloudFormation and avoid incurring any unintended charges.

This blog introduced how to use the native MediaPackage CloudFormation resources in your live streaming automation. We hope it encourages you to make further customizations to the template presented here, or write your own.

Resources: