fix(mcp): use audio-stream instead of direct I2S - fixes fast mp3/voice playback

- old optimization bypassed audio-stream resampler (16k direct -> MCLK 4.096MHz vs 11.29MHz native)
- brick game correct because it used audio_stream path
- now MCP uses audio_stream_open_output/input + read/write matching native logger stack
- falls back to direct I2S only if audio-stream device missing
This commit is contained in:
Adolfo Reyna
2026-07-18 18:30:19 -04:00
parent 2aa8e7a839
commit 8970143f72
2 changed files with 120 additions and 31 deletions
@@ -6,6 +6,7 @@
#include <string>
#include <vector>
#include <tactility/device.h>
#include <tactility/drivers/audio_stream.h>
namespace tt::mcp {
@@ -25,6 +26,8 @@ struct McpSystemState {
// Audio device status
Device* i2sDevice = nullptr;
Device* audioStreamDevice = nullptr;
AudioStreamHandle audioHandle = nullptr;
volatile bool audioBusy = false;
volatile bool audioRunning = false; // Used to abort play/record loop
+104 -18
View File
@@ -19,6 +19,7 @@ constexpr auto* TAG = "McpSystem";
#include <esp_log.h>
#include <esp_heap_caps.h>
#include <tactility/drivers/i2s_controller.h>
#include <tactility/drivers/audio_stream.h>
#include <mbedtls/base64.h>
#include <Tactility/hal/power/PowerDevice.h>
#include <tactility/hal/Device.h>
@@ -427,19 +428,35 @@ std::string getScreenshotPbmBase64() {
return encoded;
}
// Audio Porting Helper
static void set_error(std::string& error, const std::string& message) {
error = message;
}
static bool get_audio_device(McpSystemState& state, std::string& error) {
// Keep i2sDevice lookup for backward compat but also resolve audio-stream device
if (state.i2sDevice == nullptr) {
state.i2sDevice = device_find_by_name("i2s0");
}
if (state.audioStreamDevice == nullptr) {
device_for_each_of_type(&AUDIO_STREAM_TYPE, &state, [](Device* dev, void* ctx)->bool {
auto* out = static_cast<McpSystemState*>(ctx);
out->audioStreamDevice = dev;
return false;
});
if (state.audioStreamDevice == nullptr) {
state.audioStreamDevice = device_find_by_name("audio-stream");
}
}
if (state.audioStreamDevice == nullptr) {
set_error(error, "Audio stream device not found");
// Fall back to i2s check to keep old path for recording without audio-stream? will fail later
if (state.i2sDevice == nullptr) {
set_error(error, "I2S device 'i2s0' was not found");
return false;
}
}
return true;
}
@@ -453,12 +470,18 @@ static bool begin_audio(McpSystemState& state, std::string& error) {
}
state.audioBusy = true;
state.audioRunning = true;
state.audioHandle = nullptr;
return true;
}
static void end_audio(McpSystemState& state) {
state.audioRunning = false;
if (state.audioHandle != nullptr) {
audio_stream_close(state.audioHandle);
state.audioHandle = nullptr;
}
state.audioBusy = false;
// legacy reset
if (state.i2sDevice != nullptr) {
device_lock(state.i2sDevice);
i2s_controller_reset(state.i2sDevice);
@@ -467,6 +490,12 @@ static void end_audio(McpSystemState& state) {
}
static bool configure_i2s(McpSystemState& state, int sample_rate, int channels, std::string& error) {
if (state.audioHandle != nullptr) {
audio_stream_close(state.audioHandle);
state.audioHandle = nullptr;
}
if (state.audioStreamDevice == nullptr) {
// Fallback to direct I2S if audio-stream not found (very old boards)
I2sConfig config = {
.communication_format = I2S_FORMAT_STAND_I2S,
.sample_rate = static_cast<uint32_t>(sample_rate),
@@ -474,7 +503,6 @@ static bool configure_i2s(McpSystemState& state, int sample_rate, int channels,
.channel_left = 0,
.channel_right = static_cast<int8_t>(channels == 2 ? 1 : I2S_CHANNEL_NONE)
};
device_lock(state.i2sDevice);
error_t result = i2s_controller_set_config(state.i2sDevice, &config);
device_unlock(state.i2sDevice);
@@ -485,6 +513,19 @@ static bool configure_i2s(McpSystemState& state, int sample_rate, int channels,
}
return true;
}
AudioStreamConfig cfg = {
.sample_rate = static_cast<uint32_t>(sample_rate),
.bits_per_sample = AUDIO_BITS_PER_SAMPLE,
.channels = static_cast<uint8_t>(channels),
};
error_t res = audio_stream_open_output(state.audioStreamDevice, &cfg, &state.audioHandle);
if (res != ERROR_NONE) {
LOG_E(TAG, "audio_stream_open_output failed: %d rate=%d ch=%d", res, sample_rate, channels);
set_error(error, "Failed to open audio stream");
return false;
}
return true;
}
static void apply_volume(uint8_t* data, size_t data_size, int volume) {
int16_t* samples = (int16_t*)data;
@@ -498,6 +539,32 @@ static void apply_volume(uint8_t* data, size_t data_size, int volume) {
static bool write_audio(McpSystemState& state, uint8_t* data, size_t data_size, int volume, std::string& error) {
apply_volume(data, data_size, volume);
if (state.audioStreamDevice != nullptr && state.audioHandle != nullptr) {
size_t offset = 0;
while (offset < data_size && state.audioRunning) {
size_t bytes_written = 0;
error_t result = audio_stream_write(
state.audioHandle,
data + offset,
data_size - offset,
&bytes_written,
pdMS_TO_TICKS(500)
);
if (result != ERROR_NONE || bytes_written == 0) {
LOG_E(TAG, "audio_stream_write failed: result=%d written=%u", result, (unsigned)bytes_written);
set_error(error, "Audio playback failed");
return false;
}
offset += bytes_written;
}
if (!state.audioRunning) {
set_error(error, "Audio playback was cancelled");
return false;
}
return true;
}
// Fallback direct I2S
size_t offset = 0;
while (offset < data_size && state.audioRunning) {
size_t bytes_written = 0;
@@ -523,6 +590,7 @@ static bool write_audio(McpSystemState& state, uint8_t* data, size_t data_size,
return true;
}
static bool resolve_file(const std::string& filename, char* path, size_t path_size, std::string& error) {
if (filename.empty() || filename.length() > 96 ||
filename.find("..") != std::string::npos ||
@@ -673,17 +741,17 @@ bool playTone(int frequency, int durationMs, int volume, std::string& error) {
}
}
// Apply volume already in samples amplitude, reuse write_audio path via audio_stream
size_t bytes_written = 0;
error_t result = i2s_controller_write(
state.i2sDevice,
samples,
count * sizeof(int16_t),
&bytes_written,
pdMS_TO_TICKS(250)
);
error_t result = ERROR_NONE;
if (state.audioHandle != nullptr) {
result = audio_stream_write(state.audioHandle, samples, count * sizeof(int16_t), &bytes_written, pdMS_TO_TICKS(500));
} else {
result = i2s_controller_write(state.i2sDevice, samples, count * sizeof(int16_t), &bytes_written, pdMS_TO_TICKS(250));
}
if (result != ERROR_NONE || bytes_written != count * sizeof(int16_t)) {
LOG_E(TAG, "Tone write failed: result=%d written=%u", result, (unsigned)bytes_written);
set_error(error, "I2S tone playback failed");
set_error(error, "Audio tone playback failed");
goto done;
}
generated += count;
@@ -710,10 +778,29 @@ bool recordVoice(int durationSec, const std::string& filename, size_t& recordedB
bool success = false;
FILE* file = NULL;
char path[256];
if (!resolve_file(filename, path, sizeof(path), error) ||
!configure_i2s(state, AUDIO_SAMPLE_RATE, 1, error)) {
if (!resolve_file(filename, path, sizeof(path), error)) {
goto done;
}
// For recording, open input stream
if (state.audioStreamDevice != nullptr) {
if (state.audioHandle != nullptr) {
audio_stream_close(state.audioHandle);
state.audioHandle = nullptr;
}
AudioStreamConfig in_cfg = {
.sample_rate = AUDIO_SAMPLE_RATE,
.bits_per_sample = AUDIO_BITS_PER_SAMPLE,
.channels = 1,
};
if (audio_stream_open_input(state.audioStreamDevice, &in_cfg, &state.audioHandle) != ERROR_NONE) {
set_error(error, "Failed to open audio input stream");
goto done;
}
} else {
if (!configure_i2s(state, AUDIO_SAMPLE_RATE, 1, error)) {
goto done;
}
}
file = fopen(path, "wb");
if (file == NULL) {
@@ -729,13 +816,12 @@ bool recordVoice(int durationSec, const std::string& filename, size_t& recordedB
size_t remaining = target - total;
size_t requested = remaining < sizeof(buffer) ? remaining : sizeof(buffer);
size_t bytes_read = 0;
error_t result = i2s_controller_read(
state.i2sDevice,
buffer,
requested,
&bytes_read,
pdMS_TO_TICKS(250)
);
error_t result = ERROR_NONE;
if (state.audioHandle != nullptr) {
result = audio_stream_read(state.audioHandle, buffer, requested, &bytes_read, pdMS_TO_TICKS(500));
} else {
result = i2s_controller_read(state.i2sDevice, buffer, requested, &bytes_read, pdMS_TO_TICKS(250));
}
if (result != ERROR_NONE || bytes_read == 0) {
LOG_E(TAG, "I2S read failed: result=%d read=%u", result, (unsigned)bytes_read);
set_error(error, "I2S recording failed");