Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

cmake_minimum_required(VERSION 3.13)

if(CMAKE_C_COMPILER_ID MATCHES "Clang")
add_compile_options(-mllvm --text-section-literals=false)
endif()
if("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}")
message(FATAL_ERROR
" In-source builds are not supported.\n"
Expand Down Expand Up @@ -58,7 +61,10 @@ set(CMAKE_ASM_FLAGS -DASSEMBLY)
# that may be exported / installed
add_library(sof_public_headers INTERFACE)

target_include_directories(sof_public_headers INTERFACE ${PROJECT_SOURCE_DIR}/src/include)
target_include_directories(sof_public_headers INTERFACE
${PROJECT_SOURCE_DIR}/src/include
${PROJECT_SOURCE_DIR}/../tflite-micro
)

# interface library that is used only as container for sof binary options
# other targets can use it to build with the same options
Expand Down
1 change: 1 addition & 0 deletions src/audio/mfcc/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ config COMP_MFCC
select MATH_DECIBELS
select MATH_FFT
select MATH_MATRIX
select MATH_PCAN
select MATH_WINDOW
select NATURAL_LOGARITHM_FIXED
select NUMBERS_NORM
Expand Down
89 changes: 74 additions & 15 deletions src/audio/mfcc/README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,84 @@
# MFCC Feature Extraction Architecture
# MFCC & PCAN Feature Extraction Architecture

This directory contains the Mel-Frequency Cepstral Coefficients (MFCC) feature extractor.
This directory contains the **Mel-Frequency Cepstral Coefficients (MFCC)** feature extractor and integrated **PCAN (Per-Channel AGC Normalization)** audio pre-processing component for Sound Open Firmware (SOF).

## Overview
---

MFCC extracts audio features commonly used as inputs for machine learning models, such as wake-word detection or speech recognition.
## 1. Overview

## Architecture Diagram
The MFCC module converts raw streaming time-domain PCM audio into compact acoustic feature representations (e.g. 13-bin cepstral coefficients, 40-bin or 80-bin Mel spectrograms) suitable for on-device machine learning models such as wake-word classifiers (TFLM, microWakeWord) and speech recognition engines (Whisper).

When enabled, **PCAN** applies per-channel adaptive dynamic gain control and piecewise root dynamic range compression across time to suppress stationary background noise and normalize channel energy before feature quantization.

---

## 2. Architecture & Data Flow

```mermaid
graph LR
In[Audio Frame] --> Win[Windowing]
Win --> FFT[FFT]
FFT --> Mel[Mel Filterbank]
Mel --> DCT[DCT]
DCT --> Out[MFCC Output Features]
In[Audio PCM Frame] --> Win[Windowing: Hamming/Hann]
Win --> FFT[FFT: 512-pt]
FFT --> Mel[Mel Filterbank: 40/80 Bins]
Mel --> PCAN[PCAN Gain Normalization]
PCAN --> Log[Log Scaling / dB]
Log --> VAD[VAD / DTX Silence Gating]
Log --> DCT[DCT: Cepstral Matrix]
DCT --> Lifter[Cepstral Lifter]
Lifter --> Out[MFCC Output Features]
```

---

## 3. Dependencies

| Dependency | Purpose |
|---|---|
| **West Manifest (`west.yml`)** | Manages workspace dependencies including `tflite-micro` |
| **`tflite-micro`** | Provides Google's upstream Apache-2.0 `microfrontend` PCAN library (`pcan_gain_control.c`, `noise_reduction.c`) |
| **GNU Octave ($\ge 5.0$)** | Required for topology configuration generation (`tune/setup_mfcc.m`) and reference vector export |
| **CMake ($\ge 3.13$)** | SOF build system |
| **Zephyr SDK / Xtensa XCC** | DSP cross-compilation toolchains for Cadence HiFi3 / HiFi4 SIMD kernels |
| **CMocka** | Host unit testing framework |

---

## 4. Build & Tuning Instructions

### 4.1 Synchronize Dependencies via West
```bash
west update
```

## Configuration and Scripts
### 4.2 Build Host Unit Tests
```bash
# Configure unit tests
cmake -S sof -B build_ut -DBUILD_UNIT_TESTS=ON -DBUILD_UNIT_TESTS_HOST=ON -DINIT_CONFIG=unit_test_defconfig

# Build all math and MFCC tests
cmake --build build_ut --target pcan
ctest --test-dir build_ut -R "pcan|auditory|dct|matrix|window|fft" --output-on-failure
```

### 4.3 Build Target DSP Firmware
```bash
west build -b intel_adsp_ace15_mtpm app
```

### 4.4 Generate Topology Configurations (Octave)
The tuning script generates binary configuration blobs and ALSA topology configurations:
```bash
cd src/audio/mfcc/tune
octave-cli --eval "setup_mfcc"
```
Available presets generated:
- `ceps13_compress_dtx.conf`: 13-bin cepstral features with VAD and DTX silence suppression.
- `mel80_compress.conf`: 80-bin linear Mel spectrogram features.
- `mel80_compress_dtx.conf`: 80-bin Mel spectrogram with DTX gating.
- `mel80_pcan_compress.conf`: 80-bin Mel spectrogram with Google PCAN dynamic AGC normalization.

---

## 5. Configuration Options

- **Kconfig**: Enables the MFCC component (`COMP_MFCC`) which selects necessary math libraries (`MATH_FFT`, `MATH_DCT`, `MATH_16BIT_MEL_FILTERBANK`, etc.). Depends on `COMP_MODULE_ADAPTER`.
- **CMakeLists.txt**: Compiles generic, common, and HIFI implementations (`mfcc_hifi3.c`, `mfcc_hifi4.c`). Provides support for Zephyr loadable extensions (`llext`).
- **mfcc.toml**: Specifies the topology configuration for the MFCC module (UUID, affinity, memory parameters, and pin formats).
- **Topology (.conf)**: Derived from `tools/topology/topology2/include/components/mfcc.conf`, configuring a `mfcc` widget object of type `effect` with UUID `73:a7:10:db:a4:1a:ea:4c:a2:1f:2d:57:a5:c9:82:eb`.
- **`CONFIG_COMP_MFCC`**: Enables the MFCC component and automatically selects required math libraries (`CONFIG_MATH_FFT`, `CONFIG_MATH_AUDITORY`, `CONFIG_MATH_DCT`, `CONFIG_MATH_PCAN`, `CONFIG_MATH_WINDOW`).
- **`CONFIG_COMP_MODULE_ADAPTER`**: Module adapter infrastructure for SOF processing components.
6 changes: 6 additions & 0 deletions src/audio/mfcc/mfcc_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <sof/math/auditory.h>
#include <sof/math/fft.h>
#include <sof/math/matrix.h>
#include <sof/math/pcan.h>
#include <sof/math/sqrt.h>
#include <sof/math/trig.h>
#include <sof/math/window.h>
Expand Down Expand Up @@ -355,6 +356,11 @@ int mfcc_stft_process(struct processing_module *mod, struct mfcc_comp_data *cd)
psy_apply_mel_filterbank_32(&state->melfb, fft->fft_out, state->power_spectra,
state->mel_log_32, mel_scale_shift);

if (state->pcan.enable_pcan) {
pcan_update_noise_estimate(&state->pcan, (const uint32_t *)state->mel_log_32);
pcan_apply(&state->pcan, (uint32_t *)state->mel_log_32);
}

if (state->mel_only) {
/* In Mel-only mode output Mel log spectra directly */
cc_count += state->dct.num_in;
Expand Down
57 changes: 57 additions & 0 deletions src/audio/mfcc/mfcc_setup.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <sof/math/auditory.h>
#include <sof/math/icomplex16.h>
#include <sof/math/icomplex32.h>
#include <sof/math/pcan.h>
#include <sof/math/trig.h>
#include <sof/math/window.h>
#include <sof/trace/trace.h>
Expand Down Expand Up @@ -459,9 +460,63 @@ int mfcc_setup(struct processing_module *mod, int max_frames, int sample_rate, i
}
}

if (config->enable_pcan) {
struct pcan_config pcfg;
uint32_t *pcan_noise;
int16_t *pcan_lut;

pcfg.strength = (config->pcan_strength > 0) ?
((float)config->pcan_strength / 32768.0f) : 0.95f;
pcfg.offset = (config->pcan_offset > 0) ?
((float)config->pcan_offset / 128.0f) : 80.0f;
pcfg.gain_bits = (config->pcan_gain_bits > 0) ?
config->pcan_gain_bits : 21;
pcfg.smoothing_coef = (config->pcan_smoothing_coef > 0) ?
(uint16_t)config->pcan_smoothing_coef : 819;
pcfg.smoothing_bits = 10;
pcfg.input_correction_bits = 0;
pcfg.enable_pcan = true;

pcan_noise = mod_zalloc(mod, config->num_mel_bins * sizeof(uint32_t));
if (!pcan_noise) {
comp_err(dev, "Failed PCAN noise estimate alloc");
ret = -ENOMEM;
goto free_vad;
}

pcan_lut = mod_zalloc(mod, PCAN_LUT_SIZE * sizeof(int16_t));
if (!pcan_lut) {
comp_err(dev, "Failed PCAN gain LUT alloc");
mod_free(mod, pcan_noise);
ret = -ENOMEM;
goto free_vad;
}

ret = pcan_populate_state(&pcfg, &state->pcan, pcan_noise, pcan_lut,
config->num_mel_bins, 10, 0);
if (ret < 0) {
comp_err(dev, "Failed PCAN state init");
mod_free(mod, pcan_noise);
mod_free(mod, pcan_lut);
goto free_vad;
}
} else {
state->pcan.enable_pcan = false;
state->pcan.noise_estimate = NULL;
state->pcan.gain_lut = NULL;
}

comp_dbg(dev, "done");
return 0;

free_vad:
if (config->enable_vad) {
mod_free(mod, cd->vad.noise_floor);
mod_free(mod, cd->vad.weights);
cd->vad.noise_floor = NULL;
cd->vad.weights = NULL;
}

free_out_stage:
mod_free(mod, state->out_stage);

Expand Down Expand Up @@ -528,6 +583,8 @@ void mfcc_free_buffers(struct processing_module *mod)
mfcc_free_and_null(mod, (void **)&cd->state.dct.matrix);
mfcc_free_and_null(mod, (void **)&cd->state.lifter.matrix);
mfcc_free_and_null(mod, (void **)&cd->state.out_stage);
mfcc_free_and_null(mod, (void **)&cd->state.pcan.noise_estimate);
mfcc_free_and_null(mod, (void **)&cd->state.pcan.gain_lut);
mfcc_free_and_null(mod, (void **)&cd->vad.noise_floor);
mfcc_free_and_null(mod, (void **)&cd->vad.weights);
}
52 changes: 35 additions & 17 deletions src/audio/mfcc/tune/setup_mfcc.m
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ function setup_mfcc()
setup.tplg_fn = 'ceps13_compress_dtx.conf';
export_mfcc_setup(gen_cfg, setup);

