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/boards/sening-xiaozhi-s3/mp3_player.cc |  203 +++++++++++++++++++++++++++++++++++++-------------
 1 files changed, 149 insertions(+), 54 deletions(-)

diff --git a/xiaozhi-esp32-2.2.6/main/boards/sening-xiaozhi-s3/mp3_player.cc b/xiaozhi-esp32-2.2.6/main/boards/sening-xiaozhi-s3/mp3_player.cc
index ca97f2b..007bcaf 100644
--- a/xiaozhi-esp32-2.2.6/main/boards/sening-xiaozhi-s3/mp3_player.cc
+++ b/xiaozhi-esp32-2.2.6/main/boards/sening-xiaozhi-s3/mp3_player.cc
@@ -30,7 +30,7 @@
 
 typedef struct {
     int count;
-    sdcard_mp3_playlist_item_t items[SDCARD_MP3_PLAYLIST_MAX];
+    sdcard_mp3_playlist_item_t items[];
 } mp3_player_task_arg_t;
 
 static TaskHandle_t mp3_player_task_handle;
@@ -41,6 +41,7 @@
 static volatile int mp3_repeat_left;
 static TickType_t s_mp3_audible_started;
 static SemaphoreHandle_t s_shadowing_listen_closed;
+static volatile bool s_mp3_offline_wake_ok;
 
 static int16_t *s_pcm_a = nullptr;
 static int16_t *s_pcm_b = nullptr;
@@ -143,23 +144,110 @@
     return !mp3_stop_requested;
 }
 
-static void wait_for_sd_dma(void)
+static bool wait_for_internal_sram(size_t need_bytes, int tries)
 {
-    for (int i = 0; i < 20; ++i) {
+    for (int i = 0; i < tries; ++i) {
         size_t largest = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
-        if (largest >= 4096) {
-            return;
+        if (largest >= need_bytes) {
+            return true;
         }
-        ESP_LOGW(TAG, "wait SD DMA largest=%u try=%d", (unsigned)largest, i);
+        ESP_LOGW(TAG, "wait SD DMA largest=%u need=%u try=%d",
+                 (unsigned)largest, (unsigned)need_bytes, i);
         vTaskDelay(pdMS_TO_TICKS(40));
     }
+    return heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA) >= need_bytes;
+}
+
+static bool sd_internal_heap_ok_for_fopen(void)
+{
+    return heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA) >= 4096 &&
+           heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL) >= 2048;
+}
+
+static FILE *fopen_mp3_if_heap_ok(const char *path)
+{
+    wait_for_internal_sram(4096, 25);
+    if (!sd_internal_heap_ok_for_fopen()) {
+        ESP_LOGE(TAG, "refuse fopen, internal DMA heap too small: %s", path);
+        return nullptr;
+    }
+    return fopen(path, "rb");
+}
+
+static void ensure_shadowing_listen_sem(void);
+
+static void close_mp3_decode_resources(esp_audio_simple_dec_handle_t *decoder,
+                                       esp_ae_rate_cvt_handle_t *rate_cvt,
+                                       FILE **fp, uint8_t **file_buf)
+{
+    if (rate_cvt && *rate_cvt) {
+        esp_ae_rate_cvt_close(*rate_cvt);
+        *rate_cvt = nullptr;
+    }
+    if (decoder && *decoder) {
+        esp_audio_simple_dec_close(*decoder);
+        *decoder = nullptr;
+    }
+    if (fp && *fp) {
+        fclose(*fp);
+        *fp = nullptr;
+    }
+    if (file_buf && *file_buf) {
+        heap_caps_free(*file_buf);
+        *file_buf = nullptr;
+    }
+}
+
+static void apply_stop_afe_for_sd(bool leave_listening)
+{
+    auto &app = Application::GetInstance();
+    if (leave_listening) {
+        app.HoldIdleForServerPrompt();
+        if (app.GetDeviceState() == kDeviceStateSpeaking) {
+            app.AbortSpeaking(kAbortReasonNone);
+        }
+        if (app.GetDeviceState() == kDeviceStateListening) {
+            app.SetDeviceState(kDeviceStateIdle);
+        }
+    }
+    auto &audio = app.GetAudioService();
+    audio.EnableVoiceProcessing(false);
+    audio.EnableWakeWordDetection(false);
+    audio.ReleaseAfeRuntime();
+}
+
+static void stop_afe_for_sd_and_wait(bool leave_listening)
+{
+    s_mp3_offline_wake_ok = false;
+    auto &app = Application::GetInstance();
+    if (app.IsOnMainLoop()) {
+        apply_stop_afe_for_sd(leave_listening);
+        wait_for_internal_sram(4096, 40);
+        return;
+    }
+    ensure_shadowing_listen_sem();
+    xSemaphoreTake(s_shadowing_listen_closed, 0);
+    app.Schedule([leave_listening]() {
+        apply_stop_afe_for_sd(leave_listening);
+        if (s_shadowing_listen_closed != nullptr) {
+            xSemaphoreGive(s_shadowing_listen_closed);
+        }
+    });
+    if (xSemaphoreTake(s_shadowing_listen_closed, pdMS_TO_TICKS(2500)) != pdTRUE) {
+        ESP_LOGW(TAG, "pause wake for SD timeout");
+    }
+    wait_for_internal_sram(4096, 40);
+}
+
+static void pause_wake_for_sd(void)
+{
+    stop_afe_for_sd_and_wait(false);
 }
 
 static uint8_t *load_mp3_to_psram(const char *path, size_t *out_len)
 {
     *out_len = 0;
-    wait_for_sd_dma();
-    FILE *fp = fopen(path, "rb");
+    FILE *fp = fopen_mp3_if_heap_ok(path);
     if (!fp) {
         return nullptr;
     }
@@ -274,16 +362,6 @@
     return true;
 }
 
