Zencoder: Creating MP4 and HLS Outputs Together

Product(s)
Zencoder
Role(s)
API Developer
API(s)
Zencoder API

This topic explains transmuxing, a process that allows you to encode your videos to MP4 and then also repackage those as HLS outputs.

Overview

With Zencoder you can encode your videos to MP4 and then also repackage those as HLS outputs, through a process we call "transmuxing" (rather than transcoding). Transmuxing will repackage existing MP4 videos into the MPEG TS segments necessary for HTTP Live Streaming (HLS), without having to re-encode the video files. Using dependent outputs you can create your H.264 files plus the segmented files all in a single job, producing faster turnaround at lower cost; transmuxed outputs are charged at 1/4 of the cost of encoding. The resulting job creates 6 outputs - 2 charged at your account's regular rate, 3 transmuxed files at 1/4 of the encoding cost, and 1 playlist, which is free.

For this, you'll create a single job with two main sets of outputs (plus the playlists). The first set of outputs will be normal H.264/AAC MP4 files with a few special settings to allow them to work as source files for HLS outputs. The second set of outputs will use those MP4 files as "source" inputs, transmuxing their content to HLS outputs. Since these are dependent outputs, they will wait until their corresponding source finishes before being scheduled to process.

The key part of the process is using source, a new option in V2 of the Zencoder API. Source tells an output to use the file created by another output on the job for processing, instead of the input file. In this situation, Zencoder will create H.264 files based on the input file at requested bitrates. As each of those outputs finishes, a segmented version will then be created from the output.

The H.264 Outputs

We'll start by creating 2 H.264 files, targeting high and low bitrates.

{
    "label": "low",
    "format": "mp4",
    "video_bitrate": 200,
    "decoder_bitrate_cap": 300,
    "decoder_buffer_size": 1200,
    "audio_sample_rate": 44100,
    "height": "288",
    "url": "s3://example-bucket/low.mp4",
    "h264_reference_frames": 1,
    "forced_keyframe_rate": "0.1",
    "audio_bitrate": 56,
    "decimate": 2
},
{
    "label": "high",
    "format": "mp4",
    "video_bitrate": 1000,
    "decoder_bitrate_cap": 1500,
    "decoder_buffer_size": 6000,
    "audio_sample_rate": 44100,
    "height": "432",
    "url": "s3://example-bucket/high.mp4",
    "h264_reference_frames": "auto",
    "h264_profile": "main",
    "forced_keyframe_rate": "0.1",
    "audio_bitrate": 56
}

The resulting files from these outputs are capable of being played on a wide variety of devices. Each targets a different bitrate and resolution, so users can be sent the appropriate file. Each is also appropriate for segmenting for HTTP Live Streaming and serving as an adaptive bitrate stream.

A few options in the request above to note:

  • forced_keyframe_rate to 0.1. This forces the video to have a keyframe every 10 seconds. The segmented files will be 10 seconds long, so this ensures will ensure that there each segment will start with a keyframe.
  • decoder_bitrate_cap is set to 1.5x the target bitrate of the file. decoder_buffer_size is set to 3.5x to 5x the target bitrate of the file. These settings will help keep a consistent bitrate throughout the file, so that the segmented segments won't vary too much in size and bitrate.

Now that the H.264 files have been created, additional outputs can be added to the request to create HTTP Live Streaming segmented files from the source, without needing to do additional encoding.

Segmented Outputs

Each of the H.264 video and the audio outputs above have labels in their API options. These labels can be used in conjunction with the source option to tell Zencoder to use the video created by the output with the given label, rather than the input file. Since the H.264 files are already at good settings for HLS, no additional encoding is necessary.

{
    "source": "low",
    "format": "ts",
    "copy_audio": "true",
    "copy_video": "true",
    "url": "s3://example-bucket/hls-low/hls-low.m3u8",
    "label": "hls-low",
    "type": "segmented"
}
{
    "source": "high",
    "format": "ts",
    "copy_audio": "true",
    "copy_video": "true",
    "url": "s3://example-bucket/hls-high/hls-high.m3u8",
    "label": "hls-high",
    "type": "segmented"
}
{
    "streams": [
    {
        "path": "hls-low/hls-low.m3u8",
        "bandwidth": 256
    },
    {
        "path": "hls-high/hls-high.m3u8",
        "bandwidth": 1056
    }
    ],
    "type": "playlist",
    "url": "s3://example-bucket/playlist.m3u8"
}

The resulting files from these outputs are segmented outputs, targeting different bandwidths, plus a playlist file for the playback device to know the available streams.

Each segmented output includes the source, and specifies one of the outputs listed earlier. They also include the copy_audio and copy_video options, which tell Zencoder to use the audio and video streams from the source file and repackage them in the new container, rather than transcode them. Finally, the outputs also specify a type of segmented and a format of ts, plus a url with an extension of .m3u8, so that Zencoder knows to create segmented HLS files.

Finally, there is also a playlist generated that references each of the segmented outputs. Note that the first file referenced in the playlist is the low bitrate video; the device playing the video will generally load the first stream listed in the playlist initially, and switch to other streams as necessary. This means that the first 10 seconds of playback will use this stream, after which the device will have enough information to select the appropriate stream to play.

NOTES

  • The HLS playlist generation currently uses no information from the other outputs in the job, but is simply a way to easily generate a correctly formatted adaptive-bitrate playlist and have it uploaded along with the other files.
  • While iOS devices will play the adaptive bitrate playlist created, VLC won't, due to errors in handling relative URLs. It will look for the segment files in the same directory as the playlist file, rather than the directory of the manifest file.

Full Request

Now that we've gone over each of the major output sections, let's pull it together in to a single API request.

{
    "input": "s3://zencodertesting/test.mov",
    "outputs": [
    {
        "label": "low",
        "format": "mp4",
        "video_bitrate": 200,
        "decoder_bitrate_cap": 300,
        "decoder_buffer_size": 1200,
        "audio_sample_rate": 44100,
        "height": "288",
        "url": "s3://example-bucket/low.mp4",
        "h264_reference_frames": 1,
        "forced_keyframe_rate": "0.1",
        "audio_bitrate": 56,
        "decimate": 2
    },
    {
        "label": "high",
        "format": "mp4",
        "video_bitrate": 1000,
        "decoder_bitrate_cap": 1500,
        "decoder_buffer_size": 6000,
        "audio_sample_rate": 44100,
        "height": "432",
        "url": "s3://example-bucket/high.mp4",
        "h264_reference_frames": "auto",
        "h264_profile": "main",
        "forced_keyframe_rate": "0.1",
        "audio_bitrate": 56
    },
    {
        "source": "low",
        "format": "ts",
        "copy_audio": "true",
        "copy_video": "true",
        "url": "s3://example-bucket/hls-low/hls-low.m3u8",
        "label": "hls-low",
        "type": "segmented"
    }
    {
        "source": "high",
        "format": "ts",
        "copy_audio": "true",
        "copy_video": "true",
        "url": "s3://example-bucket/hls-high/hls-high.m3u8",
        "label": "hls-high",
        "type": "segmented"
    }
    {
        "streams": [
        {
            "path": "hls-low/hls-low.m3u8",
            "bandwidth": 256
        },
        {
            "path": "hls-high/hls-high.m3u8",
            "bandwidth": 1056
        }
        ],
        "type": "playlist",
        "url": "s3://example-bucket/playlist.m3u8"
    }
    ]
}