Streaming Camera to Chromecast

I have a little cheap WiFi camera I’ve been meaning to do something with for a while. The on board web access doesn’t really work any more because it only supports Flash or Java Applet to view the stream.

But camera supports the ONVIF standard so it offers a rtsp:// feed so I can point Linux apps like mplayer at it and see the stream.

The camera is currently sat on my window sill looking out over the valley which is a pretty good view.

View from upstairs window

I thought it would be interesting to stream the view to the TV in my Living room while I’m working from home at the moment. It is also a way to check the weather without having to get up in the morning and open the blind.

I have a Chromecast in the back back of both TVs so using this seamed like it would be the easiest option.

Chromecast

Chromecasts support a number of different media types but for video we have 2 common codec that will work across all the currently available device types.

  • H264
  • VP8

And we have 3 options to deliver the video stream

  • HLS
  • DASH
  • SmoothStreaming

These are all basically the same, they chop the video up into short segments and generate a play list that points to the segments in order and the consumer downloads each segment. When it reaches the end of the list it downloads the list again which will now hold the next list of segments.

There is a plugin for Nginx that supports both HLS and DASH which looked like a good place to start.

NGINX

I’m running this whole stack on a Raspberry Pi 4 running Raspbian Buster.

$ sudo apt-get install nginx libnginx-mod-rtmp ffmpeg

Once the packages are installed the following needs to be added to the end of the /etc/nginx/nginx.conf file. This sets up a rtmp listener that we can stream the video to which will then be turned into both a HLS and DASH stream to be consumed.

...
rtmp {
  server {
    listen 1935; # Listen on standard RTMP port
    chunk_size 4000;

    application show {
      live on;
      # Turn on HLS
      hls on;
      hls_type live;
      hls_path /var/www/html/hls/;
      hls_fragment 5s;
      hls_playlist_length 20s;
      
      # Turn on DASH      
      dash on;
      dash_path /var/www/html/dash/;
      dash_fragment 5s;
      dash_playlist_length 20s;

      # disable consuming the stream from nginx as rtmp
      deny play all;
    }
  }
}

The playlist and video segments get written to /var/www/html/hls and /var/www/html/dash respectively. Because they will be short lived and replaced very regularly it’s a bad idea to write these to an SD card as they will just cause excessive flash wear.

To get round this I’ve mounted tmpfs filesystems at those points with the following entries in /etc/fstab

tmpfs	/var/www/html/dash	tmpfs	defaults,noatime,size=50m
tmpfs	/var/www/html/hls	tmpfs	defaults,noatime,size=50m

Now we have the playlists and segments being generated in a sensible way we need to server them up. I added the following to the /etc/nginx/sites-enabled/default file

server {
  listen 8080;
  listen [::]:8080;

  sendfile off;
  tcp_nopush on;
  directio 512;
  default_type application/octet-stream;

  location / {
    add_header 'Cache-Control' 'no-cache';
    add_header 'Access-Control-Allow-Origin' '*' always;
    add_header 'Access-Control-Allow-Credentials' 'true';
    add_header 'Access-Control-Expose-Headers' 'Content-Length';

    if ($request_method = 'OPTIONS') {
      add_header 'Access-Control-Allow-Origin' '*';
      add_header 'Access-Control-Allow-Credentials' 'true';
      add_header 'Access-Control-Max-Age' 1728000;
      add_header 'Content-Type' 'text/plain charset=UTF-8';
      add_header 'Content-Length' 0;
      return 204;
    }

    types {
      application/dash+xml mpd;
      application/vnd.apple.mpegurl m3u8;
      video/mp2t ts;
    }

    root /var/www/html/;
  }
}

Now we have the system to stream the content in an acceptable format we need to get the video from the camera into nginx. We can use ffmpeg to do this.

ffmpeg -re -rtsp_transport tcp -i rtsp://192.168.1.104:554/live/ch1 -vcodec libx264 -vprofile baseline -acodec aac -strict -2 -f flv rtmp://localhost/show/stream

This reads from the RTSP stream rtsp://192.168.1.104:554/live/ch1 and streams it into the rtmp://localhost/show/stream. The show part is the name of the application declared in the rtmp section in the nginx.conf and the stream will be the name of the HLS or DASH stream. In this case the following:

  • HLS -> http://192.168.1.98/hls/stream.m3u8
  • DASH -> http://192.168.1.98/dash/stream.mpd

If you change the end of the `rtmp://localhost/show/XXXX` URL you can create multiple streams from different sources with different names (just make sure the tmpfs mounts have enough space for all the streams).

Testing

Node-RED Chromecast flow

I’ve been using the Node-RED Chromecast node to test the streams. The DASH stream is working pretty well, but the HLS is a bit more fragile for some reason. Latency is currently about 20-30 seconds which is appears mainly to be determined by the size and number of chunks in the playlist used but if I wind the fragment size down any lower than 5s or the 20s for the playlist length.

Next

Now it’s basically working the next steps are to add support for Camera devices to my Google Assistant Node-RED service so I can request the stream via voice and have it show on my Google Home Hub as well. I’m also building a standalone Google Smart Home Action just for Camera feeds using an all Google stack just as a learning exercise in Firebase.

At the moment the stream is only available from inside my network, I’ll probably proxy it to my external web server as well and add on authentication. The Google Assistant can be given a Bearer Auth Token along with the URL which means I’ll be able to view the stream on my phone while out. While not important for this stream it would be for other Security camera type applications.

Chromecast UK Launch and iPlayer support


I mentioned last year that I had managed to get somebody to bring me a Google Chromecast back from the US and I was pretty impressed with it.

