From 220e4fab27b7f419b3e89e2f2d28ae789aac09d7 Mon Sep 17 00:00:00 2001
From: wangzhibo <wangzhibo@shsening.com>
Date: 星期四, 08 十月 2026 16:29:55 +0800
Subject: [PATCH] 5种场景模式家长端ok
---
xiaozhi-esp32-2.2.6/main/application.cc | 359 +++++++++++++++++++++++++++++++++++++++++++++++++++--------
1 files changed, 308 insertions(+), 51 deletions(-)
diff --git a/xiaozhi-esp32-2.2.6/main/application.cc b/xiaozhi-esp32-2.2.6/main/application.cc
index f4cb306..ec1cdfb 100644
--- a/xiaozhi-esp32-2.2.6/main/application.cc
+++ b/xiaozhi-esp32-2.2.6/main/application.cc
@@ -16,6 +16,7 @@
#include <cstring>
#include <memory>
#include <esp_log.h>
+#include <esp_heap_caps.h>
#include <cJSON.h>
#include <driver/gpio.h>
#include <arpa/inet.h>
@@ -25,7 +26,6 @@
extern "C" void SDCard_Pause_Play(void) __attribute__((weak));
extern "C" void SDCard_Resume_Play(void) __attribute__((weak));
-extern "C" void SDCard_Stop_Play(void) __attribute__((weak));
extern "C" bool SDCard_Is_Playing(void) __attribute__((weak));
extern "C" bool SDCard_Is_Paused(void) __attribute__((weak));
@@ -37,6 +37,16 @@
return SDCard_Is_Paused && SDCard_Is_Paused();
}
+static bool ShouldHoldIdleForLessonWork() {
+ return Board_IsLessonSyncing() || Board_IsRepeaterTask() || Board_IsShadowingTask() ||
+ Board_IsWaitingStartPhrase();
+}
+
+static bool IsLessonDetectText(const std::string& text) {
+ return text.rfind("[lesson_sync]", 0) == 0 ||
+ text.rfind("[app_task]english_learning|", 0) == 0;
+}
+
static void PauseSdCardMp3() {
if (SDCard_Pause_Play) {
SDCard_Pause_Play();
@@ -46,12 +56,6 @@
static void ResumeSdCardMp3() {
if (SDCard_Resume_Play) {
SDCard_Resume_Play();
- }
-}
-
-static void StopSdCardMp3() {
- if (SDCard_Stop_Play) {
- SDCard_Stop_Play();
}
}
@@ -240,6 +244,7 @@
}
void Application::Run() {
+ main_loop_task_ = xTaskGetCurrentTaskHandle();
// Set the priority of the main task to 10
vTaskPrioritySet(nullptr, 10);
@@ -331,6 +336,7 @@
display->UpdateStatusBar();
auto state = GetDeviceState();
if (state == kDeviceStateConnecting || state == kDeviceStateSpeaking ||
+ state == kDeviceStateActivating ||
(state == kDeviceStateIdle && suppress_wake_until_listen_)) {
audio_service_.KeepCodecReadyForPlayback();
}
@@ -341,10 +347,16 @@
if (greeting_listen_wait_ticks_ > 0) {
greeting_listen_wait_ticks_--;
if (greeting_listen_wait_ticks_ == 0) {
- ESP_LOGW(TAG, "greeting TTS missing, start listening");
- SetListeningMode(GetDefaultListeningMode());
+ if (ShouldHoldIdleForLessonWork()) {
+ ESP_LOGI(TAG, "skip greeting listen while lesson task");
+ } else {
+ ESP_LOGW(TAG, "greeting TTS missing, start listening");
+ SetListeningMode(GetDefaultListeningMode());
+ }
}
}
+ } else if (Board_IsLessonSyncing()) {
+ boot_wake_restore_ticks_ = 0;
} else if (++boot_wake_restore_ticks_ >= 20) {
ESP_LOGW(TAG, "boot websocket not opened, restore wake word");
suppress_wake_until_listen_ = false;
@@ -358,7 +370,7 @@
speaking_started_us_ > 0) {
const int64_t wait_ms = (esp_timer_get_time() - speaking_started_us_) / 1000;
if (wait_ms >= 1000 && (wait_ms / 1000) <= 8) {
- ESP_LOGW(TAG, "TTS audio still missing after %lldms", (long long)wait_ms);
+ ESP_LOGW(TAG, "TTS audio still missing after %dms", (int)wait_ms);
}
}
if (protocol_ && IsSdCardMp3Playing() && protocol_->IsAudioChannelConnected()) {
@@ -379,7 +391,33 @@
pending_detect_text_.clear();
}
}
+ if (hold_idle_for_prompt_ && !pending_detect_text_.empty() &&
+ GetDeviceState() == kDeviceStateIdle &&
+ protocol_ && protocol_->IsAudioChannelConnected()) {
+ if (pending_detect_retry_ticks_ > 0) {
+ pending_detect_retry_ticks_--;
+ } else if (pending_detect_retries_left_ > 0) {
+ pending_detect_retries_left_--;
+ pending_detect_retry_ticks_ = 2;
+ ESP_LOGW(TAG, "retry app_task detect: %s left=%d",
+ pending_detect_text_.c_str(), pending_detect_retries_left_);
+ SendListenDetectNow(pending_detect_text_);
+ } else if (IsLessonDetectText(pending_detect_text_) || ShouldHoldIdleForLessonWork()) {
+ ESP_LOGW(TAG, "lesson detect unanswered, keep idle: %s",
+ pending_detect_text_.c_str());
+ pending_detect_text_.clear();
+ pending_detect_retries_left_ = 0;
+ pending_detect_retry_ticks_ = 0;
+ } else {
+ ESP_LOGW(TAG, "app_task detect unanswered, start listening");
+ hold_idle_for_prompt_ = false;
+ suppress_wake_until_listen_ = false;
+ pending_detect_text_.clear();
+ SetListeningMode(GetDefaultListeningMode());
+ }
+ }
+ Board_PollPendingLessonPlayback();
// Print debug info every 10 seconds
if (clock_ticks_ % 10 == 0) {
SystemInfo::PrintHeapStats();
@@ -621,6 +659,10 @@
if (GetDeviceState() == kDeviceStateIdle) {
break;
}
+ // 婵�娲昏姹傚け璐ユ椂鍐嶆挱涓�閬� 6 浣嶇爜锛岄伩鍏嶅彧鍦ㄥ紑鏈虹灛闂存挱涓�娆¤鍚紡銆�
+ if (ota_->HasActivationCode() && i < 9) {
+ ShowActivationCode(ota_->GetActivationCode(), ota_->GetActivationMessage());
+ }
}
}
}
@@ -666,10 +708,13 @@
}
if (state == kDeviceStateListening || state == kDeviceStateIdle) {
tts_audio_expected_ = true;
+ if (!ShouldHoldIdleForLessonWork()) {
+ hold_idle_for_prompt_ = false;
+ }
}
tts_response_started_ = true;
- ESP_LOGI(TAG, "TTS audio packet bytes=%u expected=%d ready=%d state=%d",
+ ESP_LOGD(TAG, "TTS audio packet bytes=%u expected=%d ready=%d state=%d",
(unsigned)shared_packet->payload.size(),
tts_audio_expected_ ? 1 : 0,
tts_speaking_state_ready_ ? 1 : 0,
@@ -695,12 +740,10 @@
ESP_LOGI(TAG, "Skip channel-closed teardown while opening/reusing websocket");
return;
}
- // 璺熻/澶嶈杩樺湪鎾椂涓嶈鍒� LOW_POWER锛氬悗闈㈣ˉ鍙� playlist_completed 浼� handshake timeout銆�
- if ((Board_IsShadowingTask() || Board_IsRepeaterTask()) && IsSdCardMp3Playing()) {
- board.SetPowerSaveLevel(PowerSaveLevel::PERFORMANCE);
- } else {
- board.SetPowerSaveLevel(PowerSaveLevel::LOW_POWER);
- }
+ // 閫氶亾鍏虫帀鍚庝細绔嬪埢鍒� idle銆佸姞杞藉敜閱掕瘝銆傝繖鏃跺啀杩� MAX_MODEM锛�
+ // MQTT 閲嶈繛浠诲姟鍜� phy_track_pll 浼氭妸鍐呴儴 SRAM 鎵撶┛锛孍SP_ERROR_CHECK 鐩存帴閲嶅惎銆�
+ // 淇濇寔 PERFORMANCE锛岀瓑 PowerSaveTimer 瓒呮椂鍚庡啀鐪熸鏂綉寰呭懡銆�
+ board.SetPowerSaveLevel(PowerSaveLevel::PERFORMANCE);
Schedule([this]() {
auto display = Board::GetInstance().GetDisplay();
opening_audio_channel_ = false;
@@ -716,6 +759,7 @@
tts_stop_pending_ = false;
tts_stop_waiting_ = false;
tts_idle_waiting_ = false;
+ tts_session_end_ = false;
tts_speaking_state_ready_ = false;
tts_activity_generation_++;
audio_service_.ResetDecoder();
@@ -748,11 +792,18 @@
if (strcmp(state->valuestring, "start") == 0) {
Schedule([this]() {
aborted_ = false;
+ if (!ShouldHoldIdleForLessonWork()) {
+ hold_idle_for_prompt_ = false;
+ }
+ pending_detect_text_.clear();
+ pending_detect_retries_left_ = 0;
+ pending_detect_retry_ticks_ = 0;
tts_audio_expected_ = true;
tts_response_started_ = false;
tts_audio_received_ = false;
tts_stop_pending_ = false;
tts_stop_waiting_ = false;
+ tts_session_end_ = false;
tts_playback_empty_ticks_ = 0;
tts_stop_wait_ticks_ = 0;
tts_activity_generation_++;
@@ -766,9 +817,15 @@
Board::GetInstance().GetAudioCodec()->output_enabled() ? 1 : 0);
});
} else if (strcmp(state->valuestring, "stop") == 0) {
- Schedule([this]() {
- ESP_LOGI(TAG, "TTS stop started=%d received=%d",
- tts_response_started_, tts_audio_received_);
+ auto session_end_item = cJSON_GetObjectItem(root, "session_end");
+ const bool session_end = cJSON_IsTrue(session_end_item) ||
+ (cJSON_IsNumber(session_end_item) && session_end_item->valuedouble != 0);
+ Schedule([this, session_end]() {
+ ESP_LOGI(TAG, "TTS stop started=%d received=%d session_end=%d",
+ tts_response_started_, tts_audio_received_, session_end ? 1 : 0);
+ if (session_end) {
+ tts_session_end_ = true;
+ }
tts_stop_pending_ = true;
FinishTtsResponseAfterPlayback();
});
@@ -785,13 +842,22 @@
return;
}
Schedule([this, display, message = std::string(text->valuestring)]() {
+ if (!ShouldHoldIdleForLessonWork()) {
+ hold_idle_for_prompt_ = false;
+ }
+ pending_detect_text_.clear();
+ pending_detect_retries_left_ = 0;
+ pending_detect_retry_ticks_ = 0;
tts_audio_expected_ = true;
tts_response_started_ = true;
tts_activity_generation_++;
tts_playback_empty_ticks_ = 0;
audio_service_.SetAudioPowerInhibit(true);
audio_service_.KeepCodecReadyForPlayback();
- if (IsSdCardMp3Playing() && !IsSdCardMp3Paused()) {
+ if (Board_OnStartPhraseSentence(message.c_str())) {
+ resume_sdcard_mp3_after_tts_ = false;
+ resume_sdcard_mp3_after_voice_ = false;
+ } else if (IsSdCardMp3Playing() && !IsSdCardMp3Paused()) {
PauseSdCardMp3();
resume_sdcard_mp3_after_tts_ = true;
}
@@ -890,6 +956,18 @@
protocol_->Start();
}
+static void WaitUntilActivationPlaybackIdle(AudioService& audio) {
+ audio.KeepCodecReadyForPlayback();
+ for (int i = 0; i < 150; ++i) {
+ if (audio.IsPlaybackQueueEmpty()) {
+ vTaskDelay(pdMS_TO_TICKS(150));
+ return;
+ }
+ audio.KeepCodecReadyForPlayback();
+ vTaskDelay(pdMS_TO_TICKS(100));
+ }
+}
+
void Application::ShowActivationCode(const std::string& code, const std::string& message) {
struct digit_sound {
char digit;
@@ -908,14 +986,24 @@
digit_sound{'9', Lang::Sounds::OGG_9}
}};
+ ESP_LOGI(TAG, "Speaking activation code: %s", code.c_str());
+ audio_service_.KeepCodecReadyForPlayback();
+ // 鍔熸斁鍒氭墦寮�鏃跺墠鍑犵櫨姣甯告槸闈欓煶锛屽厛绋充綇鍐嶆挱锛岄伩鍏嶅彧鍚埌鎻愮ず闊冲惉涓嶅埌鏁板瓧銆�
+ vTaskDelay(pdMS_TO_TICKS(400));
+
// This sentence uses 9KB of SRAM, so we need to wait for it to finish
Alert(Lang::Strings::ACTIVATION, message.c_str(), "link", Lang::Sounds::OGG_ACTIVATION);
+ WaitUntilActivationPlaybackIdle(audio_service_);
for (const auto& digit : code) {
auto it = std::find_if(digit_sounds.begin(), digit_sounds.end(),
[digit](const digit_sound& ds) { return ds.digit == digit; });
if (it != digit_sounds.end()) {
+ ESP_LOGI(TAG, "Playing activation digit: %c", digit);
+ audio_service_.KeepCodecReadyForPlayback();
audio_service_.PlaySound(it->sound);
+ WaitUntilActivationPlaybackIdle(audio_service_);
+ vTaskDelay(pdMS_TO_TICKS(250));
}
}
}
@@ -966,6 +1054,87 @@
return protocol_->SendWakeWordDetected(text);
}
+void Application::HoldIdleForServerPrompt() {
+ hold_idle_for_prompt_ = true;
+ suppress_wake_until_listen_ = true;
+ auto state = GetDeviceState();
+ if (state == kDeviceStateListening) {
+ SetDeviceState(kDeviceStateIdle);
+ }
+}
+
+void Application::ReleaseHoldIdle() {
+ hold_idle_for_prompt_ = false;
+ suppress_wake_until_listen_ = false;
+ boot_wake_restore_ticks_ = 0;
+}
+
+void Application::StartListeningAfterPrompt() {
+ SetListeningMode(GetDefaultListeningMode());
+}
+
+void Application::ArmPendingDetectRetry(const std::string& text, int retries) {
+ if (text.empty() || retries <= 0) {
+ return;
+ }
+ pending_detect_text_ = text;
+ pending_detect_retries_left_ = retries;
+ pending_detect_retry_ticks_ = 2;
+}
+
+bool Application::IsAudioChannelConnected() const {
+ return protocol_ && protocol_->IsAudioChannelConnected();
+}
+
+void Application::AnnounceReturnedToChat() {
+ pending_detect_text_.clear();
+ pending_detect_retries_left_ = 0;
+ pending_detect_retry_ticks_ = 0;
+ suppress_wake_until_listen_ = false;
+ audio_service_.KeepCodecReadyForPlayback();
+ auto display = Board::GetInstance().GetDisplay();
+ display->SetStatus("瀵硅瘽妯″紡");
+ display->SetEmotion("happy");
+ display->SetChatMessage("assistant", "鎴戜滑鏉ヨ亰澶╁惂銆�");
+ audio_service_.PlaySound(Lang::Sounds::OGG_SUCCESS);
+ ESP_LOGI(TAG, "announced local return to chat");
+}
+
+void Application::ReconnectAfterLesson(const std::string& detect) {
+ // 璇炬枃缁撴潫 WS 宸叉柇锛氬厛閲婃斁 AFE锛屽啀鎻℃墜琛ュ彂 skill_event銆�
+ // Server 浼氭挱銆屾硾鍚�/璺熻缁撴潫銆傛垜浠潵鑱婂ぉ鍚с�傘�嶈瀹屽啀 listening銆�
+ pending_detect_text_.clear();
+ pending_detect_retries_left_ = 0;
+ pending_detect_retry_ticks_ = 0;
+ suppress_wake_until_listen_ = true;
+ audio_service_.SetAudioPowerInhibit(false);
+ audio_service_.KeepCodecReadyForPlayback();
+ audio_service_.EnableVoiceProcessing(false);
+ audio_service_.EnableWakeWordDetection(false);
+ audio_service_.ReleaseAfeRuntime();
+
+ auto state = GetDeviceState();
+ if (state == kDeviceStateListening || state == kDeviceStateSpeaking) {
+ SetDeviceState(kDeviceStateIdle);
+ }
+
+ if (detect.empty() || !protocol_) {
+ ESP_LOGW(TAG, "lesson reconnect skipped, stay local chat");
+ AnnounceReturnedToChat();
+ audio_service_.EnableWakeWordDetection(true);
+ return;
+ }
+
+ // 娉涘惉鏃堕暱浼氭妸 WiFi 寰呮満鍏虫帀锛堣窡璇绘湁鍚獥鎵�浠ヤ笉浼氾級銆備笉瑕佺敤 IsNetworkReady()
+ // 鐩存帴鏀惧純锛氬悗闈� OpenAudioChannel 浼� EnsureNetworkReadyForWake锛岃繛涓婂啀鎾粨鏉熻銆�
+ ESP_LOGI(TAG, "lesson reconnect for %s", detect.c_str());
+ Board::GetInstance().SetPowerSaveLevel(PowerSaveLevel::PERFORMANCE);
+ SetDeviceState(kDeviceStateConnecting);
+ Schedule([this, detect]() {
+ ContinueOpenAudioChannelForDetect(detect);
+ });
+}
+
void Application::SendListenDetect(const std::string& text, bool open_if_idle) {
Schedule([this, text, open_if_idle]() {
if (!protocol_ || text.empty()) {
@@ -977,6 +1146,7 @@
Board::GetInstance().SetPowerSaveLevel(PowerSaveLevel::PERFORMANCE);
protocol_->MarkIncomingActivity();
if (protocol_->SendWakeWordDetected(text)) {
+ Board_NoteLessonStartDetectSent();
return;
}
ESP_LOGW(TAG, "detect send failed, reopen audio channel");
@@ -1089,6 +1259,18 @@
// AFE 璧锋潵鍚庡唴閮ㄥ爢鍙墿鍑� KB锛寈TaskCreate(8KB) 浼氱洿鎺ュけ璐ャ��
audio_service_.EnableVoiceProcessing(false);
audio_service_.EnableWakeWordDetection(false);
+ const size_t dma_before = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
+ ESP_LOGI(TAG, "OpenAudioChannel DMA largest=%u free=%u",
+ (unsigned)dma_before,
+ (unsigned)heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
+ if (dma_before < 8192) {
+ ESP_LOGW(TAG, "low DMA, release AFE before websocket");
+ audio_service_.ClearWakeWordBuffers();
+ audio_service_.ReleaseAfeRuntime();
+ vTaskDelay(pdMS_TO_TICKS(50));
+ ESP_LOGI(TAG, "OpenAudioChannel DMA after AFE release=%u",
+ (unsigned)heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA));
+ }
Board::GetInstance().SetPowerSaveLevel(PowerSaveLevel::PERFORMANCE);
audio_service_.SetAudioPowerInhibit(true);
@@ -1136,25 +1318,45 @@
listening_mode_ = GetDefaultListeningMode();
suppress_wake_until_listen_ = true;
if (!OpenAudioChannelWithRetry(2)) {
- pending_detect_text_ = detect_text;
- if (pending_detect_retries_left_ <= 0) {
- pending_detect_retries_left_ = 3;
+ // 璇炬枃缁撴潫鍚庨�氶亾宸叉鏃朵笉瑕佸弽澶� handshake锛氫細鍗″湪銆岃繛鎺ヤ腑銆嶅苟鍏虫帀鍞ら啋璇嶃��
+ const bool skip_retry = detect_text.rfind("[skill_event]", 0) == 0;
+ if (skip_retry) {
+ pending_detect_text_.clear();
+ pending_detect_retries_left_ = 0;
+ pending_detect_retry_ticks_ = 0;
+ } else {
+ pending_detect_text_ = detect_text;
+ if (pending_detect_retries_left_ <= 0) {
+ pending_detect_retries_left_ = 3;
+ }
+ pending_detect_retry_ticks_ = 3;
}
- pending_detect_retry_ticks_ = 3;
suppress_wake_until_listen_ = false;
- audio_service_.EnableWakeWordDetection(true);
SetDeviceState(kDeviceStateIdle);
- ESP_LOGW(TAG, "boot websocket failed, will retry detect in 3s");
+ if (skip_retry) {
+ ESP_LOGW(TAG, "websocket failed after lesson, keep idle chat without retry");
+ AnnounceReturnedToChat();
+ } else {
+ ESP_LOGW(TAG, "boot websocket failed, will retry detect in 3s");
+ }
+ if (IsLessonDetectText(detect_text) || ShouldHoldIdleForLessonWork()) {
+ suppress_wake_until_listen_ = true;
+ audio_service_.EnableWakeWordDetection(false);
+ } else {
+ audio_service_.EnableWakeWordDetection(true);
+ }
return;
}
pending_detect_text_.clear();
pending_detect_retries_left_ = 0;
pending_detect_retry_ticks_ = 0;
- greeting_listen_wait_ticks_ = 8;
+ greeting_listen_wait_ticks_ = (IsLessonDetectText(detect_text) || ShouldHoldIdleForLessonWork()) ? 0 : 8;
if (!detect_text.empty()) {
protocol_->MarkIncomingActivity();
- protocol_->SendWakeWordDetected(detect_text);
+ if (protocol_->SendWakeWordDetected(detect_text)) {
+ Board_NoteLessonStartDetectSent();
+ }
}
// 淇濇寔寰呭懡涓斾笉寮� AFE锛氭杩庤瘝浠� idle 杩� speaking銆傝瀹屽啀 listening 寮�楹︺��
SetDeviceState(kDeviceStateIdle);
@@ -1185,9 +1387,20 @@
if (!IsSdCardMp3Playing() || IsSdCardMp3Paused()) {
return;
}
- // 闊抽鍦ㄦ挱锛氫笉涓婅褰曢煶/闂茶亰锛屽彧鐣欑绾垮彛浠わ紙閫�鍑哄璇�/璺熻銆佹垜浠亰澶╁惂锛夈��
+ // 闊抽鍦ㄦ挱锛氫笉涓婅褰曢煶/闂茶亰锛屽彧鐣欑绾垮彛浠わ紙閫�鍑烘硾鍚�/璺熻銆佹垜浠亰澶╁惂锛夈��
+ // 璇炬枃杩樺湪璇� SD 鏃剁姝㈡媺璧� AFE锛氫細鎶� SDMMC DMA 闇�瑕佺殑杩炵画鍐呴儴 SRAM 鍚冨厜锛宖open abort銆�
audio_service_.EnableVoiceProcessing(false);
- audio_service_.EnableWakeWordDetection(audio_service_.IsAfeWakeWord());
+ const size_t largest_internal = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL);
+ if (!SDCard_Mp3OfflineWakeAllowed() || largest_internal < 32768) {
+ audio_service_.EnableWakeWordDetection(false);
+ if (!SDCard_Mp3OfflineWakeAllowed()) {
+ ESP_LOGD(TAG, "offline wake deferred until MP3 cached");
+ } else {
+ ESP_LOGW(TAG, "skip offline wake, internal largest=%u", (unsigned)largest_internal);
+ }
+ } else {
+ audio_service_.EnableWakeWordDetection(audio_service_.IsAfeWakeWord());
+ }
if (GetDeviceState() != kDeviceStateListening) {
return;
}
@@ -1241,7 +1454,9 @@
void Application::CloseShadowingListenWindow()
{
audio_service_.EnableVoiceProcessing(false);
- audio_service_.EnableWakeWordDetection(audio_service_.IsAfeWakeWord());
+ // 涓嬩竴鍙ヨ繕瑕� fopen/瑙g爜锛屻�岃窡璇荤粨鏉熴�峊TS 杩樿 mbedtls AES銆傝繖閲屼笉瑕佺珛鍒绘媺璧�
+ // AFE 鍞ら啋璇嶏紝鍚﹀垯鍐呴儴 SRAM 鍙墿 1KB 杩炵画鍧楋紝SD DMA / SSL 閮戒細澶辫触銆�
+ audio_service_.EnableWakeWordDetection(false);
if (protocol_ && protocol_->IsAudioChannelOpened()) {
while (auto packet = audio_service_.PopPacketFromSendQueue()) {
if (!protocol_->SendAudio(std::move(packet))) {
@@ -1326,7 +1541,21 @@
if (state == kDeviceStateIdle) {
PauseSdCardMp3ForVoice();
- audio_service_.EncodeWakeWord();
+ size_t dma_largest = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
+ // 鍐嶈鍚庡敜閱掕瘝 AFE 杩樺崰鐫�鍐呴儴鍫嗭紝DMA 寰�寰�涓嶅缂栫爜 opus銆�
+ // 鏃ч�昏緫浼氳烦杩囩紪鐮佸嵈缁х画闃诲绛� opus锛屼富寰幆鍗℃锛�15s 鍚� codec 鎺夌數锛屽儚杞叧鏈恒��
+ if (dma_largest < 12288) {
+ ESP_LOGW(TAG, "low DMA before wake handshake, release AFE largest=%u",
+ (unsigned)dma_largest);
+ audio_service_.ReleaseAfeRuntime();
+ dma_largest = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
+ }
+ if (dma_largest >= 8192) {
+ audio_service_.EncodeWakeWord();
+ } else {
+ ESP_LOGW(TAG, "skip wake opus encode, DMA largest=%u", (unsigned)dma_largest);
+ audio_service_.AbortWakeWordEncode();
+ }
auto wake_word = audio_service_.GetLastWakeWord();
if (!protocol_->IsAudioChannelOpened()) {
@@ -1429,6 +1658,7 @@
tts_stop_pending_ = false;
tts_stop_waiting_ = false;
tts_idle_waiting_ = false;
+ tts_session_end_ = false;
tts_speaking_state_ready_ = false;
tts_activity_generation_++;
}
@@ -1437,16 +1667,18 @@
display->ClearChatMessages(); // Clear messages first
display->SetEmotion("neutral"); // Then set emotion (wechat mode checks child count)
audio_service_.EnableVoiceProcessing(false);
- if (suppress_wake_until_listen_) {
+ // 鍐嶈鏂嚎浼� speaking鈫抣istening鈫抜dle锛歱rocessor AFE 娌¢噴鏀惧氨鎶婂敜閱掕瘝 AFE 鎷夎捣鏉ワ紝
+ // audio_detection 寤轰笉璧锋潵锛宖eed 鍒峰睆鐩村埌鍐呴儴 SRAM 鎵撶┛銆�
+ if (!suppress_wake_until_listen_) {
+ audio_service_.ReleaseVoiceProcessorRuntime();
+ }
+ if (IsSdCardMp3Playing() && !IsSdCardMp3Paused()) {
+ ApplyMp3CommandOnlyMic();
+ } else if (suppress_wake_until_listen_ || Board_IsLessonSyncing() ||
+ Board_IsRepeaterTask() || Board_IsShadowingTask()) {
audio_service_.EnableWakeWordDetection(false);
audio_service_.SetAudioPowerInhibit(true);
audio_service_.KeepCodecReadyForPlayback();
- } else if (Board_IsRepeaterTask()) {
- audio_service_.EnableVoiceProcessing(false);
- audio_service_.EnableWakeWordDetection(audio_service_.IsAfeWakeWord());
- audio_service_.SetAudioPowerInhibit(false);
- } else if (IsSdCardMp3Playing() && !IsSdCardMp3Paused()) {
- ApplyMp3CommandOnlyMic();
} else {
audio_service_.EnableWakeWordDetection(true);
audio_service_.SetAudioPowerInhibit(false);
@@ -1522,6 +1754,10 @@
// Do nothing
break;
}
+}
+
+bool Application::IsOnMainLoop() const {
+ return main_loop_task_ != nullptr && xTaskGetCurrentTaskHandle() == main_loop_task_;
}
void Application::Schedule(std::function<void()>&& callback) {
@@ -1710,6 +1946,7 @@
tts_stop_pending_ = false;
tts_stop_waiting_ = false;
tts_idle_waiting_ = false;
+ tts_session_end_ = false;
tts_speaking_state_ready_ = false;
tts_activity_generation_++;
@@ -1770,20 +2007,28 @@
}
void Application::FinishTtsResponse() {
+ const bool end_session = tts_session_end_;
+ const bool hold_idle = hold_idle_for_prompt_;
tts_audio_expected_ = false;
tts_response_started_ = false;
tts_audio_received_ = false;
tts_stop_pending_ = false;
tts_stop_waiting_ = false;
tts_idle_waiting_ = false;
+ tts_session_end_ = false;
tts_speaking_state_ready_ = false;
tts_playback_empty_ticks_ = 0;
tts_stop_wait_ticks_ = 0;
speaking_started_us_ = 0;
audio_service_.SetAudioPowerInhibit(false);
- suppress_wake_until_listen_ = false;
- pending_detect_text_.clear();
- pending_detect_retries_left_ = 0;
+ if (hold_idle || ShouldHoldIdleForLessonWork()) {
+ suppress_wake_until_listen_ = true;
+ } else {
+ suppress_wake_until_listen_ = false;
+ pending_detect_text_.clear();
+ pending_detect_retries_left_ = 0;
+ pending_detect_retry_ticks_ = 0;
+ }
ResumeSdCardMp3AfterVoice();
if (resume_sdcard_mp3_after_tts_ && !sdcard_mp3_stop_requested_) {
resume_sdcard_mp3_after_tts_ = false;
@@ -1792,8 +2037,12 @@
resume_sdcard_mp3_after_tts_ = false;
}
if (GetDeviceState() == kDeviceStateSpeaking) {
- if (listening_mode_ == kListeningModeManualStop ||
+ if (end_session || hold_idle || ShouldHoldIdleForLessonWork() ||
+ listening_mode_ == kListeningModeManualStop ||
(IsSdCardMp3Playing() && !IsSdCardMp3Paused())) {
+ ESP_LOGI(TAG, "TTS done, return to idle session_end=%d hold=%d lesson=%d",
+ end_session ? 1 : 0, hold_idle ? 1 : 0,
+ ShouldHoldIdleForLessonWork() ? 1 : 0);
SetDeviceState(kDeviceStateIdle);
} else {
listening_mode_ = GetDefaultListeningMode();
@@ -1803,6 +2052,7 @@
if (IsSdCardMp3Playing() && !IsSdCardMp3Paused()) {
ApplyMp3CommandOnlyMic();
}
+ Board_FlushPendingLessonPlayback();
if (power_off_requested_) {
power_off_requested_ = false;
Board_Enter_Power_Off();
@@ -1866,8 +2116,9 @@
tts_stop_wait_ticks_++;
const int empty_needed = tts_audio_received_ ? 1 : 2;
if (playback_empty && tts_playback_empty_ticks_ >= empty_needed) {
- ESP_LOGI(TAG, "TTS done, return to listen empty=%d wait=%d received=%d",
- tts_playback_empty_ticks_, tts_stop_wait_ticks_, tts_audio_received_);
+ ESP_LOGI(TAG, "TTS done empty=%d wait=%d received=%d session_end=%d",
+ tts_playback_empty_ticks_, tts_stop_wait_ticks_, tts_audio_received_,
+ tts_session_end_ ? 1 : 0);
FinishTtsResponse();
} else if (!playback_empty && tts_stop_wait_ticks_ >= 30) {
ESP_LOGW(TAG, "TTS playback stuck after stop, return to listen");
@@ -1878,13 +2129,13 @@
// 娆㈣繋璇�/闀垮洖绛旓細stop 娌″埌鏃讹紝鍙湪銆屼竴鐩存病鏀跺埌 opus銆嶆垨銆屾挱瀹屽悗闀挎椂闂寸┖闃熷垪銆嶆墠鍥炲惉銆�
// 涓嶈鐢ㄨ璇濆閽� 15s/闃熷垪绌� 2s锛氫腑鑻卞璇濋暱鍙ヤ細鍦ㄥ彞闂寸┖闅欐垨 15 绉掑琚帎鏂��
- if (!tts_audio_received_ && elapsed_ms >= 5000) {
- ESP_LOGW(TAG, "TTS stop missing, return to listen elapsed=%lldms received=0 empty=%d",
- (long long)elapsed_ms, tts_playback_empty_ticks_);
+ if (!tts_audio_received_ && elapsed_ms >= 10000) {
+ ESP_LOGW(TAG, "TTS stop missing, return to listen elapsed=%dms received=0 empty=%d",
+ (int)elapsed_ms, tts_playback_empty_ticks_);
FinishTtsResponse();
} else if (playback_empty && tts_audio_received_ && tts_playback_empty_ticks_ >= 8) {
- ESP_LOGW(TAG, "TTS stop missing after idle playback elapsed=%lldms empty=%d",
- (long long)elapsed_ms, tts_playback_empty_ticks_);
+ ESP_LOGW(TAG, "TTS stop missing after idle playback elapsed=%dms empty=%d",
+ (int)elapsed_ms, tts_playback_empty_ticks_);
FinishTtsResponse();
}
}
@@ -1901,6 +2152,12 @@
return false;
}
+ // 娉涘惉鏄� idle 鎾� SD MP3锛孉udioService 闃熷垪鏄┖鐨勶紝浼氳褰撴垚鍙紤鐪犮��
+ // 璺熻姣忓彞鏈夊惉绐楁墍浠ヤ笉浼氳繘 WiFi 寰呮満锛涙硾鍚細鎶婄綉鍏虫帀锛岀粨鏉熻灏卞彂涓嶅嚭鍘汇��
+ if (IsSdCardMp3Playing() || Board_IsRepeaterTask() || Board_IsShadowingTask()) {
+ return false;
+ }
+
if (!audio_service_.IsIdle()) {
return false;
}
--
Gitblit v1.9.1