0

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 :)

1 Answer 1

0

Small update on the issue. Finally had some time to look into it some more. Initially I updated Node to the latest LTS and changed my capture implementation to use V4L2 directly, instead of raspivid. This didn't help at all, still the same behaviour.

I figured the problem lied elsewhere, so I started looking at the system configuration and discovered that GPU overclocking and overvolt settings were applied. I don't recall ever setting that, but maybe I just forgot about it.

Anyway, after removing the overclock settings, the problem has completely disappeared. It works like before again :) Kind of a dumb problem, but I'm glad it has been fixed. Also, nice bonus, the direct V4L2 implementation has improved CPU usage. It stays at around 10% now.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.