How to build LocalTV Part 3: Encoding and Encryption

This is part 3 in a multi part series about how localtv is setup. If you missed it , you can read part1 and part2 .

This entry will explain how we encode our video for modern devices and retransmission over the internet. I assume that you have followed part 2 and now have an uncompressed MPEG2 stream of tv channels. Our encoder will consume those streams.

Open Source Software

Similar to our use of tvheadend, our goal was to use as little custom software as possible and and keep it all open source. This has two benefits, first it’s free. Second you get to tap into a pool of developers maintaining the software and so not need to pay to make and maintain it yourself. The three key pieces of free software we do use here are Linux to power everything and where all sample commands will run, ffmpeg to encode the video and Apache to actually serve it on the internet.

Encoding with ffmpeg

Ffmpeg is the industry standard for video encoding and conversion. If you use a service that encodes and serves video such as YouTube it is almost certainly using ffmpeg in the backend. It is old, well tested and well maintained. This is what we will use.

HLS

HTTP live stream (aka HLS) is an internet video broadcast standard that Apple invented. While it was made for Apple devices it has become an industry standard. When you view video on the internet (such as YouTube) you are likely using HLS or something similar. The way HLS works is there is a playlist file that lists in order lots of little bits of video. All of this is served over a normal HTTP server like Apache. There are no social protocols involved it’s just files served over HTTP. HLS is the only format the native iOS video player will support.

Tying it all together

Ffmpeg has an option to output HLS files. You could use a different encoder and packager (such as Bento4) but having one is working well for me. HLS allows you to have multiple different streams of different quality for different connections but these examples have just one stream. Having multiple streams just involves duplicating the arguments to ffmpeg with different settings.

What we will do here is:

Mpeg2 -> ffmpeg -> Apache

Software Encoders

The final piece of the ffmpeg puzzle is picking an encoder. You will find two kinds of encoder, software encoders which work on the CPU and hardware encoders that run on the GPU. I’ve found software encoders to to produce superior quality video and are more compatible with streams. They are also easier to start with. In our example, we use a software encoder for Fox. There is something odd about that broadcast in Boston that it makes our hardware encoders fail but the software H264 encoder works fine. The software encoder also encodes closed captions properly none of our hardware encoders do it properly. That is why Fox is currently the only channel with closed caption support. This a basic command that will get a single channel working :

   ffmpeg    -loglevel info -hide_banner -re -y -i  http://<MPEG stream> -err_detect careful \
   -map 0:0 -map 0:1  \
   -vcodec libx264  -sc_threshold 0 -g 48 -keyint_min 48 -c:a copy -ar 48k  \
  -maxrate:v 2000k -bufsize:v 3000k -b:a 192k -crf 20 -fps_mode auto \
  -hls_time 6 \
  -hls_list_size 20 \
  -hls_flags delete_segments \
  -master_pl_name 720.m3u8 \
  -hls_segment_filename fox/%v_%03d.ts fox/%v.m3u8

The one drawback here is -c:a copy which set it to copy the audio stream instead of re-encoding it. In is our command to encode FOX, we use a custom build of ffmpeg that enables the libfdk_aac codec which is not enabled by default for license reasons. This was done to change the existing AC3 audio to AAC to support Android and Roku which can’t play the native AC3 audio used in ATSC broadcasts.

if you run this command you will have a stream in the fox folder. Serve that via Apache and your iPhone will be able directly play a stream of the channel by opening the fox/720.m3u8 file in Safari.

There are two drawbacks to this command however, first is the CPU usage from the software encoder is high. The second, is you will be writing to the fox folder continuously and the disk introduces latency and increases wear and tear on you drive. You want the fox folder above to be on a RAM disk. This way all reading and writing happens entire in RAM. You should see a significant performance boost from this.

sudo mount -t tmpfs -o size=5000m tmpfs /mnt/ramdisk
cd /mnt/ramdisk

Hardware Encoders

As I mentioned above software encoders while easy to get going are not as efficient as hardware encoders. This is one of the benefits of using H264, lots of old hardware has hardware encoders. Even the humble raspberry pi 2 and 3 has h264 hardware encoders. If you are looking for the say that you can use up that box of spare computer parts you have held on for a rainy day, this is likely the day.

The kind of hardware encoder you use and what the quality of the video it outputs is entirely dependent on your GPU. Each manufacturer has their own encoders. For example, Intel has quick sync and Nvidia has NVENC. In our case we primarily use intel quick sync that is available on i5 and up chips. We have a 2012 dell desktop that encodes most of our channels. This older quicksync encoder while very efficient does not seem to support closed captions well. This is something we are working on addressing. All of our HD channels are encoded with this encoder. While a single 2012 desktop could maybe have encoder a single channel in software it can encode half a dozen HD channels with the hardware encoder and have no impact on the CPU. This is the command we use to encode with a intel quicksync:

