Skip to content

Commit

Permalink
applications: nrf5340_audio: Module to read LC3 files from SD card
Browse files Browse the repository at this point in the history
Reads and parses LC3 files from the SD card, and provides a function
to read the LC3 file one frame at a time. Uses the existing SD card
module.

OCT-3031

Signed-off-by: Andreas Vibeto <andreas.vibeto@nordicsemi.no>
  • Loading branch information
andvib authored and rlubos committed Aug 5, 2024
1 parent 4848a49 commit 39cea52
Show file tree
Hide file tree
Showing 12 changed files with 793 additions and 0 deletions.
2 changes: 2 additions & 0 deletions applications/nrf5340_audio/src/modules/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,7 @@ target_sources_ifdef(CONFIG_NRF5340_AUDIO_POWER_MEASUREMENT app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/power_meas.c)
target_sources_ifdef(CONFIG_NRF5340_AUDIO_SD_CARD_MODULE app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/sd_card.c)
target_sources_ifdef(CONFIG_NRF5340_AUDIO_SD_CARD_LC3_FILE app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/lc3_file.c)
target_sources_ifdef(CONFIG_SD_CARD_PLAYBACK app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/sd_card_playback.c)
15 changes: 15 additions & 0 deletions applications/nrf5340_audio/src/modules/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,21 @@ source "subsys/logging/Kconfig.template.log_config"

endif # NRF5340_AUDIO_SD_CARD_MODULE

config NRF5340_AUDIO_SD_CARD_LC3_FILE
bool "SD card LC3 file support"
depends on NRF5340_AUDIO_SD_CARD_MODULE
default n
help
Include support for reading and parsing LC3 files from an SD card.

if NRF5340_AUDIO_SD_CARD_LC3_FILE

module = MODULE_SD_CARD_LC3_FILE
module-str = module-sd-card-lc3-file
source "subsys/logging/Kconfig.template.log_config"

endif # NRF5340_AUDIO_SD_CARD_LC3_FILE

menuconfig SD_CARD_PLAYBACK
bool "Enable playback from SD card"
depends on NRF5340_AUDIO_SD_CARD_MODULE
Expand Down
123 changes: 123 additions & 0 deletions applications/nrf5340_audio/src/modules/lc3_file.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/

#include "lc3_file.h"
#include "sd_card.h"

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(sd_card_lc3_file, CONFIG_MODULE_SD_CARD_LC3_FILE_LOG_LEVEL);

#define LC3_FILE_ID 0xCC1C

int lc3_file_frame_get(struct lc3_file_ctx *file, uint8_t *buffer, size_t buffer_size)
{
int ret;

if ((file == NULL) || (buffer == NULL)) {
LOG_ERR("Nullptr received");
return -EINVAL;
}

/* Read frame header */
uint16_t frame_header;
size_t frame_header_size = sizeof(frame_header);

ret = sd_card_read((char *)&frame_header, &frame_header_size, &file->file_object);
if (ret) {
LOG_ERR("Failed to read frame header: %d", ret);
return ret;
}

if ((frame_header_size == 0) || (frame_header == 0)) {
LOG_ERR("No more frames to read");
return -ENODATA;
}

LOG_DBG("Size of frame is %d", frame_header);

if (buffer_size < frame_header) {
LOG_ERR("Buffer size too small: %d < %d", buffer_size, frame_header);
return -ENOMEM;
}

/* Read frame data */
size_t frame_size = frame_header;

ret = sd_card_read((char *)buffer, &frame_size, &file->file_object);
if (ret) {
LOG_ERR("Failed to read frame data: %d", ret);
return ret;
}

if (frame_size != frame_header) {
LOG_ERR("Frame size mismatch: %d != %d", frame_size, frame_header);
return -EIO;
}

return 0;
}

int lc3_file_open(struct lc3_file_ctx *file, const char *file_name)
{
int ret;
size_t size = sizeof(file->lc3_header);

if ((file == NULL) || (file_name == NULL)) {
LOG_ERR("Nullptr received");
return -EINVAL;
}

ret = sd_card_open(file_name, &file->file_object);
if (ret) {
LOG_ERR("Failed to open file: %d", ret);
return ret;
}

/* Read LC3 header and store in struct */
ret = sd_card_read((char *)&file->lc3_header, &size, &file->file_object);
if (ret) {
LOG_ERR("Failed to read the LC3 header: %d", ret);
return ret;
}

if (file->lc3_header.file_id != LC3_FILE_ID) {
LOG_ERR("Invalid file ID: 0x%04x", file->lc3_header.file_id);
return -EINVAL;
}

return 0;
}