Yesterday was the official UK launch and to go with it the BBC announced that the Android and iOS iPlayer applications would both be supporting the device. This is great news and really adds to the capability of the device.

Up until now I’ve mainly been using mine to watch a films and TV series I’ve bought from Google Play and to play my music through the TV + sounds system in my living room. But with iPlayer support I can see it getting a lot more use, as I tended to watch iPlayer content on my Nexus 7 which is ok, but a little bit small.

The iPlayer app just adds the little cast icon to the title bar of the app and if you connect to a Chromecast it sends the device rather than firing up the video app on the phone. The BBC seam to have done a decent first pass with a nice lock screen integration showing the program art. I gave it a test drive catching up on the last 2 episodes of Salamander that I had missed, the quality was great and playback was near instant (but that may be as much to do with my fibre broadband as anything else).

I’m even seriously considering buying my folks one so they can use iPlayer as the only time they’ve used it so far is when I’ve been back up home and plugged one of the laptops into the TV for them. Dad has an Android phone already and there was some talk of them getting a tablet for my niece to use when she visits.

The only problem I see with it is the price, at £30 it’s 3 times the cheapest price you can get a NOW TV Box for.

The only thing left if for me to finally get round to updating my MythTV setup so I can make use of the HLS support in the new version and write a Android app to display recorded shows via the Chromecast.

Google Chromecast

I managed to get my hands on a Google Chromecast at the weekend. Many thanks to Mike Carew for bringing one back from the US for me via Dale.

Having unpacked the stick I plugged it into my TV and plugged the usb cable in to power it. At first nothing happened and the little notification light on the device stayed red. but replugging the power cable it jumped into life. The instructions directing me to http://www.google.com/chromecast/setup, I had to do this in the Chrome browser and on my Windows laptop as there is no setup app for Linux at the moment (There is a config app for Android, but this is only available for US users at the moment)

When I got to the point where I had to configure which WiFi network the chromecast should connect to there was a problem as my router’s SSID was not showing in the list. It took a couple of minutes for me to remember that I had set my router to use channel 13 as it’s normally lightly used. The reason it is lightly used is because in the US you can only use channels up to 11. A quick change of channel later and the network showed up in the list.

The next part is the only bit that is not as slick as it should be. The Chromecast was fully configured but when I tried to use one of the apps (I’ll talk about those in a moment) it would not show a Chromecast available to send data to. The problem was that my router had done it’s usual trick of walling each of the separate WiFi device from each other, this feature can be called a few things but the most common seams to be apisolation. In a place with shared WiFi like a coffee shop or hotel this is good as it stops people snooping on or attacking your machine, in the home environment this may not be suitable and in this case very much unwanted. I had run into this problem before as one of my MythTV frontends is on WiFi and I had changed the settings to allow WiFi cross talking but the router seams to forget the setting pretty quickly, my usual trick was to reboot my router if I needed to log into it from my laptop to fix things. This was going to become a real issue with the Chromecast. After bit of digging I found a forum post about how to tweak the settings via the telnet interface so quickly ran up an expect script to do it when needed.

#!/usr/bin/expect

set timeout 20
set name SuperUser
set pass ###########

spawn telnet 192.168.1.254

expect "Username : "
send "$namer"
expect "Password : "
send "$passr"
expect "{SuperUser}=>"
send "wireless mssid ifconfig ssid_id=0 apisolation=disabledr"
expect "{SuperUser}=>"
send "saveallr"
expect "{SuperUser}=>"
send "exitr"

This gets called by the script I’ve got bound to a button on my remote driving LIRC that changes the input on my TV from RGB used for MythTV to the HDMI socket used by the Chromecast which ensures my network is always setup properly. I really shouldn’t have to do this but O2 Wifibox III I have is not the best.

Once I’d got all that out of the way time to start actually using this thing for what’s made for. Out of the box there is support for the Chromecast baked into the latest version of the Android YouTube app, Google Play Music, Google Play Movies and Netflix app. I don’t have a Netflix account at the moment so I tried out the other 3.

YouTube app

When the YouTube app finds a Chromecast on the local network it adds the little cast icon to the Action bar. When you tap on this it displays a pop-up to all you to select the Chromecast (if you have more than one on the network) and then rather than play the video on the devices screen they are played on the TV. Play/Pause and volume control are available on the device. One other really nice feature is that the Chromecast maintains a queue of videos to play so you can add to the queue from your phone while it’s playing the current video, in fact if you can do this from multiple devices at the same time. This means you can take it in turns with your mates to see who can find best cat video.

Google Play Movies
Much like YouTube Google Play movies lets you play content on the Chromecast. I had rented a copy of Mud the week before getting hold of my Chromecast so I watched this on the TV rather than on my Nexus 7. The only odd part was that I had downloaded a copy to the device and it would not let me watch it via the Chromecast without deleting the local copy.

Google Play Music
The music app works as expected, showing the cover art on the screen while it plays the tracks. Because it streams tracks directly from the cloud if you are working through a playlist and hit a track that you have added directly to the storage on the phone then it will refuse to play even if you have pushed a copy of the file to Google Music’s cloud storage.

Away from applications on your Android device there is a plugin for the Chrome browser which allows you to share the content of any tab on the large screen. I need to have a look at using this for giving HTML5 based presentations.

There is a API for interacting with the Chromecast and and I’m going to have a look at writing an app to push MythTV recordings so I can replace one of my MythTV frontends. First impressions of the API make me think this shouldn’t be too hard if I can set up the right transcoding.

Over all I’m pretty impressed with the Chromecast and I’m still debating if I should ask my folks to bring me another one back as they are out in the US at the moment.