AWS for M&E Blog

Enable Microsoft Smooth Streaming to resource-constrained devices using Lambda@Edge and AWS Elemental MediaPackage

AWS Elemental MediaPackage is a highly scalable video origination and just-in-time packaging service. Using either a live HLS input from an encoder such as AWS Elemental MediaLive or media files from Amazon Simple Storage Service (Amazon S3), the service packages video content with optional encryption to HLS, DASH-ISO, CMAF and Microsoft Smooth (MSS) formats.

The majority of new televisions, games consoles, set-top boxes, and other media devices consume one or more of HLS, DASH-ISO or CMAF formats. However, a large number of legacy devices still consume MSS. With older devices, memory and processing power can be highly constrained, making long timelines on live streams or VODs impractical as lengthy MSS manifests can lead to device playback failure. This blog post explains how you can move your workflow to MediaPackage and still enable the use of longer manifests with Lambda@Edge on legacy, resource-constrained MSS devices.

Leveraging Amazon CloudFront and Lambda@Edge

The MSS v2.2 specification allows the option of using a FragmentRepeat field on segments that share the same attributes and length to greatly reduce manifest length. Using Lambda@Edge and Amazon CloudFront, we can implement the repeat tag to provide an effective method to enable these devices to support long catch-up windows.

This following is an excerpt of a typical MSS manifest from MediaPackage.

<StreamIndex Type="video" Name="video" Subtype="" Chunks="60" TimeScale="10000000" Url="Events(203_1626076726)/QualityLevels({bitrate})/Fragments(v={start time})" QualityLevels="4">
    <QualityLevel Index="0" Bitrate="5000000" CodecPrivateData="0000000127640032AC72180780227E5C0440000003004000000F38180004C4B40002625BBDEE03E1108B340000000128FBAF2C" FourCC="H264" MaxWidth="1920" MaxHeight="1080"/>
    <c d="20000000" t="11201783000000"/>
    <c d="20000000"/>
    <c d="20000000"/>
    <c d="20000000"/>
    <c d="20000000"/>
    <c d="20000000"/>
    <c d="20000000"/>
    <c d="20000000"/>
    <c d="20000000"/>
    <c d="20000000"/>
    <c d="20000000"/>
    <c d="20000000"/>
    <c d="20000000"/>
    <c d="20000000"/>
    <c d="20000000"/>
    <c d="20000000"/>
    <c d="20000000"/>
    <c d="20000000"/>
    <c d="20000000"/>
    <c d="20000000"/>
     etc....
  </StreamIndex>

The same manifest excerpt with the repeat tag applied results in a much more byte-efficient method for the players to consume.

<StreamIndex Type="video" Name="video" Subtype="" Chunks="60" TimeScale="10000000" Url="Events(203_1626076726)/QualityLevels({bitrate})/Fragments(v={start time})" QualityLevels="4">
    <QualityLevel Index="0" Bitrate="5000000" CodecPrivateData="0000000127640032AC72180780227E5C0440000003004000000F38180004C4B40002625BBDEE03E1108B340000000128FBAF2C" FourCC="H264" MaxWidth="1920" MaxHeight="1080"/>
    <c d="20000000" t="11201783000000"/>
        <c d="20000000" t="11201783000000" r="59"/>
  </StreamIndex>

To demonstrate, we will build a simple live workflow using MediaLive, MediaPackage and Amazon CloudFront.

Create a live workflow

First, create a MediaPackage channel and ensure Create a CloudFront distribution for this channel is selected:

Screen capture of Amazon CloudFront radio select buttons in MediaPackage

Next, create a simple MediaLive channel that comprises one video rendition and one audio rendition, egressing to a MediaPackage output group, with these settings:

  • Codec Settings = h264
  • Framerate Control = Specified
  • Framerate Numerator = 25
  • Framerate Denominator = 1
  • GOP Size = 48
  • GOP Size Units = Frames

A 48 frame GOP is used here to create audio and video segments that are of equal length, which will result in the most efficient manifest reduction.

Select the MediaPackage channel created earlier as the output destination. Make sure you have configured the width, height, bitrate, and aspect ratio settings for your video rendition, and attached an input source to the channel, all of which are required before being able to save the channel configuration.

