Files
DigiRadio/Software/main/phone_stream.cpp
micheleandClaude Sonnet 5 e8f79c4b79 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
2026-08-18 08:03:39 +02:00

66 lines
1.9 KiB
C++

/**
* @file phone_stream.cpp
* @brief net::PhoneStreamSink implementation over the shared I2S sink.
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*/
#include "phone_stream.hpp"
#include "esp32_i2s_sink.hpp"
#include <array>
#include <cstdint>
namespace phone_stream {
namespace {
/** Convert one chunk of interleaved 16-bit stereo PCM to the ADAU's
* 32-bit-slot I2S format and hand it to the shared sink in one write,
* same rationale as web_radio_stream.cpp's writeFrame(): one
* i2s_channel_write() call per sample would be a needless source of
* jitter under sustained streaming. */
bool writePcm16Stereo(const std::int16_t* interleaved,
std::size_t frameCount)
{
constexpr std::size_t kMaxFramesPerCall = 1024U;
static std::int32_t out[kMaxFramesPerCall * 2U];
std::size_t offset = 0U;
while (offset < frameCount) {
const std::size_t batch =
(frameCount - offset) < kMaxFramesPerCall
? (frameCount - offset)
: kMaxFramesPerCall;
for (std::size_t i = 0U; i < batch; ++i) {
const std::int16_t left = interleaved[(offset + i) * 2U];
const std::int16_t right = interleaved[(offset + i) * 2U + 1U];
out[i * 2U] = static_cast<std::int32_t>(left) << 16;
out[i * 2U + 1U] = static_cast<std::int32_t>(right) << 16;
}
if (!esp32_i2s_sink::writeSamples(out, batch * 2U)) {
return false;
}
offset += batch;
}
return true;
}
} // namespace
net::PhoneStreamSink& sink() noexcept
{
static net::PhoneStreamSink instance{
.tryAcquire = &esp32_i2s_sink::tryAcquire,
.release = &esp32_i2s_sink::release,
.writePcm16Stereo = &writePcm16Stereo,
};
return instance;
}
} // namespace phone_stream