ffmpeg   -loglevel error -hide_banner  -hwaccel vaapi -vaapi_device /dev/dri/renderD128 -i http://<MPEG2 stream>  -vf 'scale=-1:720,format=nv12,hwupload' -map 0:0 -map 0:1 -threads 8 -y  -acodec libfdk_aac -b:a 384k  -b:v 3500k -maxrate:v 3500k -vcodec h264_vaapi -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 6  -hls_list_size 20 -hls_flags delete_segments  -qp 28 -bufsize:v 6400k   -hls_segment_filename streams/abc/1080_%03d.ts streams/abc/1080.m3u8

In general it looks a lot like the software encoder earlier except we indicate the h264_vaapi hardware encoder. Depending on what the source material of your channel is you may want to scale everything to 720p. We do this for many channels:

-vf 'scale=-1:720,format=nv12,hwupload'

The primary reason you would do this is if your source channel is 720p but your local broadcaster has upscaled it to 1080i there isn’t a lot of reason for you to try to send out 1080i if it was originally 720p. This is the case with several channels in Boston. The scale relates to the bit rate -b:v 3500k , ultimately this is how much data you want to represent single pixel. 1080i is much bigger than 720p so you would have to increase you bit rate dramatically for 1080i video. In real world terms this translates to much bigger files and more data used. As you will see in part 4, more data served means greater cost.

Our non HD sub channels (e.g. Laff an MeTV) are actually encoded by old Raspberry PIs (2 and 3s) I had lying around in a bin of old computer parts. A Raspberry PI 3 could in theory actually encode a HD channel but I have had issues with the CPU throttling and would need more active thermal management on the pi3. On the Pi (32 bit) we use an encoder called OMX:


ffmpeg  -loglevel error -hide_banner -re -y -i  http://<MPEG stream> \
-err_detect careful  -map 0:v:0 -map 0:a:0  \
-vcodec h264_omx  -sc_threshold 0 -g 48 -keyint_min 48 -c:a copy -ar 48k  \
-b:v:0 3500k  -b:a:0 192k   -profile:v main \
-var_stream_map "v:0,a:0"  -fps_mode auto \
-hls_time 6 \
-hls_list_size 20 \
-hls_flags delete_segments \
-hls_segment_filename laff/480_%03d.ts laff/480.m3u8

Protecting your stream

At this point you are broadcasting your tv streams in HLS format and any device can watch the video. However these streams are free for everyone to view. You should protect them in someway. There are different ways of doing this. Apple has a DRM scheme called FairPlay and Google has a scheme called Widevine. Oddly enough, Widevine is what the broadcasters use to DRM encrypt ATSC3. My understanding is main drawbacks of each is Apples DRM does not work on Android and Googles DRM does not work on iOS. There is an encryption scheme included in HLS that does work with both. We create an encryption key with openssl and include the following flag in all of our ffmpeg commands

-hls_key_info_file enc.keyinfo

We rotate our encryption keys continuously and deliver it to clients out of band. In our case it is pushed to all clients via Apple’s APNS. The key is never persisted anywhere and needs to be fetched every time the app starts. Generally this works well but if you have ever seen an error about fetching playback urls, this is what the issue probably is. For us to send you the decryption key we need verify you are in our service area. We ask for location access on the client but also run many checks on the server to determine where you are (or if you are trying to use a VPN).

In theory everyone is receiving the same video encoded and encrypted once. It’s not perfect but it does make it really painful to view our streams when you are not authorized. It is also not something a casual pirate will be able to do.

At this point you have replicated Localtv+ as it exited in late 2023. We quickly discovered even with good upload speeds we can’t serve more than a few concurrent viewers. We added a CDN in-between to address this and were able to scale up to thousands of viewers. In part 4 we will discuss content distribution networks (CDNs) and costs.

How to build LocalTV Part 2: TV Signals

This is part 2 in our multi part series about how LocalTV+ is made. If you haven’t seen it, part 1 covers the way the non profit is set up.

Today, we will talk about how we acquire TV signals and prepare them for retransmission. This will talk a lot about the way we get signals in Boston as well as details of the American digital TV standard, ATSC .

ATSC

ATSC is the American broadcast TV standard. It replaced the older analog NTSC standard with the digital when the transition completed in 2009. At the moment there is a movement to ATSC3 also called NextgenTV. We do not use ATSC3 because it is DRM encrypted with Google’s Widevine DRM and there is no hardware other than a handful Android tv boxes that can receive ATSC3.

First thing to note is ATSC is a very old standard going back to the original release in 1990. As a result the requirements decode the video stream are very very low. The video is encoded in MPEG2. By modern standards the videos this produces are very big and very inefficiently encoded. Thankfully it also means nearly anything modern can handle it with ease.

