Add PUT /api/stream/phone: raw PCM audio push from a phone app

New endpoint accepts chunked, header-less, 16-bit LE stereo PCM @ 48 kHz
in the request body and writes it straight to the shared I2S sink for as
long as the connection stays open. Chosen deliberately unencoded (no
MP3/AAC/Opus decode) to keep this path as simple and low-risk as
possible — the companion app controls the encoding on its side.

Extracted the I2S TX channel that used to be owned outright by
web_radio_stream.cpp into main/esp32_i2s_sink.cpp, shared by both
producers with a simple tryAcquire()/release() exclusivity guard — web
radio streaming and a phone PCM stream would otherwise fight over the
same physical wire. web_radio_stream.cpp now acquires/releases around
each streamWhileEnabled() cycle instead of owning the channel itself.

net::PhoneStreamSink is a plain function-pointer struct (not a class
hierarchy) threaded through NetBootstrap::start() -> SetupWebServer::start()
-> HttpRouteContext, so components/net stays free of I2S driver headers;
main/phone_stream.cpp supplies the concrete functions (bound to
esp32_i2s_sink) and does the int16->ADAU 32-bit-slot conversion, batched
per chunk rather than per sample for the same reason as the web radio
stutter fix (6974095/7e65394 lineage).

Verified by build only — not confirmed live yet (board disconnected this
session); the actual phone app that will exercise this endpoint doesn't
exist yet either.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0178rASQ6ZETPMUamvpoR2KR
This commit is contained in:
2026-08-18 08:03:39 +02:00
co-authored by Claude Sonnet 5
parent 39984a97d1
commit e8f79c4b79
11 changed files with 394 additions and 70 deletions
+8
View File
@@ -23,6 +23,8 @@
#include "si4684/Si4684Tuner.hpp"
#include "station/StationService.hpp"
#include "tuner/TunerService.hpp"
#include "esp32_i2s_sink.hpp"
#include "phone_stream.hpp"
#include "web_radio_stream.hpp"
#include "webradio/WebRadioService.hpp"
@@ -197,6 +199,11 @@ extern "C" void app_main()
static webradio::WebRadioService webRadioService(store);
if (!esp32_i2s_sink::open()) {
ESP_LOGW(kTag, "shared I2S sink open failed — web radio and phone "
"streaming will be unavailable");
}
auto netResult = net::NetBootstrap::start(
store,
tunerService,
@@ -206,6 +213,7 @@ extern "C" void app_main()
integration,
otaService,
webRadioService,
phone_stream::sink(),
hardware::HardwareBootstrap::companionChipStatus(),
hardware::HardwareBootstrap::deviceIdentity());
if (!netResult) {