Updated dependencies.

This commit is contained in:
Joe Kearney 2026-02-07 16:06:20 -06:00
parent d86c494d45
commit 58748fcef1
101 changed files with 5845 additions and 2391 deletions

View file

@ -35,6 +35,7 @@
#include "sdkconfig.h"
#include "audio_player.h"
#include "audio_instance.h"
#include "audio_wav.h"
#include "audio_mp3.h"
@ -94,16 +95,18 @@ typedef struct audio_instance {
HMP3Decoder mp3_decoder;
mp3_instance mp3_data;
#endif
format i2s_format; // last configured i2s format
} audio_instance_t;
static audio_instance_t instance;
static audio_instance_t *g_instance = NULL; // when non-null, in legacy non-mixer mode
audio_player_state_t audio_player_get_state() {
return instance.state;
audio_player_state_t audio_instance_get_state(audio_instance_handle_t h) {
audio_instance_t *i = static_cast<audio_instance_t *>(h);
return i ? i->state : AUDIO_PLAYER_STATE_IDLE;
}
esp_err_t audio_player_callback_register(audio_player_cb_t call_back, void *user_ctx)
{
esp_err_t audio_instance_callback_register(audio_instance_handle_t h, audio_player_cb_t call_back, void *user_ctx) {
#if CONFIG_IDF_TARGET_ARCH_XTENSA
ESP_RETURN_ON_FALSE(esp_ptr_executable(reinterpret_cast<void*>(call_back)), ESP_ERR_INVALID_ARG,
TAG, "Not a valid call back");
@ -111,15 +114,14 @@ esp_err_t audio_player_callback_register(audio_player_cb_t call_back, void *user
ESP_RETURN_ON_FALSE(reinterpret_cast<void*>(call_back), ESP_ERR_INVALID_ARG,
TAG, "Not a valid call back");
#endif
instance.s_audio_cb = call_back;
instance.audio_cb_usrt_ctx = user_ctx;
audio_instance_t *i = static_cast<audio_instance_t *>(h);
CHECK_INSTANCE(i);
i->s_audio_cb = call_back;
i->audio_cb_usrt_ctx = user_ctx;
return ESP_OK;
}
// This function is used in some optional logging functions so we don't want to
// have a cppcheck warning here
// cppcheck-suppress unusedFunction
const char* event_to_string(audio_player_callback_event_t event) {
switch(event) {
case AUDIO_PLAYER_CALLBACK_EVENT_IDLE:
@ -141,7 +143,7 @@ const char* event_to_string(audio_player_callback_event_t event) {
return "unknown event";
}
static audio_player_callback_event_t state_to_event(audio_player_state_t state) {
audio_player_callback_event_t state_to_event(audio_player_state_t state) {
audio_player_callback_event_t event = AUDIO_PLAYER_CALLBACK_EVENT_UNKNOWN;
switch(state) {
@ -186,15 +188,15 @@ static void set_state(audio_instance_t *i, audio_player_state_t new_state) {
}
}
static void audio_instance_init(audio_instance_t &i) {
i.event_queue = NULL;
i.s_audio_cb = NULL;
i.audio_cb_usrt_ctx = NULL;
i.state = AUDIO_PLAYER_STATE_IDLE;
static void audio_instance_init(audio_instance_t *i) {
i->event_queue = NULL;
i->s_audio_cb = NULL;
i->audio_cb_usrt_ctx = NULL;
i->state = AUDIO_PLAYER_STATE_IDLE;
memset(&i->i2s_format, 0, sizeof(i->i2s_format));
}
static esp_err_t mono_to_stereo(uint32_t output_bits_per_sample, decode_data &adata)
{
static esp_err_t mono_to_stereo(uint32_t output_bits_per_sample, decode_data &adata) {
size_t data = adata.frame_count * (output_bits_per_sample / BITS_PER_BYTE);
data *= 2;
@ -234,13 +236,9 @@ static esp_err_t mono_to_stereo(uint32_t output_bits_per_sample, decode_data &ad
return ESP_OK;
}
static esp_err_t aplay_file(audio_instance_t *i, FILE *fp)
{
static esp_err_t aplay_file(audio_instance_t *i, FILE *fp) {
LOGI_1("start to decode");
format i2s_format;
memset(&i2s_format, 0, sizeof(i2s_format));
esp_err_t ret = ESP_OK;
audio_player_event_t audio_event = { .type = AUDIO_PLAYER_REQUEST_NONE, .fp = NULL };
@ -348,9 +346,9 @@ static esp_err_t aplay_file(audio_instance_t *i, FILE *fp)
// break out and exit if we aren't supposed to continue decoding
if(decode_status == DECODE_STATUS_CONTINUE)
{
// if mono, convert to stereo as es8311 requires stereo input
// if mono and force_stereo set, convert to stereo as es8311 requires stereo input
// even though it is mono output
if(i->output.fmt.channels == 1) {
if(i->output.fmt.channels == 1 && i->config.force_stereo) {
LOGI_3("c == 1, mono -> stereo");
ret = mono_to_stereo(i->output.fmt.bits_per_sample, i->output);
if(ret != ESP_OK) {
@ -359,17 +357,17 @@ static esp_err_t aplay_file(audio_instance_t *i, FILE *fp)
}
/* Configure I2S clock if the output format changed */
if ((i2s_format.sample_rate != i->output.fmt.sample_rate) ||
(i2s_format.channels != i->output.fmt.channels) ||
(i2s_format.bits_per_sample != i->output.fmt.bits_per_sample)) {
i2s_format = i->output.fmt;
LOGI_1("format change: sr=%d, bit=%d, ch=%d",
i2s_format.sample_rate,
i2s_format.bits_per_sample,
i2s_format.channels);
i2s_slot_mode_t channel_setting = (i2s_format.channels == 1) ? I2S_SLOT_MODE_MONO : I2S_SLOT_MODE_STEREO;
ret = i->config.clk_set_fn(i2s_format.sample_rate,
i2s_format.bits_per_sample,
if ((i->i2s_format.sample_rate != i->output.fmt.sample_rate) ||
(i->i2s_format.channels != i->output.fmt.channels) ||
(i->i2s_format.bits_per_sample != i->output.fmt.bits_per_sample)) {
i->i2s_format = i->output.fmt;
LOGI_1("format change: sr=%d, bit=%lu, ch=%lu",
i->i2s_format.sample_rate,
i->i2s_format.bits_per_sample,
i->i2s_format.channels);
i2s_slot_mode_t channel_setting = (i->i2s_format.channels == 1) ? I2S_SLOT_MODE_MONO : I2S_SLOT_MODE_STEREO;
ret = i->config.clk_set_fn(i->i2s_format.sample_rate,
i->i2s_format.bits_per_sample,
channel_setting);
ESP_GOTO_ON_ERROR(ret, clean_up, TAG, "i2s_set_clk");
}
@ -380,17 +378,22 @@ static esp_err_t aplay_file(audio_instance_t *i, FILE *fp)
* audio decoding to occur while the previous set of samples is finishing playback, in order
* to ensure playback without interruption.
*/
size_t i2s_bytes_written = 0;
size_t bytes_to_write = i->output.frame_count * i->output.fmt.channels * (i2s_format.bits_per_sample / 8);
size_t bytes_written = 0;
size_t bytes_to_write = i->output.frame_count * i->output.fmt.channels * (i->i2s_format.bits_per_sample / 8);
LOGI_2("c %d, bps %d, bytes %d, frame_count %d",
i->output.fmt.channels,
i2s_format.bits_per_sample,
bytes_to_write,
i->output.frame_count);
i->config.write_fn(i->output.samples, bytes_to_write, &i2s_bytes_written, portMAX_DELAY);
if(bytes_to_write != i2s_bytes_written) {
ESP_LOGE(TAG, "to write %d != written %d", bytes_to_write, i2s_bytes_written);
// NOTE: to aid transition in api, using write_fn2 based on write_ctx assignment
if (i->config.write_ctx)
i->config.write_fn2(i->output.samples, bytes_to_write, &bytes_written, portMAX_DELAY, i->config.write_ctx);
else
i->config.write_fn(i->output.samples, bytes_to_write, &bytes_written, portMAX_DELAY);
if(bytes_to_write != bytes_written) {
ESP_LOGE(TAG, "to write %d != written %d", bytes_to_write, bytes_written);
}
} else if(decode_status == DECODE_STATUS_NO_DATA_CONTINUE)
{
@ -405,8 +408,7 @@ clean_up:
return ret;
}
static void audio_task(void *pvParam)
{
static void audio_task(void *pvParam) {
audio_instance_t *i = static_cast<audio_instance_t*>(pvParam);
audio_player_event_t audio_event;
@ -451,13 +453,13 @@ static void audio_task(void *pvParam)
}
}
i->config.mute_fn(AUDIO_PLAYER_UNMUTE);
if (i->config.mute_fn) i->config.mute_fn(AUDIO_PLAYER_UNMUTE);
esp_err_t ret_val = aplay_file(i, audio_event.fp);
if(ret_val != ESP_OK)
{
ESP_LOGE(TAG, "aplay_file() %d", ret_val);
}
i->config.mute_fn(AUDIO_PLAYER_MUTE);
if (i->config.mute_fn) i->config.mute_fn(AUDIO_PLAYER_MUTE);
if(audio_event.fp) fclose(audio_event.fp);
}
@ -476,128 +478,155 @@ static esp_err_t audio_send_event(audio_instance_t *i, audio_player_event_t even
return ESP_OK;
}
esp_err_t audio_player_play(FILE *fp)
{
/* ================= New multi-instance API ================= */
esp_err_t audio_instance_play(audio_instance_handle_t h, FILE *fp) {
audio_instance_t *i = static_cast<audio_instance_t *>(h);
CHECK_INSTANCE(i);
LOGI_1("%s", __FUNCTION__);
audio_player_event_t event = { .type = AUDIO_PLAYER_REQUEST_PLAY, .fp = fp };
return audio_send_event(&instance, event);
return audio_send_event(i, event);
}
esp_err_t audio_player_pause(void)
{
esp_err_t audio_instance_pause(audio_instance_handle_t h) {
audio_instance_t *i = static_cast<audio_instance_t *>(h);
CHECK_INSTANCE(i);
LOGI_1("%s", __FUNCTION__);
audio_player_event_t event = { .type = AUDIO_PLAYER_REQUEST_PAUSE, .fp = NULL };
return audio_send_event(&instance, event);
return audio_send_event(i, event);
}
esp_err_t audio_player_resume(void)
{
esp_err_t audio_instance_resume(audio_instance_handle_t h) {
audio_instance_t *i = static_cast<audio_instance_t *>(h);
CHECK_INSTANCE(i);
LOGI_1("%s", __FUNCTION__);
audio_player_event_t event = { .type = AUDIO_PLAYER_REQUEST_RESUME, .fp = NULL };
return audio_send_event(&instance, event);
return audio_send_event(i, event);
}
esp_err_t audio_player_stop(void)
{
esp_err_t audio_instance_stop(audio_instance_handle_t h) {
audio_instance_t *i = static_cast<audio_instance_t *>(h);
CHECK_INSTANCE(i);
LOGI_1("%s", __FUNCTION__);
audio_player_event_t event = { .type = AUDIO_PLAYER_REQUEST_STOP, .fp = NULL };
return audio_send_event(&instance, event);
return audio_send_event(i, event);
}
/**
* Can only shut down the playback thread if the thread is not presently playing audio.
* Call audio_player_stop()
*/
static esp_err_t _internal_audio_player_shutdown_thread(void)
{
static esp_err_t _internal_audio_player_shutdown_thread(audio_instance_t *i) {
CHECK_INSTANCE(i);
LOGI_1("%s", __FUNCTION__);
audio_player_event_t event = { .type = AUDIO_PLAYER_REQUEST_SHUTDOWN_THREAD, .fp = NULL };
return audio_send_event(&instance, event);
return audio_send_event(i, event);
}
static void cleanup_memory(audio_instance_t &i)
{
static void cleanup_memory(audio_instance_t *i) {
#if defined(CONFIG_AUDIO_PLAYER_ENABLE_MP3)
if(i.mp3_decoder) MP3FreeDecoder(i.mp3_decoder);
if(i.mp3_data.data_buf) free(i.mp3_data.data_buf);
if(i->mp3_decoder) MP3FreeDecoder(i->mp3_decoder);
if(i->mp3_data.data_buf) free(i->mp3_data.data_buf);
#endif
if(i.output.samples) free(i.output.samples);
if(i->output.samples) free(i->output.samples);
vQueueDelete(i.event_queue);
vQueueDelete(i->event_queue);
}
esp_err_t audio_player_new(audio_player_config_t config)
{
esp_err_t audio_instance_new(audio_instance_handle_t *h, audio_player_config_t *config) {
BaseType_t task_val;
audio_instance_init(instance);
ESP_RETURN_ON_FALSE(h != NULL, ESP_ERR_INVALID_ARG, TAG, "handle pointer is NULL");
ESP_RETURN_ON_FALSE(*h == NULL, ESP_ERR_INVALID_ARG, TAG, "instance is not NULL");
ESP_RETURN_ON_FALSE(config, ESP_ERR_INVALID_ARG, TAG, "null config");
instance.config = config;
audio_instance_t *i = static_cast<audio_instance_t *>(calloc(1, sizeof(audio_instance_t)));
if (i == NULL) return ESP_ERR_NO_MEM;
audio_instance_init(i);
i->config = *config;
/* Audio control event queue */
instance.event_queue = xQueueCreate(4, sizeof(audio_player_event_t));
ESP_RETURN_ON_FALSE(NULL != instance.event_queue, -1, TAG, "xQueueCreate");
i->event_queue = xQueueCreate(4, sizeof(audio_player_event_t));
ESP_RETURN_ON_FALSE(NULL != i->event_queue, -1, TAG, "xQueueCreate");
/** See https://github.com/ultraembedded/libhelix-mp3/blob/0a0e0673f82bc6804e5a3ddb15fb6efdcde747cd/testwrap/main.c#L74 */
instance.output.samples_capacity = MAX_NCHAN * MAX_NGRAN * MAX_NSAMP;
instance.output.samples_capacity_max = instance.output.samples_capacity * 2;
instance.output.samples = static_cast<uint8_t*>(malloc(instance.output.samples_capacity_max));
LOGI_1("samples_capacity %d bytes", instance.output.samples_capacity_max);
i->output.samples_capacity = MAX_NCHAN * MAX_NGRAN * MAX_NSAMP;
i->output.samples_capacity_max = i->output.samples_capacity * 2;
i->output.samples = static_cast<uint8_t*>(malloc(i->output.samples_capacity_max));
LOGI_1("samples_capacity %d bytes", i->output.samples_capacity_max);
int ret = ESP_OK;
ESP_GOTO_ON_FALSE(NULL != instance.output.samples, ESP_ERR_NO_MEM, cleanup,
ESP_GOTO_ON_FALSE(NULL != i->output.samples, ESP_ERR_NO_MEM, cleanup,
TAG, "Failed allocate output buffer");
#if defined(CONFIG_AUDIO_PLAYER_ENABLE_MP3)
instance.mp3_data.data_buf_size = MAINBUF_SIZE * 3;
instance.mp3_data.data_buf = static_cast<uint8_t*>(malloc(instance.mp3_data.data_buf_size));
ESP_GOTO_ON_FALSE(NULL != instance.mp3_data.data_buf, ESP_ERR_NO_MEM, cleanup,
i->mp3_data.data_buf_size = MAINBUF_SIZE * 3;
i->mp3_data.data_buf = static_cast<uint8_t*>(malloc(i->mp3_data.data_buf_size));
ESP_GOTO_ON_FALSE(NULL != i->mp3_data.data_buf, ESP_ERR_NO_MEM, cleanup,
TAG, "Failed allocate mp3 data buffer");
instance.mp3_decoder = MP3InitDecoder();
ESP_GOTO_ON_FALSE(NULL != instance.mp3_decoder, ESP_ERR_NO_MEM, cleanup,
i->mp3_decoder = MP3InitDecoder();
ESP_GOTO_ON_FALSE(NULL != i->mp3_decoder, ESP_ERR_NO_MEM, cleanup,
TAG, "Failed create MP3 decoder");
#endif
instance.running = true;
memset(&i->i2s_format, 0, sizeof(i->i2s_format));
i->running = true;
task_val = xTaskCreatePinnedToCore(
(TaskFunction_t) audio_task,
"Audio Task",
4 * 1024,
&instance,
(UBaseType_t) instance.config.priority,
(TaskHandle_t * const) NULL,
(BaseType_t) instance.config.coreID);
i,
(UBaseType_t) i->config.priority,
(TaskHandle_t *) NULL,
(BaseType_t) i->config.coreID);
ESP_GOTO_ON_FALSE(pdPASS == task_val, ESP_ERR_NO_MEM, cleanup,
TAG, "Failed create audio task");
// start muted
instance.config.mute_fn(AUDIO_PLAYER_MUTE);
if (i->config.mute_fn)
i->config.mute_fn(AUDIO_PLAYER_MUTE);
*h = i;
return ret;
// At the moment when we run cppcheck there is a lack of esp-idf header files this
// means cppcheck doesn't know that ESP_GOTO_ON_FALSE() etc are making use of this label
// cppcheck-suppress unusedLabelConfiguration
cleanup:
cleanup_memory(instance);
cleanup_memory(i);
free(i);
i = NULL;
return ret;
}
esp_err_t audio_player_delete() {
esp_err_t audio_instance_delete(audio_instance_handle_t h) {
audio_instance_t *i = static_cast<audio_instance_t *>(h);
CHECK_INSTANCE(i);
const int MAX_RETRIES = 5;
int retries = MAX_RETRIES;
while(instance.running && retries) {
while(i->running && retries) {
// stop any playback and shutdown the thread
audio_player_stop();
_internal_audio_player_shutdown_thread();
audio_instance_stop(i);
_internal_audio_player_shutdown_thread(i);
vTaskDelay(pdMS_TO_TICKS(100));
retries--;
}
cleanup_memory(instance);
cleanup_memory(i);
free(i);
i = NULL;
// if we ran out of retries, return fail code
if(retries == 0) {
@ -606,3 +635,46 @@ esp_err_t audio_player_delete() {
return ESP_OK;
}
/* ================= Legacy API implemented via default instance ================= */
audio_player_state_t audio_player_get_state() {
return audio_instance_get_state(g_instance);
}
esp_err_t audio_player_callback_register(audio_player_cb_t call_back, void *user_ctx) {
return audio_instance_callback_register(g_instance, call_back, user_ctx);
}
esp_err_t audio_player_play(FILE *fp) {
return audio_instance_play(g_instance, fp);
}
esp_err_t audio_player_pause() {
return audio_instance_pause(g_instance);
}
esp_err_t audio_player_resume() {
return audio_instance_resume(g_instance);
}
esp_err_t audio_player_stop() {
return audio_instance_stop(g_instance);
}
esp_err_t audio_player_new(audio_player_config_t config) {
if (g_instance) return ESP_OK;
config.force_stereo = true; // preserve legacy behavior
audio_instance_handle_t h = NULL;
ESP_RETURN_ON_ERROR(audio_instance_new(&h, &config), TAG, "failed to create new audio instance");
g_instance = static_cast<audio_instance_t *>(h);
return ESP_OK;
}
esp_err_t audio_player_delete() {
if (g_instance) {
audio_instance_delete(g_instance);
g_instance = NULL;
}
return ESP_OK;
}