Acquiring the signal

We use a single Televes 14883 antenna mounted on the roof of a building in Brookline, MA. It is high enough up and and has a clear line of sight to most of the TV broadcast towers which are in Needham, MA. This is a very good antenna and it also picks up some very weak channels like MeTV Toons and several Providence stations. This antenna has a 5g/LTE filter built in. This matters in Boston because if you do not have it, certain channels like Fox (WFXT) receives a lot of interference due to its proximity on the spectrum. If you own an antenna and regularly get interference on your channels adding a cheap filter like this may change your life.

Computer hardware

Above I mention how the video standard is very old. We use an old Dell desktop computer from 2012 running Ubuntu Linux I had lying around to power this entire service. It is more than capable for this task. To actually receive the ATSC1 signal on the PC we need specialized (but very cheap) hardware. These are TV tuners. We use a combination of multi tuner cards and USB tuners we acquired on eBay. ATSC1 tuners aren’t really made much anymore but eBay is full of them. We have a few WinTV 950Q single usb tuners that go for $15-$20. We also have WinTV HVR 2250 PCIE cards that have two tuners and go for $30-$40. Since we would quickly run out of PCIE slots and USB ports doing this one at a time, we also have a TBS 6704 quad tuner card . How many tuners you need really depends on how many channels you want to offer. Many channels are broadcast along with other channels and can be picked up by a single tuner. An example of this would be WHDH and WLVI in Boston. But usually it’s a safe bet that HD channels like CBS, ABC, FOX, NBC will all need a tuner each. Along with them you get sub channels like MeTV which are in SD. If you look at the rabbit ears report you can see in Boston, MeTV comes with ABC. So this would only require a single tuner to get both.

Now that we have the antenna, filter and tuner all hooked up to a computer we need some software to actually read the TV signals. For this we use an open source software called TVheadend. It will scan for channels and publish a url with MPEG2 streams for all of your channels. In addition it will pick up the TV guide data that the stations publish along with the TV signal. TV headend publishes a REST API with JSON. It is also worth noting that you can likely use a multi tuner device like Tablo, HDhomerun to get the TV signal and have it publish it as an MPEG2 stream but I am not familiar with how it works or if it exposes as much directly as tvheadend does.

Now what?

Once you have this setup you can access your local TV stations video and guide data as if it were any other REST API on the internet. The TvHeadend data structures and APIs are actually the same data structures used in LocalTV+. We do not directly expose our TVheadend service but expose certain end points via an otherwise unremarkable Java application. You could totally build an app around tvheadend however I note above that it publishes the ATSC MPEG2 video stream directly. These files are very big, predating the internet and are not suitable for publishing on the internet. Part 3 of this guide will cover how we encode our videos for distribution on the internet.

How to build LocalTV Part 1: Legal

This is the first of a multi part series where we detail the steps we took to make LocalTV+ a reality. The hope is that others in other parts of the country can emulate this process to make their own “LocalTV+” that serves their own community. People often ask if we will expand and the answer is always no. Beyond the legality of it, we will not expand because this service is run by three guys with day jobs who volunteer their time. Since it picks up actual TV signals there is a need for someone to be physically be present in each market to setup and maintain the antenna and encoder hardware.

This first post will deal with the legality of the service. This post is not legal advice and I am not a lawyer. This is simply what we did. However I would stress that this is likely the most important step for anyone looking to start a similar service.

LocalTV+ exists because US copyright law explicitly mentions how secondary transmissions can be legal. Specifically it says:

(a)Certain Secondary Transmissions Exempted.—The secondary transmission of a performance or display of a work embodied in a primary transmission is not an infringement of copyright if—

(5)the secondary transmission is not made by a cable system but is made by a governmental body, or other nonprofit organization, without any purpose of direct or indirect commercial advantage, and without charge to the recipients of the secondary transmission other than assessments necessary to defray the actual and reasonable costs of maintaining and operating the secondary transmission service.

To do this we created a non profit, Mass Local TV Inc which runs LocalTV+, owns its physical assets, bank accounts, application source code etc. In addition, we decided that our non profit would be a 501(c)(3) non profit where donations are tax exempt. In Massachusetts, we also had to file paperwork with the state to allow us to legally fundraise in the state. We used a service called BryteBridge to actual handle and file all of our legal paper work. Unlike many other things we did, this is a step where we have not gone with the most inexpensive option. Technically this is not necessary. You could follow any number of guides on line to do this yourself. This is just what we chose to do.

We made some additional decisions, you may note that the law above actually does allow charging for the service “to defray the actual and reasonable costs of maintaining and operating the secondary transmission service” . We made a decision to try to offer this as a free, donation funded service. This would allow us to avoid one of the traps that Locast fell into. They charged $5 a month and generated millions of dollars in revenue. On a similar note we are careful to have no ads or privacy invading metrics in the app. We also provide an unmodified signal. What you see is what we picked up on the antenna. There are no interruptions or preroll ads on the stream.

