Stream video from the AVA-based robot vacuum's camera. video_monitor drives the
on-board Cedar H264 encoder via libtrecorder.so and feeds frames to
vacuumstreamer.so, an LD_PRELOAD shim that intercepts the Agora RTC SDK calls
and re-serves the raw H264 stream over TCP port 6969. A client such as
go2rtc (on the robot or on a separate host)
turns that into RTSP/WebRTC.
Tested on: Dreame L10s Ultra (r2338 firmware).
video_monitor is built entirely from source — see Build. The camera
powers on only when a TCP client connects to port 6969.
Both vacuumstreamer.so (the shim) and video_monitor (the camera bridge)
cross-compile for AArch64 inside a Docker toolchain image. build.sh fetches the
device libraries it links against directly from the robot — nothing binary is
checked in.
# 1. Build the toolchain image (once)
docker build -t vacuumstreamer .
# 2. Fetch device libs + build both artifacts
VACUUM_ROBOT_IP=192.168.x.y ./build.sh
# non-default SSH key/options:
VACUUM_ROBOT_IP=192.168.x.y SSH_OPTS="-i ~/.ssh/id_robot" ./build.shbuild.sh pulls these from /usr/lib on the robot into ./libs/ (gitignored,
cached between builds) and links against them — they only satisfy the linker; at
runtime the on-device copies are used via rpath=/usr/lib:
| Library | Purpose |
|---|---|
libc.so.6 |
pins entry point to GLIBC_2.17 |
libpthread.so.0 |
pthread |
libtrecorder.so |
Cedar H264 recorder API (TRecorder) |
libMemAdapter.so |
CedarX DMA-memory ops |
libawrecorder.so |
AW encoder (AwEncoderCreate) + Cedar encoder chain |
Because they are fetched from the target, the build automatically matches your
robot firmware. The remaining transitive dependencies (libVE, libvencoder,
libcdc_base, …) are resolved at runtime from /usr/lib. To re-fetch after a
firmware update, delete ./libs/ and rebuild.
video_monitor.c is a stripped-down reimplementation of the stock binary,
reverse-engineered from its oneChannelTest/CallbackFromTRecorder. The verified
TRecorder setup sequence is:
CreateTRecorder → TRreset → TRSetRecorderCfgPath(path,len) → TRsetCamera(0)
→ TRsetOutput → TRsetMaxRecordTimeMs → TRsetRecorderCallback(cb) → TRprepare → TRstart(.,2)
The callback receives H264 (what == 2; info->[0] = buffer, info->[8] = size) and
forwards it via agora_rtc_send_video_data, which vacuumstreamer.so redirects to TCP
clients. Three device quirks are handled in source: the GetMemAdapterOpsS →
MemAdapterGetOpsS rename (shimmed), linking libawrecorder.so for AwEncoderCreate,
and prepending a fixed 22-byte SPS/PPS to every IDR (the encoder emits none in-band).
Copy vacuumstreamer.so, the freshly built video_monitor, start.sh, and the
configuration files to the robot:
ssh root@${VACUUM_ROBOT_IP} "mkdir -p /data/vacuumstreamer"
scp -O vacuumstreamer.so root@${VACUUM_ROBOT_IP}:/data/vacuumstreamer/vacuumstreamer.so
scp -O video_monitor root@${VACUUM_ROBOT_IP}:/data/vacuumstreamer/video_monitor
scp -O start.sh root@${VACUUM_ROBOT_IP}:/data/vacuumstreamer/start.sh
scp -Or dist/ava/conf/video_monitor/ root@${VACUUM_ROBOT_IP}:/data/vacuumstreamer/ava_conf_video_monitor
ssh root@${VACUUM_ROBOT_IP} "chmod +x /data/vacuumstreamer/video_monitor /data/vacuumstreamer/start.sh"start.sh bind-mounts the encoder config and launches video_monitor (detached via
setsid) under the shim. Hook it into the robot's user boot script,
/data/_root_postboot.sh (run by /etc/rc.sysinit):
ssh root@${VACUUM_ROBOT_IP} 'grep -q vacuumstreamer/start.sh /data/_root_postboot.sh ||
printf "\n# vacuumstreamer camera bridge\nif [ -x /data/vacuumstreamer/start.sh ]; then\n\t/data/vacuumstreamer/start.sh\nfi\n" >> /data/_root_postboot.sh'Start it now without rebooting:
ssh root@${VACUUM_ROBOT_IP} "/data/vacuumstreamer/start.sh"The raw H264 stream is then available at tcp://${VACUUM_ROBOT_IP}:6969 (Annex-B, with
SPS/PPS on every keyframe). Verify with:
ffprobe -v error -select_streams v -show_entries stream=codec_name,width,height \
tcp://${VACUUM_ROBOT_IP}:6969Run go2rtc on the robot, or on a separate host (e.g. your Frigate box) to keep the robot idle. On the robot:
curl -L https://github.com/AlexxIT/go2rtc/releases/download/v1.9.9/go2rtc_linux_arm64 \
-o /data/vacuumstreamer/go2rtc
chmod +x /data/vacuumstreamer/go2rtc
/data/vacuumstreamer/go2rtc -c '{"streams": {"vacuum_cam": "tcp://127.0.0.1:6969"}}'From another host, point the source at the robot instead:
tcp://${VACUUM_ROBOT_IP}:6969.
Credits to @Uberi, source: https://anthony-zhang.me/blog/offline-robot-vacuum/