Using a raspberry pi with a microphone to hear an audio alarm using FFT in python

If your smoke alarm or, in my case, water alarm goes off you want to know right away – even if you are currently half way across the world traveling in China. I run a fish tank. I take many precautions but you really can’t be too safe. I bought a set of Honeywell water sensors which I highly recommend. Sadly, this particular alarm is not IoT enabled. In fact, last I checked all the IoT alarm systems were terribly reviewed and overpriced. Hopefully that gets fixed soon. Until then, I needed to make do with what I had.

Rather than ripping the alarms open and hard wiring in a detection, I wanted to use an audio detection method so I could listen to many devices around the house. I wanted to receive a text message if any alarm went off and my raspberry pi could hear it. I saw some commercial devices for sale but what is the fun in that, honestly? I scoured github and google for some example code, but did not find much. So, here is my quick and dirty audio alarm sensor code.

DISCLAIMER: Under no circumstances should this code be used in any safety or production setting. Using an audio mechanism to listen for safety alerts is just plain dumb and could easily malfunction causing serious injury or death. This is for education purposes only.

This code works on a set of moving windows to detect confirmed alarm beeps. My alarm beeps at 3500Hz with a regular interval. I used a fast fourier transform with numpy in python to isolate the most intense sounds. I then use quadratic interpolation to determine the frequency of the most intense sound wave. If enough “blips” fill a window to detect a “beep,” and enough “beeps” fill a larger window, then the program indicates the alarm is active. If enough non-beep detected windows pass, it will clear the alarm detection.

At some point I’ll need to learn machine learning and teach it how to automatically recognize different types of alarms! Anyone have some tips?

