#include "system.h" #include "audio_codec.h" #include "board.h" #include #include #include #include #include #include #include #include #include #include #include #include #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 typedef struct { char path[SDCARD_PATH_MAX_LEN]; float speed; } mp3_player_task_arg_t; static TaskHandle_t mp3_player_task_handle; static volatile bool mp3_stop_requested; static volatile bool mp3_pause_requested; static std::vector convert_channels(const int16_t *input, size_t frame_count, uint8_t src_channels, uint8_t dst_channels) { std::vector output(frame_count * dst_channels); if (src_channels == dst_channels) { std::memcpy(output.data(), input, output.size() * sizeof(int16_t)); return output; } 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(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(ch, src_channels - 1)]; } } } return output; } static bool is_supported_speed(float speed) { 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 (diff < 0.001f) { return true; } } return false; } static bool wait_if_paused() { while (mp3_pause_requested && !mp3_stop_requested) { vTaskDelay(pdMS_TO_TICKS(20)); } return !mp3_stop_requested; } 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) { 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; } auto channel_converted = convert_channels(reinterpret_cast(buffer), frame_count, src_channels, dst_channels); if (src_rate == dst_rate || !rate_cvt_handle) { codec->OutputData(channel_converted); 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) { ESP_LOGE(TAG, "get resample output size failed"); return false; } std::vector resampled(max_out_frames * dst_channels); uint32_t actual_out_frames = max_out_frames; if (esp_ae_rate_cvt_process(rate_cvt_handle, reinterpret_cast(channel_converted.data()), frame_count, reinterpret_cast(resampled.data()), &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); return true; } static void mp3_player_task(void *arg) { mp3_player_task_handle = xTaskGetCurrentTaskHandle(); mp3_player_task_arg_t *task_arg = static_cast(arg); char path[SDCARD_PATH_MAX_LEN]; strlcpy(path, task_arg->path, sizeof(path)); float speed = task_arg->speed; 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; vTaskDelete(nullptr); return; } auto codec = Board::GetInstance().GetAudioCodec(); if (!codec) { ESP_LOGE(TAG, "audio codec unavailable"); fclose(fp); mp3_player_task_handle = nullptr; mp3_stop_requested = false; vTaskDelete(nullptr); return; } esp_audio_dec_register_default(); esp_audio_simple_dec_register_default(); 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); esp_audio_simple_dec_unregister_default(); esp_audio_dec_unregister_default(); fclose(fp); mp3_player_task_handle = nullptr; mp3_stop_requested = false; vTaskDelete(nullptr); return; } uint8_t *in_buf = static_cast(malloc(MP3_READ_BUFFER_SIZE)); uint8_t *out_buf = static_cast(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", path, speed); if (wait_if_paused()) { codec->EnableOutput(true); } while (ret == ESP_AUDIO_ERR_OK && !mp3_stop_requested) { if (!wait_if_paused()) { break; } int 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(read_size), .eos = 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(realloc(out_buf, out_frame.needed_size)); 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 = static_cast(info.sample_rate * speed); mp3_channels = info.channel; if (mp3_sample_rate != static_cast(codec->output_sample_rate())) { esp_ae_rate_cvt_cfg_t rate_cvt_cfg = { .src_rate = mp3_sample_rate, .dest_rate = static_cast(codec->output_sample_rate()), .channel = static_cast(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)) { ret = ESP_AUDIO_ERR_FAIL; break; } } 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 (rate_cvt_handle) { esp_ae_rate_cvt_close(rate_cvt_handle); } if (!output_enabled_before) { codec->EnableOutput(false); } free(in_buf); free(out_buf); esp_audio_simple_dec_close(decoder); 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); mp3_player_task_handle = nullptr; mp3_stop_requested = false; mp3_pause_requested = false; vTaskDelete(nullptr); } extern "C" esp_err_t SDCard_Play_MP3_File(const char *path) { return SDCard_Play_MP3_File_With_Speed(path, 1.0f); } extern "C" esp_err_t SDCard_Play_MP3_File_With_Speed(const char *path, float speed) { if (!path || path[0] == '\0' || !is_supported_speed(speed)) { return ESP_ERR_INVALID_ARG; } 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(calloc(1, sizeof(mp3_player_task_arg_t))); if (!arg) { return ESP_ERR_NO_MEM; } strlcpy(arg->path, path, sizeof(arg->path)); arg->speed = speed; mp3_stop_requested = false; mp3_pause_requested = start_paused; 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; return ESP_FAIL; } return ESP_OK; } extern "C" void SDCard_Stop_Play(void) { mp3_pause_requested = false; if (!mp3_player_task_handle) { ESP_LOGW(TAG, "MP3 player is not running"); 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; } 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; }