View on GitHub

raspberry_ninja

Publish or capture VDO.Ninja streams with Python (Raspberry Pi, Linux, Mac, Windows WSL)

Raspberry Pi 3 test bench — 2026-08-02

Host

The current workspace was deployed to /home/pi/raspberry_ninja. The previous clean checkout is preserved at /home/pi/raspberry_ninja.pre-bench-20260802-a73ffaa.

Encoder results

Path Result Notes
Raspberry Ninja x264/H.264 Pass Browser received 640×360 at 15 fps; approximately 57% process CPU, 51.5°C
Raspberry Ninja VP8 Pass Browser received 640×360 at 15 fps; approximately 70% process CPU, 54.2°C
FFmpeg h264_v4l2m2m Pass Hardware encoder /dev/video11 produced H.264 successfully
GStreamer v4l2h264enc Fail bcm2835-codec rejects stream start with Failed enabling i/p port, ret -3
FFmpeg h264_omx Unavailable Legacy OMX libraries are not installed

No thermal throttling was reported during the WebRTC tests.

GStreamer hardware H.264 was tried at 320×240, 640×360, and 1280×720; with I420 and NV12; with videoconvert and v4l2convert; and with the supported V4L2 I/O modes. All variants failed, while FFmpeg’s V4L2 M2M path worked. This isolates the current limitation to the installed GStreamer/driver integration rather than missing encoder hardware.

Signaling finding

Passing an official handshake server explicitly previously generated and overwrote puuid, causing incoming viewer requests to be discarded as PUUID NOT SAME. A regression fix now treats the primary and backup VDO.Ninja handshake servers as official while retaining generated UUID behavior for custom servers and preserving an explicitly supplied --puuid.

The fixed explicit-server path was verified end to end against wss://wss.vdo.ninja:443: a normal browser viewer received 640×360 H.264 video, the publisher received an SDP answer, and no PUUID NOT SAME rejection occurred. At the measurement point, the publisher used approximately 18% process CPU, the Pi reported 52.1°C, and thermal throttling remained clear.

Regression checks

Multi-device follow-up

The same checkout was also exercised on a Raspberry Pi Zero 2 W running 32-bit Raspberry Pi OS 13 (Trixie), Linux 6.18.39, GStreamer 1.26.2, and Python 3.13.5. The Zero has 424 MiB usable RAM. A third Pi running Pi-hole was identified at 10.0.0.175/10.0.0.176, but SSH is disabled there, so it was limited to network-service inventory.

Physical HDMI capture

The Pi 3 USB HDMI adapter exposes MJPEG at 1280x720@120, 640x400@210, and 640x360@210 in v4l2-ctl, although 100 frames actually arrived in about 4.4 seconds. Forcing the requested 15 or 25 fps on v4l2src failed with not-negotiated. The capture path now constrains only advertised source modes and applies the requested rate after JPEG decoding.

v4l2jpegdec also timed out on both adapter and synthetic JPEG input, so the new runtime frame probe selects jpegdec on this host. Software encoders now stay in system memory through videoconvert; routing them through v4l2convert caused another negotiation failure.

End-to-end physical tests at 640x360@15 passed:

Publisher Receiver/recording Result
Pi 3 HDMI capture, x264/H.264 Zero 2 W, direct MPEG-TS Pass; H.264 decode exit 0
Pi 3 HDMI capture, VP8 Zero 2 W, direct WebM Pass; WebM decode exit 0

The Pi 3 remained unthrottled at 45.6-48.9 C. The Zero remained unthrottled at 40.8-41.9 C.

Headless viewer

The Zero received VP8 through RN_FORCE_SINK=fakesink, activated the remote display branch, reported 0% post-repair loss, and returned to the idle branch after the sender stopped. Its automatic recovery path tore down the stale peer and re-issued the play request. Memory use during the test was 121 MiB.

Recording and HLS

Three-minute Zero 2 W soak

The Zero published a 640x360@10 H.264 test source with Opus at a 400 kbps video target to the Pi 3 subprocess recorder.