From e8373bd0c098d7559e9e7c1f669b383c6d0ba27e Mon Sep 17 00:00:00 2001 From: Eduard Iten Date: Thu, 7 May 2026 09:24:26 +0200 Subject: [PATCH] Tried to improve audio --- firmware/libs/audio/src/audio.c | 387 ++++++++++++++++++-------------- firmware/prj.conf | 2 - firmware/src/main.c | 13 +- 3 files changed, 224 insertions(+), 178 deletions(-) diff --git a/firmware/libs/audio/src/audio.c b/firmware/libs/audio/src/audio.c index 7887571..1b092f0 100644 --- a/firmware/libs/audio/src/audio.c +++ b/firmware/libs/audio/src/audio.c @@ -13,17 +13,12 @@ const struct device *i2s_dev = DEVICE_DT_GET(DT_ALIAS(i2s_audio)); K_MSGQ_DEFINE(audio_cmd_q, sizeof(struct audio_cmd_msg), 10, 4); K_SEM_DEFINE(audio_files_count_sem, 0, 1); -K_SEM_DEFINE(audio_file_select_sem, 0, 1); K_MEM_SLAB_DEFINE(audio_cache_slab, CONFIG_AUDIO_CACHE_SLAB_SIZE, CONFIG_AUDIO_CACHE_SLAB_COUNT, 4); -struct k_work_q audio_work_q; -K_THREAD_STACK_DEFINE(audio_work_q_stack, CONFIG_AUDIO_WORKQUEUE_STACK_SIZE); -struct k_work select_next_file_work; - enum audio_thread_state_t { - AUDIO_ARMED, + AUDIO_IDLE, AUDIO_PRECACHING, AUDIO_WAIT_FOR_CACHE, AUDIO_PLAYING, @@ -35,8 +30,7 @@ enum audio_thread_state_t #define EV_CACHE_READY BIT(2) #define EV_CACHE_DONE BIT(3) #define EV_STATE_STEP BIT(4) -#define EV_AUTOSTART BIT(5) -#define EV_ALL (EV_PLAY_RANDOM | EV_MSGQ_NOT_EMPTY | EV_CACHE_READY | EV_CACHE_DONE | EV_STATE_STEP | EV_AUTOSTART) +#define EV_ALL (EV_PLAY_RANDOM | EV_MSGQ_NOT_EMPTY | EV_CACHE_READY | EV_CACHE_DONE | EV_STATE_STEP) K_EVENT_DEFINE(audio_events); @@ -50,13 +44,15 @@ struct audio_ctx_t char next_file_name[CONFIG_FS_MGMT_MAX_PATH_LENGTH]; struct fs_file_t file; bool is_file_open; + bool cache_ready_signaled; + uint8_t cached_blocks; ssize_t audio_size; ssize_t cached_bytes; } audio_ctx; static struct i2s_config i2s_cfg = { .word_size = 16, - .channels = 2, + .channels = 1, .format = I2S_FMT_DATA_FORMAT_I2S, .options = I2S_OPT_BIT_CLK_MASTER | I2S_OPT_FRAME_CLK_MASTER, .frame_clk_freq = 16000, @@ -70,7 +66,39 @@ K_MUTEX_DEFINE(audio_ctx_mutex); atomic_t thread_state = ATOMIC_INIT(0); atomic_t num_files = ATOMIC_INIT(0); -static uint8_t audio_mono_stage[CONFIG_AUDIO_CACHE_SLAB_SIZE / 2]; +static const char *audio_state_name(enum audio_thread_state_t state) +{ + switch (state) + { + case AUDIO_IDLE: + return "IDLE"; + case AUDIO_PRECACHING: + return "PRECACHING"; + case AUDIO_WAIT_FOR_CACHE: + return "WAIT_FOR_CACHE"; + case AUDIO_PLAYING: + return "PLAYING"; + case AUDIO_DRAINING: + return "DRAINING"; + default: + return "UNKNOWN"; + } +} + +static void audio_set_state(enum audio_thread_state_t new_state, const char *reason) +{ + enum audio_thread_state_t old_state = atomic_get(&thread_state); + + if (old_state != new_state) + { + LOG_INF("Audio state %s -> %s (%s)", + audio_state_name(old_state), + audio_state_name(new_state), + reason); + } + + atomic_set(&thread_state, new_state); +} int audio_queue_play(const char *filename, bool is_interrupt) { @@ -102,40 +130,32 @@ int audio_queue_play(const char *filename, bool is_interrupt) } /* - * Wake immediately for interrupts or when not currently playing. - * Non-interrupt commands during playback are picked up after drain. + * Wake immediately for interrupts or when idle. + * Non-interrupt commands during active playback are picked up after drain. */ - if (is_interrupt || (atomic_get(&thread_state) != AUDIO_PLAYING)) + if (is_interrupt || (atomic_get(&thread_state) == AUDIO_IDLE)) { k_event_set(&audio_events, EV_MSGQ_NOT_EMPTY); } - LOG_DBG("Enqueued audio command: filename='%s', is_interrupt=%d", cmd.filename, cmd.is_interrupt); + LOG_INF("Enqueued audio command: filename='%s', is_interrupt=%d, queued=%u", cmd.filename, + cmd.is_interrupt, (unsigned int)k_msgq_num_used_get(&audio_cmd_q)); return 0; } -static int audio_select_random_sound(void) +static int audio_select_random_to_buf(char *buf, size_t buf_size) { - k_sem_reset(&audio_file_select_sem); if (k_sem_take(&audio_files_count_sem, K_FOREVER) != 0) { - LOG_ERR("Failed to take audio files count semaphore"); - k_sem_give(&audio_files_count_sem); - k_mutex_unlock(&audio_ctx_mutex); return -EFAULT; } int count = atomic_get(&num_files); - - k_mutex_lock(&audio_ctx_mutex, K_FOREVER); if (count == 0) { - LOG_WRN("No audio files available to select, returning no files sound"); - FS_MGMT_ASSEMBLE_PATH(audio_ctx.next_file_name, FS_SYSTEM_PATH, CONFIG_AUDIO_NO_SAMPLES_SAMPLE); - - k_sem_give(&audio_file_select_sem); + LOG_WRN("No audio files available, using fallback sound"); + snprintf(buf, buf_size, "%s/%s", FS_SYSTEM_PATH, CONFIG_AUDIO_NO_SAMPLES_SAMPLE); k_sem_give(&audio_files_count_sem); - k_mutex_unlock(&audio_ctx_mutex); return -ENOENT; } @@ -143,29 +163,22 @@ static int audio_select_random_sound(void) struct fs_dir_t dir; struct fs_dirent entry; int rc; + bool found = false; fs_dir_t_init(&dir); rc = fs_mgmt_pm_opendir(&dir, FS_AUDIO_PATH); if (rc < 0) { LOG_ERR("Failed to open audio directory '%s': %d", FS_AUDIO_PATH, rc); - k_sem_give(&audio_file_select_sem); k_sem_give(&audio_files_count_sem); - k_mutex_unlock(&audio_ctx_mutex); return rc; } int current_index = 0; - bool found = false; while (1) { rc = fs_readdir(&dir, &entry); - if (rc < 0) - { - LOG_ERR("Directory read error: %d", rc); - break; - } - if (entry.name[0] == '\0') + if (rc < 0 || entry.name[0] == '\0') { break; } @@ -173,8 +186,8 @@ static int audio_select_random_sound(void) { if (current_index == random_index) { - FS_MGMT_ASSEMBLE_PATH(audio_ctx.next_file_name, FS_AUDIO_PATH, entry.name); - LOG_DBG("Selected random audio file: %s", audio_ctx.next_file_name); + snprintf(buf, buf_size, "%s/%s", FS_AUDIO_PATH, entry.name); + LOG_DBG("Selected random audio file: %s", buf); found = true; break; } @@ -183,9 +196,7 @@ static int audio_select_random_sound(void) } fs_mgmt_pm_closedir(&dir); - k_sem_give(&audio_file_select_sem); k_sem_give(&audio_files_count_sem); - k_mutex_unlock(&audio_ctx_mutex); return found ? 0 : -ENOENT; } @@ -230,31 +241,11 @@ void audio_refresh_files(void) fs_mgmt_pm_closedir(&dir); k_sem_give(&audio_files_count_sem); atomic_set(&num_files, count); - audio_select_random_sound(); -} - -static void select_next_file_work_handler(struct k_work *work) -{ - ARG_UNUSED(work); - LOG_DBG("Select next file work handler"); - audio_select_random_sound(); + LOG_INF("Audio refresh found %d file(s) in %s", count, FS_AUDIO_PATH); } static int audio_init(void) { - struct k_work_queue_config audio_work_q_config = { - .name = "audio_work_q", - .no_yield = false, - .essential = true, - .work_timeout_ms = 0}; - - k_work_queue_start(&audio_work_q, - audio_work_q_stack, - K_THREAD_STACK_SIZEOF(audio_work_q_stack), - CONFIG_AUDIO_WORKQUEUE_PRIORITY, &audio_work_q_config); - - k_work_init(&select_next_file_work, select_next_file_work_handler); - if (!device_is_ready(i2s_dev)) { LOG_ERR("I2S device not ready"); @@ -272,14 +263,7 @@ static int audio_init(void) return 0; } -SYS_INIT(audio_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); // Stelle sicher, dass dies nach der FS-Initialisierung erfolgt - -static void audio_trigger_next_file_selection(void) -{ - k_sem_reset(&audio_file_select_sem); - LOG_DBG("Triggering workq file selection"); - k_work_submit_to_queue(&audio_work_q, &select_next_file_work); -} +SYS_INIT(audio_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); void audio_start_random_playback(void) { @@ -293,11 +277,12 @@ void audio_thread(void *arg1, void *arg2, void *arg3) ARG_UNUSED(arg2); ARG_UNUSED(arg3); + LOG_INF("Audio thread waiting for filesystem ready event"); k_event_wait(&event_mgmt_events, EVENT_MGMT_FS_READY, false, K_FOREVER); + LOG_INF("Audio thread received filesystem ready event"); audio_refresh_files(); - atomic_set(&thread_state, AUDIO_PRECACHING); - k_event_set(&audio_events, EV_STATE_STEP); + audio_set_state(AUDIO_IDLE, "filesystem ready"); while (1) { @@ -311,109 +296,138 @@ void audio_thread(void *arg1, void *arg2, void *arg3) k_event_clear(&audio_events, EV_STATE_STEP); } + /* + * EV_PLAY_RANDOM: button press requests a random sound. + * If idle, start it immediately. Otherwise queue it as a non-interrupt command + * so it plays after the current sound finishes. + */ if (active_events & EV_PLAY_RANDOM) { - LOG_DBG("Play random event received"); k_event_clear(&audio_events, EV_PLAY_RANDOM); - if (state == AUDIO_ARMED) + char random_file[CONFIG_FS_MGMT_MAX_PATH_LENGTH]; + if (audio_select_random_to_buf(random_file, sizeof(random_file)) == 0) { - i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_START); - atomic_set(&thread_state, AUDIO_PLAYING); - } - else - { - k_event_set(&audio_cache_event, AUDIO_CACHE_EVT_STOP); - i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DROP); - - audio_select_random_sound(); - atomic_set(&thread_state, AUDIO_PRECACHING); - k_event_set(&audio_events, EV_STATE_STEP); + if (state == AUDIO_IDLE) + { + k_mutex_lock(&audio_ctx_mutex, K_FOREVER); + strncpy(audio_ctx.next_file_name, random_file, sizeof(audio_ctx.next_file_name) - 1); + audio_ctx.next_file_name[sizeof(audio_ctx.next_file_name) - 1] = '\0'; + k_mutex_unlock(&audio_ctx_mutex); + audio_set_state(AUDIO_PRECACHING, "play random while idle"); + k_event_set(&audio_events, EV_STATE_STEP); + } + else + { + struct audio_cmd_msg cmd; + strncpy(cmd.filename, random_file, sizeof(cmd.filename) - 1); + cmd.filename[sizeof(cmd.filename) - 1] = '\0'; + cmd.is_interrupt = false; + if (k_msgq_put(&audio_cmd_q, &cmd, K_NO_WAIT) != 0) + { + LOG_WRN("Random play queue full, discarding"); + } + else + { + LOG_INF("Random play queued: '%s'", random_file); + } + } } continue; } switch (state) { - case AUDIO_PRECACHING: - LOG_DBG("Audio thread starting precache task"); - k_event_set(&audio_cache_event, AUDIO_CACHE_EVT_START); - atomic_set(&thread_state, AUDIO_WAIT_FOR_CACHE); - break; - - case AUDIO_WAIT_FOR_CACHE: - if (active_events & EV_CACHE_READY) - { - k_event_clear(&audio_events, EV_CACHE_READY); - atomic_set(&thread_state, AUDIO_ARMED); - - if (k_event_wait(&audio_events, EV_AUTOSTART, false, K_NO_WAIT) & EV_AUTOSTART) - { - k_event_clear(&audio_events, EV_AUTOSTART); - i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_START); - atomic_set(&thread_state, AUDIO_PLAYING); - LOG_DBG("Autostarting queued audio playback"); - } - else - { - LOG_DBG("System Armed. Waiting for Buzzer..."); - audio_trigger_next_file_selection(); - } - } - break; - - case AUDIO_ARMED: + case AUDIO_IDLE: if (active_events & EV_MSGQ_NOT_EMPTY) { k_event_clear(&audio_events, EV_MSGQ_NOT_EMPTY); - i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DROP); - k_event_set(&audio_cache_event, AUDIO_CACHE_EVT_STOP); struct audio_cmd_msg cmd; - k_msgq_get(&audio_cmd_q, &cmd, K_NO_WAIT); + if (k_msgq_get(&audio_cmd_q, &cmd, K_NO_WAIT) != 0) + { + LOG_WRN("EV_MSGQ_NOT_EMPTY set but queue empty"); + break; + } k_mutex_lock(&audio_ctx_mutex, K_FOREVER); strncpy(audio_ctx.next_file_name, cmd.filename, sizeof(audio_ctx.next_file_name) - 1); audio_ctx.next_file_name[sizeof(audio_ctx.next_file_name) - 1] = '\0'; k_mutex_unlock(&audio_ctx_mutex); - k_event_set(&audio_events, EV_AUTOSTART); - atomic_set(&thread_state, AUDIO_PRECACHING); + LOG_INF("Dequeued command while idle: '%s'", cmd.filename); + audio_set_state(AUDIO_PRECACHING, "command while idle"); k_event_set(&audio_events, EV_STATE_STEP); } break; + case AUDIO_PRECACHING: + LOG_INF("Precaching '%s'", audio_ctx.next_file_name); + i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_PREPARE); + k_event_set(&audio_cache_event, AUDIO_CACHE_EVT_START); + audio_set_state(AUDIO_WAIT_FOR_CACHE, "cache start requested"); + break; + + case AUDIO_WAIT_FOR_CACHE: + if (active_events & EV_MSGQ_NOT_EMPTY) + { + k_event_clear(&audio_events, EV_MSGQ_NOT_EMPTY); + + struct audio_cmd_msg cmd; + if (k_msgq_peek(&audio_cmd_q, &cmd) == 0 && cmd.is_interrupt) + { + /* Interrupt while caching: flush and restart for the new file */ + k_event_set(&audio_cache_event, AUDIO_CACHE_EVT_STOP); + i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_PREPARE); + k_msgq_get(&audio_cmd_q, &cmd, K_NO_WAIT); + + k_mutex_lock(&audio_ctx_mutex, K_FOREVER); + strncpy(audio_ctx.next_file_name, cmd.filename, sizeof(audio_ctx.next_file_name) - 1); + audio_ctx.next_file_name[sizeof(audio_ctx.next_file_name) - 1] = '\0'; + k_mutex_unlock(&audio_ctx_mutex); + + LOG_INF("Interrupt while caching, switching to '%s'", cmd.filename); + audio_set_state(AUDIO_PRECACHING, "interrupt while caching"); + k_event_set(&audio_events, EV_STATE_STEP); + break; + } + /* Non-interrupt: already queued, will play after current */ + } + + if (active_events & EV_CACHE_READY) + { + k_event_clear(&audio_events, EV_CACHE_READY); + i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_START); + audio_set_state(AUDIO_PLAYING, "cache primed, starting"); + LOG_INF("Playback started: '%s'", audio_ctx.next_file_name); + } + break; + case AUDIO_PLAYING: if (active_events & EV_MSGQ_NOT_EMPTY) { k_event_clear(&audio_events, EV_MSGQ_NOT_EMPTY); struct audio_cmd_msg cmd; - if (k_msgq_peek(&audio_cmd_q, &cmd) == 0) + if (k_msgq_peek(&audio_cmd_q, &cmd) == 0 && cmd.is_interrupt) { - if (cmd.is_interrupt) - { - i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DROP); - k_event_set(&audio_cache_event, AUDIO_CACHE_EVT_STOP); + k_event_set(&audio_cache_event, AUDIO_CACHE_EVT_STOP); + i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DROP); + k_msgq_get(&audio_cmd_q, &cmd, K_NO_WAIT); - if (k_msgq_get(&audio_cmd_q, &cmd, K_NO_WAIT) == 0) - { - k_mutex_lock(&audio_ctx_mutex, K_FOREVER); - strncpy(audio_ctx.next_file_name, cmd.filename, sizeof(audio_ctx.next_file_name) - 1); - audio_ctx.next_file_name[sizeof(audio_ctx.next_file_name) - 1] = '\0'; - k_mutex_unlock(&audio_ctx_mutex); + k_mutex_lock(&audio_ctx_mutex, K_FOREVER); + strncpy(audio_ctx.next_file_name, cmd.filename, sizeof(audio_ctx.next_file_name) - 1); + audio_ctx.next_file_name[sizeof(audio_ctx.next_file_name) - 1] = '\0'; + k_mutex_unlock(&audio_ctx_mutex); - k_event_set(&audio_events, EV_AUTOSTART); - atomic_set(&thread_state, AUDIO_PRECACHING); - k_event_set(&audio_events, EV_STATE_STEP); - } - } - else - { - LOG_DBG("Non-interrupt command queued during playback; will process after drain"); - } + LOG_INF("Interrupting playback with '%s'", cmd.filename); + audio_set_state(AUDIO_PRECACHING, "interrupt during playback"); + k_event_set(&audio_events, EV_STATE_STEP); + } + else + { + LOG_DBG("Non-interrupt command queued; will play after drain"); } - break; } @@ -421,26 +435,60 @@ void audio_thread(void *arg1, void *arg2, void *arg3) { k_event_clear(&audio_events, EV_CACHE_DONE); i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DRAIN); - atomic_set(&thread_state, AUDIO_DRAINING); + audio_set_state(AUDIO_DRAINING, "cache complete"); } break; case AUDIO_DRAINING: + { + if (active_events & EV_MSGQ_NOT_EMPTY) + { + k_event_clear(&audio_events, EV_MSGQ_NOT_EMPTY); + + struct audio_cmd_msg cmd; + if (k_msgq_peek(&audio_cmd_q, &cmd) == 0 && cmd.is_interrupt) + { + k_msgq_get(&audio_cmd_q, &cmd, K_NO_WAIT); + k_event_set(&audio_cache_event, AUDIO_CACHE_EVT_STOP); + i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DROP); + + k_mutex_lock(&audio_ctx_mutex, K_FOREVER); + strncpy(audio_ctx.next_file_name, cmd.filename, sizeof(audio_ctx.next_file_name) - 1); + audio_ctx.next_file_name[sizeof(audio_ctx.next_file_name) - 1] = '\0'; + k_mutex_unlock(&audio_ctx_mutex); + + LOG_INF("Interrupt during drain, switching to '%s'", cmd.filename); + audio_set_state(AUDIO_PRECACHING, "interrupt during drain"); + k_event_set(&audio_events, EV_STATE_STEP); + break; + } + } + if (k_mem_slab_num_free_get(&audio_cache_slab) == CONFIG_AUDIO_CACHE_SLAB_COUNT) { - LOG_DBG("Audio for file drained, ready for next file"); + LOG_DBG("Drain complete"); if (k_msgq_num_used_get(&audio_cmd_q) > 0) { - k_event_set(&audio_events, EV_MSGQ_NOT_EMPTY); + struct audio_cmd_msg cmd; + k_msgq_get(&audio_cmd_q, &cmd, K_NO_WAIT); + + k_mutex_lock(&audio_ctx_mutex, K_FOREVER); + strncpy(audio_ctx.next_file_name, cmd.filename, sizeof(audio_ctx.next_file_name) - 1); + audio_ctx.next_file_name[sizeof(audio_ctx.next_file_name) - 1] = '\0'; + k_mutex_unlock(&audio_ctx_mutex); + + LOG_INF("Drain complete, next: '%s'", cmd.filename); + audio_set_state(AUDIO_PRECACHING, "drain complete, queued command"); + k_event_set(&audio_events, EV_STATE_STEP); } else { - atomic_set(&thread_state, AUDIO_PRECACHING); - k_event_set(&audio_events, EV_STATE_STEP); + audio_set_state(AUDIO_IDLE, "drain complete, no more commands"); } } break; } + } } } @@ -453,7 +501,6 @@ void audio_pump_thread(void *arg1, void *arg2, void *arg3) ARG_UNUSED(arg2); ARG_UNUSED(arg3); - uint8_t num_channels = 1; void *mem_slab; while (1) { @@ -487,10 +534,13 @@ void audio_pump_thread(void *arg1, void *arg2, void *arg3) if (rc == 0) { + bool signal_cache_ready = false; + k_mutex_lock(&audio_ctx_mutex, K_FOREVER); if (!audio_ctx.is_file_open) { + LOG_INF("Opening audio file '%s' for playback", audio_ctx.next_file_name); rc = fs_mgmt_pm_open(&audio_ctx.file, audio_ctx.next_file_name, FS_O_READ); if (rc < 0) { @@ -502,8 +552,10 @@ void audio_pump_thread(void *arg1, void *arg2, void *arg3) } audio_ctx.is_file_open = true; + audio_ctx.cache_ready_signaled = false; + audio_ctx.cached_blocks = 0; - // NEU: Größen- und Zähler-Initialisierung exklusiv hier! + /* File length and cache position are initialized once per playback start. */ audio_ctx.audio_size = fs_mgmt_get_audio_data_len(&audio_ctx.file); audio_ctx.cached_bytes = 0; @@ -533,8 +585,8 @@ void audio_pump_thread(void *arg1, void *arg2, void *arg3) continue; } - ssize_t bytes_to_read = MIN(CONFIG_AUDIO_CACHE_SLAB_SIZE / 2, remaining_bytes); - ssize_t bytes_read = fs_read(&audio_ctx.file, audio_mono_stage, bytes_to_read); + ssize_t bytes_to_read = MIN(CONFIG_AUDIO_CACHE_SLAB_SIZE, remaining_bytes); + ssize_t bytes_read = fs_read(&audio_ctx.file, mem_slab, bytes_to_read); if (bytes_read <= 0) // <= 0, um EOF (0) und Fehler (< 0) abzufangen { @@ -556,9 +608,9 @@ void audio_pump_thread(void *arg1, void *arg2, void *arg3) audio_ctx.cached_bytes += bytes_read; // LOG_DBG("Cached %u%% ", (int)((audio_ctx.cached_bytes * 100) / audio_ctx.audio_size)); - if (bytes_read > CONFIG_AUDIO_CACHE_SLAB_SIZE / 2) + if (bytes_read > CONFIG_AUDIO_CACHE_SLAB_SIZE) { - LOG_ERR("Read size %d exceeds half slab size %d", (int)bytes_read, CONFIG_AUDIO_CACHE_SLAB_SIZE / 2); + LOG_ERR("Read size %d exceeds slab size %d", (int)bytes_read, CONFIG_AUDIO_CACHE_SLAB_SIZE); k_mem_slab_free(&audio_cache_slab, &mem_slab); fs_close(&audio_ctx.file); audio_ctx.is_file_open = false; @@ -569,9 +621,9 @@ void audio_pump_thread(void *arg1, void *arg2, void *arg3) if ((bytes_read & 0x1) != 0) { - if (bytes_read >= (CONFIG_AUDIO_CACHE_SLAB_SIZE / 2)) + if (bytes_read >= (CONFIG_AUDIO_CACHE_SLAB_SIZE)) { - LOG_ERR("Odd mono byte count at half-slab boundary: %d", (int)bytes_read); + LOG_ERR("Odd PCM byte count at slab boundary: %d", (int)bytes_read); k_mem_slab_free(&audio_cache_slab, &mem_slab); fs_close(&audio_ctx.file); audio_ctx.is_file_open = false; @@ -580,31 +632,23 @@ void audio_pump_thread(void *arg1, void *arg2, void *arg3) continue; } - audio_mono_stage[bytes_read] = 0; + ((uint8_t *)mem_slab)[bytes_read] = 0; bytes_read++; } - if (bytes_read < CONFIG_AUDIO_CACHE_SLAB_SIZE / 2) + if (bytes_read < CONFIG_AUDIO_CACHE_SLAB_SIZE) { - memset(audio_mono_stage + bytes_read, 0, (CONFIG_AUDIO_CACHE_SLAB_SIZE / 2) - bytes_read); - bytes_read = CONFIG_AUDIO_CACHE_SLAB_SIZE / 2; + memset((uint8_t *)mem_slab + bytes_read, 0, (CONFIG_AUDIO_CACHE_SLAB_SIZE) - bytes_read); + bytes_read = CONFIG_AUDIO_CACHE_SLAB_SIZE; } - if (num_channels == 1) + audio_ctx.cached_blocks++; + + if (!audio_ctx.cache_ready_signaled && + (audio_ctx.cached_blocks >= 2 || audio_ctx.cached_bytes >= audio_ctx.audio_size)) { - uint8_t *dst = (uint8_t *)mem_slab; - - for (size_t i = 0; i < (size_t)bytes_read; i += 2) - { - uint8_t lo = audio_mono_stage[i]; - uint8_t hi = audio_mono_stage[i + 1]; - size_t out = i * 2; - - dst[out] = lo; - dst[out + 1] = hi; - dst[out + 2] = lo; - dst[out + 3] = hi; - } + audio_ctx.cache_ready_signaled = true; + signal_cache_ready = true; } k_mutex_unlock(&audio_ctx_mutex); @@ -614,6 +658,13 @@ void audio_pump_thread(void *arg1, void *arg2, void *arg3) k_mem_slab_free(&audio_cache_slab, &mem_slab); continue; } + + if (signal_cache_ready) + { + LOG_DBG("Cache ready for '%s' after %u block(s)", audio_ctx.next_file_name, + (unsigned int)audio_ctx.cached_blocks); + k_event_set(&audio_events, EV_CACHE_READY); + } } } } diff --git a/firmware/prj.conf b/firmware/prj.conf index 0046e86..a0f3ab6 100644 --- a/firmware/prj.conf +++ b/firmware/prj.conf @@ -17,5 +17,3 @@ CONFIG_MAIN_STACK_SIZE=2048 CONFIG_INIT_STACKS=y CONFIG_THREAD_STACK_INFO=y CONFIG_STACK_SENTINEL=y - -# CONFIG_LOG_MODE_IMMEDIATE=y \ No newline at end of file diff --git a/firmware/src/main.c b/firmware/src/main.c index 9a3a917..eab4cd5 100644 --- a/firmware/src/main.c +++ b/firmware/src/main.c @@ -57,14 +57,11 @@ int main(void) } #endif - LOG_INF("Init complete. Starting audio playback test..."); - - k_sleep(K_SECONDS(1)); + LOG_INF("Init complete."); + + k_sleep(K_MSEC(500)); + LOG_INF("Playing test audio files..."); audio_queue_play("/lfs/sys/update", false); - k_sleep(K_SECONDS(1)); - audio_start_random_playback(); // Starte die Wiedergabe eines zufälligen Sounds - k_sleep(K_SECONDS(1)); - audio_queue_play("/lfs/sys/404", true); - + audio_queue_play("/lfs/sys/confirm", false); k_sleep(K_FOREVER); } \ No newline at end of file