Compare commits

...

7 Commits

Author SHA1 Message Date
Adolfo Reyna c7dc66e04b 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
2026-07-18 19:55:47 -04:00
Adolfo Reyna 15aad3f585 fix(files): launch mp3 player from file explorer - audio support
- SupportedFiles: add isSupportedAudioFile (.mp3/.wav/.ogg/.flac)
- Files View::viewFile: if audio file, loader->start one.tactility.mp3player with file bundle
- Tactility.cpp: hide mcpoverride (screensaver internal), keep mcpsettings visible
- Fixes MP3 no longer running from file explorer, plus audio fast playback already fixed via audio-stream (Mp3Player/BookPlayer/VoiceRecorder use audio_stream_open_output not direct i2s_controller_write)
- MCP dev coexistence already fixed: DisplayIdle lock inversion 3629ffef, WebServer serverEnabled=ws||mcp, unauth /api/mcp, video 8081/8083 vs dev 6666
2026-07-18 19:18:28 -04:00
Adolfo Reyna 9a307e522d 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
2026-07-18 18:30:19 -04:00
Adolfo Reyna c3abe79c90 fix(mcp): register McpSettings + McpOverride - Settings shows MCP Screen 2026-07-18 18:19:34 -04:00
Adolfo Reyna 05ce4335ff fix(mcp): register McpSettings+McpOverride in Tactility.cpp - show in Settings 2026-07-18 18:12:08 -04:00
Adolfo Reyna ec2a6c0b88 fix(es3c28p): MCP flash - PR#569 CYD+explicit srcs+no HAL, 512K cache, screensaver
- vendor CYD, useDeprecatedHal=false, CMake explicit SRCS TactilityKernel only
- module.cpp without HAL, retains es3c28p_module symbol for devicetree
- LVGL 512K PSRAM cache retained
- MCP: McpScreensaver full-screen on lv_layer_top, tap to close, triggered via ensureOverrideScreen()
- flashed to /dev/cu.usbmodem101 2884320 -> 1672312 hash verified
2026-07-18 18:09:08 -04:00
Adolfo Reyna 0966cd45a4 fix(es3c28p): PR#569 feedback + retain 512K cache - CYD vendor, explicit srcs, no HAL 2026-07-18 18:04:25 -04:00
12 changed files with 180 additions and 85 deletions
+2 -4
View File
@@ -1,6 +1,4 @@
file(GLOB_RECURSE SOURCE_FILES source/*.c*)
idf_component_register( idf_component_register(
SRCS ${SOURCE_FILES} SRCS "source/module.cpp"
REQUIRES TactilityKernel Tactility REQUIRES TactilityKernel
) )
+3 -1
View File
@@ -1,4 +1,4 @@
general.vendor=LCDWIKI general.vendor=CYD
general.name=ES3C28P general.name=ES3C28P
apps.launcherAppId=Launcher apps.launcherAppId=Launcher
@@ -20,5 +20,7 @@ lvgl.colorDepth=16
storage.userDataLocation=SD storage.userDataLocation=SD
dependencies.useDeprecatedHal=false
sdkconfig.CONFIG_LV_CACHE_DEF_SIZE=524288 sdkconfig.CONFIG_LV_CACHE_DEF_SIZE=524288
sdkconfig.CONFIG_LV_IMAGE_HEADER_CACHE_DEF_CNT=16 sdkconfig.CONFIG_LV_IMAGE_HEADER_CACHE_DEF_CNT=16
-4
View File
@@ -1,8 +1,4 @@
#include <tactility/module.h> #include <tactility/module.h>
#include <Tactility/hal/Configuration.h>
// Legacy placeholder (required until legacy HAL is cleaned up everywhere)
extern const tt::hal::Configuration hardwareConfiguration = {};
extern "C" { extern "C" {
@@ -553,9 +553,14 @@ error_t close_stream(AudioStreamHandle handle_base) {
Device* codec = is_input ? data->input_codec : data->output_codec; Device* codec = is_input ? data->input_codec : data->output_codec;
AudioStreamHandleImpl** slot = is_input ? &data->open_input : &data->open_output; 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); xSemaphoreTake(data->mutex, portMAX_DELAY);
if (handle->closing) { if (handle->closing) {
// Already being closed by another caller (e.g. concurrent set_enabled + app close).
xSemaphoreGive(data->mutex); xSemaphoreGive(data->mutex);
return ERROR_NONE; return ERROR_NONE;
} }
@@ -563,14 +568,16 @@ error_t close_stream(AudioStreamHandle handle_base) {
if (*slot == handle) { if (*slot == handle) {
*slot = nullptr; *slot = nullptr;
} }
bool other_still_open = (other_slot != nullptr && *other_slot != nullptr && *other_slot != reinterpret_cast<AudioStreamHandleImpl*>(1));
bool must_drain = (handle->busy_count > 0); bool must_drain = (handle->busy_count > 0);
bool should_close_codec = !codec_shared || !other_still_open;
xSemaphoreGive(data->mutex); xSemaphoreGive(data->mutex);
if (must_drain && handle->drain_semaphore != nullptr) { if (must_drain && handle->drain_semaphore != nullptr) {
xSemaphoreTake(handle->drain_semaphore, portMAX_DELAY); xSemaphoreTake(handle->drain_semaphore, portMAX_DELAY);
} }
if (codec != nullptr) { if (should_close_codec && codec != nullptr) {
audio_codec_close(codec); audio_codec_close(codec);
} }
+26 -6
View File
@@ -53,16 +53,36 @@ error_t open(Device* device, const struct AudioCodecStreamConfig* config) {
}; };
if (data->is_open) { if (data->is_open) {
// open_direction == BOTH already serves INPUT-only or OUTPUT-only requests on the // ES8311 is configured for WORK_MODE_BOTH, so an already-open device
// same sample settings -- only an exact direction mismatch (e.g. requesting BOTH // can serve the opposite direction without reopening, provided sample
// while opened for INPUT only) needs a reopen. // settings match. Promote open_direction to BOTH when we see a
bool direction_compatible = data->open_direction == config->direction // complementary request.
|| data->open_direction == AUDIO_CODEC_DIR_BOTH; 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 bool same_config = direction_compatible
&& data->open_sample_info.bits_per_sample == sample_info.bits_per_sample && data->open_sample_info.bits_per_sample == sample_info.bits_per_sample
&& data->open_sample_info.channel == sample_info.channel && data->open_sample_info.channel == sample_info.channel
&& data->open_sample_info.sample_rate == sample_info.sample_rate; && 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) { if (esp_codec_dev_open(data->codec_device, &sample_info) != ESP_CODEC_DEV_OK) {
@@ -1,11 +1,3 @@
#pragma once #pragma once
#include <string> #include <string>
namespace tt::app::files { bool isSupportedAppFile(const std::string& filename); bool isSupportedImageFile(const std::string& filename); bool isSupportedTextFile(const std::string& filename); bool isSupportedAudioFile(const std::string& filename); } // namespace
namespace tt::app::files {
bool isSupportedAppFile(const std::string& filename);
bool isSupportedImageFile(const std::string& filename);
bool isSupportedTextFile(const std::string& filename);
} // namespace
@@ -6,6 +6,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <tactility/device.h> #include <tactility/device.h>
#include <tactility/drivers/audio_stream.h>
namespace tt::mcp { namespace tt::mcp {
@@ -25,6 +26,8 @@ struct McpSystemState {
// Audio device status // Audio device status
Device* i2sDevice = nullptr; Device* i2sDevice = nullptr;
Device* audioStreamDevice = nullptr;
AudioStreamHandle audioHandle = nullptr;
volatile bool audioBusy = false; volatile bool audioBusy = false;
volatile bool audioRunning = false; // Used to abort play/record loop volatile bool audioRunning = false; // Used to abort play/record loop
@@ -37,6 +37,7 @@ class DisplayIdleService final : public Service {
bool backlightOff = false; bool backlightOff = false;
static void stopScreensaverCb(lv_event_t* e); static void stopScreensaverCb(lv_event_t* e);
void stopScreensaverLocked();
/** @pre Caller must hold LVGL lock */ /** @pre Caller must hold LVGL lock */
void activateScreensaver(); void activateScreensaver();
@@ -63,6 +64,7 @@ public:
* arbitrary threads while the timer is running. * arbitrary threads while the timer is running.
*/ */
void stopScreensaver(); void stopScreensaver();
void startMcpScreensaver();
/** /**
* Check if the screensaver is currently active. * Check if the screensaver is currently active.
+3
View File
@@ -151,6 +151,7 @@ namespace app {
namespace apwebserver { extern const AppManifest manifest; } namespace apwebserver { extern const AppManifest manifest; }
namespace crashdiagnostics { extern const AppManifest manifest; } namespace crashdiagnostics { extern const AppManifest manifest; }
namespace webserversettings { extern const AppManifest manifest; } namespace webserversettings { extern const AppManifest manifest; }
namespace mcpsettings { extern const AppManifest manifest; }
#if CONFIG_TT_TDECK_WORKAROUND == 1 #if CONFIG_TT_TDECK_WORKAROUND == 1
namespace keyboardsettings { extern const AppManifest manifest; } // T-Deck only for now namespace keyboardsettings { extern const AppManifest manifest; } // T-Deck only for now
namespace trackballsettings { extern const AppManifest manifest; } // T-Deck only for now namespace trackballsettings { extern const AppManifest manifest; } // T-Deck only for now
@@ -213,6 +214,8 @@ static void registerInternalApps() {
#ifdef ESP_PLATFORM #ifdef ESP_PLATFORM
addAppManifest(app::apwebserver::manifest); addAppManifest(app::apwebserver::manifest);
addAppManifest(app::webserversettings::manifest); addAppManifest(app::webserversettings::manifest);
addAppManifest(app::mcpsettings::manifest);
// mcpoverride internal only via McpScreensaver, not shown in launcher
addAppManifest(app::crashdiagnostics::manifest); addAppManifest(app::crashdiagnostics::manifest);
addAppManifest(app::development::manifest); addAppManifest(app::development::manifest);
#if defined(CONFIG_TT_TDECK_WORKAROUND) #if defined(CONFIG_TT_TDECK_WORKAROUND)
+1 -27
View File
@@ -1,29 +1,3 @@
#include <Tactility/StringUtils.h> #include <Tactility/StringUtils.h>
#include <Tactility/TactilityCore.h> #include <Tactility/TactilityCore.h>
namespace tt::app::files { constexpr auto* TAG = "Files"; bool isSupportedAppFile(const std::string& filename) { return filename.ends_with(".app"); } bool isSupportedImageFile(const std::string& filename) { return string::lowercase(filename).ends_with(".png"); } bool isSupportedTextFile(const std::string& filename) { std::string l=string::lowercase(filename); return l.ends_with(".txt")||l.ends_with(".ini")||l.ends_with(".json")||l.ends_with(".yaml")||l.ends_with(".yml")||l.ends_with(".lua")||l.ends_with(".js")||l.ends_with(".properties"); } bool isSupportedAudioFile(const std::string& filename) { std::string l=string::lowercase(filename); return l.ends_with(".mp3")||l.ends_with(".wav")||l.ends_with(".ogg")||l.ends_with(".flac"); } } // namespace
namespace tt::app::files {
constexpr auto* TAG = "Files";
bool isSupportedAppFile(const std::string& filename) {
return filename.ends_with(".app");
}
bool isSupportedImageFile(const std::string& filename) {
// Currently only the PNG library is built into Tactility
return string::lowercase(filename).ends_with(".png");
}
bool isSupportedTextFile(const std::string& filename) {
std::string filename_lower = string::lowercase(filename);
return filename_lower.ends_with(".txt") ||
filename_lower.ends_with(".ini") ||
filename_lower.ends_with(".json") ||
filename_lower.ends_with(".yaml") ||
filename_lower.ends_with(".yml") ||
filename_lower.ends_with(".lua") ||
filename_lower.ends_with(".js") ||
filename_lower.ends_with(".properties");
}
} // namespace tt::app::filebrowser
+10 -1
View File
@@ -1,4 +1,5 @@
#include <Tactility/app/files/SupportedFiles.h> #include <Tactility/app/files/SupportedFiles.h>
#include <Tactility/Bundle.h>
#include <Tactility/app/files/View.h> #include <Tactility/app/files/View.h>
#include <Tactility/Platform.h> #include <Tactility/Platform.h>
@@ -228,9 +229,17 @@ void View::viewFile(const std::string& path, const std::string& filename) {
if (kernel::getPlatform() == kernel::PlatformEsp) { if (kernel::getPlatform() == kernel::PlatformEsp) {
notes::start(processed_filepath); notes::start(processed_filepath);
} else { } else {
// Remove forward slash, because we need a relative path
notes::start(processed_filepath.substr(1)); notes::start(processed_filepath.substr(1));
} }
} else if (isSupportedAudioFile(filename)) {
#ifdef ESP_PLATFORM
auto bundle = std::make_shared<Bundle>();
bundle->putString("file", processed_filepath);
auto loader = service::loader::findLoaderService();
if (loader) {
loader->start("one.tactility.mp3player", bundle);
}
#endif
} else { } else {
LOG_W(TAG, "Opening files of this type is not supported"); LOG_W(TAG, "Opening files of this type is not supported");
} }
+107 -18
View File
@@ -19,6 +19,7 @@ constexpr auto* TAG = "McpSystem";
#include <esp_log.h> #include <esp_log.h>
#include <esp_heap_caps.h> #include <esp_heap_caps.h>
#include <tactility/drivers/i2s_controller.h> #include <tactility/drivers/i2s_controller.h>
#include <tactility/drivers/audio_stream.h>
#include <mbedtls/base64.h> #include <mbedtls/base64.h>
#include <Tactility/hal/power/PowerDevice.h> #include <Tactility/hal/power/PowerDevice.h>
#include <tactility/hal/Device.h> #include <tactility/hal/Device.h>
@@ -427,19 +428,35 @@ std::string getScreenshotPbmBase64() {
return encoded; return encoded;
} }
// Audio Porting Helper // Audio Porting Helper
static void set_error(std::string& error, const std::string& message) { static void set_error(std::string& error, const std::string& message) {
error = message; error = message;
} }
static bool get_audio_device(McpSystemState& state, std::string& error) { 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) { if (state.i2sDevice == nullptr) {
state.i2sDevice = device_find_by_name("i2s0"); 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) { if (state.i2sDevice == nullptr) {
set_error(error, "I2S device 'i2s0' was not found"); set_error(error, "I2S device 'i2s0' was not found");
return false; return false;
} }
}
return true; return true;
} }
@@ -453,12 +470,18 @@ static bool begin_audio(McpSystemState& state, std::string& error) {
} }
state.audioBusy = true; state.audioBusy = true;
state.audioRunning = true; state.audioRunning = true;
state.audioHandle = nullptr;
return true; return true;
} }
static void end_audio(McpSystemState& state) { static void end_audio(McpSystemState& state) {
state.audioRunning = false; state.audioRunning = false;
if (state.audioHandle != nullptr) {
audio_stream_close(state.audioHandle);
state.audioHandle = nullptr;
}
state.audioBusy = false; state.audioBusy = false;
// legacy reset
if (state.i2sDevice != nullptr) { if (state.i2sDevice != nullptr) {
device_lock(state.i2sDevice); device_lock(state.i2sDevice);
i2s_controller_reset(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) { 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 = { I2sConfig config = {
.communication_format = I2S_FORMAT_STAND_I2S, .communication_format = I2S_FORMAT_STAND_I2S,
.sample_rate = static_cast<uint32_t>(sample_rate), .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_left = 0,
.channel_right = static_cast<int8_t>(channels == 2 ? 1 : I2S_CHANNEL_NONE) .channel_right = static_cast<int8_t>(channels == 2 ? 1 : I2S_CHANNEL_NONE)
}; };
device_lock(state.i2sDevice); device_lock(state.i2sDevice);
error_t result = i2s_controller_set_config(state.i2sDevice, &config); error_t result = i2s_controller_set_config(state.i2sDevice, &config);
device_unlock(state.i2sDevice); device_unlock(state.i2sDevice);
@@ -485,6 +513,19 @@ static bool configure_i2s(McpSystemState& state, int sample_rate, int channels,
} }
return true; 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) { static void apply_volume(uint8_t* data, size_t data_size, int volume) {
int16_t* samples = (int16_t*)data; 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) { static bool write_audio(McpSystemState& state, uint8_t* data, size_t data_size, int volume, std::string& error) {
apply_volume(data, data_size, volume); 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; size_t offset = 0;
while (offset < data_size && state.audioRunning) { while (offset < data_size && state.audioRunning) {
size_t bytes_written = 0; size_t bytes_written = 0;
@@ -523,6 +590,7 @@ static bool write_audio(McpSystemState& state, uint8_t* data, size_t data_size,
return true; return true;
} }
static bool resolve_file(const std::string& filename, char* path, size_t path_size, std::string& error) { static bool resolve_file(const std::string& filename, char* path, size_t path_size, std::string& error) {
if (filename.empty() || filename.length() > 96 || if (filename.empty() || filename.length() > 96 ||
filename.find("..") != std::string::npos || 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; size_t bytes_written = 0;
error_t result = i2s_controller_write( error_t result = ERROR_NONE;
state.i2sDevice, if (state.audioHandle != nullptr) {
samples, result = audio_stream_write(state.audioHandle, samples, count * sizeof(int16_t), &bytes_written, pdMS_TO_TICKS(500));
count * sizeof(int16_t), } else {
&bytes_written, result = i2s_controller_write(state.i2sDevice, samples, count * sizeof(int16_t), &bytes_written, pdMS_TO_TICKS(250));
pdMS_TO_TICKS(250) }
);
if (result != ERROR_NONE || bytes_written != count * sizeof(int16_t)) { if (result != ERROR_NONE || bytes_written != count * sizeof(int16_t)) {
LOG_E(TAG, "Tone write failed: result=%d written=%u", result, (unsigned)bytes_written); 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; goto done;
} }
generated += count; generated += count;
@@ -710,10 +778,32 @@ bool recordVoice(int durationSec, const std::string& filename, size_t& recordedB
bool success = false; bool success = false;
FILE* file = NULL; FILE* file = NULL;
char path[256]; char path[256];
if (!resolve_file(filename, path, sizeof(path), error) || if (!resolve_file(filename, path, sizeof(path), error)) {
!configure_i2s(state, AUDIO_SAMPLE_RATE, 1, error)) {
goto done; 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;
}
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;
}
}
file = fopen(path, "wb"); file = fopen(path, "wb");
if (file == NULL) { if (file == NULL) {
@@ -729,13 +819,12 @@ bool recordVoice(int durationSec, const std::string& filename, size_t& recordedB
size_t remaining = target - total; size_t remaining = target - total;
size_t requested = remaining < sizeof(buffer) ? remaining : sizeof(buffer); size_t requested = remaining < sizeof(buffer) ? remaining : sizeof(buffer);
size_t bytes_read = 0; size_t bytes_read = 0;
error_t result = i2s_controller_read( error_t result = ERROR_NONE;
state.i2sDevice, if (state.audioHandle != nullptr) {
buffer, result = audio_stream_read(state.audioHandle, buffer, requested, &bytes_read, pdMS_TO_TICKS(500));
requested, } else {
&bytes_read, result = i2s_controller_read(state.i2sDevice, buffer, requested, &bytes_read, pdMS_TO_TICKS(250));
pdMS_TO_TICKS(250) }
);
if (result != ERROR_NONE || bytes_read == 0) { if (result != ERROR_NONE || bytes_read == 0) {
LOG_E(TAG, "I2S read failed: result=%d read=%u", result, (unsigned)bytes_read); LOG_E(TAG, "I2S read failed: result=%d read=%u", result, (unsigned)bytes_read);
set_error(error, "I2S recording failed"); set_error(error, "I2S recording failed");