This guide will show you how to generate RFC compliant HLS to ensure you are playing smoothly on every Apple operating system and device
Alongside MPEG-DASH, HLS, is one of the most popular streaming formats out there, and because it was initially created independently by Apple for their own environment, it is natively supported on every Apple device and OS that they have produced so far. Nevertheless, despite the fact that all the components of their streaming solution come from the same company, there are a number of minimum requirements and limitations that you will need to be aware of if you want to provide broad support across all Apple powered platforms such as iOS, tvOS or macOS.
Minimum requirements
If you want or need to support older Apple devices, you should also keep their specific capabilities in mind, so your customers are still able to consume your content on their device. Apple’s authoring requirements are split into general requirements, which apply to all platforms. However, some of those requirements, are overruled by specific requirements for tvOS, iOS or macOS. The whole list consists of more than 100 “required” or “recommended” points, which covers every aspect of the video workflow from creating HLS content, to its delivery and security aspects.
For the sake of simplicity we will focus on the requirements that you are most likely to need to be aware of in order to provide proper HLS content to your customers.
General requirements
Video encoding
- Required video codec: H264/AVC
- Profile and Level must not exceed High Profile, Level 4.2
- Keyframe interval should be 2 seconds
- Each video segment must start with an I-Frame
- De-interlaced content only
- VoD: The measured average segment bitrate must be within 10% of the AVERAGE-BANDWIDTH attribute (required in the master playlist)
- VoD: The measured peak bitrate must be within 10% of the BANDWIDTH attribute
- All video renditions should have the same aspect ratios
- A segment duration of 6 seconds should be used
Audio encoding
- You must provide Stereo audio
- AAC should be used for audio stream with a bitrate > 64 kbps
Storage/Delivery
- Provide correct content types:
- Playlists: application/x-mpegURL or vnd.apple.mpegURL
- Media Segments: video/MP2T
- Playlists must be delivered using gzip content-encoding
A hint for iOS Apps
- Ignoring one of the following limits could result in a denial of your app to be distributed through the Apple store
- If you are using videos in your app, which exceeds either 10 minutes duration, or 5 MB per 5 minute period, you must use HTTP Live Streaming.
If this video content is delivered over cellular networks, you are required to provide at least one stream with a bandwidth 64kbps or lower (this can be an audio-only stream or an audio stream with a still image)
Is my content compliant?
To make your life easier, you can check whether or not your HLS content complies with Apple’s requirements and recommendations with their validation tool: “mediastreamvalidator”. It can simply parse your HLS playlist and check if it is compliant with the HTTP Live Streaming specification, or it can also download all the segments of your content, and determine their duration, average and maximum bitrate, and various other properties. The latter is the default setting.
You can download this tool here in the developer area of Apple. In order to use it, an Apple developer account is required, as well as an Apple device, which is running Apple’s own OS (OS X, macOS).
How does the validation work?
mediastreamvalidator <hlsContentURL> will load the playlist, and its variant playlists, if present, and validate them against the HTTP Live Streaming specification. Then, the validator checks, if all segment URI’s are actually reachable and starts to download them. During that phase, it also checks the provided content type of the content, and throws an error message, if it is missing or invalid.
This enables the mediastreamvalidator to measure the peak and average bitrate of a variant stream, to compare the results with the values of the BANDWIDTH, and AVERAGE-BANDWIDTH attribute, which have to be stated in the master playlist. For Video on Demand content, those values are allowed to be 10% off the provided value stated in the master playlist. Otherwise a “Must Fix” error message is thrown (see picture below).
If you just want to check if your playlists are valid, mediastreamvalidator –parse-playlist-only <hlsContentURL> is the right command for you. It will load the playlist files only, and validate them as described in the beginning.
If everything is fine, you will just see the validation results for each variant stream of your content.
Recommended settings
Those renditions are recommended by Apple. As mentioned before, each rendition should have the same aspect ratio, otherwise the customer would see changes in the resolution, when the player adapts the quality of the stream, which lowers the users Quality of Experience (QoE). Further it is also important, that you setup up your encoding in a way, so that the resulting bitrate of your content, actually provides the average bit rate and peak bitrate, mentioned in your master playlist.
Video average bit rate (kb/s) | Resolution | Frame rate |
---|---|---|
145 | 416 x 234 | ≤ 30 fps |
365 | 480 x 270 | ≤ 30 fps |
730 | 640 x 360 | ≤ 30 fps |
1100 | 768 x 432 | ≤ 30 fps |
2000 | 960 x 540 | same as source |
3000 | 1280 x 720 | same as source |
4500 | same as source | same as source |
6000 | same as source | same as source |
7800 | same as source | same as source |
Encoding example
So, let’s create an HLS content, which will pass the mediastreamvalidator with flying colours. The following example is using our Bitmovin PHP API Client, which is available on Github, so is the example as well.
1. Get the Bitmovin PHP API Client
You can either download it from Github or install it using composer. Please see the API client’s repository for more information about the setup.
2. Initialize the Bitmovin API Client
In order to use the API client, we have to initialize it first.
$client = new \Bitmovin\BitmovinClient('INSERT YOUR API KEY HERE');
That’s it^^. The client is now ready to use. Now, we can start preparing the configurations for your input source, output destination, and encoding, containing all the renditions, we want to create for the compliant HLS content.
3. Create a input configuration
For the sake of simplicity we are using an HTTP(S) input, although many other input sources such as AWS S3, Google Cloud Storage, Microsoft Azure, Aspera, and (S)FTP are also supported.
$inputURL = 'http://example.com/path/to/your/movie.mp4'; $input = new HttpInput($inputURL);
4. Create an output configuration
Here you define either to directly transfer your encoding results to your preferred storage type (AWS S3, Google Cloud Storage, Microsoft Azure, (S)FTP), or you can store the encoding on your own Bitmovin storage. Direct transfer as well as storage are features of our new Bitmovin API. The first allows you to keep the turnaround time of your encoding very low, so your encoded content becomes available as quickly as possible on your own storage. The latter enables you to keep a backup of your encoding, transfer them later on to another storage and so on. If you want to do both, you can do that as well now :). As AWS S3 is a very common cloud storage, we will use it for this example.
//CREATE AN OUTPUT $gcsAccessKey = 'YOUR-ACCESS-KEY'; $gcsSecretKey = 'YOUR-SECRET-KEY'; $gcsBucketName = 'YOUR-BUCKETNAME'; $gcsPrefix = 'PATH/TO/YOUR/OUTPUT-DESTINATION/'; $gcsOutput = new GcsOutput($gcsAccessKey, $gcsSecretKey, $gcsBucketName, $gcsPrefix);
5. Create an encoding profile configuration
An encoding profile configuration contains all the encoding related configurations for video/audio renditions as well as the encoding environment itself.
Create encoding profile configuration:
$encodingProfile = new EncodingProfileConfig(); $encodingProfile->name = 'HLS compliant content #1'; $encodingProfile->cloudRegion = CloudRegion::GOOGLE_EUROPE_WEST_1; $rate = 30;//framerate of your input file $keyFrameInt = 2; //key frame interval in seconds $segmentLength = 6; //segment duration in seconds
Create video quality configuration
I have created a little helper function for creating the recommended video stream configurations provided by Apple. It basically just creates an H264VideoStreamConfig and sets its properties. In order to achieve the recommended key frame interval of two seconds, we just have to provide the maxGop property. Its value describes the maximum size in frames of a Group Of Pictures. If the maximum size is reached, our encoding service will to create a new GOP, which starts with an key frame. Therefore, we need to know the frame rate of the input file and the interval we want to achieve. If you multiply those values, you get the GOP size, our encoding service has to use (see below).
function createH264VideoStreamConfig($input, $profile, $bitrate, $width, $height = null, $rate = null, $keyFrameInterval = 2) { $videoStreamConfig = new H264VideoStreamConfig(); $videoStreamConfig->input = $input; $videoStreamConfig->width = $width; $videoStreamConfig->height = $height; $videoStreamConfig->bitrate = $bitrate; $videoStreamConfig->rate = $rate; $videoStreamConfig->profile = $profile; if (!is_null($rate)) { $videoStreamConfig->maxGop = $rate * $keyFrameInterval; } return $videoStreamConfig; }
With this little helper it is easy to setup the recommended renditions and their required properties:
$encodingProfile->videoStreamConfigs[] = createH264VideoStreamConfig($httpInput, H264Profile::HIGH, 4800000, 1920, null, $rate, $keyFrameInt); $encodingProfile->videoStreamConfigs[] = createH264VideoStreamConfig($httpInput, H264Profile::HIGH, 3000000, 1280, null, $rate, $keyFrameInt); $encodingProfile->videoStreamConfigs[] = createH264VideoStreamConfig($httpInput, H264Profile::MAIN, 2000000, 960, null, $rate, $keyFrameInt); $encodingProfile->videoStreamConfigs[] = createH264VideoStreamConfig($httpInput, H264Profile::MAIN, 1100000, 768, null, $rate, $keyFrameInt); $encodingProfile->videoStreamConfigs[] = createH264VideoStreamConfig($httpInput, H264Profile::MAIN, 730000, 640, null, $rate, $keyFrameInt); $encodingProfile->videoStreamConfigs[] = createH264VideoStreamConfig($httpInput, H264Profile::BASELINE, 365000, 480, null, $rate, $keyFrameInt); $encodingProfile->videoStreamConfigs[] = createH264VideoStreamConfig($httpInput, H264Profile::BASELINE, 145000, 416, null, $rate, $keyFrameInt);
Create audio quality configuration
$asc160 = new AudioStreamConfig(); $asc160->input = $httpInput; $asc160->bitrate = 160000; $asc160->rate = 48000; $asc160->name = 'English'; $asc160->lang = 'en'; $asc160->position = 1; $encodingProfile->audioStreamConfigs[] = $asc160;
An AudioStreamConfig results in an audio stream, which is using the AAC codec. As our target bitrate is greater than 64kbps, we fulfill this recommendation as well.
Create Encoding configuration
This configuration object acts as a container for all the previous configurations from above and will be passed to the BitmovinClient in order to start the encoding.
// CREATE JOB CONFIG $jobConfig = new JobConfig(); // ASSIGN OUTPUT $jobConfig->output = $gcsOutput; // ASSIGN ENCODING PROFILES TO JOB $jobConfig->encodingProfile = $encodingProfile; // ENABLE HLS OUTPUT $hlsOutput = new HlsOutputFormat(); $hlsOutput->segmentLength = 6; $jobConfig->outputFormat[] = $hlsOutput;
Lets check, if we met all the requirements/recommendations:
- Required video codec: H264/AVC
We are using the H264VideoStreamConfig, whose name already indicate the usage of the H264/AVC codec. - Profile and Level must not exceed High Profile, Level 4.2
We are using the HIGH profile for the HD and FullHD renditions only and their bitrate, framerate and resolution don’t exceed the requirements for level 4.2 - Keyframe interval should be 2 seconds
We provided maxGop which causes the encoder provide a key frame every two seconds - Each video segment must start with an I-Frame
Our encoding service, is doing that by default, as this is crucial to do seamless transitions between renditions during playback. - De-interlaced content only
Our test content isn’t interlaced, therefore there is no need for that, however it is support by our encoding service as well. - All video renditions should have the same aspect ratios
Yes, we only provide a value for width, our encoding service calculate the respective height based on the aspect ratio of the input file - A segment duration of 6 seconds should be used
Yes, as we provide a segment length of 6 seconds in our encoding configuration, however this is just a recommendation, so using the default value of 4 seconds is fine as well.
Start the encoding
Finally, we can start the encoding. runJobAndWaitForCompletion() will return as soon as the encoding is finished and transferred/stored successfully.
$client->runJobAndWaitForCompletion($jobConfig);
As you can see creating HLS compliant content is not too hard as long as you provide correct encoding settings. In this case, our API Client also takes of the creation of the HLS master and variant playlists. However, you also have the possibility to create the HLS playlists exactly as you need them, with our API as well. How this can be done, will be part of a different blog post 😉
Not long ago, Apple announced the support to use fragmented MP4 content for HLS, which would eliminate the need to encode your content explicitly for devices that supports HLS only. This being said, you could reduce your storage costs by more than 50%, and your CDN costs as well, as you could use one single content type for all available platforms. Read more about it here in our Blog Post about fMP4 and how you can create it with our Bitmovin API.