Add Docker Compose stack and Pi notes
LiveKit on host networking so WebRTC can advertise a reachable address, plus the server and website.
This commit is contained in:
@@ -0,0 +1,16 @@
|
|||||||
|
LIVEKIT_API_KEY=devkey
|
||||||
|
LIVEKIT_API_SECRET=devsecretdevsecretdevsecretdevsecret
|
||||||
|
|
||||||
|
PUBLIC_LIVEKIT_URL=ws://localhost:7880
|
||||||
|
PUBLIC_API_URL=http://localhost:8080
|
||||||
|
|
||||||
|
SESSION_SECRET=change-me-to-a-long-random-string
|
||||||
|
SESSION_TTL_HOURS=12
|
||||||
|
|
||||||
|
BOOTSTRAP_ADMIN_EMAIL=admin@pistation.local
|
||||||
|
BOOTSTRAP_ADMIN_PASSWORD=changeme
|
||||||
|
|
||||||
|
PIN_ROTATION_SECONDS=45
|
||||||
|
PIN_GRACE_SECONDS=15
|
||||||
|
|
||||||
|
CORS_ORIGINS=http://localhost:3000,http://localhost:5173
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
name: pistation
|
||||||
|
|
||||||
|
services:
|
||||||
|
livekit:
|
||||||
|
image: livekit/livekit-server:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
command: --config /etc/livekit.yaml
|
||||||
|
environment:
|
||||||
|
LIVEKIT_KEYS: "${LIVEKIT_API_KEY}: ${LIVEKIT_API_SECRET}"
|
||||||
|
volumes:
|
||||||
|
- ./livekit.yaml:/etc/livekit.yaml:ro
|
||||||
|
network_mode: host
|
||||||
|
|
||||||
|
server:
|
||||||
|
build:
|
||||||
|
context: ..
|
||||||
|
dockerfile: server/Dockerfile
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- livekit
|
||||||
|
environment:
|
||||||
|
BIND_ADDRESS: 0.0.0.0:8080
|
||||||
|
DATABASE_URL: sqlite:///data/pistation.db?mode=rwc
|
||||||
|
MEDIA_DIR: /data/media
|
||||||
|
PACKAGE_DIR: /data/packages
|
||||||
|
PUBLIC_API_URL: ${PUBLIC_API_URL}
|
||||||
|
LIVEKIT_URL: ${PUBLIC_LIVEKIT_URL}
|
||||||
|
LIVEKIT_API_KEY: ${LIVEKIT_API_KEY}
|
||||||
|
LIVEKIT_API_SECRET: ${LIVEKIT_API_SECRET}
|
||||||
|
SESSION_SECRET: ${SESSION_SECRET}
|
||||||
|
SESSION_TTL_HOURS: ${SESSION_TTL_HOURS}
|
||||||
|
BOOTSTRAP_ADMIN_EMAIL: ${BOOTSTRAP_ADMIN_EMAIL}
|
||||||
|
BOOTSTRAP_ADMIN_PASSWORD: ${BOOTSTRAP_ADMIN_PASSWORD}
|
||||||
|
PIN_ROTATION_SECONDS: ${PIN_ROTATION_SECONDS}
|
||||||
|
PIN_GRACE_SECONDS: ${PIN_GRACE_SECONDS}
|
||||||
|
CORS_ORIGINS: ${CORS_ORIGINS}
|
||||||
|
volumes:
|
||||||
|
- server-data:/data
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
|
||||||
|
web:
|
||||||
|
build:
|
||||||
|
context: ..
|
||||||
|
dockerfile: apps/web-client/Dockerfile
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- server
|
||||||
|
environment:
|
||||||
|
PORT: 3000
|
||||||
|
HOST: 0.0.0.0
|
||||||
|
PUBLIC_API_URL: ${PUBLIC_API_URL}
|
||||||
|
PUBLIC_LIVEKIT_URL: ${PUBLIC_LIVEKIT_URL}
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
server-data:
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
port: 7880
|
||||||
|
bind_addresses:
|
||||||
|
- 0.0.0.0
|
||||||
|
|
||||||
|
rtc:
|
||||||
|
tcp_port: 7881
|
||||||
|
port_range_start: 50000
|
||||||
|
port_range_end: 50100
|
||||||
|
use_external_ip: false
|
||||||
|
|
||||||
|
turn:
|
||||||
|
enabled: true
|
||||||
|
udp_port: 3478
|
||||||
|
domain: localhost
|
||||||
|
|
||||||
|
room:
|
||||||
|
auto_create: true
|
||||||
|
empty_timeout: 300
|
||||||
|
max_participants: 50
|
||||||
|
|
||||||
|
logging:
|
||||||
|
level: info
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"serverUrl": "http://pistation.local:8080",
|
||||||
|
"enrollmentToken": "paste-the-token-from-the-admin-panel",
|
||||||
|
"joinUrl": "pistation.local:3000"
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# Spike: video performance on the Pi Zero 2W
|
||||||
|
|
||||||
|
The original question was whether WebKitGTK could reach hardware H.264 decode. That turned
|
||||||
|
out to be moot: WebKitGTK ships without WebRTC at all, so the kiosk cannot receive media in
|
||||||
|
the webview.
|
||||||
|
|
||||||
|
The kiosk now joins the room from Rust with the native LiveKit SDK. Decoding happens there,
|
||||||
|
each frame is scaled and JPEG encoded, and the result is pushed to the webview to be drawn
|
||||||
|
on a canvas. The question this spike answers has therefore changed: can a Zero 2W decode the
|
||||||
|
incoming stream **and** re-encode it fast enough to look live.
|
||||||
|
|
||||||
|
The decode half is still worth measuring with the commands below, because libwebrtc uses the
|
||||||
|
same V4L2 path. The encode half is measured from the app itself, by lowering
|
||||||
|
`PISTATION_VIDEO_MAX_WIDTH`, `PISTATION_VIDEO_FPS` and `PISTATION_VIDEO_QUALITY` until the
|
||||||
|
picture keeps up.
|
||||||
|
|
||||||
|
## Baseline
|
||||||
|
|
||||||
|
The Zero 2W has a VideoCore IV class block exposed through V4L2 stateful decode at
|
||||||
|
`/dev/video10`, reached through GStreamer's `v4l2h264dec`.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Confirm the kernel exposes the decoder.
|
||||||
|
|
||||||
|
```
|
||||||
|
ls -l /dev/video10 /dev/video11 /dev/video12
|
||||||
|
v4l2-ctl -d /dev/video10 --all | head -40
|
||||||
|
```
|
||||||
|
|
||||||
|
`/dev/video10` must report `Codec Decoder` capabilities.
|
||||||
|
|
||||||
|
2. Confirm GStreamer picks the hardware element over the software one.
|
||||||
|
|
||||||
|
```
|
||||||
|
gst-inspect-1.0 v4l2h264dec
|
||||||
|
gst-launch-1.0 videotestsrc num-buffers=600 ! video/x-raw,width=1280,height=720 \
|
||||||
|
! x264enc tune=zerolatency ! h264parse ! v4l2h264dec ! fpsdisplaysink text-overlay=false
|
||||||
|
```
|
||||||
|
|
||||||
|
Record the reported average FPS. Under 25 means the decode path is not viable.
|
||||||
|
|
||||||
|
3. Run the same clip through the software decoder for comparison.
|
||||||
|
|
||||||
|
```
|
||||||
|
gst-launch-1.0 videotestsrc num-buffers=600 ! video/x-raw,width=1280,height=720 \
|
||||||
|
! x264enc tune=zerolatency ! h264parse ! avdec_h264 ! fpsdisplaysink text-overlay=false
|
||||||
|
```
|
||||||
|
|
||||||
|
If step 2 and step 3 report similar numbers, WebKit is almost certainly using
|
||||||
|
software decode and the hardware element is not being selected.
|
||||||
|
|
||||||
|
4. Measure the real path. Start the kiosk, join from a laptop, share a 1280x720
|
||||||
|
screen, and sample CPU while the stream is live.
|
||||||
|
|
||||||
|
```
|
||||||
|
systemctl start pistation-kiosk
|
||||||
|
top -b -n 30 -d 1 | grep -E "WebKitWebProcess|pistation-kiosk"
|
||||||
|
```
|
||||||
|
|
||||||
|
Sustained WebKitWebProcess CPU above roughly 85 percent of one core means the
|
||||||
|
decode is running in software.
|
||||||
|
|
||||||
|
5. Force the issue if step 4 is inconclusive.
|
||||||
|
|
||||||
|
```
|
||||||
|
GST_DEBUG=v4l2*:5 systemctl start pistation-kiosk
|
||||||
|
journalctl -u pistation-kiosk | grep -i v4l2h264dec
|
||||||
|
```
|
||||||
|
|
||||||
|
## Recording the result
|
||||||
|
|
||||||
|
| Measurement | Value | Pass threshold |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `v4l2h264dec` 720p30 FPS | | 25 or higher |
|
||||||
|
| WebKitWebProcess CPU, 720p live share | | under 85 percent of one core |
|
||||||
|
| Frames dropped over 5 minutes | | under 1 percent |
|
||||||
|
| Annotation input to on-screen latency | | under 250 ms |
|
||||||
|
|
||||||
|
## If the spike fails
|
||||||
|
|
||||||
|
1. Try 960x540 at 20 fps by capping the publisher in `apps/web-client` before
|
||||||
|
declaring failure. The Zero 2W often passes at that resolution.
|
||||||
|
2. If it still fails, build `services/compositor-bot`. GStreamer composites the
|
||||||
|
screen share and the annotation canvas server side and sends the Pi one stream,
|
||||||
|
which drops the Pi's job to plain video playback.
|
||||||
Reference in New Issue
Block a user