Skip to main content

2 posts tagged with "Audio"

View All Tags

DshanPI-A1 Audio Recording, Playback and Noise Analysis

· 14 min read
Yuxuan
100askTeam yuxuan.

Audio Playback

Speaker Device

Let's first look at how to use this speaker. First, list the audio playback devices.

aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: rockchipes8388 [rockchip-es8388], device 0: dailink-multicodecs ES8323 HiFi-0 [dailink-multicodecs ES8323 HiFi-0]
Subdevices: 1/1
Subdevice #0: subdevice #0
card 1: rockchiphdmiin [rockchip,hdmiin], device 0: 2a640000.sai-dummy_codec dummy_codec-0 [2a640000.sai-dummy_codec dummy_codec-0]
Subdevices: 1/1
Subdevice #0: subdevice #0
card 2: rockchipdp0 [rockchip-dp0], device 0: rockchip-dp0 spdif-hifi-0 [rockchip-dp0 spdif-hifi-0]
Subdevices: 1/1
Subdevice #0: subdevice #0
card 3: rockchiphdmi [rockchip-hdmi], device 0: rockchip-hdmi i2s-hifi-0 [rockchip-hdmi i2s-hifi-0]
Subdevices: 1/1
Subdevice #0: subdevice #0

It can be seen that a total of 4 audio cards are detected (card 0 ~ card 3)

  • Play audio to speaker/headphones: use card 0, device 0
  • HDMI audio output: use card 3, device 0
  • DisplayPort audio: use card 2, device 0
  • Capture HDMI input audio: use card 1, device 0

The speaker is card0: subdevice#0, and the corresponding ALSA device is hw:0,0

**** List of PLAYBACK Hardware Devices ****
card 0: rockchipes8388 [rockchip-es8388], device 0: dailink-multicodecs ES8323 HiFi-0 [dailink-multicodecs ES8323 HiFi-0]
Subdevices: 1/1
Subdevice #0: subdevice #0

Create and Play a Simple Test Tone

# Create a 1kHz sine wave WAV file at 8kHz sample rate (5 seconds)
ffmpeg -f lavfi -i "sine=frequency=1000:duration=5" -c:a pcm_s16le -ar 8000 test_tone.wav
# Play the file
aplay test_tone.wav
Playing WAVE 'test_tone.wav' : Signed 16 bit Little Endian, Rate 8000 Hz, Mono

The test commands all succeeded, but no sound was heard. We need to check the properties of this speaker. It is very likely an audio routing issue. First, we need to determine the troubleshooting approach:

The audio chip (ES8388) is designed with:

  • Software control layer: Speaker Switch - controls whether the audio stream is sent to the chip
  • Hardware control layer: OUT1/OUT2 Switch - controls the chip's physical pin output

Audio Playback Debugging

First, open the audio visualization tool to take a look.

alsamixer -c 0

image-20251205094259863

It can be seen that the playback status is abnormal: MM 00. We need to further confirm which configuration is the problem. Use the PulseAudio control tool.

pactl list short sinks
0 alsa_output.0.HiFi__hw_rockchipes8388__sink module-alsa-card.c s16le 2ch 44100Hz SUSPENDED
1 alsa_output.1.stereo-fallback module-alsa-card.c s16le 2ch 44100Hz SUSPENDED

Some explanations:

  • Sink: the endpoint of audio output
  • PulseAudio architecture
Application -> Audio stream -> PulseAudio server -> Sink -> Hardware
(player) (mixer) (output device) (sound card)

Status description

  • RUNNING: audio is playing
  • IDLE: idle, ready
  • SUSPENDED: suspended, power-saving mode
  • UNLINKED: not connected

Device details

  • sink 0: alsa_output.0.HiFi__hw_rockchipes8388__sink
    • Corresponds to ALSA sound card 0 (ES8388 audio chip)
    • High fidelity (HiFi) output
  • sink 1: alsa_output.1.stereo-fallback
    • Fallback/backup output device
    • Used when the primary device is unavailable

Both audio sinks are in SUSPENDED state

0 ... SUSPENDED
1 ... SUSPENDED

SUSPENDED state means:

  • PulseAudio thinks there is no audio stream to play
  • To save power, it automatically suspends audio output
  • The system is ready to receive audio, but there is currently no active audio stream

Root cause chain:

  1. At system startup -> PulseAudio loads audio devices

  2. No active audio stream -> PulseAudio suspends the device (SUSPENDED)

  3. Hardware routing not activated -> OUT1/OUT2 switches are off by default

  4. When starting to play audio:

    • PulseAudio wakes up the device
    • But the hardware switches (OUT/OUT2) are still off
    • Need to manually amixer -c 0 sset 'OUT1' on

    After my testing, after turning on

    amixer -c 0 sset 'OUT2' on

    the speaker audio can be played normally. Here as a record, I list some useful commands I used during debugging.

    #1. View more detailed sink information
    pactl list sinks
    Sink #0
    ...
    Sink #1
    ...
    # 2. View current audio streams
    pactl list sink-inputs
    #3. ALSA content control
    amixer -c 0 scontents
    ...
    amixer -c 0 get 'Speaker'
    amixer -c 0 get 'Master'
    amixer -c 0 get 'Headphone'

