Noise Suppression Model (NSNet)
NSNet is a deep-learning-based noise suppression model for low-power embedded MCUs.
Overview
NSNet currently offers two models: for maximum AI noise-suppression performance, use nsnet2; for a balance between noise reduction and speech distortion, use nsnet3.
nsnet2 is a quantized neural noise suppression model with the following features:
Sample rate: 16 kHz, 16-bit PCM
Frame length: 1024 samples (64 ms), frame shift: 512 samples (32 ms)
ERB-based spectral masking: the network estimates a time-frequency mask which is applied to the noisy spectrum
Single-channel processing, plus a multi-channel shared-mask mode (see Multi-Channel Shared-Mask Processing below)
Supported chips: ESP32-S3, ESP32-S31 and ESP32-P4
nsnet3 is a float32 noise suppression model with the following features:
Sample rate: 16 kHz, 16-bit PCM
Frame length: 512 samples (32 ms), frame shift: 256 samples (16 ms), for lower end-to-end latency
ERB-based complex ratio mask (CRM): float32 inference with no quantization loss, and only 48.2K parameters (about 100 KB of weights)
Single-channel processing, plus the same multi-channel shared-mask mode as nsnet2 (identical
create_mc()/process_mc()semantics)Supported chips: ESP32-P4 and ESP32-S31 (both measured; the interface also builds for the ESP32-S3 target)
About 20% single-core CPU usage (measured on ESP32-P4 @ 400 MHz)
Note
Select the model via idf.py menuconfig -> ESP Speech Recognition -> Select noise suppression model -> Deep noise suppression v2 (nsnet2) or Deep noise suppression v3 (nsnet3).
Use NSNet
The interface is defined in esp_nsn_iface.h. All operations go through the esp_nsn_iface_t function table, which is obtained from the model name. nsnet2 and nsnet3 implement exactly the same interface, so the application layer does not need to distinguish between them.
Basic Flow (single channel):
Get the model interface and create an instance
#include "esp_nsn_iface.h" #include "esp_nsn_models.h" #include "model_path.h" srmodel_list_t *models = esp_srmodel_init("model"); char *model_name = esp_srmodel_filter(models, ESP_NSNET_PREFIX, NULL); const esp_nsn_iface_t *nsnet = esp_nsnet_handle_from_name(model_name); esp_nsn_data_t *nsnet_data = nsnet->create(model_name);
Process audio frames
Each call to
process()consumes and returnsget_samp_chunksize()samples — the frame shift depends on the selected model (512 samples = 32 ms for nsnet2, 256 samples = 16 ms for nsnet3); allocate buffers according to the returned value:int chunk = nsnet->get_samp_chunksize(nsnet_data); // nsnet2: 512, nsnet3: 256 int16_t in[chunk], out[chunk]; nsnet->process(nsnet_data, in, out);
Release resources
nsnet->destroy(nsnet_data);
nsnet3 Weight Loading
nsnet3 ships its weights as a private binary in the model partition (nsnet3_data / nsnet3_index, packaged and flashed when the SR_NSN_NSNET3 Kconfig option is selected). On create(), the weights are copied out tensor by tensor (with per-tensor name and length verification). With the default configuration (SR_NSNET3_MEM_PSRAM=y), the weights, streaming state and activation buffers all live in PSRAM (about 240 KB total) and use almost no internal SRAM; disabling the option moves everything into internal SRAM (about 270 KB), where weight reads run at the same speed as on-chip rodata. Targets without PSRAM automatically use internal SRAM.
Examples
The examples/nsnet application demonstrates both interfaces:
SD card test: reads a 16 kHz 16-bit WAV file from the SD card, processes all input channels (using
create_mc()/process_mc()when the input has more than one channel, with channel 0 as the reference), writes the enhanced multi-channel WAV back, and reports CPU load, real-time factor and memory usage (also written to a performance log file on the SD card).USB_SERIAL_JTAG streaming test: streams audio between a host PC and the chip over the USB serial interface. On the host side, run:
python3 stream_host.py --port /dev/ttyACM1 --in test_4ch_in.wav --out out.wav
The host sends interleaved 16 kHz 16-bit frames (
get_samp_chunksize()samples per channel per frame) and reads back the enhanced frames; the output WAV keeps the processed channel count.
Resource Consumption
Measured on ESP32-P4 @ 400 MHz (OCT PSRAM @ 250 MHz, flash @ 80 MHz):
Model (frame shift) |
Channels |
Memory |
Time per Frame (us) |
CPU Usage (%) |
|---|---|---|---|---|
nsnet2 (32 ms) |
1 |
internal SRAM |
4534 |
14.2 |
nsnet2 (32 ms) |
4 (shared mask) |
internal SRAM |
6476 |
20.2 |
nsnet3 (16 ms) |
1 |
PSRAM (default) |
3418 |
21.37 |
nsnet3 (16 ms) |
1 |
internal SRAM |
3206 |
20.04 |
nsnet3 (16 ms) |
4 (shared mask) |
PSRAM (default) |
4318 |
26.99 |
nsnet3 (16 ms) |
4 (shared mask) |
internal SRAM |
4018 |
25.11 |
Note
With the shared-mask mode, each additional channel costs far less than an independent instance (about +1.7 CPU percentage points per channel for nsnet3).
nsnet3 scores a sample-by-sample SNR of 44.55 dB against the official golden streaming output.
For general model resource occupancy, see Resource Occupancy.
Measured on ESP32-S31 @ 320 MHz, OCT PSRAM @ 250 MHz (nsnet3, 16 ms frame shift, flash @ 80 MHz):
Model (frame shift) |
Channels |
Memory |
Time per Frame (us) |
CPU Usage (%) |
|---|---|---|---|---|
nsnet3 (16 ms) |
1 |
PSRAM (default) |
5680 |
35.50 |
nsnet3 (16 ms) |
1 |
internal SRAM |
4353 |
27.21 |
nsnet3 (16 ms) |
4 (shared mask) |
PSRAM (default) |
6815 |
42.59 |
nsnet3 (16 ms) |
4 (shared mask) |
internal SRAM |
5387 |
33.67 |
Note
nsnet3 also scores a sample-by-sample SNR of 44.55 dB on ESP32-S31 (against the official golden streaming output, identical to ESP32-P4).
With the default configuration (
SR_NSNET3_MEM_PSRAM=y), nsnet3 occupies about 240 KB of PSRAM and almost no internal SRAM at runtime; with the option disabled it occupies about 270 KB of internal SRAM (about 100 KB of weights copied from the model partition oncreate()), and CPU usage follows the “internal SRAM” rows above.