% Mel spectrogram with PCAN normalization and compress PCM output
setup = get_mel_spectrogram_config();
setup.enable_pcan = true;
setup.compress_output = true;
setup.tplg_fn = 'mel80_pcan_compress.conf';
export_mfcc_setup(gen_cfg, setup);

end

function cfg = get_mfcc_default_config()
Expand Down Expand Up @@ -94,6 +101,11 @@ function setup_mfcc()
cfg.dtx_silence_hops_interval = 0;
cfg.update_controls = false;
cfg.compress_output = false;
cfg.enable_pcan = false;
cfg.pcan_strength = 0.95;
cfg.pcan_offset = 80.0;
cfg.pcan_gain_bits = 21;
cfg.pcan_smoothing_coef = 819;
end

function cfg = get_mel_spectrogram_config()
Expand All @@ -113,7 +125,7 @@ function setup_mfcc()
cfg.num_mel_bins = 80;
cfg.preemphasis_coefficient = 0;
cfg.raw_energy = false;
cfg.remove_dc_offset = false;
cfg.remove_dc_offset = true;
cfg.round_to_power_of_two = true;
cfg.sample_frequency = 16000;
cfg.snip_edges = true;
Expand All @@ -123,20 +135,25 @@ function setup_mfcc()
cfg.vtln_low = 0;
cfg.vtln_warp = 1.0;
cfg.window_type = 'hann';
cfg.mel_log = 'log10';
cfg.mel_log = 'log'; % Set to 'db' for librosa, set to 'log10' for matlab
cfg.pmin = 1e-10;
cfg.top_db = 8; % applied for log10, would be 80 dB clamp for decibels as 10*log10()
cfg.mel_offset = 4.0; % For whisper like Mel scale and normalize
cfg.mel_scale = 0.25; % For whisper like Mel scale and normalize
cfg.mmax_init = 0; % Initial value max Mel value, data clamp is mmax - top_db
cfg.mmax_coef = 0; % Dynamic max Mel value decay coefficient (zero lock to found max)
cfg.dynamic_mmax = true;
cfg.enable_vad = true;
cfg.top_db = 80.0;
cfg.mel_offset = 4.0; % Whisper: (mel + 4.0) * 0.25
cfg.mel_scale = 0.25;
cfg.mmax_init = 0;
cfg.mmax_coef = 0.005; % Whisper mmax tracking: slow decay
cfg.dynamic_mmax = false;
cfg.enable_vad = false;
cfg.enable_dtx = false;
cfg.dtx_trailing_silence_hops = 0;
cfg.dtx_silence_hops_interval = 0;
cfg.update_controls = true;
cfg.update_controls = false;
cfg.compress_output = false;
cfg.enable_pcan = false;
cfg.pcan_strength = 0.95;
cfg.pcan_offset = 80.0;
cfg.pcan_gain_bits = 21;
cfg.pcan_smoothing_coef = 819;
end