There are multiple solutions to this problem, listed below.

Solution 1: Prevent auto-suspend

# Edit PulseAudio configuration
vi /etc/pulse/default.pa
# Prevent auto-suspend
load-module module-suspend-on-idle timeout=0 # 0 means never suspend
# Increase timeout
load-module module-suspend-on-idle timeout=3600 # 1 hour
# Restart PulseAudio
pulseaudio -k
pulseaudio --start

Solution 2: Automatically activate hardware at startup

# Create startup script /etc/pulse/audio-init.sh
#!/bin/bash
# Wait for PulseAudio to start
sleep 3
# Activate hardware output
amixer -c 0 sset 'OUT2' on
amixer -c 0 sset 'Speaker' on
# Set appropriate volume
amixer -c 0 sset 'Output 2' 90%

Solution 3: Use udev rules

# Create /etc/udev/rules.d/90-audio.rules
ACTION=="add", SUBSYSTEM=="sound", KERNEL=="card0", \
RUN+="/usr/bin/amixer -c 0 sset 'OUT2' on"
# Takes effect after reboot

Solution 4: Simplest temporary test

# Activate the device before playing
amixer -c 0 sset 'Speaker' on
amixer -c 0 sset 'OUT2' on
amixer -c 0 sset 'Output 2' 90%
# You can also permanently save settings (did not take effect)
alsactl store

Summary: The problem is actually:

  • Software layer: PulseAudio is normal
  • Driver layer: ALSA correctly identifies the device
  • Hardware layer: OUT2 physical switch needs to be manually activated

tips: Audio routing

# Assume there are multiple audio devices:
# 0 - built-in speaker
# 1 - USB headset
# 2 - HDMI output

# Send Chrome audio to the headset
pactl move-sink-input $(pactl list short sink-inputs | grep chrome | awk '{print $1}') 1
# Send music player to HDMI
pactl move-sink-input $(pactl list short sink-inputs | grep spotify | awk '{print $1}') 2

Audio Recording

The hardware used is the 100ask 200w USB camera + audio MEMS integrated module, as shown in the figure below.

7de0c186abbafe3783a045089397308b

Device Information Acquisition

First, we need to obtain the information of the entire MEMS. It communicates with RK3576 via USB. There are several ways to see its information.

v4l2-sysfs-path
Video device: video36
video: video37
sound card: hw:4
pcm capture: hw:4,0
mixer: hw:4
Video device: video37
sound card: hw:4
pcm capture: hw:4,0
mixer: hw:4
.....
alsactl info
......
- card: 4
id: Camera
name: USB 2.0 Camera
longname: lihappe8 Corp. USB 2.0 Camera at usb-xhci-hcd.8.auto-1.2.2, high speed
driver_name: USB-Audio
mixer_name: USB Mixer
components: USB038f:0541
controls_count: 4
pcm:
- stream: CAPTURE
devices:
- device: 0
id: USB Audio
name: USB Audio
subdevices:
- subdevice: 0
name: subdevice #0
.....

It can be seen that the name of this device in ALSA is hw:4,0

Audio Recording Test

# Record a segment of background noise
arecord -D hw:4,0 -f S16_LE -r 8000 -c 2 -d 10 noise
Recording WAVE 'noise.wav' : Signed 16 bit Little Endian, Rate 8000 Hz, Stereo
aplay noise.wav
Playing WAVE 'noise.wav' : Signed 16 bit Little Endian, Rate 8000 Hz, Stereo

A clear "squeak" sound can be heard. The self-noise of this MEMS is still quite large. Noise reduction is needed later before normal voice communication can be performed.

Noise Analysis

Use sox to generate a spectrogram.

sox noise.wav -n spectrogram -o noise_spectrogram.pn

noise_spectrogram

# Global spectrum
sox noise.wav -n spectrogram -d 10 -x 1200 -z 80 -o noise_full.png

noise_full

sox noise.wav -r 2000 -c 1 noise_2k.wav
sox noise_2k.wav -n spectrogram -d 10 -x 1200 -z 80 -o noise_low.png

noise_low

Overall observation: the background noise is "approximately white noise + strong low-frequency peaks + slight mid-frequency texture noise"

1. There is a very obvious low-frequency energy blob around 0~200Hz (especially <100Hz)

This generally means:

  • Mechanical/power-related noise
  • Fan, vibration, chassis resonance
  • Power ripple (50Hz / 60Hz + fundamental)
  • Microphone directivity/enclosure coupling amplifying ultra-low frequency noise

2. The 1kHz~4kHz range is random noise (approximately white noise) with low energy

This indicates the microphone self-noise is typical:

  • MEMS microphone inherent noise
  • ADC self-noise
  • Amplifier input noise

This part is the system noise floor.

