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
66 lines
1.9 KiB
C++
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
|