int lc3_file_close(struct lc3_file_ctx *file)
{
int ret;

if (file == NULL) {
LOG_ERR("Nullptr received");
return -EINVAL;
}

ret = sd_card_close(&file->file_object);
if (ret) {
LOG_ERR("Failed to close file: %d", ret);
return ret;
}

return ret;
}

int lc3_file_init(void)
{
int ret;

ret = sd_card_init();
if (ret) {
LOG_ERR("Failed to initialize SD card: %d", ret);
return ret;
}

return 0;
}
79 changes: 79 additions & 0 deletions applications/nrf5340_audio/src/modules/lc3_file.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/

#ifndef LC3_FILE_H__
#define LC3_FILE_H__

#include <stddef.h>
#include <stdint.h>

#include <zephyr/fs/fs.h>
#include <zephyr/sys/util.h>

struct lc3_file_header {
uint16_t file_id; /* Constant value, 0xCC1C */
uint16_t hdr_size; /* Header size, 0x0012 */
uint16_t sample_rate; /* Sample frequency / 100 */
uint16_t bit_rate; /* Bit rate / 100 (total for all channels) */
uint16_t channels; /* Number of channels */
uint16_t frame_duration; /* Frame duration in ms * 100 */
uint16_t rfu; /* Reserved for future use */
uint16_t signal_len_lsb; /* Number of samples in signal, 16 LSB */
uint16_t signal_len_msb; /* Number of samples in signal, 16 MSB (>> 16) */
} __packed;

struct lc3_file_ctx {
struct fs_file_t file_object;
struct lc3_file_header lc3_header;
uint32_t number_of_samples;
};

/**
* @brief Get the next LC3 frame from the file.
*
* @param[in] file Pointer to the file context.
* @param[out] buffer Pointer to the buffer to store the frame.
* @param[in] buffer_size Size of the buffer.
*
* @retval -ENODATA No more frames to read.
* @retval 0 Success.
*/
int lc3_file_frame_get(struct lc3_file_ctx *file, uint8_t *buffer, size_t buffer_size);

/**
* @brief Open a LC3 file for reading
*
* @details Opens the file and reads the LC3 header.
*
* @param[in] file Pointer to the file context.
* @param[in] file_name Name of the file to open.
*
* @retval -ENODEV SD card init failed. SD card likely not inserted.
* @retval 0 Success.
*/
int lc3_file_open(struct lc3_file_ctx *file, const char *file_name);

/**
* @brief Close a LC3 file.
*
* @param[in] file Pointer to the file context.
*
* @retval -EPERM SD card operation is not ongoing.
* @retval 0 Success.
*/
int lc3_file_close(struct lc3_file_ctx *file);

/**
* @brief Initialize the LC3 file module.
*
* Initializes the SD card and mounts the file system.
*
* @retval -ENODEV SD card init failed. SD card likely not inserted.
* @retval 0 Success.
*/
int lc3_file_init(void);

#endif /* LC3_FILE_H__ */
87 changes: 87 additions & 0 deletions tests/nrf5340_audio/fakes/sd_card/lc3_file_data.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/

#include <zephyr/sys/util.h>

#include "lc3_file_data.h"