3. High frequency (>6kHz) has no strange peaks at all

This is very good -> no obvious:

  • Digital EMI
  • Clock leakage
  • Sampling jitter noise

4. No obvious "howling mode" appears

None of the three figures show typical howling characteristics:

  • Howling is usually a fixed-frequency bright line that remains unchanged
  • The figures only show a pulse at the starting moment (may be the click sound when recording starts)

No stable peak persists over time.

Per-figure analysis


(A) First figure: Full spectrum (up to 4kHz)

The characteristics look like:

Overall reddish/purple, noise density is high but uniform Obvious vertical line around 50Hz 100Hz, 150Hz also have slight energy

-> This is almost certainly:

Power frequency noise (50/60Hz) + harmonics (100/150Hz)

Reasons:

  • USB power supply brings a lot of 50/60Hz hum
  • Poor isolation of the sound card or microphone analog front-end
  • Unclean ground (such as USB common ground loop)

(B) Second figure: Version with narrowed dynamic range (-80dBFS)

This figure additionally exposes:

There is a very narrow horizontal line at 1.8kHz ~ 2.2kHz

Very faint, but stably present.

This indicates:

System clock/PLL interference leakage

Common in:

  • I2S/MCLK leakage
  • Clock coupling of the microphone on the PCB
  • Digital power noise superimposed on the microphone analog part

This part will not cause howling, but will reduce SNR.


(C) Third figure: Low frequency to 1000Hz

Very typical:

The noise in the <150Hz region is much higher than other frequency bands

Like a large "bulb" shape, very obvious.

This indicates:

Low-frequency vibration + power noise are the main noise floor sources

Including:

  • Chassis vibration, fan, tabletop resonance
  • Power 50/60Hz + harmonics
  • The microphone's own LF roll-off is insufficient

Comprehensive judgment: Background noise composition ratio

Noise typeRatioFeature
Low-frequency mechanical/power noise (<200Hz)50%Largest source, from power, chassis, vibration
Power frequency leakage 50/60Hz + harmonics25%Strongest fixed peak in the figure
Microphone inherent white noise20%Random noise scattered in 1k~4kHz
Digital clock leakage (around 2kHz)5%A very weak but visible thin line

PSD Noise Model Analysis

Use Python to generate a PSD noise model.

import numpy as np
import matplotlib.pyplot as plt
from scipy.io import wavfile
from scipy.signal import welch, find_peaks, spectrogram, butter, sosfilt
import IPython.display as ipd
import os

rate, data = wavfile.read("/mnt/data/noise.wav")
if data.ndim>1:
data = data.mean(axis=1)
data = data.astype(np.float32)
N = len(data)
duration = N / rate

# Calculate overall RMS and dBFS
# Assuming 16-bit PCM if dtype was int16; determine scale
# infer max possible value from original dtype by reloading header:
import struct
# Determine dtype max
# but we'll normalize by max of int16 if dtype came as int16, else use max(abs(data))
max_possible = 32768.0
rms = np.sqrt(np.mean(data**2))
dbfs_rms = 20*np.log10(rms / max_possible) if rms>0 else -np.inf

# Welch PSD
f, Pxx = welch(data, fs=rate, nperseg=4096, scaling='density')
# find peaks in PSD (in linear)
peaks, props = find_peaks(Pxx, height=np.max(Pxx)*0.15, distance=5)
peak_freqs = f[peaks]
peak_heights = props['peak_heights']

# Find dominant low-frequency peak under 500Hz
low_idx = np.where(f<=500)[0]
low_f = f[low_idx]
low_P = Pxx[low_idx]
lp_peaks, lp_props = find_peaks(low_P, height=np.max(low_P)*0.2)
lp_freqs = low_f[lp_peaks]
lp_heights = lp_props['peak_heights']

# Short-time energy to find transient (e.g., first second pulse)
frame_ms = 20
frame_len = int(rate * frame_ms/1000)
hop = frame_len//2
frames = []
for start in range(0, N-frame_len, hop):
frames.append(np.sum(data[start:start+frame_len]**2))
frames = np.array(frames)
frame_times = (np.arange(len(frames))*hop)/rate

# detect where energy spikes relative to median
median_e = np.median(frames)
spikes = np.where(frames > median_e*8)[0] # 8x median
spike_times = frame_times[spikes]

# Spectrogram
f_s, t_s, Sxx = spectrogram(data, fs=rate, nperseg=2048, noverlap=1024, scaling='density', mode='magnitude')

# Plot PSD with peaks marked
plt.figure(figsize=(10,5))
plt.semilogy(f, Pxx, color='tab:orange')
plt.scatter(peak_freqs, peak_heights, color='k', zorder=5)
for pf, ph in zip(peak_freqs, peak_heights):
plt.text(pf, ph*1.1, f"{pf:.0f} Hz", fontsize=8, ha='center')
plt.xlim(0, rate/2)
plt.xlabel("Frequency (Hz)")
plt.ylabel("PSD")
plt.title("Welch PSD with detected peaks")
plt.grid(alpha=0.3)
plt.tight_layout()
plt.savefig("/mnt/data/psd_peaks.png")

