RTSP to HLS and upload to AWS MediaPackage
Tags: video, ffmpeg, javascript
Published: 2021-12-23 12:47:28

In this blog I will describe how I take an RTSP feed, transcodes to HLS and push HLS to AWS MediaPackage origin for Internet distribution.

In my example I have a [TP-Link Tapo C200](https://www.tp-link.com/se/home-networking/cloud-camera/tapo-c200/) camera which is a relatively cheap home security wifi camera. The audio and video stream from the camera is accessible using the RTSP transport protocol.

![TP-Link Tapo C200 camera](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/09swabw86vsoj19kndlh.png)

You configure the camera with the Tapo app and to enable access to the RTSP stream you need to remove the microSD card and set a camera account username and password. It is available under the Advanced settings.
Obtain the IP address to the camera under the camera settings. In this example we will assume it is `10.0.0.4` and the RTSP address is then `rtsp://<username>:<password>@10.0.0.4/stream1` for the HQ stream.

## Transcode and generate HLS

Once you have the stream you need to transcode it to HLS and in this example we are using [ffmpeg](https://ffmpeg.org) and will create 3 variants (1080, 720 and 360).

```
ffmpeg -fflags nobuffer -rtsp_transport tcp \
  -i rtsp://<username>:<password>@10.0.0.4/stream1 \
  -max_muxing_queue_size 1024 \
  -filter_complex "[0:v]split=3[v1][v2][v3];[v1]copy[v1out];[v2]scale=w=1280:h=720[v2out];[v3]scale=w=640:h=360[v3out]" \
  -map [v1out] -c:v:0 libx264 -x264-params "nal-hrd=cbr:force-cfr=1" \
    -b:v:0 5M -maxrate:v:0 5M -minrate:v:0 5M -bufsize:v:0 10M \
    -preset ultrafast -g 48 -sc_threshold 0 -keyint_min 48 \
  -map [v2out] -c:v:1 libx264 -x264-params "nal-hrd=cbr:force-cfr=1" \
    -b:v:1 3M -maxrate:v:1 3M -minrate:v:1 3M -bufsize:v:1 3M \
    -preset ultrafast -g 48 -sc_threshold 0 -keyint_min 48 \
  -map [v3out] -c:v:2 libx264 -x264-params "nal-hrd=cbr:force-cfr=1" \
    -b:v:2 1M -maxrate:v:2 1M -minrate:v:2 1M -bufsize:v:2 1M \
    -preset ultrafast -g 48 -sc_threshold 0 -keyint_min 48 \
  -map a:0 -c:a:0 aac -b:a:0 256k -ar 48000 -ac 2 \
  -map a:0 -c:a:1 aac -b:a:1 128k -ar 48000 -ac 2 \
  -map a:0 -c:a:2 aac -b:a:2 128k -ar 48000 -ac 2 \
  -f hls -hls_time 10 -hls_flags "independent_segments+delete_segments" \
    -hls_segment_type mpegts \
    -hls_segment_filename /media/hls/master_%v_%02d.ts \
    -hls_list_size 6 -master_pl_name master.m3u8 \
    -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2" /media/hls/master_%v.m3u8    
```

We will have ffmpeg to write the output HLS to disk and to be able to serve the HLS to video players we will setup an HTTP server.

## Serve HLS using NodeJS and Fastify

To serve the HLS we will create a NodeJS service based on [fastify](https://www.fastify.io).

```javascript
const server = fastify();
server.register(require("fastify-static"), {
  root: "/media/hls", // this is where ffmpeg outputs the HLS
  prefix: "/",
});
server.listen(8000);
```

The HLS will then be available at `http://localhost:8000/master.m3u8`


## Upload HLS to AWS MediaPackage

To make the HLS available for streaming over Internet we will upload the HLS to AWS MediaPackage origin service. Follow the [instructions](https://docs.aws.amazon.com/mediapackage/latest/ug/live-content.html) on how to configure it for delivering live content.

We will use the [@eyevinn/hls-pull-push](https://www.npmjs.com/package/@eyevinn/hls-pull-push) NPM library to upload it to an AWS MediaPackage ingest endpoint. This library provides a service that pulls HLS from an endpoint and then push the HLS to an origin, and an output plugin for AWS MediaPackage is available.

Create an instance and register the MediaPackage output plugin.

```javascript
const { HLSPullPush, MediaPackageOutput } = require("@eyevinn/hls-pull-push");

const pullPushService = new HLSPullPush();
const outputPlugin = new MediaPackageOutput();
pullPushService.registerPlugin("mediapackage", outputPlugin);
```

We need to wait for the HLS to be available before we can start to pull and push the HLS.

```javascript
waitForHlsIsAvailable() {
  return new Promise((resolve, reject) => {
    let t = setInterval(() => {
      const file = "/media/hls/master.m3u8";
      fs.access(file, fs.constants.F_OK, (err) => {
        if (!err) {
          clearInterval(t);
          resolve();
        }
      });
    }, 1000);
  });
}
```

Once we have the HLS available we can start the pull-push service and start a fetcher. A fetcher is the process that pulls and push the HLS.

```javascript
await waitForHlsIsAvailable();
pullPushService.listen(8001);
const outputDest = outputPlugin.createOutputDestination({
  ingestUrls: [{
    url: <mediapackage-url>,
    username: <mediapackage-username>,
    password: <mediapackage-password>,
  }], pullPushService.getLogger());

const source = new URL("http://localhost:8000/master.m3u8");
const sessionId = pullPushService.startFetcher({
  name: "rtsp",
  url: source.href,
  destPlugin: outputDest,
  destPluginName: "mediapackage"
});
outputDest.attachSessionId(sessionId);
```

What we now have running can be illustrated with the diagram below.

![Diagram of RTSP to HLS and upload to AWS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xdy5a46osoian7c4rxnu.png)

## Docker Container

If you don't want to build this from scratch you can use our `rtsphls` [Docker container](https://hub.docker.com/r/eyevinntechnology/rtsphls) instead.

```
docker run --rm -e RTSP=rtsp://<username>:<password>@10.0.0.4/stream1 \ 
  -e MEDIAPACKAGE_URL=<ingesturl> \
  -e MEDIAPACKAGE_USERNAME=<username> \
  -e MEDIAPACKAGE_PASSWORD=<password> \
  -p 8000:8000 eyevinntechnology/rtsphls
```

The source code is available on our [GitHub](https://github.com/Eyevinn/rtsp-hls-push).

A live example with a feed from the view from our office is available [here](https://web.player.eyevinn.technology/?manifest=https%3A%2F%2Fd2fz24s2fts31b.cloudfront.net%2Fout%2Fv1%2F6484d7c664924b77893f9b4f63080e5d%2Fmanifest.m3u8).

## About Eyevinn Technology
Eyevinn Technology is an independent consultant firm specialized in video and streaming. Independent in a way that we are not commercially tied to any platform or technology vendor.

At Eyevinn, every software developer consultant has a dedicated budget reserved for open source development and contribution to the open source community. This give us room for innovation, team building and personal competence development. And also gives us as a company a way to contribute back to the open source community.

Want to know more about Eyevinn and how it is to work here. Contact us at work@eyevinn.se!