Files
buzzer/firmware/src/audio.c

386 lines
11 KiB
C

#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/random/random.h>
#include <zephyr/device.h>
#include <zephyr/drivers/i2s.h>
#include <string.h>
#include <audio.h>
#include <fs.h>
#include <io.h>
LOG_MODULE_REGISTER(audio, LOG_LEVEL_INF);
K_MEM_SLAB_DEFINE(audio_slab, AUDIO_BLOCK_SIZE, AUDIO_BLOCK_COUNT, 4);
K_MSGQ_DEFINE(free_slab_msgq, sizeof(void *), AUDIO_BLOCK_COUNT, 4);
K_MSGQ_DEFINE(filled_slab_msgq, sizeof(void *), AUDIO_BLOCK_COUNT, 4);
K_SEM_DEFINE(audio_start_sem, 0, 1); // Buzzer
K_SEM_DEFINE(fs_sync_sem, 0, 1); // USB Sync
/* Get the node identifier for the alias "audio-i2s" */
#define I2S_NODE DT_ALIAS(audio_i2s)
/* Verify the node exists to avoid cryptic compiler errors */
#if !DT_NODE_EXISTS(I2S_NODE)
#error "Audio I2S alias not defined in devicetree"
#endif
static const struct device *const i2s_dev = DEVICE_DT_GET(I2S_NODE);
static struct k_poll_event poll_events[3];
static struct fs_file_t cached_file;
static bool cached_file_open;
static bool cached_file_eof;
static char cached_filename[64];
int get_random_file(const char *path, char *out_filename, size_t max_len);
static int audio_prepare_random_file(void)
{
int rc;
if (cached_file_open)
{
fs_close(&cached_file);
cached_file_open = false;
}
rc = get_random_file(AUDIO_PATH, cached_filename, sizeof(cached_filename));
if (rc < 0)
{
LOG_ERR("No random file available in %s (%d)", AUDIO_PATH, rc);
return rc;
}
fs_file_t_init(&cached_file);
rc = fs_open(&cached_file, cached_filename, FS_O_READ);
if (rc < 0)
{
LOG_ERR("Failed to open cache file %s (%d)", cached_filename, rc);
return rc;
}
cached_file_open = true;
cached_file_eof = false;
LOG_DBG("Priming from file: %s", cached_filename);
return 0;
}
static int audio_fill_slab_from_cache(void *block)
{
ssize_t bytes_read;
uint8_t *bytes = block;
if (!cached_file_open || cached_file_eof)
{
return -ENODATA;
}
bytes_read = fs_read(&cached_file, bytes, AUDIO_BLOCK_SIZE);
if (bytes_read < 0)
{
LOG_ERR("fs_read failed: %d", (int)bytes_read);
return (int)bytes_read;
}
if (bytes_read == 0)
{
cached_file_eof = true;
return -ENODATA;
}
if (bytes_read < AUDIO_BLOCK_SIZE)
{
memset(&bytes[bytes_read], 0, AUDIO_BLOCK_SIZE - bytes_read);
cached_file_eof = true;
}
return 0;
}
static void audio_prime_prefill(uint32_t target_blocks)
{
uint32_t primed = 0;
while (primed < target_blocks)
{
void *block;
if (k_msgq_get(&free_slab_msgq, &block, K_MSEC(20)) != 0)
{
break;
}
if (audio_fill_slab_from_cache(block) == 0)
{
if (k_msgq_put(&filled_slab_msgq, &block, K_NO_WAIT) == 0)
{
primed++;
}
else
{
k_mem_slab_free(&audio_slab, &block);
break;
}
}
else
{
k_mem_slab_free(&audio_slab, &block);
break;
}
}
LOG_DBG("Prefilled %u/%u slabs", primed, target_blocks);
}
int get_random_file(const char *path, char *out_filename, size_t max_len)
{
struct fs_dir_t dirp;
struct fs_dirent entry;
int file_count = 0;
int rc;
fs_dir_t_init(&dirp);
rc = fs_opendir(&dirp, path);
if (rc < 0)
return rc;
while (fs_readdir(&dirp, &entry) == 0 && entry.name[0] != '\0')
{
if (entry.type == FS_DIR_ENTRY_FILE)
{
file_count++;
}
}
fs_closedir(&dirp);
if (file_count == 0)
return -ENOENT;
uint32_t random_index = sys_rand32_get() % file_count;
rc = fs_opendir(&dirp, path);
if (rc < 0)
return rc;
int current_index = 0;
while (fs_readdir(&dirp, &entry) == 0 && entry.name[0] != '\0')
{
if (entry.type == FS_DIR_ENTRY_FILE)
{
if (current_index == random_index)
{
snprintf(out_filename, max_len, "%s/%s", path, entry.name);
break;
}
current_index++;
}
}
fs_closedir(&dirp);
return 0;
}
void audio_thread(void *arg1, void *arg2, void *arg3)
{
LOG_DBG("Audio thread started");
int rc;
k_poll_event_init(&poll_events[0], K_POLL_TYPE_SEM_AVAILABLE, K_POLL_MODE_NOTIFY_ONLY, &audio_start_sem);
k_poll_event_init(&poll_events[1], K_POLL_TYPE_SEM_AVAILABLE, K_POLL_MODE_NOTIFY_ONLY, &fs_sync_sem);
k_poll_event_init(&poll_events[2], K_POLL_TYPE_MSGQ_DATA_AVAILABLE, K_POLL_MODE_NOTIFY_ONLY, &free_slab_msgq);
while (1)
{
bool is_playing = false;
io_status(false);
uint32_t queued = 0;
rc = audio_prepare_random_file();
if (rc == 0)
{
audio_prime_prefill(AUDIO_BLOCK_COUNT);
}
else
{
LOG_ERR("Failed to prepare audio file, will retry on play event: %d", rc);
k_sleep(K_SECONDS(5));
continue;
}
while (1)
{
k_poll(poll_events, ARRAY_SIZE(poll_events), K_FOREVER);
if (poll_events[0].state & K_POLL_STATE_SEM_AVAILABLE)
{
int trigger_rc;
int drop_rc;
void *block;
queued = 0;
LOG_DBG("Handling PLAY event");
k_sem_take(&audio_start_sem, K_NO_WAIT);
poll_events[0].state = K_POLL_STATE_NOT_READY;
if (is_playing)
{
LOG_DBG("Audio already playing, canceling current playback and restarting");
drop_rc = i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DROP);
if (drop_rc < 0)
{
LOG_WRN("I2S drop trigger failed: %d", drop_rc);
}
while (k_msgq_get(&filled_slab_msgq, &block, K_NO_WAIT) == 0)
{
k_mem_slab_free(&audio_slab, &block);
}
rc = audio_prepare_random_file();
if (rc == 0)
{
audio_prime_prefill(MIN(2, AUDIO_BLOCK_COUNT)); // Sofort mit 2 Blöcken vorfüllen für schnelleren Restart
}
else
{
LOG_ERR("Failed to prepare audio file, will retry on play event: %d", rc);
break;
}
}
LOG_INF("PLAY: %u slabs ready (prefilled)", k_msgq_num_used_get(&filled_slab_msgq));
while (k_msgq_get(&filled_slab_msgq, &block, K_NO_WAIT) == 0)
{
rc = i2s_write(i2s_dev, block, AUDIO_BLOCK_SIZE);
if (rc == 0)
{
queued++;
}
else
{
LOG_ERR("i2s_write prefilled block failed: %d", rc);
k_mem_slab_free(&audio_slab, &block);
}
}
if (queued > 0)
{
trigger_rc = i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_START);
if (trigger_rc < 0)
{
LOG_DBG("I2S start trigger failed: %d", trigger_rc);
}
else
{
is_playing = true;
io_status(true);
}
}
else
{
LOG_WRN("PLAY requested, but no prefilled slabs available");
}
}
if (poll_events[1].state & K_POLL_STATE_SEM_AVAILABLE)
{
LOG_DBG("Handling FS SYNC event");
k_sem_take(&fs_sync_sem, K_NO_WAIT);
poll_events[1].state = K_POLL_STATE_NOT_READY;
}
if (poll_events[2].state & K_POLL_STATE_MSGQ_DATA_AVAILABLE)
{
void *block;
while (k_msgq_get(&free_slab_msgq, &block, K_NO_WAIT) == 0)
{
if (audio_fill_slab_from_cache(block) == 0)
{
if (is_playing)
{
rc = i2s_write(i2s_dev, block, AUDIO_BLOCK_SIZE);
if (rc != 0)
{
LOG_ERR("i2s_write refill block failed: %d", rc);
k_mem_slab_free(&audio_slab, &block);
}
}
else if (k_msgq_put(&filled_slab_msgq, &block, K_NO_WAIT) != 0)
{
k_mem_slab_free(&audio_slab, &block);
LOG_ERR("Audio not playing, but filled queue is full, freeing slab");
}
}
else
{
k_mem_slab_free(&audio_slab, &block);
}
}
poll_events[2].state = K_POLL_STATE_NOT_READY;
}
if (is_playing && cached_file_eof)
{
LOG_INF("Reached end of file, draining I2S");
i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DRAIN);
break;
}
}
}
}
K_THREAD_DEFINE(audio_thread_id, AUDIO_THREAD_STACK_SIZE, audio_thread, NULL, NULL, NULL, AUDIO_THREAD_PRIORITY, 0, 0);
void slab_thread(void *arg1, void *arg2, void *arg3)
{
LOG_DBG("Slab thread started");
void *block;
while (1)
{
k_mem_slab_alloc(&audio_slab, &block, K_FOREVER);
k_msgq_put(&free_slab_msgq, &block, K_FOREVER);
}
}
K_THREAD_DEFINE(slab_thread_id, 512, slab_thread, NULL, NULL, NULL, AUDIO_THREAD_PRIORITY + 1, 0, 0);
int audio_init(void)
{
LOG_DBG("Initializing audio subsystem...");
if (!device_is_ready(i2s_dev))
{
LOG_ERR("I2S device not ready");
return -ENODEV;
}
/* Initial configuration of the I2S peripheral */
struct i2s_config config = {
.word_size = AUDIO_WORD_WIDTH,
.channels = 2,
.format = I2S_FMT_DATA_FORMAT_I2S,
.options = I2S_OPT_BIT_CLK_MASTER | I2S_OPT_FRAME_CLK_MASTER,
.frame_clk_freq = AUDIO_SAMPLE_RATE,
.mem_slab = &audio_slab,
.block_size = AUDIO_BLOCK_SIZE,
.timeout = SYS_FOREVER_MS,
};
int ret = i2s_configure(i2s_dev, I2S_DIR_TX, &config);
if (ret < 0)
{
LOG_ERR("Failed to configure I2S: %d", ret);
return ret;
}
LOG_INF("Audio subsystem initialized successfully, %u bits @ %u.%03u kHz",
config.word_size, config.frame_clk_freq / 1000, config.frame_clk_freq % 1000);
return 0;
}
void audio_play(void)
{
LOG_DBG("Posting PLAY event");
k_sem_give(&audio_start_sem);
}