/*******************
* VALID DATASET 1 *
*******************/
const uint8_t lc3_file_dataset1_valid[] = {
0x1c, 0xcc, 0x12, 0x00, 0xe0, 0x01, 0x40, 0x01, 0x01, 0x00, 0xe8, 0x03, 0x00, 0x00, 0x80,
0x07, 0x00, 0x00, 0x28, 0x00, 0x50, 0x46, 0x07, 0x52, 0x03, 0x2c, 0x8f, 0xe1, 0xe9, 0x21,
0x73, 0x81, 0x25, 0x82, 0x9f, 0x94, 0x7a, 0xa4, 0xa3, 0xa2, 0xc9, 0x50, 0xa2, 0xc3, 0xeb,
0x43, 0xe0, 0xff, 0xf0, 0x14, 0x20, 0x86, 0xdd, 0x2f, 0xda, 0x48, 0xe2, 0x39, 0x92, 0xec,
0x28, 0x00, 0x3f, 0x48, 0x6d, 0xc7, 0x7f, 0xae, 0x83, 0xad, 0x95, 0xd7, 0x66, 0xa1, 0x9b,
0xcf, 0x3e, 0xdb, 0x1b, 0x41, 0xb4, 0xc8, 0x80, 0x6d, 0xf9, 0x13, 0x7b, 0x43, 0x36, 0x2f,
0xc6, 0xe1, 0xaa, 0x11, 0xd2, 0xce, 0xe9, 0x01, 0x62, 0x0b, 0x94, 0xfc, 0x28, 0x00, 0x82,
0x8a, 0x0f, 0x07, 0x74, 0x19, 0x24, 0x41, 0xbc, 0xcd, 0x38, 0x73, 0xba, 0x0a, 0x51, 0xde,
0x2c, 0x9c, 0x77, 0x7b, 0xc9, 0x03, 0x7a, 0xee, 0x29, 0x1b, 0x0a, 0x02, 0xc7, 0x03, 0x7c,
0xd6, 0xd0, 0xb6, 0xbb, 0x2d, 0x63, 0xbe, 0xb4, 0x54, 0x28, 0x00, 0x1e, 0xc2, 0x47, 0x7e,
0xc7, 0x1a, 0xf8, 0x46, 0xde, 0x60, 0xbb, 0xbf, 0x7e, 0x67, 0x94, 0xbd, 0x96, 0x44, 0x36,
0xbc, 0xcf, 0x59, 0x3f, 0xa0, 0xfd, 0x8f, 0x2c, 0x57, 0x89, 0x2a, 0xb3, 0x78, 0xdd, 0xe0,
0xdc, 0xbb, 0x65, 0x0f, 0x54, 0x5c, 0x28, 0x00, 0xff, 0xed, 0x1b, 0x0e, 0x0b, 0x34, 0xdc,
0xad, 0xe1, 0x34, 0xad, 0xba, 0xb9, 0xfc, 0x85, 0x72, 0x60, 0x9c, 0xac, 0x00, 0x00, 0x03,
0xf2, 0x14, 0xc2, 0xf6, 0xc7, 0x5a, 0xd3, 0xd8, 0x94, 0x10, 0xce, 0x1c, 0xb8, 0x32, 0xb1,
0xbd, 0xa3, 0x3c};
const size_t lc3_file_dataset1_valid_size = ARRAY_SIZE(lc3_file_dataset1_valid);

const uint8_t lc3_file_dataset1_valid_frame1[] = {
0x50, 0x46, 0x07, 0x52, 0x03, 0x2c, 0x8f, 0xe1, 0xe9, 0x21, 0x73, 0x81, 0x25, 0x82,
0x9f, 0x94, 0x7a, 0xa4, 0xa3, 0xa2, 0xc9, 0x50, 0xa2, 0xc3, 0xeb, 0x43, 0xe0, 0xff,
0xf0, 0x14, 0x20, 0x86, 0xdd, 0x2f, 0xda, 0x48, 0xe2, 0x39, 0x92, 0xec};
const size_t lc3_file_dataset1_valid_frame1_size = ARRAY_SIZE(lc3_file_dataset1_valid_frame1);

const uint8_t lc3_file_dataset1_valid_frame2[] = {
0x3f, 0x48, 0x6d, 0xc7, 0x7f, 0xae, 0x83, 0xad, 0x95, 0xd7, 0x66, 0xa1, 0x9b, 0xcf,
0x3e, 0xdb, 0x1b, 0x41, 0xb4, 0xc8, 0x80, 0x6d, 0xf9, 0x13, 0x7b, 0x43, 0x36, 0x2f,
0xc6, 0xe1, 0xaa, 0x11, 0xd2, 0xce, 0xe9, 0x01, 0x62, 0x0b, 0x94, 0xfc};
const size_t lc3_file_dataset1_valid_frame2_size = ARRAY_SIZE(lc3_file_dataset1_valid_frame2);

const uint8_t lc3_file_dataset1_valid_frame3[] = {
0x82, 0x8a, 0x0f, 0x07, 0x74, 0x19, 0x24, 0x41, 0xbc, 0xcd, 0x38, 0x73, 0xba, 0x0a,
0x51, 0xde, 0x2c, 0x9c, 0x77, 0x7b, 0xc9, 0x03, 0x7a, 0xee, 0x29, 0x1b, 0x0a, 0x02,
0xc7, 0x03, 0x7c, 0xd6, 0xd0, 0xb6, 0xbb, 0x2d, 0x63, 0xbe, 0xb4, 0x54};
const size_t lc3_file_dataset1_valid_frame3_size = ARRAY_SIZE(lc3_file_dataset1_valid_frame3);

