Around 2 years ago, I made a simple server in Node.JS, running on a RPi Zero W, that started a raspivid process and captured MJPEG frames from the output. These were send to a website using a websocket.
I wanted to rework my setup to work via HTTP recently. But after updating my RPi and rewriting my setup, I noticed some weird behaviour. After starting either my old (websocket) server or my new (http) server, Raspivid would stop providing data to Node after a random amount of minutes. I'm 100% certain this wasn't an issue in the past, because I'd let it run for multiple hours, multiple times.
I tried isolating the issue by removing all "unnecessary" parts, keeping only the process spawning:
const spawn = require("child_process").spawn;
//JPEG start byte
const startByte = Buffer.alloc(2);
startByte.writeUInt16LE(0xd8ff, 0);
//Build command
const command = "raspivid";
const args = [];
args.push("-t", "0");
args.push("-w", "1280");
args.push("-h", "720");
args.push("-fps", "30");
args.push("-cd", "MJPEG");
args.push("-n", "-o", "-");
//Spawn process
const process = spawn(command, args, {stdio: ['ignore', 'pipe', 'ignore']});
//Data event
let buffer = Buffer.alloc(0);
process.stdout.on("data", data => {
buffer = Buffer.concat([buffer, data]);
//Look for end byte in buffer
const index = buffer.lastIndexOf(startByte);
if (index > 0) {
const frame = buffer.slice(0, index);
buffer = buffer.slice(index);
console.log("got frame: ", frame.length);
}
});
This little bit of code will lock-up eventually (tested on Node 12 and 14). I tried all possible data/error events, but they don't even trigger. After some researching, I found it could be related to buffering. Node doesn't provide any control over this though, so I made a Python variant of the above:
import subprocess
def run():
command = ["raspivid", "-t", "0", "-w", "1280", "-h", "720", "-fps", "30", "-cd", "MJPEG", "-n", "-o", "-"]
# Run command
process = subprocess.Popen(command, shell=False, stdout=subprocess.PIPE)
# Prepare search data
search_data = 0xd8ff
search_buffer = search_data.to_bytes(2, byteorder="little", signed=False)
# Setup
buffer = bytearray(0)
check_size = 8 * 1024
check_current = 0
# Wait for data
while True:
data = process.stdout.read(64)
buffer.extend(data)
# Check if we have received enough data to warrant a frame search
check_current = check_current + len(data)
if check_current >= check_size:
frame_idx = buffer.rfind(search_buffer)
# If JPEG frame found, extract it
if frame_idx > 0:
frame = buffer[0:frame_idx]
buffer = buffer[frame_idx:]
print("got frame", len(frame))
# Reset current check value
check_current = 0
# Press the green button in the gutter to run the script.
if __name__ == '__main__':
run()
The above code does actually seem to work for prolonged periods of time, but there's a catch. Notice the process.stdout.read(64)
. A value of 64 works, but if I increase this value it will break just like the Node code. The higher, the faster / more likely it is to break. It also eventually breaks when using .readline()
.
Other factors that seem to increase the likeliness of breaking (unsure however, due to the randomness of it breaking):
- Increasing GPU memory split
- Running the code in a
screen
and detaching
In Python I also tried playing around with the subprocess bufsize
argument in combination with read()
block size, but only the above setup seems consistent.
The big issue here is I don't really want to use Python, and also using a block size of 64 increases CPU usage by around 3 times the old solution. The old (Node) setup was around 10-20% CPU, and this setup goes up to 50-60%. Increasing the block size lowers CPU usage but breaks eventually.
Does anyone know why this happens? Or how I could go about fixing it? I'm really not sure what else I can try. Also apologies for the long post :)