Simple RPi Video Camera with HTTP Live Streaming (HLS)

The simple JavaScript express() server and video client code samples below demonstrate the essential elements of  HTTP Live Streaming (HLS) by a Raspberry Pi and Camera. The code was tested on the RPi Zero 2W test set shown in the accompanying figure.

The libcamera-vid and ffmpeg tools comprise the software pipeline for the video stream. The libcamera-vid output is incompatible with the HTML5 <video> element in the stream client; the ffmpeg tool formats the video stream for HTML5 compatibility.

Project Requirements 

HW:

  • Raspberry Pi with Video Camera installed

SW:

  • Node 
  • NPM
  • libcamera and tools
  • ffmpeg

 

RPi Zero2W Test Set - Camera Mount
RPi Camera Test Set (w/ plugboard mounted HC-SR04 Ultrasonic Sensor in background, right)
				
					/** Nodejs Server Code 
Streams Raspberry Pi Camera video
*/ 

const express = require('express');
const { spawn } = require('child_process');
const fs = require('fs');
/** @global  */
const logger = require('morgan');

const path = require('path');
const app = express();
const port = 8554;

app.use(logger('dev'));

// Serve static files from public directory
app.use(express.static('public'));

// Route to start streaming
app.get('/start-stream', (req, res) => {
    // Start the camera stream using libcamera-vid piped into FFmpeg for HLS conversion
    const cameraStream = spawn('libcamera-vid', ['-t', '0', '-n', '--inline', '--listen', '-o', '-']);
    console.log('Spawned cameraStream ...');
    const ffmpeg = spawn('ffmpeg', [
        '-i', '-',
        '-c:v', 'copy',
        '-f', 'hls',
        '-preset', 'ultrafast',
        '-tune', 'zerolatency',
        '-g', '30', 
        '-hls_time', '2',
        '-hls_list_size', '3',
        '-hls_flags', 'delete_segments+append_list',
        '-loglevel', 'error',  
        './public/stream.m3u8'
    ]);
    console.log('Spawned ffmpeg transcoder ...');    

    cameraStream.stdout.pipe(ffmpeg.stdin);

    console.log('Setup cameraStream - ffmpeg pipe ...');
    
    cameraStream.stderr.on('data', (data) => {
        console.error(`libcamera-vid stderr: ${data}`);
    });

    ffmpeg.stderr.on('data', (data) => {
        console.error(`ffmpeg stderr: ${data}`);
    });

    res.send('Streaming started');
});

// Start the Express server
app.listen(port, () => {
    console.log(`Server running at http://localhost:${port}`);
});

				
			
				
					<!-- Client-Side HTML Code for Video Display -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Camera Stream</title>
</head>
<body>
    <video id="videoPlayer" controls autoplay></video>
    <script>
        document.getElementById('videoPlayer').src = '/stream.m3u8';
    </script>
</body>
</html>
				
			

Video Pipeline Attributes

libcamera-vid
  • -t 0.   Run indefinitely (ie, -t specifies the stream duration as N seconds; if N=0, the stream runs indefinitely)
  • -n    Run without the libcamera preview window (the preview would disrupt the video pipeline in this configuration)
  • — inline   Embeds the camera controls in the video stream
  • — listen    
  • -o –  Send the output video to stdout.
ffmpeg
  • -i – Use stdin as input
  • -c:v  copy    Copies the video stream (-c:v) without recoding the stream.
  • -f  hls   Format the libcamera-vid H.264 video stream for HLS to the client
  • -preset ultrafast
  • -tune zerolatency 
  • -g 30   The ‘-g’ flag sets the number of pictures (frames) in a group
  • -hls_time 2
  • -hls_list_size 3
  • -loglevel error
  • ./public/stream.m3u8  Serve the stream to the static directory “.public” with name ‘stream.m3u8’