In the MediaPackage channel, create a Microsoft Smooth endpoint, keeping the default 2 second segment length and manifest window of 300 seconds (5 minutes). Select the Startover window tick box and set the value to 3600 seconds (1 hour).

Screen capture of MediaPackage endpoint configuration settings

Start the MediaLive channel and wait for it to report a running state. Upon cURLing the CloudFront endpoint, which can be obtained from the MediaPackage endpoint tab, the resulting StreamIndex for audio and video should look like this.

  <StreamIndex Type="video" Name="video" Subtype="" Chunks="156" TimeScale="10000000" Url="Events(203_1637581544)/QualityLevels({bitrate})/Fragments(v={start time})" QualityLevels="1">
    <QualityLevel Index="0" Bitrate="1000000" CodecPrivateData="00000001274D401EB91816824D80A102020240000003004000000CB818003D090007A13BDEE03E1108A70000000128FEBC80" FourCC="H264" MaxWidth="720" MaxHeight="576"/>
    <c d="19200000" t="12944600000"/>
    <c d="19200000"/>
    <c d="19200000"/>
    <c d="19200000"/> 
    <c d="19200000"/>

    etc.

  <StreamIndex Type="audio" Name="audio_1" Language="und" Subtype="" Chunks="156" TimeScale="10000000" Url="Events(203_1637581544)/QualityLevels({bitrate})/Fragments(a_2_0={start time})">
    <QualityLevel Index="0" Bitrate="192000" CodecPrivateData="1190" FourCC="AACL" AudioTag="255" Channels="2" SamplingRate="48000" BitsPerSample="16" PacketSize="4"/>
    <c d="19200000" t="12944693333"/>
    <c d="19200000"/>
    <c d="19200000"/>
    <c d="19200000"/>
    <c d="19200000"/>

    etc. 

Add Lambda@Edge functionality

We will now add Lambda@Edge functionality to the workflow we have created. First, select the CloudFront distribution that was created by MediaPackage under the behaviors tab, click to edit the first one in the list, index.ism/*.  Change the path pattern to  */index.ism/Manifest.

Screen capture of configuring behavior settings in CloudFront

Before saving the changes, make sure that under Additional Settings the Smooth Streaming option is set to No, as origination will be from MediaPackage, and not from S3. Once saved, the changes will take a few minutes for CloudFront to deploy.

The Lambda@Edge authoring environment and toolset UI is available in the US East (N. Virginia) region. Create a Lambda function in this region, selecting Author from Scratch with Python 3.x as the runtime.

In the code tab, copy and paste the following Python code before using Deploy to save.

import urllib3
import os
import sys
#Function to obtain segment duration
def obtain_duration(line):
    dur_start_pos = line.find(' d=\"')
    dur_end_pos = line.find('\"', dur_start_pos+4)
    duration_str = line[dur_start_pos:dur_end_pos+1]
    return duration_str
#Function to take MSS manifest and add r= tags
def split_and_shrink(manifest):
    output=''
    #flag if we are inside a StreamIndex tag
    compacting = False
    original_t_start = ''
    duration_str = ''
    count = 0
    for line in manifest.splitlines():
        if compacting:
            if (line.find(duration_str) == -1):
                compacting = False
                #Keep track of timestamp
                end_pos = original_t_start.find("/>")
                temp = original_t_start[:end_pos]
                #Only add repeat if >1 repeated segments
                if count>0 :
                    temp += ' r=\"' + str(count) + '\"'
                output += temp + '/>\n'
                count = 0
                #Reached end of StreamIndex tag
                if line.find('</StreamIndex>') >0:
                    output += '  </StreamIndex>\n'
                else:
                    #Handle segment duration change in timeline
                    original_t_start = line
                    duration_str = obtain_duration(line)
                    compacting = True
            else:
                count += 1
        else:
            #Extract new timestamp
            if line.find(' t=\"') >0:
                compacting = True
                original_t_start = line
                duration_str = obtain_duration(line)
            else:
                #Not a line we need to worry about so just add to manifest
                output += line + '\n'                
    return output   