const uint8_t lc3_file_dataset1_valid_frame4[] = {
0x1e, 0xc2, 0x47, 0x7e, 0xc7, 0x1a, 0xf8, 0x46, 0xde, 0x60, 0xbb, 0xbf, 0x7e, 0x67,
0x94, 0xbd, 0x96, 0x44, 0x36, 0xbc, 0xcf, 0x59, 0x3f, 0xa0, 0xfd, 0x8f, 0x2c, 0x57,
0x89, 0x2a, 0xb3, 0x78, 0xdd, 0xe0, 0xdc, 0xbb, 0x65, 0x0f, 0x54, 0x5c};
const size_t lc3_file_dataset1_valid_frame4_size = ARRAY_SIZE(lc3_file_dataset1_valid_frame4);

const uint8_t lc3_file_dataset1_valid_frame5[] = {
0xff, 0xed, 0x1b, 0x0e, 0x0b, 0x34, 0xdc, 0xad, 0xe1, 0x34, 0xad, 0xba, 0xb9, 0xfc,
0x85, 0x72, 0x60, 0x9c, 0xac, 0x00, 0x00, 0x03, 0xf2, 0x14, 0xc2, 0xf6, 0xc7, 0x5a,
0xd3, 0xd8, 0x94, 0x10, 0xce, 0x1c, 0xb8, 0x32, 0xb1, 0xbd, 0xa3, 0x3c};
const size_t lc3_file_dataset1_valid_frame5_size = ARRAY_SIZE(lc3_file_dataset1_valid_frame5);

/*************************
* INVALID FRAME DATASET *
*************************/
const uint8_t lc3_file_dataset_invalid_frame[] = {
0x1c, 0xcc, 0x12, 0x00, 0xe0, 0x01, 0x40, 0x01, 0x01, 0x00, 0xe8, 0x03,
0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0x28, 0x00, 0x50, 0x46, 0x07, 0x52,
0x03, 0x2c, 0x8f, 0xe1, 0xe9, 0x21, 0x73, 0x81, 0x25, 0x82, 0x9f, 0x94,
0x7a, 0xa4, 0xa3, 0xa2, 0xc9, 0x50, 0xa2, 0xc3, 0xeb, 0x43};
const size_t lc3_file_dataset_invalid_frame_size = ARRAY_SIZE(lc3_file_dataset_invalid_frame);

/**************************
* INVALID HEADER DATASET *
**************************/
const uint8_t lc3_file_dataset_invalid_header[] = {
0x1c, 0xFF, /* Invalid file id, is normally 0xcc1c */
0x12, 0x00, /* hdr_size: 0x0012 */
0x40, 0x01, /* sample_rate: 0x0140 */
0xe0, 0x01, /* bit_rate: 0x01e0 */
0x01, 0x00, /* channels: 0x0001 */
0xe8, 0x03, /* frame_duration: 0x03e8 */
0x00, 0x00, /* rfu: 0x0000 */
0x6c, 0x00, /* signal_len_lsb: 0x006c */
0x42, 0x00 /* signal_len_msb: 0x0042 */
};
const size_t lc3_file_dataset_invalid_header_size = ARRAY_SIZE(lc3_file_dataset_invalid_header);
49 changes: 49 additions & 0 deletions tests/nrf5340_audio/fakes/sd_card/lc3_file_data.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/

#ifndef LC3_FILE_DATA_H__
#define LC3_FILE_DATA_H__

#include <zephyr/types.h>

/*******************
* VALID DATASET 1 *
*******************/
extern const uint8_t lc3_file_dataset1_valid[];
extern const size_t lc3_file_dataset1_valid_size;

extern const uint8_t lc3_file_dataset1_valid_frame1[];
extern const size_t lc3_file_dataset1_valid_frame1_size;

extern const uint8_t lc3_file_dataset1_valid_frame2[];
extern const size_t lc3_file_dataset1_valid_frame2_size;

extern const uint8_t lc3_file_dataset1_valid_frame3[];
extern const size_t lc3_file_dataset1_valid_frame3_size;

extern const uint8_t lc3_file_dataset1_valid_frame4[];
extern const size_t lc3_file_dataset1_valid_frame4_size;

extern const uint8_t lc3_file_dataset1_valid_frame5[];
extern const size_t lc3_file_dataset1_valid_frame5_size;

/*************************
* INVALID FRAME DATASET *
*************************/
/* LC3 dataset where the header is valid, but the frame contains less data than the frame header
* specifies
*/
extern const uint8_t lc3_file_dataset_invalid_frame[];
extern const size_t lc3_file_dataset_invalid_frame_size;

/**************************
* INVALID HEADER DATASET *
**************************/
/* LC3 dataset header where the header ID is invalid */
extern const uint8_t lc3_file_dataset_invalid_header[];
extern const size_t lc3_file_dataset_invalid_header_size;

#endif /* LC3_FILE_DATA_H__ */
Loading

0 comments on commit 39cea52

Please sign in to comment.