Building a bird camera

I had to build a web camera for watching some newly hatched birds. From previous experiments I already had a spare Raspberry Pi wit an attached camera. Due to bandwidth limitations in the wireless network I wanted to host the resulting media on one of my public servers. I’m using ffmpeg with HLS streaming as this works in most browsers out-of-the-box.

Prepare Apache server

We need a place where ffmpeg can upload its files. I use Apache with DAV enabled:

a2enmod dav_fs dav

The requests timeout module has to be disabled as ffmpeg will otherwise timeout.

a2dismod reqtimeout

Now modify /etc/apache2/sites-available/default-ssl.conf and add something like this at the end

<Directory /var/www/html/cam>
  Require all granted
  Dav filesystem
  <LimitExcept GET POST OPTIONS>
    <RequireAll>
      # This provides only limited security
      Require ip 127.0.0.1
      Require expr %{HTTP_USER_AGENT} == 'Lavf/58.20.100'
      Require expr %{REQUEST_FILENAME} =~ /cam\.m3u8|cam[0-9]+\.ts/
    </RequireAll>
  </LimitExcept>
</Directory>

Restart Apache:

apachectl configtest
apachectl graceful

Setup simple page

Put this in /var/www/html/cam.html or somewhere else:

<html>
  <head>
    <title>Cam</title>
    <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
  </head>
  <body>
    <video controls="controls" width="1280" height="720" autoplay="autoplay" id="video" >
      <source src="cam/cam.m3u8" type="application/x-mpegURL" />
    </video>
    <script>
  var video = document.getElementById('video');
  var videoSrc = 'https://birdy.pmhahn.de/cam/cam.m3u8';
  if (Hls.isSupported()) {
    var hls = new Hls();
    hls.loadSource(videoSrc);
    hls.attachMedia(video);
    hls.on(Hls.Events.MANIFEST_PARSED, function() {
      video.play();
    });
  } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
    video.src = videoSrc;
    video.addEventListener('loadedmetadata', function() {
      video.play();
    });
  }
    </script>
  </body>
</html>

Actually HLS does not work with Chromium and I had to add the HLS.js fallback.

Setup camera

On the Raspberry Pi install ffmpeg:

apt install ffmpeg

Setup script /home/pi/ffcam to take two picture a second, but create a h.264 stream with 25 frames for compatibility reasons.

#!/bin/bash
LEN='10'  # length of each fragment in seconds
declare -a ARGS=(
  -hide_banner
  -nostats
  -v level+error
  # -v trace

  -f video4linux2
  -input_format h264
  -video_size 1280x720
  -framerate 2
  -i /dev/video0

  -r 25
  -vcodec copy
  -an -sn
  -flags +cgop
  -g 30

  -f hls
  -hls_init_time "$LEN"
  -hls_time "$LEN"
  -hls_list_size 60
  -hls_delete_threshold 10
  -hls_allow_cache 1
  # -hls_segment_filename 'cam%04d.ts'
  -hls_segment_type mpegts
  -hls_flags delete_segments+program_date_time  # +temp_file
  # -hls_playlist_type event
  # -master_pl_name cam.m3u8
  # -master_pl_publish_rate 1
  -method PUT
  # -ignore_io_errors
  -timeout 120
  # -headers 'Token: N6TXb058UGWh32MCeA9U'
  https://birdy.pmhahn.de/cam/cam.m3u8
)
exec ffmpeg "${ARGS[@]}"

And a systemd service file /etc/systemd/system/ffcam.service to start that:

[Unit]
Description=Run bird stream
ConditionPathExists=/dev/video0
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=/home/pi/ffcam
Restart=always
RestartSec=10
User=pi
WorkingDirectory=/home/pi

[Install]
WantedBy=multi-user.target

Start the service now and also on next reboot:

systemctl start ffcam.service
systemctl enable ffcam.service

Gotchas

  • You have to use a minimum frame rate of 2 or get black frames only otherwise
  • Do not specify -hls_segment_filename or otherwise the segments will not get uploaded - only the .m3u8 will.
  • Do not specify -hls_playlist_type … as they implicitly reset to -hls_list_size 0, which leads to an unbound number of segments.
  • Adding additional HTTP -headers does not work for HLS.

Links

Other plays

Live streaming via UDP broadcast:

ffmpeg -hide_banner -f video4linux2 -input_format h264 -video_size 640x480 -framerate 2 -i /dev/video0 -vcodec copy -an -sn -f mpegts udp://192.168.178.45:1234
ffplay -i udp://192.168.178.45:1234
Written on May 12, 2020