Tried to improve audio
This commit is contained in:
@@ -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,65 +296,89 @@ 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);
|
||||
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
|
||||
{
|
||||
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);
|
||||
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;
|
||||
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);
|
||||
@@ -377,9 +386,20 @@ void audio_thread(void *arg1, void *arg2, void *arg3)
|
||||
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("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;
|
||||
|
||||
@@ -389,31 +409,25 @@ void audio_thread(void *arg1, void *arg2, void *arg3)
|
||||
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);
|
||||
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_event_set(&audio_events, EV_AUTOSTART);
|
||||
atomic_set(&thread_state, AUDIO_PRECACHING);
|
||||
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 during playback; will process after drain");
|
||||
LOG_DBG("Non-interrupt command queued; will play after drain");
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -421,28 +435,62 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
K_THREAD_DEFINE(audio_thread_id, CONFIG_AUDIO_THREAD_STACK_SIZE, audio_thread, NULL, NULL, NULL,
|
||||
CONFIG_AUDIO_THREAD_PRIORITY, 0, 0);
|
||||
@@ -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)
|
||||
{
|
||||
uint8_t *dst = (uint8_t *)mem_slab;
|
||||
audio_ctx.cached_blocks++;
|
||||
|
||||
for (size_t i = 0; i < (size_t)bytes_read; i += 2)
|
||||
if (!audio_ctx.cache_ready_signaled &&
|
||||
(audio_ctx.cached_blocks >= 2 || audio_ctx.cached_bytes >= audio_ctx.audio_size))
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -57,14 +57,11 @@ int main(void)
|
||||
}
|
||||
#endif
|
||||
|
||||
LOG_INF("Init complete. Starting audio playback test...");
|
||||
LOG_INF("Init complete.");
|
||||
|
||||
k_sleep(K_SECONDS(1));
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user