# Plot spectrogram (dB)
Sxx_db = 20*np.log10(Sxx + 1e-12)
plt.figure(figsize=(10,5))
plt.pcolormesh(t_s, f_s, Sxx_db, shading='gouraud')
plt.colorbar(label='dB')
plt.ylim(0, 4000)
plt.xlabel("Time (s)")
plt.ylabel("Frequency (Hz)")
plt.title("Spectrogram (dB)")
plt.tight_layout()
plt.savefig("/mnt/data/spectrogram_db.png")

# Prepare summary
summary = {
"sampling_rate": rate,
"duration_s": duration,
"rms": float(rms),
"dbfs_rms": float(dbfs_rms),
"dominant_peaks_hz": [float(p) for p in peak_freqs[:8]],
"dominant_peaks_vals": [float(p) for p in peak_heights[:8]],
"low_freq_peaks_hz": [float(p) for p in lp_freqs],
"low_freq_peaks_vals": [float(p) for p in lp_heights],
"spike_times_s": [float(s) for s in spike_times[:10]],
"spectrogram_image": "/mnt/data/spectrogram_db.png",
"psd_image": "/mnt/data/psd_peaks.png"
}

import json
with open("/mnt/data/noise_analysis_summary.json","w") as f:
json.dump(summary, f, indent=2)

# Display small tables and figures
from caas_jupyter_tools import display_dataframe_to_user
import pandas as pd

df_peaks = pd.DataFrame({
"freq_hz": peak_freqs,
"psd_val": peak_heights
})
display_dataframe_to_user("Detected PSD Peaks", df_peaks.head(20))

plt.figure(figsize=(10,3))
plt.plot(frame_times, 10*np.log10(frames+1e-12))
plt.xlabel("Time (s)")
plt.ylabel("Frame energy (dB)")
plt.title("Short-time frame energy (20ms frames)")
plt.grid(alpha=0.3)
plt.tight_layout()
plt.savefig("/mnt/data/frame_energy.png")
plt.show()

# Show audio player
ipd.display(ipd.Audio("/mnt/data/noise.wav"))

summary

image-20251205153910716

image-20251205153954232

image-20251205154029359

image-20251205154110367

Key quantitative results obtained:

  • Sample rate: 8000 Hz
  • Duration: 10.0 s
  • Overall RMS = 223.11 (samples), converted to -43.34 dBFS (normalized to 16-bit full scale 32768): indicates the noise floor is moderately high.
  • Detected significant frequency peaks (Welch PSD peak, in order of intensity, listing the first few):
    • approximately 74.2 Hz, 85.94 Hz, 103.52 Hz, 113.28 Hz, 146.48 Hz, 167.97 Hz, and smaller 396 Hz, etc.
  • Main peaks in the low-frequency band (≤500 Hz): 85.94, 95.70, 103.52, 113.28, 146.48 Hz - multiple near-frequency peaks, like a collection of power frequency/switching power supply harmonics or mechanical resonances.
  • Transient energy peaks (short-time energy frames, 20 ms) detected at: approximately 0.36-0.38 s there are several short pulses (may be startup click/human touch/transient events).

Conclusion: The noise consists of obvious low-frequency "hum/ripple/vibration" + broadband white noise (mid-to-high frequency), with low frequency being the main energy location.

Noise Reduction Scheme

1. Add a 2nd-order high-pass filter (fc = 80 Hz) to the audio chain

  • Directly remove most of the mechanical/power low-frequency without damaging the voice bandwidth (voice is mainly >100Hz).
  • Implementation: use the butter/sos in the DSP library or implement biquad yourself (Direct Form I or II).

2 Targeted notch filter

  • If there are still a few extremely narrow strong peaks after HPF (such as 103Hz), add one or two notches with Q=20~40. The higher the Q, the narrower the bandwidth, and the less it damages adjacent frequency bands, but the coefficients are closer to 1.
  • Consider real-time requirements: need to cascade hp -> notch(strong peak 1) -> notch(strong peak 2).

3 The essence is still hardware

  • Replace or improve the analog power supply (low-noise LDO, more bypass capacitors, star ground) or use a digital MEMS microphone with better SNR.
  • On embedded boards, keep switching power supply traces as far away from microphone differential/analog lines as possible.

Considerations for real-time audio quality

  • If very low latency is required (e.g., echo cancellation / real-time loopback), use IIR (biquad) one-way real-time implementation (extremely low latency), but note that the phase will change. If phase change is not allowed (e.g., for more accurate positioning), using FIR + zero-phase will have a latency cost.

Reference Filter Coefficients

Note: The coefficients given below are in the standard second-order section (biquad) format, arranged as [b0, b1, b2, a0, a1, a2] (a0 has been normalized to 1 or needs to be normalized when given). When implementing, usually normalize a0 to 1, and then implement according to Direct Form I/II.

