Real-Time Background Noise Cancellation of Microphone Input on Linux

I’m surprised at how little information there is about this topic, short of the majority of forums indicating it cannot be done. Having a poor quality mic, both integrated into a laptop as well as a cheap external microphone, I know my sound quality is not the best, and a fair bit of background noise can be picked up. When I discovered that audio mixing software, such as Audacity, can do a very good job of removing background noise based on a sound sample, I figured there would be a way of doing this in real-time. Although I have not found any information on doing this with Audacity (nor did I expect to), I only really found a single Ask Ubuntu forum answer with an explanation of how to accomplish this, and to my surprise (despite it not being the ‘best answer’ of that thread), it works, and quite well to boot.

Update: I have updated the script in hopes it will solve some of the problems that have been brought up. Details can be found in my update post. I have also posted it on GitHub for easier maintenance.

By utilizing SoX audio software, the microphone input is redirected through it, and output into a loopback device, which can be chosen in PulseAudio’s volume mixer application. I have composed a bash script to essentially automate the process. Before using this, SoX must be installed, and the alsa loopback device must be loaded into the kernel:

Debian/Ubuntu: sudo apt-get install sox

Arch: pacman -S sox

sudo modprobe snd_aloop


#!/bin/bash

time=5

workDir=$(mktemp -d)

noiseFile="noise.wav"

#record noise sample
record()
{
	read -p "Recording background noise. Keep quiet for $time seconds. Press Enter to start."
	sleep 0.5
	parecord -d $input $noiseFile &
	PID=$!
	sleep $time
	kill $PID
	echo "Playing back noise sample"
	paplay $noiseFile
}

cd $workDir

#detect if aloop module is loaded
#allow the user to load the module here
	if [ $(lsmod | grep -c snd_aloop) -eq 0 ]; then
		echo "ALOOP module is not loaded."
		read -p "Attempt to load the module?[y/n]" yn
	    case $yn in
	        [Yy]* ) sudo modprobe snd_aloop;;
	        [Nn]* ) exit;;
	        * ) echo "Please answer yes or no.";;
	    esac
	fi

#get list of input and output devices (with details)
	inputs=$(pactl list short sources | grep -E -v '(monitor|aloop)')
	outputs=$(pactl list short sinks | grep aloop)

#abort if devices aren't available
	if [ $(echo "$inputs" | wc -l) -lt 1 ]; then
		echo "No input devices detected. Aborting"
		exit
	fi

	if [ $(echo "$outputs" | wc -l) -lt 1 ]; then
		echo "No output devices detected. Aborting"
		exit
	fi

inputIndex=0
#allow user selection when multiple devices are available
	if [ $(echo "$inputs" | wc -l) -gt 1 ]; then
		echo "Multiple input devices detected. Select from this list:"
		echo "$inputs" | awk -F '\\s' '{print NR-1," ",$2}'
		read -p "Enter an index:" inputIndex
		exit
	fi
	inputs=$(echo "$inputs" | awk -v idx=$inputIndex '{if (NR-1 == idx) print}')

outputIndex=0
#allow user selection when multiple devices are available
	if [ $(echo "$outputs" | wc -l) -gt 1 ]; then
		echo "Multiple output devices detected. Select from this list:"
		echo "$outputs" | awk -F '\\s' '{print NR-1," ",$2}'
		read -p "Enter an index:" outputIndex
		exit
	fi
	outputs=$(echo "$outputs" | awk -v idx=$outputIndex '{if (NR-1 == idx) print}')

#load device names
	input=$(echo "$inputs" | awk -F '\\s' '{print $2}')
	output=$(echo "$outputs" | awk -F '\\s' '{print $2}')

#get input device specs
	format=$(echo "$inputs" | awk -F '\\s' '{print $4}')
	#number type
	tmp=$(echo $format | grep -o "^[fus]")
	case $tmp in
		f ) inputEncoding="floating-point";;
		s ) inputEncoding="signed-integer";;
		u ) inputEncoding="unsigned-integer";;
	esac
	#bit count
	inputBits=$(echo $format | grep -o "[0-9]*")
	#endianness
	tmp=$(echo $format | grep -o "[bl]e$")
	case $tmp in
		be ) inputEndian="-B";;
		le ) inputEndian="-L";;
	esac
	#channels
	inputChannels=$(echo "$inputs" | awk -F '\\s' '{print $5}' | grep -o "[0-9]*")
	#bitrate
	inputBitrate=$(echo "$inputs" | awk -F '\\s' '{print $6}' | grep -o "[0-9]*")

