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>
      # This provides only limited security
      Require ip
      Require expr %{HTTP_USER_AGENT} == 'Lavf/58.20.100'
      Require expr %{REQUEST_FILENAME} =~ /cam\.m3u8|cam[0-9]+\.ts/

Restart Apache:

apachectl configtest
apachectl graceful

Setup simple page

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

    <script src=""></script>
    <video controls="controls" width="1280" height="720" autoplay="autoplay" id="video" >
      <source src="cam/cam.m3u8" type="application/x-mpegURL" />
  var video = document.getElementById('video');
  var videoSrc = '';
  if (Hls.isSupported()) {
    var hls = new Hls();
    hls.on(Hls.Events.MANIFEST_PARSED, function() {;
  } else if (video.canPlayType('application/')) {
    video.src = videoSrc;
    video.addEventListener('loadedmetadata', function() {;

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

Setup camera

On the Rasberry 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.

LEN='10'  # length of each fragment in seconds
declare -a ARGS=(
  -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'
exec ffmpeg "${ARGS[@]}"

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

Description=Run bird stream



Start the service now and also on next reboot:

systemctl start ffcam.service
systemctl enable ffcam.service


  • 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.


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://
ffplay -i udp://
Written on May 12, 2020