def lambda_handler(event, context):
    http_request = event['Records'][0]['cf']['request']    
    protocol = http_request['origin']['custom']['protocol']
    domain = http_request['origin']['custom']['domainName']
    uri = http_request['uri']
    query = http_request['querystring']
    if query != '':
        query = '?=' + query
    final_to_emp = protocol+'://'+domain+uri+query    
    #Request manifest from MediaPackage
    http = urllib3.PoolManager()
    resp = http.request('GET', final_to_emp,timeout=urllib3.Timeout(connect=2.0, read=10.0),retries=False)
    mod_body = resp.data.decode('utf-8')    
    #Shrink manifest
    mod_body = split_and_shrink(mod_body)

    #Remove headers which CF does not accept in return
    empheaders = resp.headers
    newhdrs = {}
    for item in empheaders:
        if (item.lower() != 'connection') and (item.lower() != 'content-length'):
            newhdrs[item.lower()]=[{'key' : item, 'value' : empheaders[item]}]
    
    response = {
        'status' : 200,
        'body' : mod_body,
        'bodyEncoding' : 'text',
        'headers' : newhdrs
    } 
    return response

The Lambda function uses basic string operators readily available in Python. This produces the fastest executing Lambda with the simplest build effort. The function could also be written using the ElementTree XML library, but the resulting code is slower to execute and results in the XML attributes being written out of order. As the aim is compatibility with legacy players, this is undesirable.

While Lambda will automatically create a role for the function, the service principle trust policy will need to be added to this role before the Lambda can be deployed to CloudFront. Under Configuration, select Permissions and then the Execution Role that was automatically created to open it in AWS Identity and Access Management (IAM). Update the Trust Relationship as described on the Setting IAM permissions and roles for Lambda@Edge page to enable execution and replication to CloudFront.

Select Deploy to Lambda@Edge under the Actions menu. Make sure the CloudFront distribution created earlier by MediaPackage is selected, which will in turn populate the list of available behaviors. Select the Cache behavior created previously, choose Origin Request as the CloudFront event and confirm the intent to deploy to all edge locations:

The reason for the Lambda script requesting the manifest from MediaPackage rather than retrieving it via an Origin Response behavior is to avoid exceeding the 1MB limit that CloudFront imposes on passing through the request body. This limit risks truncating the manifests before they can be processed by the Lambda script.

After a few minutes, the deployment of the Lambda function to CloudFront edge locations will complete. The final workflow is depicted in the following diagram:

Figure 2: The final workflow, complete with Lambda@Edge function deployed to CloudFront

Curling the same CloudFront endpoint as before will now return a version of the same content that is functionally the same, but formulated in a more compact way.

  <StreamIndex Type="video" Name="video" Subtype="" Chunks="156" TimeScale="10000000" Url="Events(203_1637581544)/QualityLevels({bitrate})/Fragments(v={start time})" QualityLevels="1">
    <QualityLevel Index="0" Bitrate="1000000" CodecPrivateData="00000001274D401EB91816824D80A102020240000003004000000CB818003D090007A13BDEE03E1108A70000000128FEBC80" FourCC="H264" MaxWidth="720" MaxHeight="576"/>
    <c d="19200000" t="12944600000" r="149"/>
  </StreamIndex>
  <StreamIndex Type="audio" Name="audio_1" Language="und" Subtype="" Chunks="156" TimeScale="10000000" Url="Events(203_1637581544)/QualityLevels({bitrate})/Fragments(a_2_0={start time})">
    <QualityLevel Index="0" Bitrate="192000" CodecPrivateData="1190" FourCC="AACL" AudioTag="255" Channels="2" SamplingRate="48000" BitsPerSample="16" PacketSize="4"/>
    <c d="19200000" t="12944693333" r="149"/>
  </StreamIndex>

Conclusion

Amazon CloudFront with Lambda@Edge provides the ability to further enhance and customize the functionality of MediaPackage for your specific requirements. This powerful combination allows moving workflows to MediaPackage while continuing to support resource-constrained legacy Microsoft Smooth clients.

Andy Chandler

Andy Chandler

Enterprise Account Engineer based in EMEA, helping customers deliver a best-in-class media experience with AWS Elemental Media Services.