Files
buzzer_2/firmware/libs/audio/src/audio.c
Eduard Iten ad2dc19641 feat(buzzy): add buzzy board support with SPI flash, fix build warnings
- Register BOARD_ROOT in CMakeLists.txt and sysbuild/CMakeLists.txt
- Add boardRoots to VS Code settings for board picker
- buzzy.dts: add nordic,pm-ext-flash chosen, external-flash and i2s-audio aliases
- nrf52840dk overlay: add external-flash alias (mirrors qspi-flash)
- fs_mgmt/Kconfig: select SPI_NOR for BOARD_BUZZY, NORDIC_QSPI_NOR for DK
- fs_mgmt.c: use external-flash alias instead of qspi-flash
- audio.c: bound snprintf to avoid truncation warning
- prj.conf: remove STACK_SENTINEL (conflicts with MPU_STACK_GUARD)
- mcuboot.conf: remove UART_CONSOLE (no SERIAL on buzzy)
- Delete mcuboot.overlay (no serial recovery needed)

Both buzzy/nrf52840 and nrf52840dk/nrf52840 build cleanly with zero warnings.
2026-05-07 11:00:14 +02:00

674 lines
23 KiB
C

#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/fs/fs.h>
#include <zephyr/drivers/i2s.h>
#include "audio.h"
#include "fs_mgmt.h"
#include "event_mgmt.h"
LOG_MODULE_REGISTER(audio, CONFIG_AUDIO_LOG_LEVEL);
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_MEM_SLAB_DEFINE(audio_cache_slab, CONFIG_AUDIO_CACHE_SLAB_SIZE, CONFIG_AUDIO_CACHE_SLAB_COUNT, 4);
enum audio_thread_state_t
{
AUDIO_IDLE,
AUDIO_PRECACHING,
AUDIO_WAIT_FOR_CACHE,
AUDIO_PLAYING,
AUDIO_DRAINING,
};
#define EV_PLAY_RANDOM BIT(0)
#define EV_MSGQ_NOT_EMPTY BIT(1)
#define EV_CACHE_READY BIT(2)
#define EV_CACHE_DONE BIT(3)
#define EV_STATE_STEP BIT(4)
#define EV_ALL (EV_PLAY_RANDOM | EV_MSGQ_NOT_EMPTY | EV_CACHE_READY | EV_CACHE_DONE | EV_STATE_STEP)
K_EVENT_DEFINE(audio_events);
#define AUDIO_CACHE_EVT_START BIT(0)
#define AUDIO_CACHE_EVT_STOP BIT(1)
K_EVENT_DEFINE(audio_cache_event);
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 = 1,
.format = I2S_FMT_DATA_FORMAT_I2S,
.options = I2S_OPT_BIT_CLK_MASTER | I2S_OPT_FRAME_CLK_MASTER,
.frame_clk_freq = 16000,
.mem_slab = &audio_cache_slab,
.block_size = CONFIG_AUDIO_CACHE_SLAB_SIZE,
.timeout = SYS_FOREVER_MS,
};
K_MUTEX_DEFINE(audio_ctx_mutex);
atomic_t thread_state = ATOMIC_INIT(0);
atomic_t num_files = ATOMIC_INIT(0);
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)
{
if (is_interrupt)
{
/* Keep hardware state changes inside audio_thread to avoid cross-thread races. */
k_msgq_purge(&audio_cmd_q);
}
if (strlen(filename) == 0)
{
return 0;
}
if (k_msgq_num_free_get(&audio_cmd_q) == 0)
{
LOG_ERR("Audio command queue is full, cannot enqueue new command");
return -ENOMEM;
}
struct audio_cmd_msg cmd;
strncpy(cmd.filename, filename, sizeof(cmd.filename) - 1);
cmd.filename[sizeof(cmd.filename) - 1] = '\0';
cmd.is_interrupt = is_interrupt;
if (k_msgq_put(&audio_cmd_q, &cmd, K_FOREVER) != 0)
{
LOG_ERR("Failed to enqueue audio command");
return -EFAULT;
}
/*
* 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_IDLE))
{
k_event_set(&audio_events, EV_MSGQ_NOT_EMPTY);
}
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_to_buf(char *buf, size_t buf_size)
{
if (k_sem_take(&audio_files_count_sem, K_FOREVER) != 0)
{
return -EFAULT;
}
int count = atomic_get(&num_files);
if (count == 0)
{
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);
return -ENOENT;
}
int random_index = k_cycle_get_32() % count;
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_files_count_sem);
return rc;
}
int current_index = 0;
while (1)
{
rc = fs_readdir(&dir, &entry);
if (rc < 0 || entry.name[0] == '\0')
{
break;
}
if (entry.type == FS_DIR_ENTRY_FILE)
{
if (current_index == random_index)
{
snprintf(buf, buf_size, "%s/%.*s", FS_AUDIO_PATH,
(int)(buf_size - sizeof(FS_AUDIO_PATH) - 1U), entry.name);
LOG_DBG("Selected random audio file: %s", buf);
found = true;
break;
}
current_index++;
}
}
fs_mgmt_pm_closedir(&dir);
k_sem_give(&audio_files_count_sem);
return found ? 0 : -ENOENT;
}
void audio_refresh_files(void)
{
// Lokale Strukturen verwenden, um Reentrancy-Probleme zu vermeiden
struct fs_dir_t dir;
struct fs_dirent entry;
int count = 0;
int rc;
k_sem_reset(&audio_files_count_sem);
fs_dir_t_init(&dir);
// Nutze deinen PM-Wrapper für den Flash-Zugriff
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);
atomic_set(&num_files, 0);
return;
}
while (1)
{
rc = fs_readdir(&dir, &entry);
if (rc < 0)
{
LOG_ERR("Directory read error: %d", rc);
break;
}
if (entry.name[0] == '\0')
{
break;
}
if (entry.type == FS_DIR_ENTRY_FILE)
{
count++;
}
}
fs_mgmt_pm_closedir(&dir);
k_sem_give(&audio_files_count_sem);
atomic_set(&num_files, count);
LOG_INF("Audio refresh found %d file(s) in %s", count, FS_AUDIO_PATH);
}
static int audio_init(void)
{
if (!device_is_ready(i2s_dev))
{
LOG_ERR("I2S device not ready");
return -ENODEV;
}
int rc = i2s_configure(i2s_dev, I2S_DIR_TX, &i2s_cfg);
if (rc < 0)
{
LOG_ERR("Failed to configure I2S: %d", rc);
return rc;
}
LOG_DBG("Audio module initialized");
return 0;
}
SYS_INIT(audio_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);
void audio_start_random_playback(void)
{
LOG_DBG("audio_start_random_playback called");
k_event_set(&audio_events, EV_PLAY_RANDOM);
}
void audio_thread(void *arg1, void *arg2, void *arg3)
{
ARG_UNUSED(arg1);
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();
audio_set_state(AUDIO_IDLE, "filesystem ready");
while (1)
{
enum audio_thread_state_t state = atomic_get(&thread_state);
k_timeout_t timeout = (state == AUDIO_DRAINING) ? K_MSEC(10) : K_FOREVER;
uint32_t active_events = k_event_wait(&audio_events, EV_ALL, false, timeout);
if (active_events & EV_STATE_STEP)
{
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)
{
k_event_clear(&audio_events, EV_PLAY_RANDOM);
char random_file[CONFIG_FS_MGMT_MAX_PATH_LENGTH];
if (audio_select_random_to_buf(random_file, sizeof(random_file)) == 0)
{
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_IDLE:
if (active_events & EV_MSGQ_NOT_EMPTY)
{
k_event_clear(&audio_events, EV_MSGQ_NOT_EMPTY);
struct audio_cmd_msg cmd;
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);
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 && cmd.is_interrupt)
{
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);
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("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;
}
if (active_events & EV_CACHE_DONE)
{
k_event_clear(&audio_events, EV_CACHE_DONE);
i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DRAIN);
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("Drain complete");
if (k_msgq_num_used_get(&audio_cmd_q) > 0)
{
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
{
audio_set_state(AUDIO_IDLE, "drain complete, no more commands");
}
}
break;
}
}
}
}
K_THREAD_DEFINE(audio_thread_id, CONFIG_AUDIO_THREAD_STACK_SIZE, audio_thread, NULL, NULL, NULL,
CONFIG_AUDIO_THREAD_PRIORITY, 0, 0);
void audio_pump_thread(void *arg1, void *arg2, void *arg3)
{
ARG_UNUSED(arg1);
ARG_UNUSED(arg2);
ARG_UNUSED(arg3);
void *mem_slab;
while (1)
{
uint32_t events = k_event_wait(&audio_cache_event, AUDIO_CACHE_EVT_START | AUDIO_CACHE_EVT_STOP, false, K_FOREVER);
if (events & AUDIO_CACHE_EVT_STOP)
{
k_mutex_lock(&audio_ctx_mutex, K_FOREVER);
if (audio_ctx.is_file_open)
{
fs_close(&audio_ctx.file);
audio_ctx.is_file_open = false;
}
k_mutex_unlock(&audio_ctx_mutex);
k_event_clear(&audio_cache_event, AUDIO_CACHE_EVT_START | AUDIO_CACHE_EVT_STOP);
continue;
}
int rc = k_mem_slab_alloc(&audio_cache_slab, &mem_slab, K_NO_WAIT);
if (rc == -ENOMEM)
{
k_event_set(&audio_events, EV_CACHE_READY);
rc = k_mem_slab_alloc(&audio_cache_slab, &mem_slab, K_FOREVER);
if (k_event_wait(&audio_cache_event, AUDIO_CACHE_EVT_STOP, false, K_NO_WAIT))
{
k_mem_slab_free(&audio_cache_slab, &mem_slab);
continue;
}
}
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)
{
LOG_ERR("Failed to open audio file '%s': %d", audio_ctx.next_file_name, rc);
k_mem_slab_free(&audio_cache_slab, &mem_slab);
k_event_clear(&audio_cache_event, AUDIO_CACHE_EVT_START);
k_mutex_unlock(&audio_ctx_mutex);
continue;
}
audio_ctx.is_file_open = true;
audio_ctx.cache_ready_signaled = false;
audio_ctx.cached_blocks = 0;
/* 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;
LOG_DBG("Audio file '%s' opened for caching, size: %d", audio_ctx.next_file_name, (int)audio_ctx.audio_size);
}
if (audio_ctx.audio_size <= 0)
{
LOG_ERR("Invalid audio size for '%s': %d", audio_ctx.next_file_name, (int)audio_ctx.audio_size);
k_mem_slab_free(&audio_cache_slab, &mem_slab);
fs_close(&audio_ctx.file);
audio_ctx.is_file_open = false;
k_event_clear(&audio_cache_event, AUDIO_CACHE_EVT_START);
k_mutex_unlock(&audio_ctx_mutex);
continue;
}
ssize_t remaining_bytes = audio_ctx.audio_size - audio_ctx.cached_bytes;
if (remaining_bytes <= 0)
{
k_mem_slab_free(&audio_cache_slab, &mem_slab);
fs_close(&audio_ctx.file);
audio_ctx.is_file_open = false;
k_event_clear(&audio_cache_event, AUDIO_CACHE_EVT_START);
k_mutex_unlock(&audio_ctx_mutex);
k_event_set(&audio_events, EV_CACHE_DONE);
continue;
}
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
{
if (bytes_read < 0)
{
LOG_ERR("Failed to read audio data: %d", (int)bytes_read);
}
k_mem_slab_free(&audio_cache_slab, &mem_slab);
// EOF erreicht -> Datei schließen und START-Bit löschen
fs_close(&audio_ctx.file);
audio_ctx.is_file_open = false;
k_event_clear(&audio_cache_event, AUDIO_CACHE_EVT_START);
k_mutex_unlock(&audio_ctx_mutex);
k_event_set(&audio_events, EV_CACHE_DONE);
continue;
}
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)
{
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;
k_event_clear(&audio_cache_event, AUDIO_CACHE_EVT_START);
k_mutex_unlock(&audio_ctx_mutex);
continue;
}
if ((bytes_read & 0x1) != 0)
{
if (bytes_read >= (CONFIG_AUDIO_CACHE_SLAB_SIZE))
{
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;
k_event_clear(&audio_cache_event, AUDIO_CACHE_EVT_START);
k_mutex_unlock(&audio_ctx_mutex);
continue;
}
((uint8_t *)mem_slab)[bytes_read] = 0;
bytes_read++;
}
if (bytes_read < CONFIG_AUDIO_CACHE_SLAB_SIZE)
{
memset((uint8_t *)mem_slab + bytes_read, 0, (CONFIG_AUDIO_CACHE_SLAB_SIZE) - bytes_read);
bytes_read = CONFIG_AUDIO_CACHE_SLAB_SIZE;
}
audio_ctx.cached_blocks++;
if (!audio_ctx.cache_ready_signaled &&
(audio_ctx.cached_blocks >= 2 || audio_ctx.cached_bytes >= audio_ctx.audio_size))
{
audio_ctx.cache_ready_signaled = true;
signal_cache_ready = true;
}
k_mutex_unlock(&audio_ctx_mutex);
if (i2s_write(i2s_dev, mem_slab, CONFIG_AUDIO_CACHE_SLAB_SIZE) < 0)
{
LOG_ERR("Failed to write audio data to I2S");
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);
}
}
}
}
K_THREAD_DEFINE(audio_cache_thread_id, CONFIG_AUDIO_PUMP_THREAD_STACK_SIZE, audio_pump_thread, NULL, NULL, NULL,
CONFIG_AUDIO_PUMP_THREAD_PRIORITY, 0, 0);