We use a service called Givebutter to handle our online donations. This isn’t technically necessary and many people have actually donated via check. We use this service because it’s easy, cheap ($1 or so when we withdraw) and takes care of the security aspect of dealing with credit card transactions. It allows us to focus on the technology.

For the iOS and tvOS AppStores as a non profit, Apple waives our $99/yr developer fee. It comes with certain restrictions with respect to in app purchases but it is a restriction we are willing to accept right now to keep costs down.

Part 2 onwards we will discuss how we use open source code, in expensive hardware and modern technology to reduce costs.

Cheap AppleTV hardware guide

My earlier post about Roku and Android devices unable to play audio from tv broadcasts is likely due them to not wanting to pay the license to Dolby for Ac3. This is an example of how a lot of manufacturers can cut corners. Apple Tvs are not ad subsidized and compared to many other devices, the newest models can be expensive , start at $129.

But that’s not the end of the story. Apple TVs are actually some of cheapest devices you can run this service on. LocalTV+ will run on any AppleTV released in the last 9 years. If it is an AppleTV 4th gen, it will work. This is a frequent question I see on reddit, “What do I need to buy to use this service?”. There is a simple rule of thumb, if it is newer than 2015, it’s one that has an App Store and you can download LocalTV+. In general we try to keep our OS requirements as low as we can but the interesting thing about AppleTV is Apple continues to support even the 9 year old models with the latest OS updates. When I look at the stats for our app downloads in the App Store, I see the 100% of devices are on iOS 17 and up. In fact only one download was on iOS 17 and 99% were on tvOS 18.

So how cheap are older devices? When you search eBay for Apple TV 4th gen, the cheapest devices go for around $30. All of these should run LocalTV+ perfectly fine. Regardless of what Apple does, we will keep our requirements as low as possible. We are lucky right now that all AppleTV run the latest version of the OS. This makes our own development and testing much easier.

Thanksgiving 2024: Looking back at a year of LocalTV+

Just before Thanksgiving in 2023, we decided that we had reached the limits of what we can test with the dozen or so beta testers we had and started the public beta. We made a big announcement post on Reddit Boston asking for beta testers. Once Thanksgiving day arrived however, everything melted down and we learned a lot of lessons. Over the past year we have taken everything we have learned from that initial test and subsequent tests over the Super Bowl, the NBA finals and the Paris Olympics to adjust and improve the app and service. The subreddit has grown to 400 members and regularly provides feedback on each iteration of the betas we’ve put out. Our users have regularly contributed with donations, keeping this free service running. We’ve grown to thousands of active users and launched the TV app in the App Store this week.

Thanksgiving 2024 will be another big test. Thankfully, whatever happens it should be much better than 2023.

Looking ahead, there are a lot of improvements coming down the pipeline for 2025. Clients for Google platforms, subtitle support for every channel, and improved video quality and less stalling to name a few.

2025 fundraising goals

Thank you everyone who has donated. We hit the 1k goal we set for 2024. At the moment we have enough to continue operations with the current level of users (around 5k) until feb 2025. We have updated the give butter campaign to be 1440 more . This is 120×12 and should be enough to continue operations with the current number of users until the end of next year.

https://givebutter.com/LocalTVPlus

We do hope to get out of beta and go live in the AppStore in the coming months. It will make the app more readily available to more users hopefully it brings more donors too.

Why you can’t hear the audio on some non Apple devices

LocalTV+ supports Airplay and it is an option to send the to your other devices. This works very well when working with Apple devices. Over the past year I have seen reports that it would randomly work on certain Roku devices. Alternatively when it does work people would report that there was no audio. Eventually I discovered that not all Roku devices support the codec we use (more on this later). To add more confusion some Roku devices while not supporting the codec directly do pass it through to your audio hardware, so if you have a receiver that does support it, it would work.

This bring us to Android, where I have discovered that the first party video player ExoPlayer does not directly support AC3 Audio (there is a way to build ffmpeg and pass it through). In fact, it seems stock Android itself does not support AC3 audio. To add more confusion here, some Android devices do have AC3 support that’s been added by the manufacturer. So while I do have a basic Android client that can play the video, there is no audio at the moment.

So what is AC3 and why do we use it? AC3 is a Dolby Digital 5.1 audio format. The ATSC signal we receive uses AC3 for audio and we pass it along unmodified. We could convert it to another format such as AAC however, converting from one lossy format to another will result in worse audio quality. The actual conversion process will also increase the load on out current encoders.

Why does Apple support it but not Google? I am not sure. My hunch is that it requires a license form Dolby who owns the format and Apple has baked it into the cost of every device.