32 thoughts on “Using a raspberry pi with a microphone to hear an audio alarm using FFT in python”

  1. Thanks for sharing this, the code looks like exactly what I was looking for…except I want to modify it to detect when our doorbell goes off!

    What did you use as a microphone and how did you connect it up to the Pi?

    1. I use a blue yeti USB microphone connected to my raspberry pi. It works great! I have a cheaper USB input too but I have not tested it yet… it likely will require lowering the sample rate.

      1. I have found issues with the current approach of using a sound detection:
        https://gist.github.com/benjaminchodroff/7d33a34e389c31a11aaa1ad16ba4c159

        It works pretty well – but it also detects my neighbors buzzing in too… Sadly, the wire which indicates “which button the user pressed outside” only goes to the base station which is in a common area of our building. That wire does not go to my unit where my raspberry pi is located. Instead, the base station detects the signal and broadcasts a (very loud) audio buzz to my unit. For some crazy reason, I am not seeing this signal very loud and I have not had a chance to explore why. I am debating whether to fix this issue (likely I am monitoring the wrong audio?) by using some amplitude detection, or whether to move my raspberry pi system to the common area (risky, but simple) so I can simply use the high/low signal coming from the button rather than trying to audio process it. My project has been on hold for a few months due to this limitation – would love your ideas.

        1. This is so very cool. Thank you for sharing these scripts! They’re wonderful. For anyone else who like me couldn’t figure out how to convert a sample/frame number to a value in seconds, this is the formula:

          The simple way of converting frame number to time is:
          ( FrameNumber ) / ( SampleRate ) which will give you a value in seconds.

          Some code that helps with this conversion:
          print(“The needle starts at ms: %d” % ((at/needle_rate)*1000,))
          print(“The needle starts at sec: %.3f” % ((at/needle_rate),))

          I got the formula from https://sound.stackexchange.com/a/46354/28016
          Also, for anyone wanting to convert Python 2 code to Python 3, use the 2to3 program:
          python3 -m pip install 2to3
          python3 -m lib2to3 wavgrep.py -w

          P.S. I’m trying to see if I can use this on my Raspberry Pi to detect when my UPS kicks in (starts beeping) and when it’s about to die (starts beeping really fast) and shutdown the Raspberry Pi some time before it dies completely. I know, there are better ways to achieve this but I have a dumb UPS with no serial or USB port and this project sounds like a lot of fun. No pun intended 🙂

  2. I had this code running on a Mac, no problem. I am trying to run it on an RPi now, and not having much success. arecord and aplay run fine, like: “arecord example.wav -D sysdefault:CARD=1”

    When I try to run your code, I get:

    {bunch of ALSA warnings not shown}

    Alarm detector working. Press CTRL-C to quit.
    freq= 42.4418160133
    Traceback (most recent call last):
    File “chodroff.py”, line 49, in
    _stream.get_read_available()), dtype=short)[-NUM_SAMPLES:]
    File “/usr/local/lib/python2.7/dist-packages/pyaudio.py”, line 608, in read
    return pa.read_stream(self._stream, num_frames, exception_on_overflow)
    IOError: [Errno -9981] Input overflowed

    Maybe those ALSA warnings are significant, but overflow sounds like a memory issue. How much memory is installed on the RPi you’re using, please?

    1. What external microphone are you using and what is the sample rate it supports? I believe your memory issue may actually be that you are exceeding the sample rate of your external microphone sound card. I have a very nice usb microphone (blue yeti – supports 48khz) but many cheap sound cards may require a smaller sample rate.

      1. Oh, my mic is a piece of junk! I had no idea it could be the mic — I suppose PNP mics must have a built -in sound card. I’ll try turning the sample rate down for a test. Thanks so much!

        I do a radio show for print-disabled people, and I’ve been looking for an excuse to get a better mic, so thanks twice!

        Ron

  3. Dear Benjamin,

    thank you for your code…

    Just search some code to detect 1750hz to open a hamradio Relais..

    I modify your code to this:

    #!/usr/bin/env python
    import pyaudio
    from numpy import zeros,linspace,short,fromstring,hstack,transpose,log
    from scipy import fft
    from time import sleep

    #Volume Sensitivity, 0.05: Extremely Sensitive, may give false alarms
    # 0.1: Probably Ideal volume
    # 1: Poorly sensitive, will only go off for relatively loud
    SENSITIVITY= 0.1
    # Alarm frequencies (Hz) to detect (Use audacity to record a wave and then do Analyze->Plot Spectrum)
    TONE = 1750
    #Bandwidth for detection (i.e., detect frequencies within this margin of error of the TONE)
    BANDWIDTH = 10
    # Show the most intense frequency detected (useful for configuration)
    beeplength=6

    frequencyoutput=False

    #Set up audio sampler –
    NUM_SAMPLES = 2048
    SAMPLING_RATE = 48000
    pa = pyaudio.PyAudio()
    _stream = pa.open(format=pyaudio.paInt16,
    channels=1, rate=SAMPLING_RATE,
    input=True,
    frames_per_buffer=NUM_SAMPLES)

    print(“1750hz detector working. Press CTRL-C to quit.”)

    blipcount=0

    while True:
    while _stream.get_read_available()< NUM_SAMPLES: sleep(0.01)
    audio_data = fromstring(_stream.read(
    _stream.get_read_available()), dtype=short)[-NUM_SAMPLES:]
    # Each data point is a signed 16 bit number, so we can normalize by dividing 32*1024
    normalized_data = audio_data / 32768.0
    intensity = abs(fft(normalized_data))[:NUM_SAMPLES/2]
    frequencies = linspace(0.0, float(SAMPLING_RATE)/2, num=NUM_SAMPLES/2)
    if frequencyoutput:
    which = intensity[1:].argmax()+1
    # use quadratic interpolation around the max
    if which != len(intensity)-1:
    y0,y1,y2 = log(intensity[which-1:which+2:])
    x1 = (y2 – y0) * .5 / (2 * y1 – y2 – y0)
    # find the frequency and output it
    thefreq = (which+x1)*SAMPLING_RATE/NUM_SAMPLES
    else:
    thefreq = which*SAMPLING_RATE/NUM_SAMPLES
    print "freq=",thefreq
    if max(intensity[(frequencies TONE-BANDWIDTH )]) > max(intensity[(frequencies TONE-2000)]) + SENSITIVITY:
    if blipcount <= beeplength:
    blipcount+=1
    if blipcount == beeplength:
    print "1750hz"
    else:
    blipcount=0

    sleep(0.01)

    It works great, but maybe there is something else to improve?

    Best regards Tommi

    1. This code would likely not work very well to detect an ambulance because most ambulances have a sweeping tone, where this code is trying to find a specific frequency. You could adapt the code to fit your purpose though.

    1. Sure, go for it – but unfortunately this particular algorithm will be very poor at accurately detecting vehicle sounds — it is simply listening for a particular frequency which is the loudest thing it can hear. Each vehicle makes many frequencies and since you are likely in a noisy outside environment it will be difficult to avoid false positives or especially recognize a particular car. Good luck 😉

  4. Good day,

    I am helping my wife to find a way to detect chainsaw for her project, have no clue about programming my self, can this code be used for the purpose I have mentioned? if so, can you guide us a bit about it

    sorry if I may sound like im asking to much :), im just too desperate :'(

    1. Sadly this is still a very difficult problem for computers to “recognize a chainsaw sound.” My code can help only a little – you might want to simply detect “very loud sounds” and then report them. For example, if you are trying to detect a chainsaw in a jungle, any sound louder than 80 decibels is likely to be non-natural and can be reported. Again, it’s not perfect – the human mind is very good at this problem! It would take a neural network perhaps to improve upon it – and there is no easy code for this!

  5. Hi sir,
    I have encounter this error to my Rpi.
    Alarm detector working. Press CTRL-C to quit.
    Traceback (most recent call last):
    File “/home/pi/Desktop/Practice/rec.py”, line 52, in
    intensity = abs(fft(normalized_data))[:NUM_SAMPLES/2]
    TypeError: slice indices must be integers or None or have an __index__ method

  6. Benjamin,

    Thanks for sharing! Do you mind sharing a bit more about your raspberry pi installation:
    1. Which raspberry pi are you using?
    2. Are you running raspbian (which version)?
    3. How did you install pyaudio, numpy, scipy?
    4. How did you get pyaudio to recognize your microphone?

    Thanks again for sharing your project!!

  7. HI, It is a fantastic piece of work, Thanks for sharing it.
    I faced an error:

    Alarm detector working Press CTRL-C to quit.
    Traceback (most recent call last):
    File “Fire_alarm.py”, line 52, in
    intensity = abs(fft(normalized_data))[:NUM_SAMPLES/2]
    TypeError: slice indices must be integers or None or have an __index__ method

    I resolved it by changing this line

    intensity = abs(fft(normalized_data))[:NUM_SAMPLES/2]
    to
    intensity = abs(fft(normalized_data))[:NUM_SAMPLES//2]

    Cheers
    Bharath

    1. What did you change this line to in order to fix the error?
      Both lines you posted above are identical.
      Python3 doesn’t like that “intensity=“ line of code. It works in Python2 only.
      The code and error:

      intensity = abs(fft(normalized_data))[:NUM_SAMPLES/2]
      TypeError: slice indices must be integers or None or have an __index__ method

      1. hey I know its July but, they added another slash:
        [:NUM_SAMPLES/2]
        [:NUM_SAMPLES//2]
        ^

  8. Was wondering if you could help me use this code to
    recognize guitar chords. For instance, if a chord is played right I would love for it
    to show a box that sais correct on 5 inch hdmi screen and if wrong box pops up said try again
    I know how to break the chords down in audacity to use with the above code
    but the first half of this not sure how to approach it such as anything dealing with the screen
    making the images no problem just the piece of code and incorporating into what you have
    within your current code. If this is to much to ask im sorry for bothering you thanks..
    Im using pi 3 with 2 speaker mic hat with the push button in the center. And 5 inch hdmi.
    All is installed and working corectly.

  9. Python3 doesn’t like that “intensity=“ line of code. It works in Python2 only.
    The code and error:

    intensity = abs(fft(normalized_data))[:NUM_SAMPLES/2]
    TypeError: slice indices must be integers or None or have an __index__ method

    What can that be changed to in Python3 to make it work?
    Has anybody run this code in Python3?

    1. Yes. You need to convert it to Python 3 code. You can use the 2to3 program for this.

      python3 -m pip install 2to3

      python3 -m lib2to3 alarmdetect.py -w

      Also change “from scipy import fft” to “from numpy.fft import fft”

  10. If you look at the two lines closely, you notice the top line has (1) ‘/’ before the 2 (at the end on the line); whereas the bottom line has ‘//’ before the 2.

  11. when i used these code it shows big warning like this ==>
    expression ‘alsa_snd_pcm_hw_params_set_period_size_near( pcm, hwparams, &alsaperiodframes, &dir )’ failed in ‘src/hostapi/alsa/pa_linux_alsa.c’, line: 924
    alsa lib pcm_dmix.c:1108:(snd_pcm_dmix_open) unable to open slave alsa lib confmisc.c:1281:(snd_func_refer) unable to find definition ‘defaults.namehint.basic’ alsa lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: no such file or directory alsa lib conf.c:5047:(snd_config_expand) evaluate error: no such file or directory alsa lib pcm.c:2565:(snd_pcm_open_noupdate) unknown pcm front alsa lib pcm.c:2565:(snd_pcm_open_noupdate) unknown pcm cards.pcm.rear alsa lib pcm.c:2565:(snd_pcm_open_noupdate) unknown pcm cards.pcm.center_lfe alsa lib pcm.c:2565:(snd_pcm_open_noupdate) unknown pcm cards.pcm.side alsa lib confmisc.c:1281:(snd_func_refer) unable to find definition ‘defaults.namehint.basic’ alsa lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: no such file or directory alsa lib conf.c:5047:(snd_config_expand) evaluate error: no such file or directory alsa lib pcm.c:2565:(snd_pcm_open_noupdate) unknown pcm iec958 alsa lib confmisc.c:1281:(snd_func_refer) unable to find definition ‘defaults.namehint.basic’ alsa lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: no such file or directory alsa lib conf.c:5047:(snd_config_expand) evaluate error: no such file or directory alsa lib pcm.c:2565:(snd_pcm_open_noupdate) unknown pcm spdif alsa lib confmisc.c:1281:(snd_func_refer) unable to find definition ‘defaults.namehint.basic’ alsa lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: no such file or directory alsa lib conf.c:5047:(snd_config_expand) evaluate error: no such file or directory alsa lib pcm.c:2565:(snd_pcm_open_noupdate) unknown pcm spdif alsa lib pcm.c:2565:(snd_pcm_open_noupdate) unknown pcm cards.pcm.hdmi alsa lib pcm.c:2565:(snd_pcm_open_noupdate) unknown pcm cards.pcm.hdmi alsa lib pcm.c:2565:(snd_pcm_open_noupdate) unknown pcm cards.pcm.modem alsa lib pcm.c:2565:(snd_pcm_open_noupdate) unknown pcm cards.pcm.modem alsa lib pcm.c:2565:(snd_pcm_open_noupdate) unknown pcm cards.pcm.phoneline alsa lib pcm.c:2565:(snd_pcm_open_noupdate) unknown pcm cards.pcm.phoneline alsa lib confmisc.c:1281:(snd_func_refer) unable to find definition ‘defaults.bluealsa.device’ alsa lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: no such file or directory alsa lib conf.c:5036:(snd_config_expand) args evaluate error: no such file or directory alsa lib pcm.c:2565:(snd_pcm_open_noupdate) unknown pcm bluealsa alsa lib confmisc.c:1281:(snd_func_refer) unable to find definition ‘defaults.bluealsa.device’ alsa lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: no such file or directory alsa lib conf.c:5036:(snd_config_expand) args evaluate error: no such file or directory alsa lib pcm.c:2565:(snd_pcm_open_noupdate) unknown pcm bluealsa alsa lib pcm_dmix.c:1108:(snd_pcm_dmix_open) unable to open slave cannot connect to server socket err = no such file or directory cannot connect to server request channel jack server is not running or cannot be started jackshmreadwriteptr::~jackshmreadwriteptr – init not done for -1, skipping unlock jackshmreadwriteptr::~jackshmreadwriteptr – init not done for -1, skipping unlock

    and that it showed error in these 48 : audio_data = fromstring(_stream.read(
    _stream.get_read_available()), dtype=short)[-NUM_SAMPLES:]
    that the oserror: [errno -9881] input overflowed

    please help me fast

    1. 46 ms = 1/44100 * 2048

      Each sample corresponds to 1/44100 seconds (the SAMPLE_RATE) and we are taking 2048 samples (NUM_SAMPLES). This results in an approximately 46ms period “blip” which we then check to see if it matches the intensity and frequency we are interested in.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.