24

I'm recording mic input using the XNA library (I don't think this is really technology specific, but it never hurts). Every time I get a sample I would like to calculate the decibels. I have done many searches on the internet and not found a rock solid example...

Here is my attempt at calculating decibels from a sample:

        double peak = 0;

        for (var i = 0; i < _buffer.Length; i = i + 2)
        {
            var sample = BitConverter.ToInt16(_buffer, i);
            if (sample > peak)
                peak = sample;
            else if (sample < -peak)
                peak = -sample;
        }

        var decibel = (20 * Math.Log10(peak/32768));

If I output the decibel value to the screen I can see the values get higher as I get louder and lower as I speak softer. However, it always hovers around -40 when I'm absolutely quiet...I would assume it would be -90. I must have a calculation wrong in the block above?? from what I have read on some sites -40 is equivalent to "soft talking"...however, it's totally quiet.

Also, If I mute my mic it goes straight to -90.

Am I doing it wrong?

Ryan Eastabrook
  • 4,085
  • 5
  • 30
  • 35

3 Answers3

39

When measuring the level of a sound signal, you should calculate the dB from the RMS value. In your sample you are looking at the absolute peak level. A single (peak) sample value determines your dB value, even when all other samples are exactly 0.

try this:

double sum = 0;
for (var i = 0; i < _buffer.length; i = i + 2)
{
    double sample = BitConverter.ToInt16(_buffer, i) / 32768.0;
    sum += (sample * sample);
}
double rms = Math.Sqrt(sum / (_buffer.length / 2));
var decibel = 20 * Math.Log10(rms);

For 'instantaneous' dB levels you would normally calculate the RMS over a segment of 20-50 ms. Note that the calculated dB value is relative to full-scale. For sound the dB value should be related to 20 uPa, and you will need to calibrate your signal to find the proper conversion from digital values to pressure values.

Han
  • 2,017
  • 17
  • 23
  • And by calibrate you mean that the each client would have to find their zero...because each device and environment will be different? For instance mine, seems to be -40 while everything is silent....would I calibrate that to zero? – Ryan Eastabrook Nov 11 '10 at 15:58
  • Normally you would use a microphone calibrator for that. The calibrator delivers a signal with a very precise known level, say 98 dB. You then measure/record this signal and derive a scale factor (to be multiplied with each sample value) such that the decibel value you calculate is 98 dB. – Han Nov 11 '10 at 17:55
  • 3
    The 98dB of the calibrator is relative to 20uPa, so the actual rms pressure level would be 20*10E-6 * 10^(98/20) = 1.59 Pascal. Sometimes you can find the microphone sensitivity in mV/Pa. Then you would only need to know the relation between the voltage on the ADC input and the digital value the ADC delivers. This would allow you to us a known voltage source (or a voltmeter) to calibrate the circuit behind the microphone, and use the microphone sensitivity to get the calibration scale factor. – Han Nov 11 '10 at 18:09
  • Although I don't fully understand all of the algorithms you're describing, I'm convinced you know much more about this than me. ;) – Ryan Eastabrook Nov 12 '10 at 01:07
  • 1
    Shouldn't it be: `Math.Sqrt(sum / (_buffer.length/2));` – Grimmace Oct 01 '12 at 19:54
5

I appreciate Han's post, and wrote a routine that can calculate decibels on 8 and 16 bit audio formats, with multiple channels using his example.

public double MeasureDecibels(byte[] samples, int length, int bitsPerSample,
        int numChannels, params int[] channelsToMeasure)
    {
        if (samples == null || length == 0 || samples.Length == 0)
        {
            throw new ArgumentException("Missing samples to measure.");
        }
        //check bits are 8 or 16.
        if (bitsPerSample != 8 && bitsPerSample != 16)
        {
            throw new ArgumentException("Only 8 and 16 bit samples allowed.");
        }
        //check channels are valid
        if (channelsToMeasure == null || channelsToMeasure.Length == 0)
        {
            throw new ArgumentException("Must have target channels.");
        }
        //check each channel is in proper range.
        foreach (int channel in channelsToMeasure)
        {
            if (channel < 0 || channel >= numChannels)
            {
                throw new ArgumentException("Invalid channel requested.");
            }
        }

        //ensure we have only full blocks. A half a block isn't considered valid.
        int sampleSizeInBytes = bitsPerSample / 8;
        int blockSizeInBytes = sampleSizeInBytes * numChannels;
        if (length % blockSizeInBytes != 0)
        {
            throw new ArgumentException("Non-integral number of bytes passed for given audio format.");
        }

        double sum = 0;
        for (var i = 0; i < length; i = i + blockSizeInBytes)
        {
            double sumOfChannels = 0;
            for (int j = 0; j < channelsToMeasure.Length; j++)
            {
                int channelOffset = channelsToMeasure[j] * sampleSizeInBytes;
                int channelIndex = i + channelOffset;
                if (bitsPerSample == 8)
                {
                    sumOfChannels = (127 - samples[channelIndex]) / byte.MaxValue;
                }
                else
                {
                    double sampleValue = BitConverter.ToInt16(samples, channelIndex);
                    sumOfChannels += (sampleValue / short.MaxValue);
                }
            }
            double averageOfChannels = sumOfChannels / channelsToMeasure.Length;
            sum += (averageOfChannels * averageOfChannels);
        }
        int numberSamples = length / blockSizeInBytes;
        double rootMeanSquared = Math.Sqrt(sum / numberSamples);
        if (rootMeanSquared == 0)
        {
            return 0;
        }
        else
        {
            double logvalue = Math.Log10(rootMeanSquared);
            double decibel = 20 * logvalue;
            return decibel;
        }
    }
William Morrison
  • 10,953
  • 2
  • 31
  • 48
3

I think Yann means that Decibels are a relative scale. If you're trying to measure the actual Sound Pressure Level or SPL, you would need to calibrate. What you're measuring is dBFS (decibels full-scale, I think). You're measuring how many decibels quieter the signal is than the loudest possible signal the system can represent (the "full-scale" signal, or 32768 for these 16-bit samples). That's why all the values are negative.

heavi5ide
  • 1,599
  • 10
  • 11