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