Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

Measuring the sound level (dB(A)) with PERL

by John-Robie (Novice)
on Nov 09, 2016 at 14:21 UTC ( [id://1175599]=perlquestion: print w/replies, xml ) Need Help??

John-Robie has asked for the wisdom of the Perl Monks concerning the following question:

Hi, I’m trying to use a normal microphone connected to a Raspberry Pi in order the measure the sound level in dB(A). The idea is, to have simple PERL program measuring the loudness (e.g. Airplanes, Traffic…). I’m aware, that simple microphones do not have the quality to do this precisely, but for my goal the figures will be sufficient… so please no philosophic discussion about mics…

The code will just get some very small variation of decibel numbers, even if I scream into the microphone – so I think I am measuring something else than sound pressure or the conversion formula is wrong.

For this test, I bought a simple microphone from a local retailer and connected it to the USB Port of the Pi. The operating system is the “2016-09-23-raspbian-jessie” image, all PERL modules have been installed with cpanminus, and do not show any errors

The microphone shows up in the dmesg as follows:

[11249.256922] usb 1-1.4: new full-speed USB device number 10 using dw +c_otg [11249.365032] usb 1-1.4: New USB device found, idVendor=0d8c, idProdu +ct=0139 [11249.365094] usb 1-1.4: New USB device strings: Mfr=1, Product=2, Se +rialNumber=0 [11249.365113] usb 1-1.4: Product: USB PnP Sound Device [11249.365130] usb 1-1.4: Manufacturer: C-Media Electronics Inc. [11249.400778] input: C-Media Electronics Inc. USB PnP Sound Dev +ice as /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.3/00 +03:0D8C:0139.0007/input/input6 [11249.457789] hid-generic 0003:0D8C:0139.0007: input,hidraw3: USB HID + v1.00 Device [C-Media Electronics Inc. USB PnP Sound Device] o +n usb-3f980000.usb-1.4/input3

As the microphone shows up at other Linux OS as well (e.g. CentOS), I do not expect any driver issues. The mic works for normal recording as well (

The module for the sound driver as been installed with

Linux OS CLI Command “modprobe snd_pcm_oss”, this installs a device /dev/dsp1 on the filesystem

This is the code I tried, there are some examples on the internet with this…

#!/usr/bin/perl use strict; use warnings; use POSIX qw(log10); # input device my $input="/dev/dsp1"; # open filehandler open(my $fh, '<', $input) or die("ERROR open $input ($!)\n"); binmode($fh); # 8 bit # 8000 hertz my $cnt = 0; my $value = 0; my $lastts = 0; while(1) { if ($cnt == 8000) { # the amplitude >= 0 <= 1 my $amplitude=$value / $cnt / 255; # calculate dba = 20 * log10(amplitude / 2 * 10**-5) my $dba = 20 * log10($amplitude / 0.00002); print "Calculated dB(A): $dba\n"; $cnt = 0; $value = 0; } my $buffer; # read one byte read($fh, $buffer, 1); if(defined($buffer)) { # get an unsigned char my $v = unpack("C", $buffer); # add the read byte to the values $value+=$v; # print "Byte read: $v\n"; $cnt++; } } close($fh);

The results shown yet show very similar values, even if I shout in the mic, The values show up every second as follows:

root@node:/srv/perl# ./dba.pl Calculated dB(A): 87.9548395267721 Calculated dB(A): 87.9599922725626 Calculated dB(A): 87.9595239674196 Calculated dB(A): 87.9586213444603 Calculated dB(A): 87.9595239674196

As byte values I can see 127 and 28 way to often – there should be more variance

I am stuck- can anyone help?

Replies are listed 'Best First'.
Re: Measuring the sound level (dB(A)) with PERL
by Eily (Monsignor) on Nov 09, 2016 at 15:28 UTC

    What you are computing is not the amplitude, but the wrong mean value. The "wrong" in mean value is because a sound signal is supposed to be variations around 0, which is not possible with your signed byte values. You'll have to find out how your values are actually coded, either with an offset (eg: substract 128 to every value), or as signed values. It's even possible that the values are not linear representations of the data, but rather logarithmic, as in A-law encoding. AFAIK, you can indeed expect one sample per byte. To see if you're decoding your input correctly, you should try to play a sinusoid beside your microphone (I'm sure you can find somewhere on the internet to have the reference A 440), and see if you manage to plot something that looks like a sinusoid centered on 0.

    Now, as I've already told you, the mean value is supposed to be 0 and is therefore useless. The power of a signal ("sound level" if you will) is only a function of its amplitude when the signal is periodic, preferably a sinusoid (a continuous beep). Otherwise you want to compute:

    my $sum = 0; my $length = @input; # number of samples in the array @input for my $value (@input) # assuming @input are samples that have already + been converted to the correct unit { my $sum += $value*value; # sum of the squares } my $dba = 10 * log10($sum/$length);

    Note that the factor before the log10 is now 10 and not 20, because the input is squared. Besides, unless you have some way of calibrating your microphone to be able to tell exactly the power of its input, you'll only be able to use the values relative to each other. Like saying "this sound is 3dB higher than that one" but not "that sound is at 50dB".

Re: Measuring the sound level (dB(A)) with PERL
by pryrt (Abbot) on Nov 09, 2016 at 15:28 UTC

    Unfortunately, my guess is that you've hit a hardware limitation: microphones have a range of volumes they can measure, beyond which they clamp.

    The sound waves induce an electrical current inside the microphone; this electrical current (or the voltage that the said current creates resistor) is converted (using an ADC: analog-to-digital-converter) into a sequence of 1s and 0s: specifically, for your 8bit system, eight 1s and 0s, which can encode a number from 0 to 255 (or -128 to +127, depending on the ADC; though usually it's 0..255 for hardware plugged into computers). So, every instant, you are reading back an integer from 0 to 255. If the sound is too strong for the given microphone at a particular instant (if the voltage or current goes beyond the range that the ADC can convert), the ADC will return a 0 or a 255, depending.

    Sound is actually made up as waves; if you were to measure a pure tone of one frequency, and plot it vs time, you would see a sine-wave of discrete integers, centered near 127.5. The center is the "zero sound pressure" point... so silence should give individual codes at 127-128. When you have sound that covers about 90% of the range of the mic+ADC, you will get codes from 12 to 242, with a peak amplitude of 115 codes -- it goes above and below the center by 115 codes (this would be a peak-to-peak amplitude of 230, or an rms amplitude of about 81 codes). If your sound just barely hit the extreme edges of your mic+ADC, you would get codes of 0 to 255, with an amplitude of about 127-128. If your sound was too loud, you would get codes of 0 to 255, with flat sections at code 0 and 255. See http://i.imgur.com/lGREL42.png for examples.

    Aside from the clamping, your algorithm is a bit funny: you are finding the average value (the sum of the individual codes, divided by count of codes), and normalizing that to fraction of the fullscale range (ie, divided by 255), but calling that the amplitude. For any sinusoid or sum of sinusoids (ie, sound), the average will be about 127.5 as long as you measure (close to) a full cycle.

    Really, what you need to do is eliminate the DC offset (subtract the average), and then find the RMS deviation from that average. As a shortcut, because you know the center will be near 127.5, just assume that as the center. Then you can some the square of the measured v minus the theoretical center value, and every 8000 samples calculate the RMS amplitude.

    ... my $sse = 0; # sum of squared error ("error" = distance from the av +erage) while(1) { if ($cnt == 8000) { # the amplitude >= 0 <= 1 ### my $amplitude=$value / $cnt / 255; my $mse = $sse / $cnt; # MSE = mean squared error = avera +ge of the sum-of-square-distances my $rms = sqrt($mse); # RMS = root mean squared error = +a kind of "average power" measurement for the whole wave my $scaled_rms = $rms / 255; # convert from codes to fracti +on of fullscale # calculated dbFS,rms = 20 * log10($rms) my $dbFSrms = 20 * log10($scaled_rms); # this is the numb +er of dB_RMS from the fullscale of your mic+ADC my $dba = $dbFSrms - $DBA_MIC_FS; # you will need to + find out your mic+ADC's fullscale capability, in dBa or dBv or dBmW print "Calculated dB(FS)_rms and dB(A)_rms: $dbFSrms dBFS, $db +a dBa\n"; # reset the count and SSE for the next 8k samples $cnt = 0; $sse = 0; } my $buffer; # read one byte read($fh, $buffer, 1); if(defined($buffer)) { # get an unsigned char my $v = unpack("C", $buffer); # print "Byte read: $v\n"; # sum the squared distance from the theoretical mean $sse += ($v-127.5)**2; $cnt++; } }

    Update: strike the first paragraph guess; I wrote that before fully reading the original code, but forgot to delete it. There still might be clamping going on, but it wasn't the primary problem.

      Thank you very much! You really made my day!

      This was the missing link to my question. I just integrated your code and am able to see quite precise values in the range from 60dB (A) to 100 dB(A). Therefore, a corrective value for $DBA_MIC_FS was integrated in the upper part of the code.

      my $DBA_MIC_FS = -110;

      Now, the values are similar compared to a standard sound meter Class 2. There are some problems with the USB mic, though: There are no smaller values as 60 db(A)… this was tested on Windows with the same mic… no lower values than 60 dB(A) :-(

      This leads to the idea, that USB mics are not built to measure sound pressure levels precisely, but more to record human voice. Additionally considering the good advice from stevieb, USB mics seem to be a dead end for noise measurements (ot this 10 EUR mic is just not good enough for this purpose)

      So, there will be a test with another USB mic and if there are similar problems, I might switch to analog mics with ADC technology. But this is out of the scope of this forum, I suppose...

        There are no smaller values as 60 db(A)� this was tested on Windows with the same mic� no lower values than 60 dB(A) :-(

        Does the mic in question have a voice activation feature? (If so, can you turn it off!)


        With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority". The enemy of (IT) success is complexity.
        In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Measuring the sound level (dB(A)) with PERL
by stevieb (Canon) on Nov 09, 2016 at 23:27 UTC

    Ok. I know you didn't want feedback on the mic, but I waited all day patiently until answers were provided, and being someone who records vocals and mixes tracks, I'm going to speak.

    I understand what you're trying to do, but as you may know, you'd be better off with a dB sound sensor module along with an ADC (Analog to Digital Converter), you can get this working very reliably with pretty accurate results on a Raspberry Pi. If you use an Arduino board, it has no problem with analog signals, so you don't need the ADC (but then, you wouldn't be able to use Perl...).

    Because it would connect to the GPIO (SPI) and not via USB, you'd need a Perl library that can manage that. A search through the CPAN should turn up a few libraries to at least get started.

    This info isn't here to chastise. It's here in case anyone in the future that is using Perl wants to do the same thing you want to do.

    disclaimer: I've never done this with my Pi (I know others who have though), but I have several ADCs, so I think I'm going to test this out to satisfy my own curiosity.

      If we talk about a comparison between analog mics and USB mics this if ok for me - I just didn’t want to go into a "You must use class A microphones" debate, because they are quite expensive (> 1500 EUR).

      With the hint "use analog mics" you made a very good point! I will have some tests on the given USB mic now and will get back with the results…

        I'm glad my post wasn't taken wrong. If you ever want to get deeper than what you're doing, feel free to send me a /msg stevieb and I'd be glad to play around.

Re: Measuring the sound level (dB(A)) with PERL
by Monk::Thomas (Friar) on Nov 09, 2016 at 15:21 UTC
    I am stuck- can anyone help?

    I find this construct quite suspicious:

    while(1) { if ($cnt == 8000) { } read($fh, $buffer, 1); if(defined($buffer)) { $cnt++; } }
    • After you've read successfully from buffer eight thousand times $stuff happens?
    • Does the 'if(defined($buffer))' really work as expected?

      actually, that part is correct. John-Robie is trying to calculate the amplitude every 8000 samples. Assuming it's an 8kHz sample rate on the mic, that would be related to the power for the last second of sound, which is quite reasonable thing to do.

        If I point the open() to a file containing a single byte (I don't have a /dev/dsp1), then the loop will happily run 80,000 times in less than 0.01 seconds. That's why I'm suspicious if that loop is really doing what it's supposed to do.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1175599]
Approved by marto
Front-paged by kcott
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others romping around the Monastery: (5)
As of 2024-04-24 06:01 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found