Search code examples
iosobjective-caccelerate-frameworkezaudio

Can someone explain how this code converts volume to decibels using the Accelerate Framework?


I'm building an iOS app using EZAudio. It's delegate returns back a float** buffer, which contains float values indicating the volume detected. This delegate is called constantly and it's work is done a different thread.

What I am trying to do is to take the float value from EZAudio and convert it into decibels.


EZAudioDelegate

Here's my simplified EZAudio Delegate for getting Microphone Data:

- (void)microphone:(EZMicrophone *)microphone hasAudioReceived:(float **)buffer withBufferSize:(UInt32)bufferSize withNumberOfChannels:(UInt32)numberOfChannels {
    /*
     *  Returns a float array called buffer that contains the stereo signal data
     *  buffer[0] is the left audio channel
     *  buffer[1] is the right audio channel
     */

    // Using a separate audio thread to not block the main UI thread
    dispatch_async(dispatch_get_main_queue(), ^{

        float decibels = [self getDecibelsFromVolume:buffer withBufferSize:bufferSize];

        NSLog(@"Decibels: %f", decibels);

    });

}

The Problem

The problem is that after implementing solutions from the links below, I do not understand how it works. If someone could explain how it converts volume to decibels I would be very grateful


The Code

The solution uses the following methods from the Accelerate Framework to convert the volume into decibels:

Below is the method getDecibelsFromVolume that is called from the EZAudio Delegate. It is passed the float** buffer and bufferSize from the delegate.

- (float)getDecibelsFromVolume:(float**)buffer withBufferSize:(UInt32)bufferSize {

    // Decibel Calculation.

    float one = 1.0;
    float meanVal = 0.0;
    float tiny = 0.1;
    float lastdbValue = 0.0;

    vDSP_vsq(buffer[0], 1, buffer[0], 1, bufferSize);

    vDSP_meanv(buffer[0], 1, &meanVal, bufferSize);

    vDSP_vdbcon(&meanVal, 1, &one, &meanVal, 1, 1, 0);


    // Exponential moving average to dB level to only get continous sounds.

    float currentdb = 1.0 - (fabs(meanVal) / 100);

    if (lastdbValue == INFINITY || lastdbValue == -INFINITY || isnan(lastdbValue)) {
        lastdbValue = 0.0;
    }

    float dbValue = ((1.0 - tiny) * lastdbValue) + tiny * currentdb;

    lastdbValue = dbValue;

    return dbValue;
}

Solution

  • I'll explain how one would compute a dB value for a signal using code and then show how that relates to the vDSP example.

    First, compute the RMS sum of a chunk of data

    double sumSquared = 0;
    for (int i = 0 ; i < numSamples ; i++)
    {
       sumSquared += samples[i]*samples[i];
    }
    double rms = sumSquared/numSamples;
    

    For more information on RMS

    Next convert the RMS value to dB

    double dBvalue = 20*log10(rms);
    

    How this relates to the example code

    vDSP_vsq(buffer[0], 1, buffer[0], 1, bufferSize);
    

    This line loops over the buffer and computes squares all of the elements in the buffer. If buffer contained the values [1,2,3,4] before the call then after the call it would contain the values [1,4,9,16]

    vDSP_meanv(buffer[0], 1, &meanVal, bufferSize);
    

    This line loops over the buffer, summing the values in the buffer and then returning the sum divided by the number of elements. So for the input buffer [1,4,9,16] in computes the sum 30, divides by 4 and returns the result 7.5.

    vDSP_vdbcon(&meanVal, 1, &one, &meanVal, 1, 1, 0);
    

    This line converts the meanVal to decibels. There is really no point in calling a vectorized function here since it is only operating on a single element. What it is doing however is plugging the parameters into the following formula:

    meanVal = n*log10(meanVal/one)
    

    where n is either 10 or 20 depending on the last parameter. In this case it is 10. 10 is used for power measurements and 20 is used for amplitudes. I think 20 would make more sense for you to use.

    The last little bit of code looks to be doing some simple smoothing of the result to make the meter a little less bouncy.