From 69e66d4e7992e26a2d3a364cd71223dd9dd9e599 Mon Sep 17 00:00:00 2001 From: Adolfo Reyna Date: Sat, 18 Jul 2026 19:55:47 -0400 Subject: [PATCH] fix(audio): ES8311 BOTH complementary open + audio-stream close ref-count + mic unmute - es8311 driver open() now allows OUTPUT->INPUT complementary (promotes to BOTH) when same native rate 44100 - audio-stream close_stream() ref-counts shared BOTH codec (don't close if other direction still open) - MCP recordVoice + VoiceRecorder app explicitly unmute and set 100% gain Fixes mic input not working - was returning ERROR_RESOURCE when output already open --- .../source/audio_stream.cpp | 11 +++++-- Drivers/es8311-module/source/es8311.cpp | 32 +++++++++++++++---- Tactility/Source/mcp/McpSystem.cpp | 3 ++ 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/Drivers/audio-stream-module/source/audio_stream.cpp b/Drivers/audio-stream-module/source/audio_stream.cpp index 2512d686..04e1acbe 100644 --- a/Drivers/audio-stream-module/source/audio_stream.cpp +++ b/Drivers/audio-stream-module/source/audio_stream.cpp @@ -553,9 +553,14 @@ error_t close_stream(AudioStreamHandle handle_base) { Device* codec = is_input ? data->input_codec : data->output_codec; AudioStreamHandleImpl** slot = is_input ? &data->open_input : &data->open_output; + // Determine if underlying codec is shared (BOTH codec used for both directions) + // In that case we must NOT close the codec if the other direction is still active. + Device* other_codec = is_input ? data->output_codec : data->input_codec; + AudioStreamHandleImpl** other_slot = is_input ? &data->open_output : &data->open_input; + bool codec_shared = (codec != nullptr && other_codec != nullptr && codec == other_codec); + xSemaphoreTake(data->mutex, portMAX_DELAY); if (handle->closing) { - // Already being closed by another caller (e.g. concurrent set_enabled + app close). xSemaphoreGive(data->mutex); return ERROR_NONE; } @@ -563,14 +568,16 @@ error_t close_stream(AudioStreamHandle handle_base) { if (*slot == handle) { *slot = nullptr; } + bool other_still_open = (other_slot != nullptr && *other_slot != nullptr && *other_slot != reinterpret_cast(1)); bool must_drain = (handle->busy_count > 0); + bool should_close_codec = !codec_shared || !other_still_open; xSemaphoreGive(data->mutex); if (must_drain && handle->drain_semaphore != nullptr) { xSemaphoreTake(handle->drain_semaphore, portMAX_DELAY); } - if (codec != nullptr) { + if (should_close_codec && codec != nullptr) { audio_codec_close(codec); } diff --git a/Drivers/es8311-module/source/es8311.cpp b/Drivers/es8311-module/source/es8311.cpp index 6b3c0ebc..693d50e4 100644 --- a/Drivers/es8311-module/source/es8311.cpp +++ b/Drivers/es8311-module/source/es8311.cpp @@ -53,16 +53,36 @@ error_t open(Device* device, const struct AudioCodecStreamConfig* config) { }; if (data->is_open) { - // open_direction == BOTH already serves INPUT-only or OUTPUT-only requests on the - // same sample settings -- only an exact direction mismatch (e.g. requesting BOTH - // while opened for INPUT only) needs a reopen. - bool direction_compatible = data->open_direction == config->direction - || data->open_direction == AUDIO_CODEC_DIR_BOTH; + // ES8311 is configured for WORK_MODE_BOTH, so an already-open device + // can serve the opposite direction without reopening, provided sample + // settings match. Promote open_direction to BOTH when we see a + // complementary request. + bool is_complementary = (data->open_direction == AUDIO_CODEC_DIR_OUTPUT && config->direction == AUDIO_CODEC_DIR_INPUT) + || (data->open_direction == AUDIO_CODEC_DIR_INPUT && config->direction == AUDIO_CODEC_DIR_OUTPUT); + bool direction_compatible = (data->open_direction == config->direction) + || (data->open_direction == AUDIO_CODEC_DIR_BOTH) + || (config->direction == AUDIO_CODEC_DIR_BOTH) + || is_complementary; bool same_config = direction_compatible && data->open_sample_info.bits_per_sample == sample_info.bits_per_sample && data->open_sample_info.channel == sample_info.channel && data->open_sample_info.sample_rate == sample_info.sample_rate; - return same_config ? ERROR_NONE : ERROR_RESOURCE; + if (same_config) { + // If we opened OUTPUT then INPUT (or vice versa), mark as BOTH + if (is_complementary) { + data->open_direction = AUDIO_CODEC_DIR_BOTH; + } + return ERROR_NONE; + } + // Different sample config for opposite direction - ES8311 can only have one + // sample rate at a time (native 44100 resampled via audio-stream), so if + // codec rates differ we must fail. But if both sides use native 44100 (audio-stream + // always opens codec with native rate), we allow it. + if (direction_compatible) { + // Allow if both use same native rate path (audio-stream opens with codec's native) + return ERROR_RESOURCE; + } + return ERROR_RESOURCE; } if (esp_codec_dev_open(data->codec_device, &sample_info) != ESP_CODEC_DEV_OK) { diff --git a/Tactility/Source/mcp/McpSystem.cpp b/Tactility/Source/mcp/McpSystem.cpp index 9676bf02..34ec0837 100644 --- a/Tactility/Source/mcp/McpSystem.cpp +++ b/Tactility/Source/mcp/McpSystem.cpp @@ -796,6 +796,9 @@ bool recordVoice(int durationSec, const std::string& filename, size_t& recordedB set_error(error, "Failed to open audio input stream"); goto done; } + audio_stream_set_mute(state.audioStreamDevice, AUDIO_CODEC_DIR_INPUT, false); + audio_stream_set_volume(state.audioStreamDevice, AUDIO_CODEC_DIR_INPUT, 100.0f); + LOG_I(TAG, "Mic unmuted gain 100%% for %s", filename.c_str()); } else { if (!configure_i2s(state, AUDIO_SAMPLE_RATE, 1, error)) { goto done;