diff --git a/app/compr/README.txt b/app/compr/README.txt index a9c28957edbb..27f3b9bd897f 100644 --- a/app/compr/README.txt +++ b/app/compr/README.txt @@ -16,7 +16,8 @@ cadence.conf - Base Cadence codec module only (no individual codecs) mp3.conf - MP3 decoder and encoder aac.conf - AAC decoder vorbis.conf - Vorbis decoder -all_codecs.conf - All supported codecs (MP3, AAC, Vorbis) +pcm.conf - PCM (wav) decoder +all_codecs.conf - All supported codecs (MP3, AAC, Vorbis, pcm) Usage Examples -------------- diff --git a/app/compr/all_codecs.conf b/app/compr/all_codecs.conf index 26b507f7f452..9ef3cd2897d8 100644 --- a/app/compr/all_codecs.conf +++ b/app/compr/all_codecs.conf @@ -8,4 +8,4 @@ CONFIG_CADENCE_CODEC_AAC_DEC=y CONFIG_CADENCE_CODEC_AAC_DEC_LIB="../cadence_libs/xa_aac_dec.a" CONFIG_CADENCE_CODEC_VORBIS_DEC=y CONFIG_CADENCE_CODEC_VORBIS_DEC_LIB="../cadence_libs/xa_vorbis_dec.a" - +CONFIG_SOF_COMPRESS_CODEC_PCM_DEC=y diff --git a/app/compr/pcm.conf b/app/compr/pcm.conf new file mode 100644 index 000000000000..18a0e0e2810b --- /dev/null +++ b/app/compr/pcm.conf @@ -0,0 +1,3 @@ +# PCM (wav) decoder +CONFIG_CADENCE_CODEC=y +CONFIG_SOF_COMPRESS_CODEC_PCM_DEC=y diff --git a/src/audio/base_fw.c b/src/audio/base_fw.c index 17c86fdb1df3..18912fb110f7 100644 --- a/src/audio/base_fw.c +++ b/src/audio/base_fw.c @@ -99,6 +99,10 @@ static void get_codec_info(struct sof_tlv **tuple) codec_info.items[codec_info.count++] = SET_CODEC_INFO_ITEM(SND_AUDIOCODEC_VORBIS, SOF_IPC_STREAM_PLAYBACK); #endif +#ifdef CONFIG_SOF_COMPRESS_CODEC_PCM_DEC + codec_info.items[codec_info.count++] = + SET_CODEC_INFO_ITEM(SND_AUDIOCODEC_PCM, SOF_IPC_STREAM_PLAYBACK); +#endif if (!codec_info.count) return; diff --git a/src/audio/module_adapter/CMakeLists.txt b/src/audio/module_adapter/CMakeLists.txt index c728ef6d54b8..e454806b5ccf 100644 --- a/src/audio/module_adapter/CMakeLists.txt +++ b/src/audio/module_adapter/CMakeLists.txt @@ -33,6 +33,9 @@ endif() zephyr_library_import(xa_mp3_enc ${CONFIG_CADENCE_CODEC_MP3_ENC_LIB}) endif() + zephyr_library_sources_ifdef(CONFIG_SOF_COMPRESS_CODEC_PCM_DEC + module/cadence_other/xa_pcm_dec.c) + if (CONFIG_COMP_DOLBY_DAX_AUDIO_PROCESSING) if(CONFIG_COMP_DOLBY_DAX_AUDIO_PROCESSING STREQUAL "m" AND DEFINED CONFIG_LLEXT) add_subdirectory(module/dolby/llext @@ -147,6 +150,10 @@ if(NOT CONFIG_COMP_MODULE_SHARED_LIBRARY_BUILD) endif() + if(CONFIG_SOF_COMPRESS_CODEC_PCM_DEC) + add_subdirectory(module/cadence_other) + endif() + if(CONFIG_COMP_DOLBY_DAX_AUDIO_PROCESSING) target_include_directories(sof PRIVATE ${PROJECT_SOURCE_DIR}/third_party/include) add_local_sources(sof module/dolby/dax.c) diff --git a/src/audio/module_adapter/Kconfig b/src/audio/module_adapter/Kconfig index a9f88dd1f2ad..5eff28ff49d8 100644 --- a/src/audio/module_adapter/Kconfig +++ b/src/audio/module_adapter/Kconfig @@ -173,6 +173,16 @@ if CADENCE_CODEC This option is a string and takes the full name of the SRC library binary. endif + config SOF_COMPRESS_CODEC_PCM_DEC + bool "SOF PCM (WAV) decoder" + help + Select to enable PCM (WAV) decoder. + This provides PCM/WAV file decoding support through a simple + implementation that handles the PCM data. Note that this is not + a Cadence codec module but an open-source addition to support the + PCM data format. In addition need to build the cplay from + tinycompress with option --enable-pcm to use this. + endif # Cadence config COMP_DOLBY_DAX_AUDIO_PROCESSING diff --git a/src/audio/module_adapter/module/cadence.c b/src/audio/module_adapter/module/cadence.c index fbec980ccef9..25c7c84228a2 100644 --- a/src/audio/module_adapter/module/cadence.c +++ b/src/audio/module_adapter/module/cadence.c @@ -74,6 +74,12 @@ struct cadence_api cadence_api_table[] = { .api = xa_src_pp, }, #endif +#ifdef CONFIG_SOF_COMPRESS_CODEC_PCM_DEC + { + .id = SOF_COMPRESS_CODEC_PCM_DEC_ID, + .api = xa_pcm_dec, + }, +#endif }; static int cadence_codec_get_api_id(uint32_t compress_id, uint32_t direction) @@ -89,6 +95,8 @@ static int cadence_codec_get_api_id(uint32_t compress_id, uint32_t direction) return CADENCE_CODEC_AAC_DEC_ID; case SND_AUDIOCODEC_VORBIS: return CADENCE_CODEC_VORBIS_DEC_ID; + case SND_AUDIOCODEC_PCM: + return SOF_COMPRESS_CODEC_PCM_DEC_ID; default: return -EINVAL; } @@ -236,6 +244,8 @@ int cadence_codec_get_samples(struct processing_module *mod) return 1152; case CADENCE_CODEC_AAC_DEC_ID: return 1024; + case SOF_COMPRESS_CODEC_PCM_DEC_ID: + return 1024; default: break; } diff --git a/src/audio/module_adapter/module/cadence_ipc4.c b/src/audio/module_adapter/module/cadence_ipc4.c index 845283a208c3..b106b1ef0a80 100644 --- a/src/audio/module_adapter/module/cadence_ipc4.c +++ b/src/audio/module_adapter/module/cadence_ipc4.c @@ -209,6 +209,9 @@ static int cadence_configure_codec_params(struct processing_module *mod) case CADENCE_CODEC_VORBIS_DEC_ID: /* No configuration needed for Vorbis */ return 0; + case SOF_COMPRESS_CODEC_PCM_DEC_ID: + /* No configuration needed for PCM decoder */ + return 0; default: break; } diff --git a/src/audio/module_adapter/module/cadence_other/CMakeLists.txt b/src/audio/module_adapter/module/cadence_other/CMakeLists.txt new file mode 100644 index 000000000000..4261b9cf8c71 --- /dev/null +++ b/src/audio/module_adapter/module/cadence_other/CMakeLists.txt @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: BSD-3-Clause + +if(CONFIG_SOF_COMPRESS_CODEC_PCM_DEC) + add_local_sources(sof xa_pcm_dec.c) +endif() diff --git a/src/audio/module_adapter/module/cadence_other/xa_pcm_dec.c b/src/audio/module_adapter/module/cadence_other/xa_pcm_dec.c new file mode 100644 index 000000000000..eaee8a1fde0c --- /dev/null +++ b/src/audio/module_adapter/module/cadence_other/xa_pcm_dec.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. +// + +#include +#include +#include +#include +#include +#include +#include + +/* Note: This is a workaround with empirically found count to stop producing + * output after pipeline eos indication. The input buffer size isn't becoming + * smaller and zero when stream ends. Without this the last buf size amount + * of data keeps looping forever. + */ +#define PCM_DEC_COUNT_SINCE_EOS_TO_DONE 12 + +/* For XA_API_CMD_GET_MEM_INFO_SIZE */ +#define PCM_DEC_IN_BUF_SIZE 16384 +#define PCM_DEC_OUT_BUF_SIZE 16384 + +/* PCM decoder state structure */ +struct xa_pcm_dec_state { + /* Configuration parameters */ + uint32_t sample_rate; + uint32_t num_channels; + uint32_t pcm_width; + + /* State variables */ + uint32_t bytes_consumed; + uint32_t bytes_produced; + uint32_t init_done; + uint32_t exec_done; + uint32_t input_over; + uint32_t eos_set_count; + + /* Memory pointers */ + void *input_buf; + void *output_buf; + uint32_t output_buf_size; + uint32_t input_bytes; +}; + +static const char lib_name[] = "PCM Decoder"; + +/* Main codec API function */ +XA_ERRORCODE xa_pcm_dec(xa_codec_handle_t handle, WORD32 cmd, WORD32 idx, pVOID value) +{ + struct xa_pcm_dec_state *state = (struct xa_pcm_dec_state *)handle; + + /* Commands that don't need initialized state */ + switch (cmd) { + case XA_API_CMD_GET_API_SIZE: + *(WORD32 *)value = sizeof(struct xa_pcm_dec_state); + return XA_NO_ERROR; + + case XA_API_CMD_GET_LIB_ID_STRINGS: + if (idx == XA_CMD_TYPE_LIB_NAME) { + strcpy((char *)value, lib_name); + return XA_NO_ERROR; + } + return XA_API_FATAL_INVALID_CMD_TYPE; + + case XA_API_CMD_GET_MEMTABS_SIZE: + /* PCM decoder needs minimal memtabs structure */ + *(WORD32 *)value = 4; + return XA_NO_ERROR; + + case XA_API_CMD_SET_MEMTABS_PTR: + /* PCM decoder doesn't use memtabs, just return success */ + return XA_NO_ERROR; + + default: + break; + } + + /* All other commands need initialized state */ + if (!handle) + return XA_PCMDEC_EXECUTE_FATAL_UNINITIALIZED; + + switch (cmd) { + case XA_API_CMD_INIT: + switch (idx) { + case XA_CMD_TYPE_INIT_API_PRE_CONFIG_PARAMS: + /* Initialize with default values */ + bzero(state, sizeof(*state)); + state->sample_rate = 48000; + state->num_channels = 2; + state->pcm_width = 16; + return XA_NO_ERROR; + + case XA_CMD_TYPE_INIT_API_POST_CONFIG_PARAMS: + /* Nothing to do here for simple PCM decoder */ + return XA_NO_ERROR; + + case XA_CMD_TYPE_INIT_PROCESS: + state->init_done = 1; + return XA_NO_ERROR; + + case XA_CMD_TYPE_INIT_DONE_QUERY: + *(WORD32 *)value = state->init_done; + return XA_NO_ERROR; + + default: + return XA_API_FATAL_INVALID_CMD_TYPE; + } + + case XA_API_CMD_SET_CONFIG_PARAM: + switch (idx) { + case XA_PCM_DEC_CONFIG_PARAM_SAMPLE_RATE: + state->sample_rate = *(WORD32 *)value; + return XA_NO_ERROR; + + case XA_PCM_DEC_CONFIG_PARAM_CHANNELS: + state->num_channels = *(WORD32 *)value; + return XA_NO_ERROR; + + case XA_PCM_DEC_CONFIG_PARAM_PCM_WIDTH: + state->pcm_width = *(WORD32 *)value; + return XA_NO_ERROR; + + case XA_PCM_DEC_CONFIG_PARAM_INTERLEAVE: + return XA_NO_ERROR; + + default: + return XA_PCMDEC_CONFIG_NONFATAL_INVALID_PCM_WIDTH; + } + + case XA_API_CMD_GET_CONFIG_PARAM: + switch (idx) { + case XA_PCM_DEC_CONFIG_PARAM_SAMPLE_RATE: + *(WORD32 *)value = state->sample_rate; + return XA_NO_ERROR; + + case XA_PCM_DEC_CONFIG_PARAM_CHANNELS: + *(WORD32 *)value = state->num_channels; + return XA_NO_ERROR; + + case XA_PCM_DEC_CONFIG_PARAM_PCM_WIDTH: + *(WORD32 *)value = state->pcm_width; + return XA_NO_ERROR; + + case XA_PCM_DEC_CONFIG_PARAM_PRODUCED: + *(WORD32 *)value = state->bytes_produced; + return XA_NO_ERROR; + + default: + return XA_API_FATAL_INVALID_CMD_TYPE; + } + + case XA_API_CMD_GET_N_MEMTABS: + /* We need 2 memory tables: input and output buffers */ + *(WORD32 *)value = 2; + return XA_NO_ERROR; + + case XA_API_CMD_GET_MEM_INFO_TYPE: + if (idx == 0) + *(WORD32 *)value = XA_MEMTYPE_INPUT; + else if (idx == 1) + *(WORD32 *)value = XA_MEMTYPE_OUTPUT; + else + return XA_API_FATAL_INVALID_CMD_TYPE; + return XA_NO_ERROR; + + case XA_API_CMD_GET_MEM_INFO_SIZE: + if (idx == 0) + *(WORD32 *)value = PCM_DEC_IN_BUF_SIZE; + else if (idx == 1) + *(WORD32 *)value = PCM_DEC_OUT_BUF_SIZE; + else + return XA_API_FATAL_INVALID_CMD_TYPE; + return XA_NO_ERROR; + + case XA_API_CMD_GET_MEM_INFO_ALIGNMENT: + *(WORD32 *)value = 4; /* 4-byte alignment */ + return XA_NO_ERROR; + + case XA_API_CMD_SET_MEM_PTR: + if (idx == 0) { + state->input_buf = value; + } else if (idx == 1) { + state->output_buf = value; + state->output_buf_size = PCM_DEC_OUT_BUF_SIZE; + } else { + return XA_API_FATAL_INVALID_CMD_TYPE; + } + return XA_NO_ERROR; + + case XA_API_CMD_SET_INPUT_BYTES: + state->input_bytes = *(WORD32 *)value; + state->bytes_consumed = 0; + if (state->input_bytes > 0) + state->exec_done = 0; + return XA_NO_ERROR; + + case XA_API_CMD_GET_OUTPUT_BYTES: + *(WORD32 *)value = state->bytes_produced; + return XA_NO_ERROR; + + case XA_API_CMD_GET_CURIDX_INPUT_BUF: + *(WORD32 *)value = state->bytes_consumed; + return XA_NO_ERROR; + + case XA_API_CMD_INPUT_OVER: + /* Indicate no more input buffers will be provided */ + state->input_over = 1; + return XA_NO_ERROR; + + case XA_API_CMD_GET_N_TABLES: + /* PCM decoder doesn't use tables */ + *(WORD32 *)value = 0; + return XA_NO_ERROR; + + case XA_API_CMD_GET_TABLE_PTR: + case XA_API_CMD_SET_TABLE_PTR: + case XA_API_CMD_GET_TABLE_INFO_SIZE: + case XA_API_CMD_GET_TABLE_INFO_ALIGNMENT: + case XA_API_CMD_GET_TABLE_INFO_PRIORITY: + /* PCM decoder doesn't use tables, return success */ + return XA_NO_ERROR; + + case XA_API_CMD_GET_MEM_INFO_PLACEMENT: + case XA_API_CMD_GET_MEM_INFO_PRIORITY: + case XA_API_CMD_SET_MEM_INFO_SIZE: + case XA_API_CMD_SET_MEM_PLACEMENT: + /* Return success for optional memory info commands */ + return XA_NO_ERROR; + + case XA_API_CMD_EXECUTE: + if (idx == XA_CMD_TYPE_DO_EXECUTE) { + uint32_t to_copy; + + state->bytes_produced = 0; + state->bytes_consumed = 0; + + if (state->input_over) { + state->eos_set_count++; + if (state->eos_set_count > PCM_DEC_COUNT_SINCE_EOS_TO_DONE) { + state->exec_done = 1; + return XA_PCMDEC_EXECUTE_NONFATAL_INSUFFICIENT_DATA; + } + } + + /* Safety check for buffers - should not happen */ + if (!state->input_buf || !state->output_buf) { + /* Consume input even if buffers invalid to avoid hang */ + state->bytes_consumed = state->input_bytes; + state->bytes_produced = 0; + return XA_NO_ERROR; + } + + /* Copy PCM data from input to output */ + to_copy = state->input_bytes; + if (to_copy > state->output_buf_size) + to_copy = state->output_buf_size; + + if (to_copy > 0) { + memcpy_s(state->output_buf, state->output_buf_size, + state->input_buf, to_copy); + state->bytes_produced = to_copy; + state->bytes_consumed = to_copy; + } else { + state->bytes_consumed = 0; + } + + return XA_NO_ERROR; + } else if (idx == XA_CMD_TYPE_DONE_QUERY) { + /* Query if execution is done */ + *(WORD32 *)value = state->exec_done; + return XA_NO_ERROR; + } + return XA_API_FATAL_INVALID_CMD_TYPE; + + default: + return XA_API_FATAL_INVALID_CMD; + } +} diff --git a/src/include/sof/audio/cadence_other/pcm_dec/xa_pcm_dec_api.h b/src/include/sof/audio/cadence_other/pcm_dec/xa_pcm_dec_api.h new file mode 100644 index 000000000000..481e6a6e3639 --- /dev/null +++ b/src/include/sof/audio/cadence_other/pcm_dec/xa_pcm_dec_api.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2026 Intel Corporation. + * + */ + +#ifndef __XA_PCM_DEC_API_H__ +#define __XA_PCM_DEC_API_H__ + +/*****************************************************************************/ +/* PCM Decoder specific API definitions */ +/*****************************************************************************/ + +/* pcm_dec-specific configuration parameters */ +enum xa_config_param_pcm_dec { + XA_PCM_DEC_CONFIG_PARAM_CHANNELS = 0, + XA_PCM_DEC_CONFIG_PARAM_SAMPLE_RATE = 1, + XA_PCM_DEC_CONFIG_PARAM_PCM_WIDTH = 2, + XA_PCM_DEC_CONFIG_PARAM_PRODUCED = 3, + XA_PCM_DEC_CONFIG_PARAM_INTERLEAVE = 5 +}; + +/* commands */ +#include "xa_apicmd_standards.h" + +/* error codes */ +#include "xa_error_standards.h" + +#define XA_CODEC_PCM_DEC 15 + +/* pcm_dec-specific error codes */ +/*****************************************************************************/ +/* Class 1: Configuration Errors */ +/*****************************************************************************/ +/* Nonfatal Errors */ +enum xa_error_nonfatal_config_pcm_dec { + XA_PCMDEC_CONFIG_NONFATAL_INVALID_PCM_WIDTH = + XA_ERROR_CODE(xa_severity_nonfatal, xa_class_config, XA_CODEC_PCM_DEC, 2) +}; + +/*****************************************************************************/ +/* Class 2: Execution Errors */ +/*****************************************************************************/ +/* Nonfatal Errors */ +enum xa_error_nonfatal_execute_pcm_dec { + XA_PCMDEC_EXECUTE_NONFATAL_INSUFFICIENT_DATA = + XA_ERROR_CODE(xa_severity_nonfatal, xa_class_execute, XA_CODEC_PCM_DEC, 0) +}; + +/* Fatal Errors */ +#define XA_PCMDEC_EXECUTE_FATAL_UNINITIALIZED \ + XA_ERROR_CODE((uint32_t)xa_severity_fatal, xa_class_execute, XA_CODEC_PCM_DEC, 0) + +#endif /* __XA_PCM_DEC_API_H__ */ diff --git a/src/include/sof/audio/module_adapter/module/cadence.h b/src/include/sof/audio/module_adapter/module/cadence.h index baefbaeea08f..b3a1abafeffb 100644 --- a/src/include/sof/audio/module_adapter/module/cadence.h +++ b/src/include/sof/audio/module_adapter/module/cadence.h @@ -32,6 +32,7 @@ extern xa_codec_func_t xa_mp3_enc; extern xa_codec_func_t xa_sbc_dec; extern xa_codec_func_t xa_vorbis_dec; extern xa_codec_func_t xa_src_pp; +extern xa_codec_func_t xa_pcm_dec; #define DEFAULT_CODEC_ID CADENCE_CODEC_WRAPPER_ID @@ -77,6 +78,7 @@ enum cadence_api_id { CADENCE_CODEC_VORBIS_DEC_ID = 0x08, CADENCE_CODEC_SRC_PP_ID = 0x09, CADENCE_CODEC_MP3_ENC_ID = 0x0A, + SOF_COMPRESS_CODEC_PCM_DEC_ID = 0xC0, }; #if CONFIG_IPC_MAJOR_4