#include #include #include #include #include #include #include #include #include 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); }