| | |
| | | #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 <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) { |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | 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() |
| | | { |
| | |
| | | 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(); |
| | |
| | | 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 = { |
| | |
| | | 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); |
| | |
| | | |
| | | 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); |
| | |
| | | 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; |
| | | } |
| | |
| | | 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, |
| | | }; |
| | |
| | | |
| | | 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; |
| | |
| | | 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 = { |
| | |
| | | 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) { |
| | |
| | | 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; |
| | |
| | | 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); |
| | | } |
| | | |
| | |
| | | |
| | | 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; |
| | |
| | | } |
| | | } |
| | | |
| | | 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; |
| | | } |
| | |
| | | { |
| | | mp3_pause_requested = false; |
| | | if (!mp3_player_task_handle) { |
| | | ESP_LOGW(TAG, "MP3 player is not running"); |
| | | return; |
| | | } |
| | | |
| | |
| | | extern "C" void SDCard_Resume_Play(void) |
| | | { |
| | | mp3_pause_requested = false; |
| | | Application::GetInstance().Schedule([]() { |
| | | Application::GetInstance().ApplyMp3CommandOnlyMic(); |
| | | }); |
| | | } |
| | | |
| | | extern "C" bool SDCard_Is_Playing(void) |