diff --git a/applications/nrf5340_audio/src/modules/lc3_file.c b/applications/nrf5340_audio/src/modules/lc3_file.c index 61bc5018438a..ebbce1d1bd37 100644 --- a/applications/nrf5340_audio/src/modules/lc3_file.c +++ b/applications/nrf5340_audio/src/modules/lc3_file.c @@ -12,6 +12,34 @@ LOG_MODULE_REGISTER(sd_card_lc3_file, CONFIG_MODULE_SD_CARD_LC3_FILE_LOG_LEVEL); #define LC3_FILE_ID 0xCC1C +static void lc3_header_print(struct lc3_file_header *header) +{ + if (header == NULL) { + LOG_ERR("Nullptr received"); + return; + } + + LOG_DBG("File ID: 0x%04x", header->file_id); + LOG_DBG("Header size: %d", header->hdr_size); + LOG_DBG("Sample rate: %d Hz", header->sample_rate * 100); + LOG_DBG("Bit rate: %d bps", header->bit_rate * 100); + LOG_DBG("Channels: %d", header->channels); + LOG_DBG("Frame duration: %d us", header->frame_duration * 10); + LOG_DBG("Num samples: %d", header->signal_len_lsb | (header->signal_len_msb << 16)); +} + +int lc3_header_get(struct lc3_file_ctx const *const file, struct lc3_file_header *header) +{ + if ((file == NULL) || (header == NULL)) { + LOG_ERR("Nullptr received"); + return -EINVAL; + } + + *header = file->lc3_header; + + return 0; +} + int lc3_file_frame_get(struct lc3_file_ctx *file, uint8_t *buffer, size_t buffer_size) { int ret; @@ -83,6 +111,9 @@ int lc3_file_open(struct lc3_file_ctx *file, const char *file_name) return ret; } + /* Debug: Print header */ + lc3_header_print(&file->lc3_header); + if (file->lc3_header.file_id != LC3_FILE_ID) { LOG_ERR("Invalid file ID: 0x%04x", file->lc3_header.file_id); return -EINVAL; diff --git a/applications/nrf5340_audio/src/modules/lc3_file.h b/applications/nrf5340_audio/src/modules/lc3_file.h index 5cd514179013..d2803bbac6d5 100644 --- a/applications/nrf5340_audio/src/modules/lc3_file.h +++ b/applications/nrf5340_audio/src/modules/lc3_file.h @@ -31,6 +31,17 @@ struct lc3_file_ctx { uint32_t number_of_samples; }; +/** + * @brief Get the LC3 header from the file. + * + * @param[in] file Pointer to the file context. + * @param[out] header Pointer to the header structure to store the header. + * + * @retval -EINVAL Invalid file context. + * @retval 0 Success. + */ +int lc3_header_get(struct lc3_file_ctx const *const file, struct lc3_file_header *header); + /** * @brief Get the next LC3 frame from the file. * diff --git a/applications/nrf5340_audio/src/modules/lc3_streamer.c b/applications/nrf5340_audio/src/modules/lc3_streamer.c index 9384c80ecd11..32784e099d06 100644 --- a/applications/nrf5340_audio/src/modules/lc3_streamer.c +++ b/applications/nrf5340_audio/src/modules/lc3_streamer.c @@ -268,6 +268,64 @@ int lc3_streamer_next_frame_get(const uint8_t streamer_idx, const uint8_t **cons return 0; } +bool lc3_streamer_file_compatible_check(const char *const filename, + const struct lc3_stream_cfg *const cfg) +{ + int ret; + bool result = true; + + if (filename == NULL || cfg == NULL) { + LOG_ERR("NULL pointer received"); + return false; + } + + if (strlen(filename) > CONFIG_FS_FATFS_MAX_LFN - 1) { + LOG_ERR("Filename too long"); + return false; + } + + struct lc3_file_ctx file; + + ret = lc3_file_open(&file, filename); + if (ret) { + LOG_ERR("Failed to open file %d", ret); + return false; + } + + struct lc3_file_header header; + + ret = lc3_header_get(&file, &header); + if (ret) { + LOG_WRN("Failed to get header %d", ret); + return false; + } + + if ((header.sample_rate * 100) != cfg->sample_rate_hz) { + LOG_WRN("Sample rate mismatch %d Hz != %d Hz", (header.sample_rate * 100), + cfg->sample_rate_hz); + result = false; + } + + if ((header.bit_rate * 100) != cfg->bit_rate_bps) { + LOG_WRN("Bit rate mismatch %d bps != %d bps", (header.bit_rate * 100), + cfg->bit_rate_bps); + result = false; + } + + if ((header.frame_duration * 10) != cfg->frame_duration_us) { + LOG_WRN("Frame duration mismatch %d us != %d us", (header.frame_duration * 10), + cfg->frame_duration_us); + result = false; + } + + ret = lc3_file_close(&file); + if (ret) { + LOG_ERR("Failed to close file %d", ret); + } + + return result; +} + int lc3_streamer_stream_register(const char *const filename, uint8_t *const streamer_idx, const bool loop) { diff --git a/applications/nrf5340_audio/src/modules/lc3_streamer.h b/applications/nrf5340_audio/src/modules/lc3_streamer.h index cd43ff8b5de7..3add5e592c65 100644 --- a/applications/nrf5340_audio/src/modules/lc3_streamer.h +++ b/applications/nrf5340_audio/src/modules/lc3_streamer.h @@ -12,14 +12,20 @@ #include #include +struct lc3_stream_cfg { + uint32_t sample_rate_hz; + uint32_t bit_rate_bps; + uint32_t frame_duration_us; +}; + /** * @brief Get the next frame for the stream. * - * @details Populates a pointer to the buffer holding the next frame. This buffer is valid until - * the next call to this function. or stream is closed. + * @details Populates a pointer to the buffer holding the next frame. This buffer is valid until + * the next call to this function. or stream is closed. * - * @param[in] streamer_idx Index of the streamer to get the next frame from. - * @param[out] frame_buffer Pointer to the buffer holding the next frame. + * @param[in] streamer_idx Index of the streamer to get the next frame from. + * @param[out] frame_buffer Pointer to the buffer holding the next frame. * * @retval 0 Success. * @retval -EINVAL Invalid streamer index. @@ -30,6 +36,22 @@ */ int lc3_streamer_next_frame_get(const uint8_t streamer_idx, const uint8_t **const frame_buffer); +/** + * @brief Verify that the LC3 header matches the stream configuration. + * + * @details Verifies that the file is valid and can be used by the LC3 streamer. + * Since there is no standard header for LC3 files, the header might be different than + * what is defined in the struct lc3_file_header. + * + * @param[in] filename Name of the file to verify. + * @param[in] cfg Stream configuration to compare against. + * + * @retval true Success. + * @retval false Header is not matching the configuration. + */ +bool lc3_streamer_file_compatible_check(const char *const filename, + const struct lc3_stream_cfg *const cfg); + /** * @brief Register a new stream that will be played by the LC3 streamer. * @@ -63,10 +85,10 @@ uint8_t lc3_streamer_num_active_streams(void); * truncated. * * @param[in] streamer_idx Index of the streamer. - * @param[out] path Pointer for string to store filepath in. + * @param[out] path Pointer for string to store file path in. * @param[in] path_len Length of string buffer. * - * @retval -EINVAL Nullpointers or invalid index given. + * @retval -EINVAL Null pointers or invalid index given. * @retval 0 Success. */ int lc3_streamer_file_path_get(const uint8_t streamer_idx, char *const path, const size_t path_len); diff --git a/samples/bluetooth/broadcast_config_tool/Kconfig b/samples/bluetooth/broadcast_config_tool/Kconfig index c01b6d9a80f8..4f2efb91f6ca 100644 --- a/samples/bluetooth/broadcast_config_tool/Kconfig +++ b/samples/bluetooth/broadcast_config_tool/Kconfig @@ -14,6 +14,12 @@ rsource "${ZEPHYR_NRF_MODULE_DIR}/applications/nrf5340_audio/src/utils/Kconfig" config TRANSPORT_BIS bool "Use BIS (Broadcast Isochronous Stream)" +config BROADCAST_CONFIG_TOOL + bool "Broadcast Configuration Tool" + depends on TRANSPORT_BIS + select EXPERIMENTAL + default y + menu "Logging" module = MAIN diff --git a/samples/bluetooth/broadcast_config_tool/src/main.c b/samples/bluetooth/broadcast_config_tool/src/main.c index a6232b4eb025..03f5c57f74e3 100644 --- a/samples/bluetooth/broadcast_config_tool/src/main.c +++ b/samples/bluetooth/broadcast_config_tool/src/main.c @@ -1584,6 +1584,36 @@ static int cmd_file_select(const struct shell *shell, size_t argc, char **argv) LOG_DBG("Selecting file %s for stream big: %d sub: %d bis: %d", file_name, big_index, sub_index, bis_index); + struct bt_audio_codec_cfg *codec_cfg = + &broadcast_param[big_index].subgroups[sub_index].group_lc3_preset.codec_cfg; + + struct lc3_stream_cfg cfg; + + ret = le_audio_freq_hz_get(codec_cfg, &cfg.sample_rate_hz); + if (ret) { + shell_error(shell, "Failed to get frequency: %d", ret); + return ret; + } + + ret = le_audio_duration_us_get(codec_cfg, &cfg.frame_duration_us); + if (ret) { + shell_error(shell, "Failed to get frame duration: %d", ret); + } + + ret = le_audio_bitrate_get(codec_cfg, &cfg.bit_rate_bps); + if (ret) { + shell_error(shell, "Failed to get bitrate: %d", ret); + } + + /* Verify that the file header matches the stream configurationn */ + /* NOTE: This will not abort the streamer if the file is not valid, only give a warning */ + bool header_valid = lc3_streamer_file_compatible_check(file_name, &cfg); + + if (!header_valid) { + shell_warn(shell, "File header verification failed. File may not be compatible " + "with stream config."); + } + ret = lc3_streamer_stream_register( file_name, &lc3_stream_infos[big_index][sub_index].lc3_streamer_idx[bis_index], true);