28

The Android MediaRecorder has a function

.getMaxAmplitude();
which, as the API tells me, "Returns the maximum absolute amplitude that was sampled since the last call to this method." but I can't find what amplitude this is? Is it in pascal or watts?

I have found on several pages on the web that you can calculate a value closely corelated to decibels using (as suggested here).

double db = (20 * Math.log10(amplitude / REFERENCE)); 

which would let me assume that the returned value is in some linear scale (probably something like milipascal...)

REFERENCE=0.1 (I am aware that this should be something like 2*10^(-5) Pascal ((20 uPascal)), but that returns strange values... 0.1 strangely works better.)

Right now I measure the MaxAmplitude() using the

getMaxAmplitude()
and put this into the variable amplitude.

This is the method:

public double getNoiseLevel() 
{
    //Log.d("SPLService", "getNoiseLevel() ");
    int x = mRecorder.getMaxAmplitude();
    double x2 = x;
    Log.d("SPLService", "x="+x);
    double db = (20 * Math.log10(x2 / REFERENCE));
    //Log.d("SPLService", "db="+db);
    if(db>0)
    {
        return db;
    }
    else
    {
        return 0;
    }
}

This is done 5 times in half a second, which gets kind of an average

for(int i=0; i<5; i++)
{
    try 
    {
            Thread.sleep(100);
    } 
    catch (InterruptedException e) 
    {
            e.printStackTrace();
            return 0;
    }
    level = level+getNoiseLevel();
    if(level>0)
    {
        counter++;
    }
}
level=level/counter;
Log.d(LOG_TAG, "level="+level);

I get something that kinda looks like decibel but I'm not sure its actualy decibel at all...

So, could anyone help me on this? It seems very odd that the API does not specefy at all what is returned...

Community
  • 1
  • 1
Lukas Ruge
  • 2,044
  • 3
  • 25
  • 41
  • This worked, thanks for explainig what getMaxAmplitude does. But I'm not sure if these are accurate dB values. When I test in some cases and try to compare values to this: http://www.newton.dep.anl.gov/askasci/phy99/phy99405.htm , there is a missing 30dB. Do you have any idea why? – Wissem Sep 12 '12 at 14:37
  • 1
    There may be several reasons: 1. This algorithm uses getMaxAmplitude as a basis for calculation which means all the less loud events in the period are ignored. This leads to results that can be above the actual dB level (and usually in natural environments they are) 2. Microphones of phones are diferent. Some are more sensibel then others. This algorithm does not take this into account, doing the same calculation on any phone. Due to these diferences some phones may give substantially higher or lower values. – Lukas Ruge Sep 13 '12 at 10:26
  • In addition, due to the limiting factor (the Value only gets up to 32767), very loud noises are not detected accuratly. Usually the cutoff will be at around 100 dB. – Lukas Ruge Sep 13 '12 at 10:31
  • Okay, is there any better solutions then? To get some accurate dB value from the mic? There is some apps in the market(i.e: sound meter) that gets this perfectly. – Wissem Sep 13 '12 at 10:49
  • Clearly there are. If you store the original data from the audio recorder you can do complex analysis on it, including using filtering/frequency anlysis to compensate for the shortcommings of the microphone. For example you can use the FFT-Packed from Stanford, and posibly calculate a better value from the sample instead of discreet maxAmplitude values. All that however is beyond the scope of the original question and has probably been discussed elswhere. – Lukas Ruge Sep 13 '12 at 13:44
  • I'm sorry but I don't understand you. What do you mean by using a FFT-Packed? Also, I've found this code http://code.google.com/p/android-labs/source/browse/trunk/NoiseAlert/src/com/google/android/noisealert/SoundMeter.java?r=2 it looks a bit accurate (if I add the +30dB value) They seem to divide max the getMaxAmplitude by 2700.0 (line 51) do you know why the did that please? – Wissem Sep 20 '12 at 16:45
  • more on fft can be found at http://en.wikipedia.org/wiki/Fast_Fourier_transform the jar is http://introcs.cs.princeton.edu/java/97data/FFT.java.html. On the 2700.0 I have no idea. Instead of a value between 0 and 32768 they now get a value up to about 12, maybee that is more convinient to them. They do not at any point actually calculate dB. – Lukas Ruge Sep 21 '12 at 08:49
  • Thanks, I will look into these! – Wissem Sep 21 '12 at 16:29
  • @Lukas You should put the answer not as an edit but as an.. answer! :) That way people can give you credit for finding out. And it's nothing strange to answer your own question ;) – Marcin Koziński Oct 17 '12 at 10:12