#get output device specs
	format=$(echo "$outputs" | awk -F '\\s' '{print $4}')
	#number type
	tmp=$(echo $format | grep -o "^[fus]")
	case $tmp in
		f ) outputEncoding="floating-point";;
		s ) outputEncoding="signed-integer";;
		u ) outputEncoding="unsigned-integer";;
	esac
	#bit count
	outputBits=$(echo $format | grep -o "[0-9]*")
	#endianness
	tmp=$(echo $format | grep -o "[bl]e$")
	case $tmp in
		be ) outputEndian="-B";;
		le ) outputEndian="-L";;
	esac
	#channels
	outputChannels=$(echo "$outputs" | awk -F '\\s' '{print $5}' | grep -o "[0-9]*")
	#bitrate
	outputBitrate=$(echo "$outputs" | awk -F '\\s' '{print $6}' | grep -o "[0-9]*")

#record noise sample
record
while true; do
    read -p "Do you wish to re-record the noise sample?[y/n]" yn
    case $yn in
        [Yy]* ) record;;
        [Nn]* ) break;;
        * ) echo "Please answer yes or no.";;
    esac
done

#create noise profile
sox $noiseFile -n noiseprof noise.prof

echo "Sending output to loopback device."
echo "Change recording port to  in PulseAudio to apply."
echo "Ctrl+C to terminate."

#filter audio from $input to $output
pacat -r -d $input --latency=1msec | sox -b $inputBits -e $inputEncoding -c $inputChannels -r $inputBitrate $inputEndian -t raw - -b $outputBits -e $outputEncoding -c $outputChannels -r $outputBitrate $outputEndian -t raw - noisered noise.prof 0.2 | pacat -p -d $output --latency=1msec

cd -
rm -rf "$workdir"

This script first tests to make sure the loopback devices are loaded, then records a 5 second noise sample. The sample is then played back to the user. If there should be a problem with it, such as a non-background sound leaked into it, it can be re-recorded. Once done, the input and output devices are determined, and an audio stream is directed from PulseAudio into SoX, then back into Pulse through the loopback device. From PulseAudio Volume Control, the input of recording devices can be changed to Loopback Analog Stereo Monitor, where the filtered audio stream is being directed.

