diff --git a/Tactility/Private/Tactility/mcp/McpSystem.h b/Tactility/Private/Tactility/mcp/McpSystem.h index e4008b73..3bb049be 100644 --- a/Tactility/Private/Tactility/mcp/McpSystem.h +++ b/Tactility/Private/Tactility/mcp/McpSystem.h @@ -6,6 +6,7 @@ #include #include #include +#include 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 diff --git a/Tactility/Source/mcp/McpSystem.cpp b/Tactility/Source/mcp/McpSystem.cpp index d9c667fe..9676bf02 100644 --- a/Tactility/Source/mcp/McpSystem.cpp +++ b/Tactility/Source/mcp/McpSystem.cpp @@ -19,6 +19,7 @@ constexpr auto* TAG = "McpSystem"; #include #include #include +#include #include #include #include @@ -427,18 +428,34 @@ 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.i2sDevice == nullptr) { - set_error(error, "I2S device 'i2s0' was not found"); - return false; + if (state.audioStreamDevice == nullptr) { + device_for_each_of_type(&AUDIO_STREAM_TYPE, &state, [](Device* dev, void* ctx)->bool { + auto* out = static_cast(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,20 +490,38 @@ static void end_audio(McpSystemState& state) { } static bool configure_i2s(McpSystemState& state, int sample_rate, int channels, std::string& error) { - I2sConfig config = { - .communication_format = I2S_FORMAT_STAND_I2S, + 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(sample_rate), + .bits_per_sample = AUDIO_BITS_PER_SAMPLE, + .channel_left = 0, + .channel_right = static_cast(channels == 2 ? 1 : I2S_CHANNEL_NONE) + }; + device_lock(state.i2sDevice); + error_t result = i2s_controller_set_config(state.i2sDevice, &config); + device_unlock(state.i2sDevice); + if (result != ERROR_NONE) { + LOG_E(TAG, "I2S configuration failed: %d", result); + set_error(error, "Failed to configure I2S"); + return false; + } + return true; + } + AudioStreamConfig cfg = { .sample_rate = static_cast(sample_rate), .bits_per_sample = AUDIO_BITS_PER_SAMPLE, - .channel_left = 0, - .channel_right = static_cast(channels == 2 ? 1 : I2S_CHANNEL_NONE) + .channels = static_cast(channels), }; - - device_lock(state.i2sDevice); - error_t result = i2s_controller_set_config(state.i2sDevice, &config); - device_unlock(state.i2sDevice); - if (result != ERROR_NONE) { - LOG_E(TAG, "I2S configuration failed: %d", result); - set_error(error, "Failed to configure I2S"); + 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; @@ -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");