2 Answers2

22

I could find the answer to this question and I'll share it here for anyone who cares: The MediaRecorder.getMaxAmplitude() function returns unsigned 16-bit integer values (0-32767). Which is probably just the abs() of the CD-quality sample values that range from -32768 to 32767. This means that they probably represent a 16-bit digitalization of the electrical output from 0-100% maximum voltage range of the microphone build into that mobile phone. Since even in one brand of mobile these microphones sometimes vary in their precise range not even to similar phones will necessarily return the same value given the same distance to the same sound source.

This value however correlates to sound pressure in Pascal since it's also a linear quantisation of the solund pressure, in the area where sound can be measured with the given microphone (which will not cover the entire sprectrum due to the limitations of the phone).

ch271828n
  • 5,683
  • 3
  • 24
  • 44
Lukas Ruge
  • 2,044
  • 3
  • 25
  • 41
  • Unsigned 16-bit integers are actually 0-65536, not 0-32767, so I guess the value is shifted so that it's positive – mcont Aug 25 '19 at 19:11
  • @mcont The answer says that "probably" the absolute of the 16 bit value ranging from -32768 to 32768 is taken. Thus, the output would be between 0 and 32768 – Kathir Sep 25 '20 at 09:23
21

Worked on this some more. Using some tests done with calibrated SPL-Meters and smart phones with diferent pure frequencies, white noise and pink noise I now know that mobile phone microphones are not usable for anything that should register anywhere above 90 to 100 dB(SPL) depending on the phone.

Asuming that 90 dB(SPL) is the maximum one can calculate that this would correspond to a pressure of 0.6325 Pa at the mic. Now asuming that p0=0.0002 Pa is the reference minimum and asuming that this would register as 0 (which would never actually happen) from getMaxAmplitude() we can corelate the values from the getMaxAmplitude() function with maximum pressure at the mic. This means a result of 16375 from getMaxAmplitude() would correspond to 0.3165 Pa maximum pressure. This of course is not very scientific since the max and min values are pure conjesture, but it gives us a starting point. We can now calculate p with

p=getMaxAmplitude()/51805.5336

Knowing the pressure at the mic we can calculate the dB(SPL)-value with the well known formula

X = 20 log_10 (p/p0)

This still will give a value that is to high since only the maximum amplitude is used in the calculations. To solve this one must not use getMaxAmplitude() and while this is slighly outside the focus of this question, I will put the code in anyway in the hope, that it helps

public class NoiseRecorder 
{

private final String TAG = SoundOfTheCityConstants.TAG;
public static double REFERENCE = 0.00002;

public double getNoiseLevel() throws NoValidNoiseLevelException
{
    Logging.e(TAG, "start new recording process");
    int bufferSize = AudioRecord.getMinBufferSize(44100,AudioFormat.CHANNEL_IN_DEFAULT,AudioFormat.ENCODING_PCM_16BIT);
    //making the buffer bigger....
    bufferSize=bufferSize*4;
    AudioRecord recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,
            44100, AudioFormat.CHANNEL_IN_DEFAULT, AudioFormat.ENCODING_PCM_16BIT, bufferSize);