High-pass: 2nd-order Butterworth (fc = 80 Hz, fs = 8000 Hz)

sos_hp first section coefficients (a0 normalized to 1):

b0 = 0.9565432255568767
b1 = -1.9130864511137533
b2 = 0.9565432255568767
a0 = 1.0
a1 = -1.911197067426073
a2 = 0.9149758348014336

Several notch filters (notch, Q=30)

(Three examples, taken from detected peak positions) Format is the same as above (b0,b1,b2,a0,a1,a2), a0 has been normalized to 1 in the output below:

  • notch @ 103.515625 Hz
b0 = 0.998648305437691
b1 = -1.99069933085876
b2 = 0.998648305437691
a0 = 1.0
a1 = -1.99069933085876
a2 = 0.9972966108753819
  • notch @ 146.484375 Hz
b0 = 0.9980904047539934
b1 = -1.9829844796660758
b2 = 0.9980904047539934
a0 = 1.0
a1 = -1.9829844796660758
a2 = 0.9961808095079867
  • notch @ 74.21875 Hz
b0 = 0.9990299707952924
b1 = -1.9946663265604048
b2 = 0.9990299707952924
a0 = 1.0
a1 = -1.9946663265604048
a2 = 0.9980599415905849

This noise analysis is the necessary material for traditional spectral subtraction audio filtering. Using the standard rnnoise AI noise reduction does not require it, but if you improve rnnoise yourself and do model fine-tuning, you need to refer to it to achieve better noise reduction results.

Implementing sox Noise Reduction and rnnoise Noise Reduction on RK3576

· 13 min read
Yuxuan
100askTeam yuxuan.

Overview

The development of audio noise reduction technology has evolved from combating physical noise to intelligent recognition and separation.

Analog Era (mid-20th century)

  • Dolby A (1965): A pioneering dynamic noise reduction technology that used the "companding" principle to reduce tape background noise.
  • Dolby B (1968): A consumer-simplified version of Dolby A, which brought cassette tapes into millions of households.
  • dbx (1971): A more aggressive companding system with a larger dynamic range.
  • Subsequent developments: More advanced analog systems such as Dolby C and SR followed.

Early Digital Era (1980s-1990s)

  • The emergence of digital signal processing chips made real-time digital filtering and spectral subtraction possible
  • Algorithms began shifting from time-domain to frequency-domain processing
  • Adaptive filtering theory matured and was applied in communications

Digital Algorithm Popularization (1990s-2010s)

  • Improvements in personal computer performance and the popularization of professional audio software (such as Audition and iZotope RX) made digital noise reduction tools widely accessible
  • The application of psychoacoustic models improved the sound quality of noise reduction
  • Active noise canceling headphones began to be commercialized (driven by manufacturers such as Bose)

Artificial Intelligence Era (2010s to present)

  • Deep learning has completely transformed noise reduction, enabling it to handle complex non-stationary noise
  • The technology is widely applied in fields such as video conferencing, voice assistants, and music streaming
  • Research directions have expanded from simple noise reduction to refined tasks such as speech separation and vocal extraction

Main Noise Reduction Methods

Spectrum-based Noise Reduction The core idea is to distinguish between noise and signal in the frequency domain:

  • Spectral subtraction: Subtract the noise spectrum from the audio
  • Wiener filtering: A better statistical noise reduction method
  • Masking effect method: Leverages human ear characteristics to preserve sound quality

Machine Learning Noise Reduction

  • Uses deep learning models (RNN, CNN, Transformer, etc.) to learn noise reduction from data
  • Can effectively handle complex scenarios and non-stationary noise
  • Supports end-to-end waveform or spectrum processing

Filtering Noise Reduction

  • Adaptive filtering: Requires a reference noise signal, used in telephony and active noise canceling headphones
  • Fixed filtering: Removes noise at specific frequencies, such as 50Hz power-line interference

Multi-microphone Technology

  • Forms a directional beam through a microphone array
  • Enhances sound from the target direction and suppresses ambient noise
  • Common in phones, conferencing equipment, and smart speakers

Traditional Processing Methods

  • Noise gate: Mutes low-level signals via a threshold
  • Simple and effective, commonly used in music production and live streaming

The following uses two methods for audio noise reduction: sox noise reduction and rnnoise noise reduction.

sox Noise Reduction

sox's noisered is a classic, non-AI noise reduction tool based on noise sampling and spectral subtraction. Its principle is intuitive and relatively simple to implement, and it is remarkably effective on stationary noise (such as fan noise, air conditioner noise, and constant current noise). The noisered effect is the core of its noise reduction. The core principle steps are as follows:

sox
  1. Analysis/Training phase: The core is building a noise fingerprint. SoX needs to first "learn" what the noise looks like. You can provide a pure noise segment (such as ambient background noise recorded before the actual recording), or let it automatically detect the low-energy "silent" portions of the audio. It analyzes these segments, computes an average noise spectrum, and saves it as a .prof file. This file is the reference baseline for subsequent noise reduction.
  2. Noise Reduction Processing phase: The core is spectral subtraction.
    • Framing and Transform: The continuous audio signal is sliced into short-time overlapping frames and converted to the frequency domain via FFT. In the frequency domain, the signal is represented as energy (amplitude) and phase at different frequencies.
    • Key Operation - Spectral Subtraction: This is the most critical step. The algorithm compares the spectrum of the current frame with the previously learned noise sample spectrum. The basic idea is very simple: clean signal spectrum ≈ noisy signal spectrum - noise spectrum.
      • Energy Subtraction: The main operation is subtraction on the energy/amplitude spectrum. If the energy of a certain frequency in the current frame is lower than or close to the energy of the noise sample at that frequency, it will be heavily suppressed; if it is much higher, it will be preserved.
      • Phase Preservation: Phase information is critical for reconstructing the sound waveform. Spectral subtraction usually does not change the phase of the original signal; it directly synthesizes a new signal using the denoised amplitude spectrum and the original phase spectrum.
      • Suppression Factor: SoX's amount parameter (0.0 to 1.0) controls the strength of the subtraction. 0.5 means only half of the noise energy is subtracted, which is more conservative and reduces distortion.
    • Synthesis and Output: The processed frequency-domain data is converted back to a time-domain waveform via IFFT, and then the short-time frames are synthesized into a continuous audio signal through the overlap-add method, ultimately producing the denoised audio.

It looks complicated, but it is simple to operate. Now let's use the sox command to demonstrate and make it clear.

sox Command Noise Reduction

#noise.wav is the background noise, a 10s audio clip recorded in a quiet state
ls
noise.wav speech.wav
#Generate a noise profile (manually extract 5-10 seconds of pure noise)
sox noise.wav -n noiseprof noise.prof
#Noise reduction, 0.21 is an empirical value, the vast majority of material will not produce water sound
sox speech.wav noisered_speech.wav noisered noise.prof 0.21
#Compare playback, the effect is obviously noticeable
aplay noise.wav
Playing WAVE 'noise.wav' : Signed 16 bit Little Endian, Rate 8000 Hz, Stereo
aplay speech.wav
Playing WAVE 'speech.wav' : Signed 16 bit Little Endian, Rate 8000 Hz, Stereo
aplay noisered_speech.wav
Playing WAVE 'noisered_speech.wav' : Signed 16 bit Little Endian, Rate 8000 Hz, Stereo

Using only the sox command for noise reduction cannot be integrated into an embedded system. Next, we will use the C language with sox's library and API to perform audio noise reduction.

Using sox's Library and API for Audio Noise Reduction

The code structure is as follows:

(base) ubuntu@ubuntu-2204:~/baiwen/sox_noise_reduction$ tree -L 2
.
├── CMakeLists.txt
├── deps
│ ├── include
│ └── lib
├── include
│ ├── custom_effects.h
│ └── sox_noise_reduction.h
├── Makefile
└── src
├── custom_effects.c
├── main.c
└── sox_noise_reduction.c

5 directories, 7 files

Build

To build this demo, you need to set the toolchain in CMakeLists.txt

# Specify the cross compiler
set(CMAKE_C_COMPILER /home/ubuntu/rk3576_AI/buildroot/output/rockchip_rk3576/host/bin/aarch64-buildroot-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER /home/ubuntu/rk3576_AI/buildroot/output/rockchip_rk3576/host/bin/aarch64-buildroot-linux-gnu-g++)

# Set sysroot
set(CMAKE_SYSROOT /home/ubuntu/rk3576_AI/buildroot/output/rockchip_rk3576/staging)
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})

# Add link libraries
target_link_libraries(sox_noise_reduction
sox
)
#Build on ubuntu22.04
(base) ubuntu@ubuntu-2204:~/baiwen/sox_noise_reduction$ make
Configuring CMake...
-- The C compiler identification is GNU 11.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/ubuntu/baiwen/sox_noise_reduction/build
Building with 16 jobs...
make[1]: Entering directory '/home/ubuntu/baiwen/sox_noise_reduction/build'
make[2]: Entering directory '/home/ubuntu/baiwen/sox_noise_reduction/build'
make[3]: Entering directory '/home/ubuntu/baiwen/sox_noise_reduction/build'
make[3]: Leaving directory '/home/ubuntu/baiwen/sox_noise_reduction/build'
make[3]: Entering directory '/home/ubuntu/baiwen/sox_noise_reduction/build'
[ 75%] Building C object CMakeFiles/sox_noise_reduction.dir/src/sox_noise_reduction.c.o
[ 75%] Building C object CMakeFiles/sox_noise_reduction.dir/src/custom_effects.c.o
[ 75%] Building C object CMakeFiles/sox_noise_reduction.dir/src/main.c.o
[100%] Linking C executable sox_noise_reduction
make[3]: Leaving directory '/home/ubuntu/baiwen/sox_noise_reduction/build'
[100%] Built target sox_noise_reduction
make[2]: Leaving directory '/home/ubuntu/baiwen/sox_noise_reduction/build'
make[1]: Leaving directory '/home/ubuntu/baiwen/sox_noise_reduction/build'

