Files
DigiRadio/Software/main/esp32_i2s_sink.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

110 lines
2.9 KiB
C++

/**
* @file esp32_i2s_sink.cpp
* @brief Shared ESP32 -> ADAU1701 I2S TX channel (web radio / phone stream).
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp32_i2s_sink.hpp"
#include "board_pins.hpp"
#include "driver/i2s_std.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include <atomic>
namespace esp32_i2s_sink {
namespace {
constexpr char kTag[] = "i2s_sink";
constexpr int kSampleRateHz = 48000;
i2s_chan_handle_t gTxHandle = nullptr;
std::atomic<bool> gInUse{false};
} // namespace
bool open()
{
if (gTxHandle != nullptr) {
return true;
}
i2s_chan_config_t chanCfg =
I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_SLAVE);
// See main/web_radio_stream.cpp history: default (6 x 240 frames =
// ~30 ms) leaves almost no headroom against producer jitter (network
// stalls, HTTP scheduling); widen it for both producers that share
// this channel.
chanCfg.dma_desc_num = 12;
chanCfg.dma_frame_num = 480;
if (i2s_new_channel(&chanCfg, &gTxHandle, nullptr) != ESP_OK) {
ESP_LOGE(kTag, "i2s_new_channel failed");
gTxHandle = nullptr;
return false;
}
i2s_std_config_t stdCfg = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(kSampleRateHz),
// 32-bit slots match ADAU1701 SerialOutRegister1 (64 BCLKs/frame).
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(
I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO),
.gpio_cfg = {
.mclk = I2S_GPIO_UNUSED,
.bclk = static_cast<gpio_num_t>(board::pins::I2sBclk),
.ws = static_cast<gpio_num_t>(board::pins::I2sLrclk),
.dout = static_cast<gpio_num_t>(board::pins::I2sDataOut),
.din = I2S_GPIO_UNUSED,
.invert_flags = {
.mclk_inv = false,
.bclk_inv = false,
.ws_inv = false,
},
},
};
if (i2s_channel_init_std_mode(gTxHandle, &stdCfg) != ESP_OK
|| i2s_channel_enable(gTxHandle) != ESP_OK) {
ESP_LOGE(kTag, "I2S TX channel init/enable failed");
i2s_del_channel(gTxHandle);
gTxHandle = nullptr;
return false;
}
ESP_LOGI(kTag, "I2S slave TX started (BCLK=%d WS=%d DOUT=%d)",
board::pins::I2sBclk, board::pins::I2sLrclk,
board::pins::I2sDataOut);
return true;
}
bool tryAcquire()
{
bool expected = false;
return gInUse.compare_exchange_strong(expected, true);
}
void release()
{
gInUse.store(false);
}
bool writeSamples(const std::int32_t* samples, std::size_t sampleCount)
{
if (gTxHandle == nullptr) {
return false;
}
std::size_t written = 0U;
const esp_err_t err =
i2s_channel_write(gTxHandle, samples, sampleCount * sizeof(std::int32_t),
&written, portMAX_DELAY);
return err == ESP_OK;
}
} // namespace esp32_i2s_sink