    short data [] = new short[bufferSize];
    double average = 0.0;
    recorder.startRecording();
    //recording data;
    recorder.read(data, 0, bufferSize);

    recorder.stop();
    Logging.e(TAG, "stop");
    for (short s : data)
    {
        if(s>0)
        {
            average += Math.abs(s);
        }
        else
        {
            bufferSize--;
        }
    }
    //x=max;
    double x = average/bufferSize;
    Logging.e(TAG, ""+x);
    recorder.release();
    Logging.d(TAG, "getNoiseLevel() ");
    double db=0;
    if (x==0){
        NoValidNoiseLevelException e = new NoValidNoiseLevelException(x);
        throw e;
    }
    // calculating the pascal pressure based on the idea that the max amplitude (between 0 and 32767) is 
    // relative to the pressure
    double pressure = x/51805.5336; //the value 51805.5336 can be derived from asuming that x=32767=0.6325 Pa and x=1 = 0.00002 Pa (the reference value)
    Logging.d(TAG, "x="+pressure +" Pa");
    db = (20 * Math.log10(pressure/REFERENCE));
    Logging.d(TAG, "db="+db);
    if(db>0)
    {
        return db;
    }
    NoValidNoiseLevelException e = new NoValidNoiseLevelException(x);
    throw e;
}
}

These values now are derived from the average of all amplitudes in an 4 seconds sample and thus more accurate. Afterwards the above decribed calculations are done. This will give a more realistic decibel value. Note that mobile phone mics still do suck and that this algorithm will not produce actual dB(SPL) but only a slighly better approximation then the one before.

To get the performance of some apps out there some more would need to be done. Most of these apps use sliding windows, meaning the keep recording and slide a window of x seconds to contnuosly evaluate the sound level. Also I will perform some evaluation what db Value is best suited to be used as max, right now it's 90 dB(SPL)/0.6325 Pa which is just a reasonable guess, it will probably be slighly above that.

As soon as I have more I'll update the info.

Lukas Ruge
  • 2,044
  • 3
  • 25
  • 41
  • Thank you so much - this is very helpful – Kylie Moden Jun 28 '13 at 19:20
  • Do you know if, when calling the method `recorder.read(data, 0, bufferSize);`, if the recorder is getting the amplitudes and storing them in the short data array? I am not completely sure if I understand what the method is doing. – Kylie Moden Jul 31 '13 at 21:17
  • These are the sampled values from the microphone that are stored. These will (as the pressure at the microphone does) oscilate between positive and negative values. So to get the amplitude you would have to use some kind of median or weighted calculation.. – Lukas Ruge Aug 01 '13 at 07:48
  • So what are these "sampled values" and what are they measured in? (ie. what is being stored in the data array) - thanks again, I really appreciate it – Kylie Moden Aug 01 '13 at 14:06
  • When air pressure from sound waves hits a microphone the pressure is translated into an electric current. Normal air pressure equals the value 0. Since sound is a wave, the current goes below and above zero, depending on the oszilation. This current is sampled at 44.100 times a second. These values are tranlated into short-values and stored in this array (this is the result of quantisation) – Lukas Ruge Aug 01 '13 at 14:48
  • also, sorry I keep asking questions - I really appreciate you taking the time to answer, what formula are you using to get 51805.5336? I completely follow on how you got .6325 Pa (since 90 dB is the declared max of the mic and you converted the value) but where is this 51805.5336 number coming from? Thanks again – Kylie Moden Aug 01 '13 at 21:35
  • Actually, nevermind, I got it! It's just the conversion factor (32767/.6325) - thanks again – Kylie Moden Aug 01 '13 at 22:36
  • What is the point of 'Math.abs(s)' when there's 'if(s>0)' before that? Actually, is the if statement necessary? – Hardell Aug 20 '14 at 08:54
  • How would you visualize the returned value from getNoiseLevel()? E.g. using a traditional "volume bar"? – l33t Jan 07 '17 at 00:40