Last active
March 2, 2022 23:32
-
-
Save lightrush/cf1a1c4c7e9bee1045c419d8b8e0fe29 to your computer and use it in GitHub Desktop.
Mic echo cancellation and noise suppression with PulseAudio and RNNoise
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
# Usage: | |
# 1. Select the input (mic) and output (sound card) you want to eliminate echo | |
# and denoise in your sound settings as default input and output devices. | |
# 2. Run `denoise`. | |
# | |
# This process can be repeated without restarting PulseAudio. Just repeat the steps. | |
# Location of RNNoise LADSPA lib on the system | |
# Get it from https://github.com/werman/noise-suppression-for-voice/releases | |
# and place librnnoise_ladspa.so at the path below or adjust it to match the lib location. | |
LIBRNNOISE="${HOME}/.local/opt/rnnoise/librnnoise_ladspa.so" | |
if [ ! -e "${LIBRNNOISE}" ]; then | |
echo "Couldn't find ${LIBRNNOISE}. Have you installed it?" | |
exit 1 | |
fi | |
# By default assume generic mono mic. | |
AEC_ARGS="'noise_suppression=false voice_detection=false extended_filter=true'" | |
MIC_CHANNELS="1" | |
LADSPA_PLUGIN_LABEL="noise_suppressor_mono" | |
# pacmd does not currently support retreiving default sink directly so we need to grep for it. | |
DEFAULT_SINK=$(pacmd info | grep -i "default sink" | head -n1 | cut -f4 -d" ") | |
DEFAULT_SOURCE=$(pacmd info | grep -i "default source" | head -n1 | cut -f4 -d" ") | |
# Special mic config for some interesting mic. This block can be repeated for other special mics. | |
# Run `pactl list sources short` and pick your special mic's name from the second column. It usually | |
# starts with "alsa_input.*" then assign the value to SPECIAL_MIC and do your custom config inside | |
# the `if` block. You can place multiple such blocks if you have multiple special mic configs. E.g. | |
# you're plugging multiple mics for different purposes like a web cam mic and a headset mic which | |
# require different configs. | |
# The following is configuring the Framework Laptop's stereo mic array. | |
SPECIAL_MIC="alsa_input.pci-0000_00_1f.3.analog-stereo" | |
if [ "${DEFAULT_SOURCE}" = "${SPECIAL_MIC}" ]; then | |
# Turn on beamforming and configure mix separation to be 7cm. | |
AEC_ARGS="'noise_suppression=false voice_detection=false extended_filter=true beamforming=true mic_geometry=-0.035,0,0,0.035,0,0'" | |
# We need 2 channels. | |
MIC_CHANNELS="2" | |
# Use the stereo suppressor plugin. | |
LADSPA_PLUGIN_LABEL="noise_suppressor_stereo" | |
fi | |
# Logitech 922 Pro | |
SPECIAL_MIC="alsa_input.usb-046d_C922_Pro_Stream_Webcam_192F2ADF-02.analog-stereo" | |
if [ "${DEFAULT_SOURCE}" = "${SPECIAL_MIC}" ]; then | |
# Can't get beamforming to work correctly yet so don't enable it. | |
# The right channel is significantly queter than the left at the same level adjustment so it | |
# could be defective and thus affecting beamforming on this particular unit. YMMV | |
# Output is very low without AGC. | |
AEC_ARGS="'experimental_agc=true agc_start_volume=200 noise_suppression=false voice_detection=false extended_filter=true'" | |
MIC_CHANNELS="2" | |
LADSPA_PLUGIN_LABEL="noise_suppressor_stereo" | |
fi | |
# Unload all modules we loaded in reverse order. | |
# This provides idempotency and therefore we can be re-run on the fly. | |
# Note that if any of the modules have been loaded multiples times, we won't unload the | |
# right module. This can be improved. | |
INDEX="$(pactl list modules short | grep module-remap-source | grep 'source_name=denoised' | cut -f1 | tail -n1)" | |
if [ -n "${INDEX}" ]; then | |
echo Unloading... "${INDEX}" | |
pactl unload-module "${INDEX}" | |
fi | |
INDEX="$(pactl list modules short | grep module-loopback | grep "sink=mic_raw_in" | cut -f1 | tail -n1)" | |
if [ -n "${INDEX}" ]; then | |
echo Unloading... "${INDEX}" | |
pactl unload-module "${INDEX}" | |
fi | |
INDEX="$(pactl list modules short | grep module-ladspa-sink | grep "sink_name=mic_raw_in" | cut -f1 | tail -n1)" | |
if [ -n "${INDEX}" ]; then | |
echo Unloading... "${INDEX}" | |
pactl unload-module "${INDEX}" | |
fi | |
INDEX="$(pactl list modules short | grep module-null-sink | grep 'sink_name=mic_denoised_out' | cut -f1 | tail -n1)" | |
if [ -n "${INDEX}" ]; then | |
echo Unloading... "${INDEX}" | |
pactl unload-module "${INDEX}" | |
fi | |
INDEX="$(pactl list modules short | grep module-echo-cancel | grep source_name=echo_cancel_source | cut -f1 | tail -n1)" | |
if [ -n "${INDEX}" ]; then | |
echo Unloading... "${INDEX}" | |
pactl unload-module "${INDEX}" | |
fi | |
# Load echo-cancel | |
if ! pactl load-module module-echo-cancel use_volume_sharing=1 use_master_format=1 aec_method=webrtc aec_args="${AEC_ARGS}" source_master="${DEFAULT_SOURCE}" sink_master="${DEFAULT_SINK}" source_name=echo_cancel_source sink_name=echo_cancel_sink; then | |
echo "Couldn't load module-echo-cancel." | |
exit 1 | |
fi | |
sleep 1 | |
# This is needed to get the echo module to actually work. | |
if ! pactl set-default-sink echo_cancel_sink; then | |
echo "Couldn't set-default-sink echo_cancel_sink." | |
exit 1 | |
fi | |
if ! pactl set-default-source echo_cancel_source; then | |
echo "Couldn't set-default-source echo_cancel_source." | |
exit 1 | |
fi | |
sleep 1 | |
# Load RNNoise | |
if ! pactl load-module module-null-sink sink_name=mic_denoised_out; then | |
echo "Couldn't load module-null-sink." | |
exit 1 | |
fi | |
if ! pactl load-module module-ladspa-sink sink_name=mic_raw_in sink_master=mic_denoised_out label="${LADSPA_PLUGIN_LABEL}" plugin="${LIBRNNOISE}" control=50; then | |
echo "Couldn't load module-ladspa-sink. Is librnnoise_ladspa.so in the right place?" | |
exit 1 | |
fi | |
if ! pactl load-module module-loopback source="echo_cancel_source" sink=mic_raw_in channels="${MIC_CHANNELS}" source_dont_move=true sink_dont_move=true latency_msec=30; then | |
echo "Couldn't load module-loopback." | |
exit 1 | |
fi | |
if ! pactl load-module module-remap-source source_name=denoised master=mic_denoised_out.monitor channels="${MIC_CHANNELS}"; then | |
echo "Couldn't load module-remap-source." | |
exit 1 | |
fi | |
sleep 1 | |
if ! pactl set-default-source denoised; then | |
echo "Couldn't set-default-source denoised." | |
exit 1 | |
fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment