From 659c470eba542383987aad227b7f918100f131fa Mon Sep 17 00:00:00 2001
From: wangzhibo <wangzhibo@shsening.com>
Date: 星期五, 11 九月 2026 15:09:05 +0800
Subject: [PATCH] 小智终端 5种 场景模式 OK

---
 xiaozhi-esp32-2.2.6/main/boards/sening-xiaozhi-s3/mp3_player.cc |  529 +++++++++++++++++++++++++++++++++++++++++++++++-----------
 1 files changed, 430 insertions(+), 99 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 cc87034..ca97f2b 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
@@ -6,7 +6,9 @@
 #include "application.h"
 
 #include <esp_log.h>
+#include <esp_heap_caps.h>
 #include <freertos/FreeRTOS.h>
+#include <freertos/semphr.h>
 #include <freertos/task.h>
 
 #include <esp_audio_simple_dec.h>
@@ -18,32 +20,60 @@
 #include <cstdio>
 #include <cstdlib>
 #include <cstring>
-#include <vector>
 
 #define TAG "sdcard_mp3"
 #define MP3_READ_BUFFER_SIZE 512
 #define MP3_OUTPUT_BUFFER_SIZE 4096
 #define MP3_PLAYER_STACK_SIZE 8192
-#define MP3_PLAYER_PRIORITY 5
+#define MP3_PLAYER_PRIORITY 2
+#define MP3_PSRAM_CACHE_MAX (768 * 1024)
 
 typedef struct {
-    char path[SDCARD_PATH_MAX_LEN];
-    float speed;
-    int repeat;
-    bool power_off_after;
+    int count;
+    sdcard_mp3_playlist_item_t items[SDCARD_MP3_PLAYLIST_MAX];
 } mp3_player_task_arg_t;
 
 static TaskHandle_t mp3_player_task_handle;
+static StackType_t *mp3_player_stack;
+static StaticTask_t *mp3_player_tcb;
 static volatile bool mp3_stop_requested;
 static volatile bool mp3_pause_requested;
 static volatile int mp3_repeat_left;
+static TickType_t s_mp3_audible_started;
+static SemaphoreHandle_t s_shadowing_listen_closed;
 