-static void pause_wake_for_sd(void)
-{
-    Application::GetInstance().Schedule([]() {
-        auto &audio = Application::GetInstance().GetAudioService();
-        audio.EnableVoiceProcessing(false);
-        audio.EnableWakeWordDetection(false);
-    });
-    vTaskDelay(pdMS_TO_TICKS(40));
-}
-
 static void wait_shadowing_listen(unsigned duration_ms)
 {
     unsigned wait_ms = Board_ShadowingListenWindowMs(duration_ms);
@@ -322,6 +400,7 @@
     if (xSemaphoreTake(s_shadowing_listen_closed, pdMS_TO_TICKS(2500)) != pdTRUE) {
         ESP_LOGW(TAG, "shadowing wait mic off timeout, force AFE stop");
         Application::GetInstance().GetAudioService().EnableVoiceProcessing(false);
+        Application::GetInstance().GetAudioService().EnableWakeWordDetection(false);
     }
 }
 
@@ -329,26 +408,26 @@
 {
     mp3_player_task_handle = xTaskGetCurrentTaskHandle();
     mp3_player_task_arg_t *task_arg = static_cast<mp3_player_task_arg_t *>(arg);
-    sdcard_mp3_playlist_item_t items[SDCARD_MP3_PLAYLIST_MAX];
-    int count = task_arg->count;
+    int count = task_arg ? task_arg->count : 0;
     if (count < 0) {
         count = 0;
     }
     if (count > SDCARD_MP3_PLAYLIST_MAX) {
         count = SDCARD_MP3_PLAYLIST_MAX;
     }
-    if (count > 0) {
-        memcpy(items, task_arg->items, sizeof(items[0]) * static_cast<size_t>(count));
-    }
-    bool power_off_after = count > 0 ? items[count - 1].power_off_after : false;
-    heap_caps_free(task_arg);
+    const sdcard_mp3_playlist_item_t *items = (task_arg && count > 0) ? task_arg->items : nullptr;
+    bool power_off_after = (items != nullptr) ? items[count - 1].power_off_after : false;
 
     auto codec = Board::GetInstance().GetAudioCodec();
-    if (!codec || count < 1) {
+    if (!codec || count < 1 || items == nullptr) {
         ESP_LOGE(TAG, "audio codec unavailable or empty playlist");
+        if (task_arg) {
+            heap_caps_free(task_arg);
+        }
         mp3_player_task_handle = nullptr;
         mp3_stop_requested = false;
         mp3_repeat_left = 0;
+        s_mp3_offline_wake_ok = false;
         Board_OnSkillPlaybackFinished(true, false, 0);
         vTaskDelete(nullptr);
         return;
@@ -371,30 +450,38 @@
         ESP_LOGE(TAG, "no memory for MP3 playback");
     }
 
+    s_mp3_offline_wake_ok = false;
     for (int item_i = 0; in_buf && out_buf && item_i < count && !mp3_stop_requested; ++item_i) {
     strlcpy(path, items[item_i].path, sizeof(path));
     float speed = SDCard_Clamp_MP3_Speed(items[item_i].speed);
     int repeat = items[item_i].repeat < 1 ? 1 : items[item_i].repeat;
-    if (item_i > 0) {
-        pause_wake_for_sd();
-    }
+    pause_wake_for_sd();
 
     size_t file_len = 0;
     uint8_t *file_buf = load_mp3_to_psram(path, &file_len);
+    if (!file_buf) {
+        pause_wake_for_sd();
+        file_buf = load_mp3_to_psram(path, &file_len);
+    }
     FILE *fp = nullptr;
     if (!file_buf) {
-        wait_for_sd_dma();
-        fp = fopen(path, "rb");
+        fp = fopen_mp3_if_heap_ok(path);
     }
     if (!file_buf && !fp) {
         ESP_LOGE(TAG, "open MP3 failed: %s", path);
         continue;
     }
 
-    if (!mp3_pause_requested) {
-        Application::GetInstance().Schedule([]() {
-            Application::GetInstance().ApplyMp3CommandOnlyMic();
-        });
+    // 鍏堟妸鏂囦欢璇昏繘 PSRAM锛屽啀寮�绂荤嚎鍙d护鍞ら啋銆傚弽杩囨潵浼氭妸 SD DMA 闇�瑕佺殑杩炵画鍐呴儴 SRAM 鍚冩帀銆�
+    if (file_buf) {
+        s_mp3_offline_wake_ok = true;
+        if (!mp3_pause_requested) {
+            Application::GetInstance().Schedule([]() {
+                Application::GetInstance().ApplyMp3CommandOnlyMic();
+            });
+        }
+    } else {
+        s_mp3_offline_wake_ok = false;
     }
 
     esp_audio_simple_dec_handle_t decoder = nullptr;
@@ -442,8 +529,7 @@
                         fp = nullptr;
                     }
                     vTaskDelay(pdMS_TO_TICKS(30));
-                    wait_for_sd_dma();
-                    fp = fopen(path, "rb");
+                    fp = fopen_mp3_if_heap_ok(path);
                     if (!fp) {
                         ESP_LOGE(TAG, "reopen MP3 failed: %s", path);
                         break;
@@ -578,6 +664,11 @@
                 unsigned duration_ms = s_mp3_audible_started
                     ? pdTICKS_TO_MS(xTaskGetTickCount() - s_mp3_audible_started)
                     : 0;
+                // 鍚獥瑕佸紑 AFE銆傚厛鍏虫帀 MP3 瑙g爜鍣紝閬垮厤鍜屽綍闊虫姠鍐呴儴 SRAM銆�
+                close_mp3_decode_resources(&decoder, &rate_cvt_handle, &fp, &file_buf);
+                audio_info_checked = false;
+                mp3_sample_rate = 0;
+                mp3_channels = 0;
                 Board_OnShadowingClipFinished(duration_ms);
                 wait_shadowing_listen(duration_ms);
                 wait_shadowing_mic_off();
@@ -585,22 +676,7 @@
             }
         }
 
-    if (rate_cvt_handle) {
-        esp_ae_rate_cvt_close(rate_cvt_handle);
-        rate_cvt_handle = nullptr;
-    }
-    if (decoder) {
-        esp_audio_simple_dec_close(decoder);
-        decoder = nullptr;
-    }
-    if (fp) {
-        fclose(fp);
-        fp = nullptr;
-    }
-    if (file_buf) {
-        heap_caps_free(file_buf);
-        file_buf = nullptr;
-    }
+    close_mp3_decode_resources(&decoder, &rate_cvt_handle, &fp, &file_buf);
     ESP_LOGI(TAG, "MP3 item %s: %s", mp3_stop_requested ? "stopped" : "finished", path);
     }
 
@@ -622,10 +698,15 @@
     unsigned duration_ms = s_mp3_audible_started
         ? pdTICKS_TO_MS(xTaskGetTickCount() - s_mp3_audible_started)
         : pdTICKS_TO_MS(xTaskGetTickCount() - play_started);
+    if (task_arg) {
+        heap_caps_free(task_arg);
+        task_arg = nullptr;
+    }
     mp3_player_task_handle = nullptr;
     mp3_stop_requested = false;
     mp3_pause_requested = false;
     mp3_repeat_left = 0;
+    s_mp3_offline_wake_ok = false;
     if (should_power_off) {
         Application::GetInstance().RequestPowerOffStandby();
     }
@@ -666,6 +747,7 @@
         return ESP_ERR_INVALID_ARG;
     }
     if (count > SDCARD_MP3_PLAYLIST_MAX) {
+        ESP_LOGW(TAG, "playlist truncated incoming=%d max=%d", count, SDCARD_MP3_PLAYLIST_MAX);
         count = SDCARD_MP3_PLAYLIST_MAX;
     }
 
@@ -679,7 +761,10 @@
     }
 
     mp3_player_task_arg_t *arg = static_cast<mp3_player_task_arg_t *>(
-        heap_caps_calloc(1, sizeof(mp3_player_task_arg_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
+        heap_caps_calloc(1,
+                         sizeof(mp3_player_task_arg_t) +
+                             sizeof(sdcard_mp3_playlist_item_t) * static_cast<size_t>(count),
+                         MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
     if (!arg) {
         return ESP_ERR_NO_MEM;
     }
@@ -748,6 +833,16 @@
     });
 }
 
+extern "C" void SDCard_PreparePlaybackAndWait(void)
+{
+    stop_afe_for_sd_and_wait(true);
+}
+
+extern "C" bool SDCard_Mp3OfflineWakeAllowed(void)
+{
+    return s_mp3_offline_wake_ok;
+}
+
 extern "C" bool SDCard_Is_Playing(void)
 {
     return mp3_player_task_handle != nullptr;

--
Gitblit v1.9.1