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:
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* @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
|
||||
Reference in New Issue
Block a user