#include "system.h"
|
#include "app_task.h"
|
|
#include "audio_codec.h"
|
#include "board.h"
|
#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>
|
#include <esp_audio_simple_dec_default.h>
|
#include <esp_audio_dec_default.h>
|
#include <esp_ae_rate_cvt.h>
|
|
#include <algorithm>
|
#include <cstdio>
|
#include <cstdlib>
|
#include <cstring>
|
|
#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 2
|
#define MP3_PSRAM_CACHE_MAX (768 * 1024)
|
|
typedef struct {
|
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 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)
|
{
|
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, input, frame_count * dst_channels * sizeof(int16_t));
|
return true;
|
}
|
|
for (size_t frame = 0; frame < frame_count; ++frame) {
|
if (dst_channels == 1) {
|
int32_t mixed = 0;
|
for (uint8_t ch = 0; ch < src_channels; ++ch) {
|
mixed += input[frame * src_channels + ch];
|
}
|
output[frame] = static_cast<int16_t>(mixed / src_channels);
|
} else if (src_channels == 1) {
|
for (uint8_t ch = 0; ch < dst_channels; ++ch) {
|
output[frame * dst_channels + ch] = input[frame];
|
}
|
} else {
|
for (uint8_t ch = 0; ch < dst_channels; ++ch) {
|
output[frame * dst_channels + ch] = input[frame * src_channels + std::min<uint8_t>(ch, src_channels - 1)];
|
}
|
}
|
}
|
return true;
|
}
|
|
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)
|
{
|
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 (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 out_frames;
|
}
|
|
|
static bool wait_if_paused()
|
{
|
while (mp3_pause_requested && !mp3_stop_requested) {
|
vTaskDelay(pdMS_TO_TICKS(20));
|
}
|
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, float speed)
|
{
|
uint8_t dst_channels = codec->output_channels();
|
uint32_t dst_rate = codec->output_sample_rate();
|
size_t frame_count = size / (sizeof(int16_t) * src_channels);
|
if (frame_count == 0) {
|
return true;
|
}
|
|
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->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, play_frames, &max_out_frames) != ESP_AE_ERR_OK) {
|
ESP_LOGE(TAG, "get resample output size failed");
|
return false;
|
}
|
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>(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;
|
}
|
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);
|
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 || 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 = {
|
.dec_type = ESP_AUDIO_SIMPLE_DEC_TYPE_MP3,
|
.dec_cfg = nullptr,
|
.cfg_size = 0,
|
.use_frame_dec = false,
|
};
|
|
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);
|
if (fp) {
|
fclose(fp);
|
fp = nullptr;
|
}
|
if (file_buf) {
|
heap_caps_free(file_buf);
|
file_buf = nullptr;
|
}
|
continue;
|
}
|
|
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;
|
|
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);
|
}
|
|
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 (!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;
|
}
|
}
|
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);
|
break;
|
}
|
}
|
|
while (ret == ESP_AUDIO_ERR_OK && !mp3_stop_requested) {
|
if (!wait_if_paused()) {
|
break;
|
}
|
|
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;
|
}
|
|
esp_audio_simple_dec_raw_t raw = {
|
.buffer = in_buf,
|
.len = static_cast<uint32_t>(read_size),
|
.eos = file_buf ? (mem_off >= file_len) : (read_size < MP3_READ_BUFFER_SIZE),
|
.consumed = 0,
|
.frame_recover = ESP_AUDIO_SIMPLE_DEC_RECOVERY_NONE,
|
};
|
|
while (raw.len > 0 && !mp3_stop_requested) {
|
if (!wait_if_paused()) {
|
break;
|
}
|
|
esp_audio_simple_dec_out_t out_frame = {
|
.buffer = out_buf,
|
.len = out_buf_size,
|
.needed_size = 0,
|
.decoded_size = 0,
|
};
|
|
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 *>(
|
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;
|
break;
|
}
|
out_buf = new_buf;
|
out_buf_size = out_frame.needed_size;
|
continue;
|
}
|
|
if (ret != ESP_AUDIO_ERR_OK) {
|
ESP_LOGE(TAG, "decode MP3 failed: %d", ret);
|
break;
|
}
|
|
if (out_frame.decoded_size > 0) {
|
if (!audio_info_checked) {
|
esp_audio_simple_dec_info_t info = {};
|
if (esp_audio_simple_dec_get_info(decoder, &info) == ESP_AUDIO_ERR_OK) {
|
ESP_LOGI(TAG, "MP3 info: %lu Hz, %u bits, %u ch", info.sample_rate, info.bits_per_sample, info.channel);
|
if (info.bits_per_sample != 16 || info.channel == 0 || codec->output_channels() == 0) {
|
ESP_LOGE(TAG, "MP3 format unsupported: %lu Hz, %u bits, %u ch", info.sample_rate, info.bits_per_sample, info.channel);
|
ret = ESP_AUDIO_ERR_NOT_SUPPORT;
|
break;
|
}
|
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 = {
|
.src_rate = mp3_sample_rate,
|
.dest_rate = static_cast<uint32_t>(codec->output_sample_rate()),
|
.channel = static_cast<uint8_t>(codec->output_channels()),
|
.bits_per_sample = 16,
|
.complexity = 2,
|
.perf_type = ESP_AE_RATE_CVT_PERF_TYPE_SPEED,
|
};
|
if (esp_ae_rate_cvt_open(&rate_cvt_cfg, &rate_cvt_handle) != ESP_AE_ERR_OK || !rate_cvt_handle) {
|
ESP_LOGE(TAG, "open MP3 resampler failed");
|
ret = ESP_AUDIO_ERR_NOT_SUPPORT;
|
break;
|
}
|
}
|
}
|
audio_info_checked = true;
|
}
|
|
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) {
|
break;
|
}
|
raw.len -= raw.consumed;
|
raw.buffer += raw.consumed;
|
raw.consumed = 0;
|
}
|
|
if (raw.eos || ret != ESP_AUDIO_ERR_OK) {
|
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);
|
}
|
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();
|
|
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;
|
mp3_repeat_left = 0;
|
if (should_power_off) {
|
Application::GetInstance().RequestPowerOffStandby();
|
}
|
if (shadowing && !stopped) {
|
Board_OnShadowingPlaylistFinished();
|
} else {
|
Board_OnSkillPlaybackFinished(stopped, power_off_after, duration_ms);
|
}
|
vTaskDelete(nullptr);
|
}
|
|
extern "C" esp_err_t SDCard_Play_MP3_File(const char *path)
|
{
|
return SDCard_Play_MP3_File_With_Speed_Repeat(path, 1.0f, 1, false);
|
}
|
|
extern "C" esp_err_t SDCard_Play_MP3_File_With_Speed(const char *path, float speed)
|
{
|
return SDCard_Play_MP3_File_With_Speed_Repeat(path, speed, 1, false);
|
}
|
|
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') {
|
return ESP_ERR_INVALID_ARG;
|
}
|
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;
|
if (mp3_player_task_handle) {
|
mp3_stop_requested = true;
|
mp3_pause_requested = false;
|
while (mp3_player_task_handle) {
|
vTaskDelay(pdMS_TO_TICKS(20));
|
}
|
}
|
|
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;
|
}
|
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 = arg->items[0].repeat;
|
|
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;
|
}
|
|
return ESP_OK;
|
}
|
|
extern "C" void SDCard_Stop_Play(void)
|
{
|
mp3_pause_requested = false;
|
if (!mp3_player_task_handle) {
|
return;
|
}
|
|
mp3_stop_requested = true;
|
}
|
|
extern "C" void SDCard_Pause_Play(void)
|
{
|
mp3_pause_requested = true;
|
}
|
|
extern "C" void SDCard_Resume_Play(void)
|
{
|
mp3_pause_requested = false;
|
Application::GetInstance().Schedule([]() {
|
Application::GetInstance().ApplyMp3CommandOnlyMic();
|
});
|
}
|
|
extern "C" bool SDCard_Is_Playing(void)
|
{
|
return mp3_player_task_handle != nullptr;
|
}
|
|
extern "C" bool SDCard_Is_Paused(void)
|
{
|
return mp3_player_task_handle != nullptr && mp3_pause_requested;
|
}
|
|
extern "C" int SDCard_Get_Repeat_Left(void)
|
{
|
return mp3_repeat_left;
|
}
|