#make push will push the sox_noise_reduction executable to buildroot's /develop via adb
(base) ubuntu@ubuntu-2204:~/baiwen/sox_noise_reduction$ make push
Running build/sox_noise_reduction...
build/sox_noise_reduction: 1 file pushed. 10.6 MB/s (23840 bytes in 0.002s)

Run

On the rk3576

root@rk3576-buildroot:/develop# ls
live_life.mp3 noise.wav sox_noise_reduction speech.wav

First, record a background noise clip noise.wav in a quiet environment, then speak a sentence into the microphone to record speech.wav

root@rk3576-buildroot:/develop# ./sox_noise_reduction
SoX audio noise reduction processing started
=======================
Noise file: noise.wav
Input file: speech.wav
Output file: noisered_output.wav
Noise reduction sensitivity: 0.21
-----------------------
Processing...
[1/2] Creating noise profile...
Creating noise profile from: noise.wav
Processing noise profile...
Noise profile created: /tmp/noise_profile_NFk1uN.prof
[2/2] Applying noise reduction...
Applying noise reduction to: speech.wav
Processing audio with noise reduction...
Noise reduction applied. Output: noisered_output.wav
Done!
Processing successful! Output file: noisered_output.wav
#Use aplay to play and compare
aplay noise.wav
aplay speech.wav
aplay noisered_output.wav

You can hear that noisered_output.wav is noticeably improved, but there is still some faint "musical noise". This is an inherent side effect of sox's spectral subtraction noise reduction. To completely remove it, you would need to design an additional Wiener filter effect plugin to further eliminate it.

The usage of sox_noise_reduction can be found in the -h help information

root@rk3576-buildroot:/develop# ./sox_noise_reduction -h
SoX audio noise reduction tool
====================

Usage: ./sox_noise_reduction [options]

Options:
-n file Noise sample file (default: noise.wav)
-i file Input speech file (default: speech.wav)
-o file Output file (default: noisered_output.wav)
-s value Noise reduction sensitivity 0.0-1.0 (default: 0.21)
-h Show this help information

Parameter description:
Sensitivity: 0.0 means strongest noise reduction (may damage speech), 1.0 means weakest noise reduction
Recommended value: 0.21 (empirical value, suitable for most audio material)

Usage examples:
./sox_noise_reduction # Use all default settings
./sox_noise_reduction -n mynoise.wav # Specify noise file
./sox_noise_reduction -i myspeech.wav # Specify input file
./sox_noise_reduction -s 0.3 # Adjust noise reduction strength
./sox_noise_reduction -o clean.wav # Specify output file

rnnoise Noise Reduction

RNNoise is an excellent open-source audio noise reduction tool, very suitable for scenarios that require entry-level learning, quick integration, and moderate performance.

  1. Technical Principle:
    • Combines traditional signal processing (spectral analysis) with deep learning (RNN neural network).
    • Input audio framing -> feature extraction (such as band energy, pitch) -> RNN predicts the gain mask for each band (VAD is also integrated within) -> outputs the denoised spectrum -> reconstructs the waveform.
  2. Lightweight Design:
    • The model is only about 86KB, suitable for embedded or real-time processing.
    • A single-core CPU can process in real time.

Advantages:

  • Open source and easy to use: Clear code, provides a C language API, easy to integrate into various projects.
  • Low latency: Frame processing latency is about 10ms (default frame length 20ms), suitable for real-time communication.
  • Strong compatibility: No third-party deep learning framework dependencies, pure C implementation.
  • Good speech preservation: While suppressing stationary noise (such as fan noise), it causes little damage to speech.

Limitations:

  • Not a general-purpose noise reduction: Mainly optimized for voice communication, limited effect on music and sudden noise (such as keyboard sounds).
  • Fixed parameters: The model is trained for general scenarios, difficult to customize for specific noise.
  • Residual "musical noise": In some scenarios, it may introduce residual noise similar to water ripples.
  • No support for high sampling rates: By default, only supports 48kHz/16kHz mono, music noise reduction requires adjustment.

Build

## Set the cross-compile toolchain path
export TOOLCHAIN_DIR="/home/ubuntu/rk3576_AI/buildroot/output/rockchip_rk3576/host/bin"
export CC="${TOOLCHAIN_DIR}/aarch64-buildroot-linux-gnu-gcc"
export CXX="${TOOLCHAIN_DIR}/aarch64-buildroot-linux-gnu-g++"
export AR="${TOOLCHAIN_DIR}/aarch64-buildroot-linux-gnu-ar"
export LD="${TOOLCHAIN_DIR}/aarch64-buildroot-linux-gnu-ld"
export RANLIB="${TOOLCHAIN_DIR}/aarch64-buildroot-linux-gnu-ranlib"
export STRIP="${TOOLCHAIN_DIR}/aarch64-buildroot-linux-gnu-strip"