function export_mfcc_setup(gen_cfg, cfg)
Expand Down Expand Up @@ -173,8 +190,9 @@ function export_mfcc_setup(gen_cfg, cfg)

v = cfg.dtx_trailing_silence_hops; [b8, j] = add_w16b(v, b8, j); % DTX trailing silence hops
v = cfg.dtx_silence_hops_interval; [b8, j] = add_w16b(v, b8, j); % DTX silence frame interval
v = cfg.pcan_smoothing_coef; [b8, j] = add_w32b(v, b8, j); % PCAN smoothing coef in Q14
% Reserved
for i = 1:5
for i = 1:4
[b8, j] = add_w32b(0, b8, j);
end

Expand All @@ -200,10 +218,9 @@ function export_mfcc_setup(gen_cfg, cfg)
v = 0; [b8, j] = add_w16b(v, b8, j); % vtln_high Qx.y TBD
v = 0; [b8, j] = add_w16b(v, b8, j); % vtln_low Qx.y TBD
v = 0; [b8, j] = add_w16b(v, b8, j); % vtln_warp Qx.y TBD
% reserved16[3]
for i = 1:3
[b8, j] = add_w16b(0, b8, j);
end
v = q_convert(cfg.pcan_strength, 15); [b8, j] = add_w16b(v, b8, j); % PCAN strength in Q1.15
v = q_convert(cfg.pcan_offset, 7); [b8, j] = add_w16b(v, b8, j); % PCAN offset in Q8.7
v = cfg.pcan_gain_bits; [b8, j] = add_w16b(v, b8, j); % PCAN gain bits
v = cfg.htk_compat; [b8, j] = add_w8b(v, b8, j); % bool
v = cfg.raw_energy; [b8, j] = add_w8b(v, b8, j); % bool
v = cfg.remove_dc_offset; [b8, j] = add_w8b(v, b8, j); % bool
Expand All @@ -216,8 +233,9 @@ function export_mfcc_setup(gen_cfg, cfg)
v = cfg.enable_dtx; [b8, j] = add_w8b(v, b8, j); % bool
v = cfg.update_controls; [b8, j] = add_w8b(v, b8, j); % bool
v = cfg.compress_output; [b8, j] = add_w8b(v, b8, j); % bool
% reserved_bool[4]
for i = 1:4
v = cfg.enable_pcan; [b8, j] = add_w8b(v, b8, j); % bool
% reserved_bool[3]
for i = 1:3
[b8, j] = add_w8b(0, b8, j);
end

Expand Down
2 changes: 2 additions & 0 deletions src/include/sof/audio/mfcc/mfcc_comp.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <sof/math/auditory.h>
#include <sof/math/dct.h>
#include <sof/math/fft.h>
#include <sof/math/pcan.h>
#include <sof/audio/mfcc/mfcc_vad.h>
#include <sof/ipc/msg.h>
#include <stddef.h>
Expand Down Expand Up @@ -99,6 +100,7 @@ struct mfcc_state {
struct mfcc_fft fft; /**< FFT related */
struct dct_plan_16 dct; /**< DCT related */
struct psy_mel_filterbank melfb; /**< Mel filter bank */
struct pcan_state pcan; /**< PCAN state */
struct mfcc_cepstral_lifter lifter; /**< Cepstral lifter coefficients */
struct mat_matrix_16b *mel_spectra; /**< Pointer to scratch */
struct mat_matrix_16b *cepstral_coef; /**< Pointer to scratch */
Expand Down
Loading
Loading