Repository navigation
Outbound Audio Streaming (Push Mode) for Remote Deployments #68
vrishabkakade
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
esp32-birdnet-mic-push.zip
Feature request: optional "push" mode (outbound stream to a relay) for when BirdNET-Go isn't on the mic's network
Summary
Today the firmware is an RTSP server: BirdNET-Go must be able to open a connection to the ESP32. I'd like an optional push mode, where the ESP32 opens an outbound connection to a server and streams its audio there.
I have a working proof of concept (about 150 lines added to v1.30).
Why this is needed
In my setup, BirdNET-Go does not run on the same network as the microphone. The mic sits at a remote site behind a router or mobile-carrier NAT, and BirdNET-Go runs on a separate server. Pull-mode RTSP doesn't work there:
Push mode fixes this. The device connects out, so it works through NAT, and each device authenticates with its own ID and secret.
Architecture
The problem today
flowchart LR subgraph SITE["Mic site (behind NAT, no inbound access)"] MIC["ESP32 mic<br/>RTSP server :8554"] end subgraph SERVER["Different network (server or cloud)"] BN["BirdNET-Go"] end BN -. "pull rtsp://device:8554/audio1<br/>blocked by NAT / no public IP" .-> MICProposed: push to a relay
flowchart LR subgraph SITE["Mic site"] MIC["ESP32 mic<br/>push mode"] end subgraph SERVER["Server (Docker)"] BR["Bridge<br/>TCP :9000 + ffmpeg"] MTX["MediaMTX<br/>RTSP :8554"] BN1["BirdNET-Go #1"] BN2["BirdNET-Go #2"] end MIC -- "outbound TCP<br/>ID:secret + raw PCM" --> BR BR -- "RTSP publish" --> MTX MTX -- "RTSP pull" --> BN1 MTX -- "RTSP pull" --> BN2BirdNET-Go is unchanged: it pulls from MediaMTX like any RTSP camera.
Connection flow
sequenceDiagram participant M as ESP32 mic participant B as Bridge participant X as MediaMTX participant G as BirdNET-Go M->>B: TCP connect to host:9000 M->>B: device_id:secret + newline B->>B: check credentials B->>X: ffmpeg publishes rtsp://mediamtx:8554/device_id loop continuous M->>B: raw s16le PCM, 48 kHz, mono B->>X: RTP L16 over RTSP end G->>X: pull rtsp://mediamtx:8554/device_id Note over M,B: If the link drops, the mic reconnects with backoff and the bridge replaces the stale sessionWire protocol
The audio is the same processed audio the RTSP path carries (gain and high-pass applied), taken from the existing ring buffer before RTP packetising.
Proof of concept
Firmware changes (all in the
.ino):pushService()called fromloop()connects out withWiFiClient, sends the ID and secret line, then streams from the existing ring buffer with non-blockingsend(..., MSG_DONTWAIT), so the web UI and loop aren't blocked.rtspServerEnabled, thermal latch, Wi-Fi state, capture running), so the stream schedule and thermal protection still apply.Server side: a short Python asyncio TCP server (about 80 lines). It checks the first line, then pipes PCM into
ffmpeg -f s16le -sample_rate 48000 -i pipe:0 -c:a pcm_s16be -f rtsp ...towards MediaMTX. A reconnect for the same ID replaces the old session, and it drops connections after 15 s without data.Tested with: firmware v1.30 (commit ec7486b), DFRobot Beetle ESP32-C6 (DFR1117), INMP441 mic, Arduino-ESP32 3.3.12, MediaMTX and BirdNET-Go in Docker. BirdNET-Go detected bird calls end to end.
Known limitations
Suggested next steps
/api/statusand MQTT.PUSH_ENABLED) so default builds don't change.Questions
Thanks for the great project!
All reactions