# Set target architecture
export ARCH="aarch64"
export CROSS_COMPILE="aarch64-buildroot-linux-gnu-"

# Set sysroot (important!)
export SYSROOT="/home/ubuntu/rk3576_AI/buildroot/output/rockchip_rk3576/staging"
export CFLAGS="--sysroot=${SYSROOT} -O2"
export LDFLAGS="--sysroot=${SYSROOT}"

echo "Toolchain setup complete"
echo "CC = $CC"

# Clone the project and build
git clone https://github.com/xiph/rnnoise.git
cd rnnoise
./autogen.sh
./configure \
--host=aarch64-buildroot-linux-gnu \
--build=x86_64-pc-linux-gnu \
--prefix=$(pwd)/tmp \
--enable-static \
--enable-shared \
CFLAGS="--sysroot=${SYSROOT} -O2" \
LDFLAGS="--sysroot=${SYSROOT}" \
CC="${CC}" \
CXX="${CXX}" \
AR="${AR}" \
LD="${LD}"
#Build
make
#Install to ./tmp
make install
(base) ubuntu@ubuntu-2204:~/rnnoise$ tree -L 2 tmp
tmp
├── include
│ └── rnnoise.h
├── lib
│ ├── librnnoise.a
│ ├── librnnoise.la
│ ├── librnnoise.so -> librnnoise.so.0.4.1
│ ├── librnnoise.so.0 -> librnnoise.so.0.4.1
│ ├── librnnoise.so.0.4.1
│ └── pkgconfig
└── share
└── doc
(base) ubuntu@ubuntu-2204:~/rnnoise$ ls examples/
rnnoise_demo rnnoise_demo.c rnnoise_demo.o

The files in the ./tmp directory are the rnnoise headers and libraries needed for integration. The rnnoise_demo under examples is a reference implementation, and there is also a .c file for reference.

adb push examples/.libs/rnnoise_demo /usr/bin/rnnoise_demo
adb push tmp/lib/librnnoise.so* /usr/lib/

Now you can do a simple test on the development board.

Test

# First check the original WAV file information
soxi speech.wav
# If the sampling rate is not 48000, first convert it to 48000
sox speech.wav -r 48000 speech_48k.wav
# Adjust the input volume to a suitable range (-3dB to -6dB)
# RNNoise requires: 48000Hz, mono, 16-bit signed integer PCM
sox speech_48k.wav -r 48000 -c 1 -e signed-integer -b 16 -t raw speech.pcm gain -n -3
# Run RNNoise noise reduction
rnnoise_demo speech.pcm rnnoise.pcm
#Convert back to WAV format
sox -r 48000 -c 1 -e signed-integer -b 16 -t raw rnnoise.pcm rnnoise.wav

Using rnnoise's Library and API for Audio Noise Reduction

Code structure

(base) ubuntu@ubuntu-2204:~/baiwen/rnnoise_reduction$ tree -L 3
.
├── CMakeLists.txt
├── deps
│ ├── include
│ │ └── rnnoise.h
│ └── lib
│ └── librnnoise.so
├── include
├── Makefile
└── src
└── main.c

5 directories, 5 files

Among them, rnnnoise.h and librnnoise.so come from the rnnoise build output, just copy them directly. CMakeLists.txt needs to update the toolchain path

# Specify the cross compiler
set(CMAKE_C_COMPILER /home/ubuntu/rk3576_AI/buildroot/output/rockchip_rk3576/host/bin/aarch64-buildroot-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER /home/ubuntu/rk3576_AI/buildroot/output/rockchip_rk3576/host/bin/aarch64-buildroot-linux-gnu-g++)

# Set sysroot
set(CMAKE_SYSROOT /home/ubuntu/rk3576_AI/buildroot/output/rockchip_rk3576/staging)
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})

Build

make clean
make
#push adb to the develop directory of the development board
make push

Test on the development board

root@rk3576-buildroot:/develop# ./rnnoise_reduction speech.wav rnnoise.wav -3
=== RNNoise audio noise reduction processing ===
1. Read WAV file: speech.wav
Sampling rate: 8000Hz, channels: 2, sample count: 40000 (5.00 seconds)
2. Apply gain: -3.0dB
3. Resample to 48kHz
After resampling: 240000 samples (5.00 seconds)
4. RNNoise noise reduction processing
Processed frames: 500, 480 samples per frame
First 5 original sample values of the first frame: -34 -28 -23 -17 -11
After processing: 240000 samples (5.00 seconds)
5. Save as WAV file: rnnoise.wav
=== Processing complete ===
root@rk3576-buildroot:/develop# aplay speech.wav
Playing WAVE 'speech.wav' : Signed 16 bit Little Endian, Rate 8000 Hz, Stereo
root@rk3576-buildroot:/develop# aplay rnnoise.wav
Playing WAVE 'rnnoise.wav' : Signed 16 bit Little Endian, Rate 48000 Hz, Mono

A noticeable noise reduction effect can be heard.