-static std::vector<int16_t> convert_channels(const int16_t *input, size_t frame_count, uint8_t src_channels, uint8_t dst_channels)
+static int16_t *s_pcm_a = nullptr;
+static int16_t *s_pcm_b = nullptr;
+static size_t s_pcm_a_cap = 0;
+static size_t s_pcm_b_cap = 0;
+
+static int16_t *ensure_psram_samples(int16_t **buf, size_t *cap, size_t samples)
 {
-    std::vector<int16_t> output(frame_count * dst_channels);
+    if (samples == 0) {
+        return *buf;
+    }
+    if (*buf && *cap >= samples) {
+        return *buf;
+    }
+    int16_t *p = static_cast<int16_t *>(
+        heap_caps_realloc(*buf, samples * sizeof(int16_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
+    if (!p) {
+        return nullptr;
+    }
+    *buf = p;
+    *cap = samples;
+    return p;
+}
+
+static bool convert_channels(const int16_t *input, size_t frame_count, uint8_t src_channels, uint8_t dst_channels,
+                             int16_t *output)
+{
+    if (!input || !output || frame_count == 0 || src_channels == 0 || dst_channels == 0) {
+        return false;
+    }
     if (src_channels == dst_channels) {
-        std::memcpy(output.data(), input, output.size() * sizeof(int16_t));
-        return output;
+        std::memcpy(output, input, frame_count * dst_channels * sizeof(int16_t));
+        return true;
     }
 
     for (size_t frame = 0; frame < frame_count; ++frame) {
@@ -63,24 +93,47 @@
             }
         }
     }
-
-    return output;
+    return true;
 }
 
-static bool is_supported_speed(float speed)
+static size_t stretch_for_speed(const int16_t *input, size_t in_frames, uint8_t channels, float speed,
+                                int16_t *output, size_t out_cap_frames)
 {
-    const float supported_speeds[] = {0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 2.0f};
-    for (size_t i = 0; i < sizeof(supported_speeds) / sizeof(supported_speeds[0]); ++i) {
-        float diff = speed - supported_speeds[i];
-        if (diff < 0) {
-            diff = -diff;
+    if (!input || !output || channels == 0 || in_frames == 0) {
+        return 0;
+    }
+    if (speed >= 0.999f && speed <= 1.001f) {
+        size_t n = in_frames * channels;
+        std::memcpy(output, input, n * sizeof(int16_t));
+        return in_frames;
+    }
+    size_t out_frames = static_cast<size_t>(static_cast<double>(in_frames) / static_cast<double>(speed) + 0.5);
+    if (out_frames < 1) {
+        out_frames = 1;
+    }
+    if (out_frames > out_cap_frames) {
+        out_frames = out_cap_frames;
+    }
+    for (size_t i = 0; i < out_frames; ++i) {
+        const double src = static_cast<double>(i) * static_cast<double>(speed);
+        size_t i0 = static_cast<size_t>(src);
+        size_t i1 = i0 + 1;
+        if (i0 >= in_frames) {
+            i0 = in_frames - 1;
         }
-        if (diff < 0.001f) {
-            return true;
+        if (i1 >= in_frames) {
+            i1 = in_frames - 1;
+        }
+        const float t = static_cast<float>(src - static_cast<double>(i0));
+        for (uint8_t c = 0; c < channels; ++c) {
+            const float a = input[i0 * channels + c];
+            const float b = input[i1 * channels + c];
+            output[i * channels + c] = static_cast<int16_t>(a + (b - a) * t);
         }
     }
-    return false;
+    return out_frames;
 }
+
 
 static bool wait_if_paused()
 {
@@ -90,8 +143,68 @@
     return !mp3_stop_requested;
 }
 
+static void wait_for_sd_dma(void)
+{
+    for (int i = 0; i < 20; ++i) {
+        size_t largest = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
+        if (largest >= 4096) {
+            return;
+        }
+        ESP_LOGW(TAG, "wait SD DMA largest=%u try=%d", (unsigned)largest, i);
+        vTaskDelay(pdMS_TO_TICKS(40));
+    }
+}
+
+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");
+    if (!fp) {
+        return nullptr;
+    }
+    size_t cap = 32 * 1024;
+    uint8_t *buf = static_cast<uint8_t *>(
+        heap_caps_malloc(cap, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
+    if (!buf) {
+        fclose(fp);
+        return nullptr;
+    }
+    size_t n = 0;
+    while (n < MP3_PSRAM_CACHE_MAX) {
+        if (n == cap) {
+            size_t ncap = cap * 2;
+            if (ncap > MP3_PSRAM_CACHE_MAX) {
+                ncap = MP3_PSRAM_CACHE_MAX;
+            }
+            uint8_t *grown = static_cast<uint8_t *>(
+                heap_caps_realloc(buf, ncap, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
+            if (!grown) {
+                break;
+            }
+            buf = grown;
+            cap = ncap;
+        }
+        size_t got = fread(buf + n, 1, cap - n, fp);
+        if (got == 0) {
+            break;
+        }
+        n += got;
+    }
+    const bool at_eof = feof(fp) != 0;
+    fclose(fp);
+    if (n == 0 || !at_eof) {
+        ESP_LOGW(TAG, "MP3 PSRAM cache miss n=%u eof=%d: %s", (unsigned)n, at_eof ? 1 : 0, path);
+        heap_caps_free(buf);
+        return nullptr;
+    }
+    *out_len = n;
+    ESP_LOGI(TAG, "cached MP3 in PSRAM %u bytes: %s", (unsigned)n, path);
+    return buf;
+}
+
 static bool output_pcm(AudioCodec *codec, esp_ae_rate_cvt_handle_t rate_cvt_handle, const uint8_t *buffer,
-    uint32_t size, uint8_t src_channels, uint32_t src_rate)
+    uint32_t size, uint8_t src_channels, uint32_t src_rate, float speed)
 {
     uint8_t dst_channels = codec->output_channels();
     uint32_t dst_rate = codec->output_sample_rate();
@@ -100,65 +213,189 @@
         return true;
     }
 
-    auto channel_converted = convert_channels(reinterpret_cast<const int16_t *>(buffer), frame_count, src_channels, dst_channels);
+    int16_t *converted = ensure_psram_samples(&s_pcm_a, &s_pcm_a_cap, frame_count * dst_channels);
+    if (!converted) {
+        ESP_LOGE(TAG, "mp3 pcm alloc failed");
+        return false;
+    }
+    if (!convert_channels(reinterpret_cast<const int16_t *>(buffer), frame_count, src_channels, dst_channels, converted)) {
+        return false;
+    }
+
+    const int16_t *play = converted;
+    size_t play_frames = frame_count;
+    if (speed < 0.999f || speed > 1.001f) {
+        size_t out_frames = static_cast<size_t>(static_cast<double>(frame_count) / static_cast<double>(speed) + 0.5);
+        if (out_frames < 1) {
+            out_frames = 1;
+        }
+        int16_t *stretched = ensure_psram_samples(&s_pcm_b, &s_pcm_b_cap, out_frames * dst_channels);
+        if (!stretched) {
+            ESP_LOGE(TAG, "mp3 stretch alloc failed");
+            return false;
+        }
+        play_frames = stretch_for_speed(converted, frame_count, dst_channels, speed, stretched, out_frames);
+        play = stretched;
+    }
+    if (play_frames == 0) {
+        return true;
+    }
+
     if (src_rate == dst_rate || !rate_cvt_handle) {
-        codec->OutputData(channel_converted);
+        codec->OutputPcm(play, static_cast<int>(play_frames * dst_channels));
+        if (s_mp3_audible_started == 0) {
+            s_mp3_audible_started = xTaskGetTickCount();
+        }
         return true;
     }
 
     uint32_t max_out_frames = 0;
-    if (esp_ae_rate_cvt_get_max_out_sample_num(rate_cvt_handle, frame_count, &max_out_frames) != ESP_AE_ERR_OK) {
+    if (esp_ae_rate_cvt_get_max_out_sample_num(rate_cvt_handle, play_frames, &max_out_frames) != ESP_AE_ERR_OK) {
         ESP_LOGE(TAG, "get resample output size failed");
         return false;
     }
-
-    std::vector<int16_t> resampled(max_out_frames * dst_channels);
+    int16_t **out_slot = (play == s_pcm_b) ? &s_pcm_a : &s_pcm_b;
+    size_t *out_cap = (play == s_pcm_b) ? &s_pcm_a_cap : &s_pcm_b_cap;
+    int16_t *resampled = ensure_psram_samples(out_slot, out_cap, max_out_frames * dst_channels);
+    if (!resampled) {
+        ESP_LOGE(TAG, "mp3 resample alloc failed");
+        return false;
+    }
     uint32_t actual_out_frames = max_out_frames;
-    if (esp_ae_rate_cvt_process(rate_cvt_handle, reinterpret_cast<esp_ae_sample_t>(channel_converted.data()), frame_count,
-            reinterpret_cast<esp_ae_sample_t>(resampled.data()), &actual_out_frames) != ESP_AE_ERR_OK) {
+    if (esp_ae_rate_cvt_process(rate_cvt_handle, reinterpret_cast<esp_ae_sample_t>(const_cast<int16_t *>(play)), play_frames,
+            reinterpret_cast<esp_ae_sample_t>(resampled), &actual_out_frames) != ESP_AE_ERR_OK) {
         ESP_LOGE(TAG, "resample MP3 PCM failed");
         return false;
     }
-
-    resampled.resize(actual_out_frames * dst_channels);
-    codec->OutputData(resampled);
+    codec->OutputPcm(resampled, static_cast<int>(actual_out_frames * dst_channels));
+    if (s_mp3_audible_started == 0) {
+        s_mp3_audible_started = xTaskGetTickCount();
+    }
     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);
+    // Server 鍚獥涓庤澶囧悓涓� 1.2脳銆傚绛� 200ms锛岄伩鍏嶄笅涓�鍙ヨ鏂囧紑澶磋涓婁竴鍙� ASR 鍚冩帀銆�
+    wait_ms += 200;
+    ESP_LOGI(TAG, "shadowing listen window %ums after clip %ums", wait_ms, duration_ms);
+    unsigned waited = 0;
+    while (waited < wait_ms && !mp3_stop_requested) {
+        unsigned slice = wait_ms - waited;
+        if (slice > 50) {
+            slice = 50;
+        }
+        vTaskDelay(pdMS_TO_TICKS(slice));
+        waited += slice;
+    }
+}
+
+static void ensure_shadowing_listen_sem(void)
+{
+    if (s_shadowing_listen_closed == nullptr) {
+        s_shadowing_listen_closed = xSemaphoreCreateBinary();
+    }
+}
+
+static void wait_shadowing_mic_off(void)
+{
+    ensure_shadowing_listen_sem();
+    xSemaphoreTake(s_shadowing_listen_closed, 0);
+    Application::GetInstance().Schedule([]() {
+        Application::GetInstance().CloseShadowingListenWindow();
+        if (s_shadowing_listen_closed != nullptr) {
+            xSemaphoreGive(s_shadowing_listen_closed);
+        }
+    });
+    // 蹇呴』绛� AFE 鐪熸鍏虫帀鍐嶈В鐮佷笅涓�鍙ャ�傚彧 delay 80ms 浼氬拰褰曢煶閲嶅彔锛屽唴閮� SRAM 浼氳鎵撴弧銆�
+    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);
+    }
 }
 
 static void mp3_player_task(void *arg)
 {
     mp3_player_task_handle = xTaskGetCurrentTaskHandle();
     mp3_player_task_arg_t *task_arg = static_cast<mp3_player_task_arg_t *>(arg);
-    char path[SDCARD_PATH_MAX_LEN];
-    strlcpy(path, task_arg->path, sizeof(path));
-    float speed = task_arg->speed;
-    int repeat = task_arg->repeat < 1 ? 1 : task_arg->repeat;
-    bool power_off_after = task_arg->power_off_after;
-    free(task_arg);
-
-    FILE *fp = fopen(path, "rb");
-    if (!fp) {
-        ESP_LOGE(TAG, "open MP3 failed: %s", path);
-        mp3_player_task_handle = nullptr;
-        mp3_stop_requested = false;
-        mp3_repeat_left = 0;
-        vTaskDelete(nullptr);
-        return;
+    sdcard_mp3_playlist_item_t items[SDCARD_MP3_PLAYLIST_MAX];
+    int count = task_arg->count;
+    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);
 
     auto codec = Board::GetInstance().GetAudioCodec();
-    if (!codec) {
-        ESP_LOGE(TAG, "audio codec unavailable");
-        fclose(fp);
+    if (!codec || count < 1) {
+        ESP_LOGE(TAG, "audio codec unavailable or empty playlist");
         mp3_player_task_handle = nullptr;
         mp3_stop_requested = false;
         mp3_repeat_left = 0;
+        Board_OnSkillPlaybackFinished(true, false, 0);
         vTaskDelete(nullptr);
         return;
     }
 
     esp_audio_dec_register_default();
     esp_audio_simple_dec_register_default();
+    uint8_t *in_buf = static_cast<uint8_t *>(
+        heap_caps_malloc(MP3_READ_BUFFER_SIZE, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
+    uint8_t *out_buf = static_cast<uint8_t *>(
+        heap_caps_malloc(MP3_OUTPUT_BUFFER_SIZE, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
+    uint32_t out_buf_size = MP3_OUTPUT_BUFFER_SIZE;
+    bool output_enabled_before = codec->output_enabled();
+    TickType_t play_started = xTaskGetTickCount();
+    s_mp3_audible_started = 0;
+    const bool shadowing = Board_IsShadowingTask();
+    char path[SDCARD_PATH_MAX_LEN] = {0};
+
+    if (!in_buf || !out_buf) {
+        ESP_LOGE(TAG, "no memory for MP3 playback");
+    }
+
+    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();
+    }
+
+    size_t file_len = 0;
+    uint8_t *file_buf = load_mp3_to_psram(path, &file_len);
+    FILE *fp = nullptr;
+    if (!file_buf) {
+        wait_for_sd_dma();
+        fp = fopen(path, "rb");
+    }
+    if (!file_buf && !fp) {
+        ESP_LOGE(TAG, "open MP3 failed: %s", path);
+        continue;
+    }
+
+    if (!mp3_pause_requested) {
+        Application::GetInstance().Schedule([]() {
+            Application::GetInstance().ApplyMp3CommandOnlyMic();
+        });
+    }
 
     esp_audio_simple_dec_handle_t decoder = nullptr;
     esp_audio_simple_dec_cfg_t dec_cfg = {
@@ -171,30 +408,24 @@
     esp_audio_err_t ret = esp_audio_simple_dec_open(&dec_cfg, &decoder);
     if (ret != ESP_AUDIO_ERR_OK) {
         ESP_LOGE(TAG, "open MP3 decoder failed: %d", ret);
-        esp_audio_simple_dec_unregister_default();
-        esp_audio_dec_unregister_default();
-        fclose(fp);
-        mp3_player_task_handle = nullptr;
-        mp3_stop_requested = false;
-        mp3_repeat_left = 0;
-        vTaskDelete(nullptr);
-        return;
+        if (fp) {
+            fclose(fp);
+            fp = nullptr;
+        }
+        if (file_buf) {
+            heap_caps_free(file_buf);
+            file_buf = nullptr;
+        }
+        continue;
     }
 
-    uint8_t *in_buf = static_cast<uint8_t *>(malloc(MP3_READ_BUFFER_SIZE));
-    uint8_t *out_buf = static_cast<uint8_t *>(malloc(MP3_OUTPUT_BUFFER_SIZE));
-    uint32_t out_buf_size = MP3_OUTPUT_BUFFER_SIZE;
-    bool output_enabled_before = codec->output_enabled();
     bool audio_info_checked = false;
     uint32_t mp3_sample_rate = 0;
     uint8_t mp3_channels = 0;
     esp_ae_rate_cvt_handle_t rate_cvt_handle = nullptr;
 
-    if (!in_buf || !out_buf) {
-        ESP_LOGE(TAG, "no memory for MP3 playback");
-        ret = ESP_AUDIO_ERR_MEM_LACK;
-    } else {
-        ESP_LOGI(TAG, "play MP3: %s, speed: %.2fx, repeat: %d, power_off_after: %d", path, speed, repeat, power_off_after ? 1 : 0);
+    ESP_LOGI(TAG, "play MP3: %s, speed: %.2fx, repeat: %d, item=%d/%d",
+             path, speed, repeat, item_i + 1, count);
 
         if (wait_if_paused()) {
             codec->EnableOutput(true);
@@ -202,13 +433,26 @@
 
         for (int pass = 0; pass < repeat && ret == ESP_AUDIO_ERR_OK && !mp3_stop_requested; ++pass) {
             mp3_repeat_left = repeat - pass;
+            s_mp3_audible_started = 0;
+            size_t mem_off = 0;
             if (pass > 0) {
-                if (fseek(fp, 0, SEEK_SET) != 0) {
-                    ESP_LOGE(TAG, "rewind MP3 failed: %s", path);
-                    break;
+                if (!file_buf) {
+                    if (fp) {
+                        fclose(fp);
+                        fp = nullptr;
+                    }
+                    vTaskDelay(pdMS_TO_TICKS(30));
+                    wait_for_sd_dma();
+                    fp = fopen(path, "rb");
+                    if (!fp) {
+                        ESP_LOGE(TAG, "reopen MP3 failed: %s", path);
+                        break;
+                    }
                 }
-                esp_audio_simple_dec_close(decoder);
-                decoder = nullptr;
+                if (decoder) {
+                    esp_audio_simple_dec_close(decoder);
+                    decoder = nullptr;
+                }
                 ret = esp_audio_simple_dec_open(&dec_cfg, &decoder);
                 if (ret != ESP_AUDIO_ERR_OK || !decoder) {
                     ESP_LOGE(TAG, "reopen MP3 decoder failed: %d", ret);
@@ -221,7 +465,21 @@
                 break;
             }
 
-            int read_size = fread(in_buf, 1, MP3_READ_BUFFER_SIZE, fp);
+            int read_size = 0;
+            if (file_buf) {
+                if (mem_off >= file_len) {
+                    break;
+                }
+                size_t chunk = file_len - mem_off;
+                if (chunk > MP3_READ_BUFFER_SIZE) {
+                    chunk = MP3_READ_BUFFER_SIZE;
+                }
+                std::memcpy(in_buf, file_buf + mem_off, chunk);
+                mem_off += chunk;
+                read_size = static_cast<int>(chunk);
+            } else {
+                read_size = fread(in_buf, 1, MP3_READ_BUFFER_SIZE, fp);
+            }
             if (read_size <= 0) {
                 break;
             }
@@ -229,7 +487,7 @@
             esp_audio_simple_dec_raw_t raw = {
                 .buffer = in_buf,
                 .len = static_cast<uint32_t>(read_size),
-                .eos = read_size < MP3_READ_BUFFER_SIZE,
+                .eos = file_buf ? (mem_off >= file_len) : (read_size < MP3_READ_BUFFER_SIZE),
                 .consumed = 0,
                 .frame_recover = ESP_AUDIO_SIMPLE_DEC_RECOVERY_NONE,
             };
@@ -248,7 +506,8 @@
 
                 ret = esp_audio_simple_dec_process(decoder, &raw, &out_frame);
                 if (ret == ESP_AUDIO_ERR_BUFF_NOT_ENOUGH) {
-                    uint8_t *new_buf = static_cast<uint8_t *>(realloc(out_buf, out_frame.needed_size));
+                    uint8_t *new_buf = static_cast<uint8_t *>(
+                        heap_caps_realloc(out_buf, out_frame.needed_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
                     if (!new_buf) {
                         ESP_LOGE(TAG, "resize MP3 output buffer failed");
                         ret = ESP_AUDIO_ERR_MEM_LACK;
@@ -274,7 +533,7 @@
                                 ret = ESP_AUDIO_ERR_NOT_SUPPORT;
                                 break;
                             }
-                            mp3_sample_rate = static_cast<uint32_t>(info.sample_rate * speed);
+                            mp3_sample_rate = info.sample_rate;
                             mp3_channels = info.channel;
                             if (mp3_sample_rate != static_cast<uint32_t>(codec->output_sample_rate())) {
                                 esp_ae_rate_cvt_cfg_t rate_cvt_cfg = {
@@ -295,10 +554,11 @@
                         audio_info_checked = true;
                     }
 
-                    if (!output_pcm(codec, rate_cvt_handle, out_frame.buffer, out_frame.decoded_size, mp3_channels, mp3_sample_rate)) {
+                    if (!output_pcm(codec, rate_cvt_handle, out_frame.buffer, out_frame.decoded_size, mp3_channels, mp3_sample_rate, speed)) {
                         ret = ESP_AUDIO_ERR_FAIL;
                         break;
                     }
+                    taskYIELD();
                 }
 
                 if (raw.consumed == 0) {
@@ -313,27 +573,55 @@
                 break;
             }
         }
+
+            if (shadowing && !mp3_stop_requested && ret == ESP_AUDIO_ERR_OK) {
+                unsigned duration_ms = s_mp3_audible_started
+                    ? pdTICKS_TO_MS(xTaskGetTickCount() - s_mp3_audible_started)
+                    : 0;
+                Board_OnShadowingClipFinished(duration_ms);
+                wait_shadowing_listen(duration_ms);
+                wait_shadowing_mic_off();
+                s_mp3_audible_started = 0;
+            }
         }
-    }
 
     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;
+    }
+    ESP_LOGI(TAG, "MP3 item %s: %s", mp3_stop_requested ? "stopped" : "finished", path);
+    }
+
     if (!output_enabled_before) {
         codec->EnableOutput(false);
     }
-    free(in_buf);
-    free(out_buf);
-    if (decoder) {
-        esp_audio_simple_dec_close(decoder);
+    if (in_buf) {
+        heap_caps_free(in_buf);
+    }
+    if (out_buf) {
+        heap_caps_free(out_buf);
     }
     esp_audio_simple_dec_unregister_default();
     esp_audio_dec_unregister_default();
-    fclose(fp);
 
-    ESP_LOGI(TAG, "MP3 playback %s: %s", mp3_stop_requested ? "stopped" : "finished", path);
+    ESP_LOGI(TAG, "MP3 playlist %s items=%d", mp3_stop_requested ? "stopped" : "finished", count);
     bool stopped = mp3_stop_requested;
     bool should_power_off = power_off_after && !stopped;
+    unsigned duration_ms = s_mp3_audible_started
+        ? pdTICKS_TO_MS(xTaskGetTickCount() - s_mp3_audible_started)
+        : pdTICKS_TO_MS(xTaskGetTickCount() - play_started);
     mp3_player_task_handle = nullptr;
     mp3_stop_requested = false;
     mp3_pause_requested = false;
@@ -341,7 +629,11 @@
     if (should_power_off) {
         Application::GetInstance().RequestPowerOffStandby();
     }
-    Board_OnSkillPlaybackFinished(stopped, power_off_after);
+    if (shadowing && !stopped) {
+        Board_OnShadowingPlaylistFinished();
+    } else {
+        Board_OnSkillPlaybackFinished(stopped, power_off_after, duration_ms);
+    }
     vTaskDelete(nullptr);
 }
 
@@ -357,11 +649,24 @@
 
 extern "C" esp_err_t SDCard_Play_MP3_File_With_Speed_Repeat(const char *path, float speed, int repeat, bool power_off_after)
 {
-    if (!path || path[0] == '\0' || !is_supported_speed(speed)) {
+    if (!path || path[0] == '\0') {
         return ESP_ERR_INVALID_ARG;
     }
-    if (repeat < 1) {
-        repeat = 1;
+    sdcard_mp3_playlist_item_t item = {};
+    strlcpy(item.path, path, sizeof(item.path));
+    item.speed = speed;
+    item.repeat = repeat;
+    item.power_off_after = power_off_after;
+    return SDCard_Play_MP3_File_Playlist(&item, 1);
+}
+
+extern "C" esp_err_t SDCard_Play_MP3_File_Playlist(const sdcard_mp3_playlist_item_t *items, int count)
+{
+    if (!items || count < 1) {
+        return ESP_ERR_INVALID_ARG;
+    }
+    if (count > SDCARD_MP3_PLAYLIST_MAX) {
+        count = SDCARD_MP3_PLAYLIST_MAX;
     }
 
     bool start_paused = mp3_pause_requested;
@@ -373,22 +678,46 @@
         }
     }
 
-    mp3_player_task_arg_t *arg = static_cast<mp3_player_task_arg_t *>(calloc(1, sizeof(mp3_player_task_arg_t)));
+    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));
     if (!arg) {
         return ESP_ERR_NO_MEM;
     }
-    strlcpy(arg->path, path, sizeof(arg->path));
-    arg->speed = speed;
-    arg->repeat = repeat;
-    arg->power_off_after = power_off_after;
+    arg->count = count;
+    for (int i = 0; i < count; ++i) {
+        strlcpy(arg->items[i].path, items[i].path, sizeof(arg->items[i].path));
+        arg->items[i].speed = SDCard_Clamp_MP3_Speed(items[i].speed);
+        arg->items[i].repeat = items[i].repeat < 1 ? 1 : items[i].repeat;
+        arg->items[i].power_off_after = items[i].power_off_after;
+    }
     mp3_stop_requested = false;
     mp3_pause_requested = start_paused;
-    mp3_repeat_left = repeat;
+    mp3_repeat_left = arg->items[0].repeat;
 
-    BaseType_t ret = xTaskCreate(mp3_player_task, "mp3_player", MP3_PLAYER_STACK_SIZE, arg, MP3_PLAYER_PRIORITY, &mp3_player_task_handle);
-    if (ret != pdPASS) {
-        free(arg);
-        mp3_player_task_handle = nullptr;
+    if (mp3_player_stack == nullptr) {
+        mp3_player_stack = static_cast<StackType_t *>(
+            heap_caps_malloc(MP3_PLAYER_STACK_SIZE, MALLOC_CAP_SPIRAM));
+    }
+    if (mp3_player_tcb == nullptr) {
+        mp3_player_tcb = static_cast<StaticTask_t *>(
+            heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL));
+    }
+    if (mp3_player_stack == nullptr || mp3_player_tcb == nullptr) {
+        ESP_LOGE(TAG, "mp3 task alloc failed internal=%u psram=%u",
+                 (unsigned)heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
+                 (unsigned)heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
+        heap_caps_free(arg);
+        mp3_repeat_left = 0;
+        return ESP_ERR_NO_MEM;
+    }
+
+    mp3_player_task_handle = xTaskCreateStatic(
+        mp3_player_task, "mp3_player", MP3_PLAYER_STACK_SIZE, arg,
+        MP3_PLAYER_PRIORITY, mp3_player_stack, mp3_player_tcb);
+    if (mp3_player_task_handle == nullptr) {
+        ESP_LOGE(TAG, "mp3 xTaskCreateStatic failed internal=%u",
+                 (unsigned)heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
+        heap_caps_free(arg);
         mp3_repeat_left = 0;
         return ESP_FAIL;
     }
@@ -400,7 +729,6 @@
 {
     mp3_pause_requested = false;
     if (!mp3_player_task_handle) {
-        ESP_LOGW(TAG, "MP3 player is not running");
         return;
     }
 
@@ -415,6 +743,9 @@
 extern "C" void SDCard_Resume_Play(void)
 {
     mp3_pause_requested = false;
+    Application::GetInstance().Schedule([]() {
+        Application::GetInstance().ApplyMp3CommandOnlyMic();
+    });
 }
 
 extern "C" bool SDCard_Is_Playing(void)

--
Gitblit v1.9.1