35 thoughts on “Real-Time Background Noise Cancellation of Microphone Input on Linux

  1. It works well, and I’ve been looking for something like this for a while, thanks for that. I create videos and hate having to rip the audio using WinFF, edit it in audacity to remove the noise, then combine the audio with a silence version of the video in kdenlive in order to have decent audio quality.

    The only issue with this, however, is that it tends to control the volume on it’s own which creates some hell of an audio spike here and there.

    Like

    • Thanks for the feedback! While I posted this a while ago, I haven’t had much need to use it, though I’ve been thinking about investigating modifications to the approach. Mostly this would be to test the effects of using different sound samples, though it seems looking into volume manipulation would also be worth while.

      Like

      • It’s exceptionally beneficial for me as I’m a video content creator for the likes of Vidme (previously YouTube) and do podcasts and various livestreams regularly as well. When recording, I can later edit the audio as mentioned above so that I can fix certain things post production, but in the cases of podcasts and other forms of livestreaming, this becomes a real issue requiring me to make sure I save a copy of it to disc as it’s streamed, then after the livestream is finished, delete the saved livestream and go about using winff to extract the audio, import to audacity, fix the audio, blah blah then reupload.

        Funny enough, I know I’m not the only one on Linux that does this. I’ve spoken with others in the community, some of which simply state that they have to use Windows in order to do their recordings and then switch to Linux for the editing. This is due to Windows having a noise cancelling feature within the sound settings that is as simple as just checking a box.

        Like

    • Hmm for something like that I’d recommend a more robust solution. Get a pair of noise-cancelling headphones, and cross the wires to reverse the polarity. That should cause it to project “cancelled” noise outward, taken from the sound produced inside. Wear them while you sleep and even you won’t hear your own snoring!

      Like

      • I don’t understand your instructions. I own a pair of Noise Canceling Headphones. If I turn them on and don’t plug then into anything, they actively dampen ambient sounds. There’s no need to modify them; his wife just needs to wear a pair and turn them on.

        I’m not sure how well they work against snoring; I’ve not tried this. And most noise canceling headphones wouldn’t be comfortable to sleep in.

        Like

      • Unless your comment is satirical (which I am assuming is not the case), I should clarify: I was being sarcastic. Those “instructions” are just a joke. If that were not the case, I would have made a tutorial (probably a YouTube video) explaining how it works.

        Like

  2. The script errors for me on what seems to be the last line. I record my backround noise fine, and it plays back as expected, then the process says “Sending output to loopback device. Change recording port to <Loopback Analog Stereo Monitor> in PulseAudio to apply. Ctrl+C to terminate.” as expected. But a split second after it errors with “Stream error: Invalid argument” and the process terminates.

    I’m not sure if maybe I’m missing a dependency or if some arguments on the last line of the script need to be changed or removed.

    I’d really like to get this to work!

    Like

    • Thanks for the feedback. I’ll have to look into this at some point. It sounds like one of the arguments being passed (most likely) to sox isn’t valid. It’s possible the specifications for one of the applications used changed with updates. I haven’t actually used this in quite a while now, so it doesn’t entirely come as a surprise to me that there would be issues now. That being said, I’m sure it’s not much to work around said problems.

      Like

      • Gosh. I almost jump of happiness by this post. But I also have this same issue.

        Stream error: Invalid argument
        pa_stream_drain(): Bad state

        Any idea?

        thanks!

        Like

      • Thanks for the feedback. Are you running the script as-is, or doing something different? I’ll have to do some more testing (and probably some updates to this post) since I haven’t touched it in quite a while.

        Like

  3. Hi cubethethird, your script looks awesome! but I don’t understand what I have to do next.

    After the message: “Sending output to loopback device. Change recording port to <Loopback Analog Stereo Monitor> in PulseAudio to apply. Ctrl+C to terminate.” , I run this command:

    `pacmd “set-default-source alsa_input.platform-snd_aloop.0.analog-stereo”` to change the recording port, is it right? and then what I supposed to do?

    thank you!

    Like

    • That’s a good question! I’ve never actually used the command line to change the ports. I really need to do some updates to this at some point. I don’t think it will work if you set the default source to the loopback device, since that may also change it for the command that is running. I’d recommend (not sure how to do this via command line) applying the change only to the application that is actively capturing/recording the input. This will allow the pacat command to continue to read the direct input, while the other application(s) can read the filtered input from the loopback device.

      Like

      • I finally managed to do what I wanted! I wanted to do a noise cancelling (like some new headphones).This is recording the noise, inverting its waves and reproducing it, so the inverted waves plus the original waves (the external noise) makes the external noise cancelling. I did it with your script changing the last line with:
        `pacat -r -d $input –latency=1nsec | sox -v -1 -b 16 -e signed -c 1 -r 44100 -t raw – -b 16 -e signed -c 1 -r 44100 -t raw – | pacat -p -d $output –latency=1nsec` and listening the output
        I’m still having problems with the delay and the volume, so I didn’t get the desired result yet.
        Do you know if there is any possibility to process the input with 0 delay?

        Like

    • I don’t think its possible to do software audio processing with 0 latency. Any amount of processing is going to add some delay. While I like the idea of doing cancellation that way, I doubt it could be effective for a real-time application.

      Like

  4. Hey man, when i run the script always say: “sudo modprobe snd_aloop” even i run the “sudo modprobe snd_aloop” and run the script after. You have any idea what’s the problem?

    Like

  5. Thank you for the valuable resources…please can you help me,is t possible to use the above code for speech to text conversion in google speech recognizer?can you tell me how to write it in python code?while i am
    giving my voice commands to microphone its capturing background noise can you please guide how to overcome it ?i am using it with rpi jessie

    Like

  6. What’s up it’s me, I am also visiting this web site on a regular basis, this web page is genuinely good and the viewers
    are in fact sharing pleasant thoughts.

    Like

  7. Anyone coming here in 2019 should note that, in addition to the solution shared by @voyeg3r (using ‘module-echo-cancel’, there’s also a way to do realtime input processing with `sox` without the alsa loopback module, by using pulseaudio’s ‘module-null-sink’. Example:

    pactl load-module module-null-sink channels=1 rate=48000
    # pacmd set-default-source null.monitor # (optional)

    sox -b $inputBits -e $inputEncoding -c $inputChannels -r $inputBitrate $inputEndian -t pulseaudio $input – -b $outputBits -e $outputEncoding -c $outputChannels -r $outputBitrate $outputEndian -t pulseaudio null – noisered noise.prof 0.2

    Then, recording applications should record from ‘null monitor’, aka “Monitor of Null Output”.

    I first got this idea from https://askubuntu.com/q/421947/495331.

    Like

Leave a comment