Android的getMaxAmplitude()函数给我什么信息?它与MediaRecorder有什么关系?

32
Android MediaRecorder提供了函数.getMaxAmplitude(),如API所述,它返回“自上次调用此方法以来采样的最大绝对幅度”,但我不知道这个幅度是什么单位?它是帕斯卡还是瓦特?
我在网上的几个页面上找到了一个与分贝密切相关的值的计算方法(如此处建议)。
double db = (20 * Math.log10(amplitude / REFERENCE)); 

这让我可以假设返回的值是按线性比例尺测量的(可能是类似于毫帕斯卡的东西...)

REFERENCE=0.1(我知道这应该是类似于2*10^(-5)帕斯卡(即20微帕斯卡),但那会返回奇怪的值... 0.1 表现得更好。)

当前,我使用

getMaxAmplitude()
测量 amplitude 变量的最大振幅。

这是这个方法:

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;
    }
}

这个操作在半秒钟内完成了5次,可以得到一个平均值。

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);

我得到了一些看起来像分贝的东西,但我不确定它是否真的是分贝...

那么,有人能帮我吗?这似乎非常奇怪,因为API根本没有明确返回什么...


这个可以用,谢谢你解释了getMaxAmplitude的作用。但我不确定这些是准确的分贝值。当我在某些情况下进行测试并尝试将值与此进行比较:http://www.newton.dep.anl.gov/askasci/phy99/phy99405.htm,会缺失30dB。你有任何想法为什么吗? - Wissem
1
可能有几个原因:
  1. 该算法以 getMaxAmplitude 作为计算基础,这意味着在该时期内所有较不响亮的事件都被忽略了。这导致的结果可能高于实际的分贝级别(通常在自然环境中是如此)。
  2. 手机的麦克风是不同的。有些比其他的更敏感。该算法没有考虑到这一点,在任何手机上进行相同的计算。由于这些差异,一些手机可能会给出明显更高或更低的值。
- Lukas Ruge
此外,由于限制因素(数值仅达到32767),非常大声的噪音无法被准确检测。通常截止值会在约100分贝左右。 - Lukas Ruge
更多关于FFT的内容可以在http://en.wikipedia.org/wiki/Fast_Fourier_transform找到。JAR包下载地址为http://introcs.cs.princeton.edu/java/97data/FFT.java.html。至于2700.0,我不清楚。现在他们得到的值不再是0到32768之间的值,而是大约12左右的值,这可能更方便他们。他们在任何时候都没有实际计算过分贝。 - Lukas Ruge
@Lukas 你应该把答案放在回答中,而不是编辑中! :) 这样人们就可以给你找到的答案点赞。回答自己的问题也没什么奇怪的 ;) - Marcin Koziński
显示剩余3条评论
2个回答

27
我找到了这个问题的答案,现在分享给所有关心此事的人: MediaRecorder.getMaxAmplitude()函数返回无符号16位整数值(0-32767)。 这可能只是范围从-32768到32767的CD质量样本值的abs() 。 这意味着它们可能代表手机内置麦克风的电气输出的最大电压范围为0-100%的16位数字化。 由于即使在同一品牌的手机中,这些麦克风的精确范围有时也会有所不同,因此即使相同的距离到达相同的声源,也不会返回相同的值。
但是,这个值与帕斯卡尔中的声压相关,因为它也是给定麦克风可以测量声音的区域的线性量化(由于手机的限制,该区域将无法覆盖整个频谱)。

无符号16位整数实际上是0-65536,而不是0-32767,因此我猜值被移位以使其为正数。 - mcont
1
@mcont 答案说,“可能”取-32768到32768范围内16位值的绝对值。因此,输出将介于0和32768之间。 - Kathir

26

对此进行了更多工作。使用用校准SPL计量仪和带有不同纯频率、白噪声和粉红噪声的智能手机进行的一些测试,我现在知道移动电话麦克风不适用于任何需要注册在90到100 dB(SPL)以上的内容,具体取决于电话。

假设90 dB(SPL)是最大值,可以计算出这将对应于麦克风处的0.6325 Pa压力。现在假设p0=0.0002 Pa是参考最小值,并假设它将被注册为0(实际上永远不会发生),从getMaxAmplitude()中我们可以将其与麦克风处的最大压力相关联。这意味着getMaxAmplitude()函数的16375的结果将对应于0.3165 Pa的最大压力。当然,这并不是非常科学的,因为最大和最小值都是纯粹的臆断,但它给了我们一个起点。现在我们可以计算p:

p = getMaxAmplitude()/ 51805.5336

知道麦克风处的压力,我们可以使用众所周知的公式来计算dB(SPL)-值:

X = 20 log_10(p / p0)

这仍然会给出一个太高的值,因为计算中仅使用最大振幅。要解决这个问题,必须不使用getMaxAmplitude(),虽然这略微超出了本问题的重点,但我仍会把代码放在这里,希望它有所帮助。

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;
}
}

现在这些值是从4秒样本中所有振幅的平均值得出的,因此更加精确。然后进行上述描述的计算,这将给出更真实的分贝值。请注意,移动电话麦克风仍然很差,并且该算法不会产生实际的dB(SPL),但比以前略微更好地近似。

为了获得一些应用程序的性能,需要做更多的工作。这些应用程序中的大部分使用滑动窗口,这意味着它们保持记录并滑动一个x秒的窗口来连续评估声音水平。此外,我将进行一些评估,确定哪个db值最适合用作最大值,目前是90 dB(SPL)/0.6325 Pa,这只是一个合理的猜测,它可能会略高于此。

只要我有更多信息,我就会更新信息。


你知道在调用 recorder.read(data, 0, bufferSize); 方法时,录音器是否会获取振幅并将其存储在短数据数组中吗?我不完全确定我理解这个方法在做什么。 - Kylie Moden
这些是存储的麦克风采样值。它们会随着麦克风的压力而振荡,产生正负值。因此,要获取振幅,您需要使用某种中位数或加权计算方法。 - Lukas Ruge
那么这些“采样值”是什么,它们是用什么来测量的?(即,数据数组中存储了什么)- 再次感谢,我非常感激。 - Kylie Moden
当声波产生的气压作用于麦克风时,该压力被转换为电流。正常的气压等于值0。由于声音是一种波动,所以电流会在零点以下和以上变化,这取决于振荡情况。该电流每秒钟进行44,100次采样。这些值被转换为短整型并存储在该数组中(这是量化的结果)。 - Lukas Ruge
另外,抱歉我一直在问问题 - 我真的很感激你抽出时间回答,你使用的公式是什么来得到51805.5336?我完全明白你如何得到0.6325帕斯卡(因为90分贝是麦克风的声明最大值,而你进行了转换),但这个51805.5336数字从哪里来的?再次感谢。 - Kylie Moden
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接