低通android PCM音频数据

Posted

技术标签:

【中文标题】低通android PCM音频数据【英文标题】:Low pass android PCM audio data 【发布时间】:2014-02-15 15:34:14 【问题描述】:

我正在开发一个吉他调音器应用程序,通过录制音频、获取音频的 FFT 并找到峰值幅度以找到基本频率。到目前为止,结果表明我的代码有效,并且在播放纯音时会返回准确的频率,尤其是在 500+hz 时,但是对于吉他的低频和响亮的谐波,结果有点混乱。

我认为我需要引入一个窗口函数和一个低通滤波器来优化我的结果并帮助我的应用检测正确的峰值,而不是谐波,但我不太确定

我已经实现了一个窗口函数,虽然我不确定它是否会影响最终结果,而且我完全不知道如何实现低通滤波器。

        byte data[] = new byte[bufferSize]; //the audio data read in
        ...

        double[] window = new double[bufferSize]; //window array

           //my window function, not sure if correct
           for(int i = 0; i< bufferSize-1; ++i)   
               window[i] = ((1 - Math.cos(i*2*Math.PI/bufferSize-1))/2);
               data[i] = (byte) (data[i] * window[i]);
             


            DoubleFFT_1D fft1d = new DoubleFFT_1D(bufferSize); 
            double[] fftBuffer = new double[bufferSize*2]; 

            for(int i = 0; i < bufferSize-1; ++i)
                fftBuffer[2*i] = data[i];
                fftBuffer[2*i+1] = 0;
            

            fft1d.complexForward(fftBuffer);


            //create/populate power spectrum
            double[] magnitude = new double[bufferSize/2];  
            maxVal = 0;
            for(int i = 0; i < (bufferSize/2)-1; ++i) 

                double real =  fftBuffer[2*i];
                double imaginary =  fftBuffer[2*i + 1];

                magnitude[i] = Math.sqrt( real*real + imaginary*imaginary ); 
                                Log.i("mag",String.valueOf(magnitude[i]) + " " + i);

            //find peak magnitude
            for(int i = 0; i < (bufferSize/2)-1; ++i)  
            if(magnitude[i] > maxVal)
                maxVal = (int) magnitude[i];           
                binNo = i;                  
                   
            

            //results
            freq = 8000 * binNo/(bufferSize/2);  
            Log.i("freq","Bin "+String.valueOf(binNo));
            Log.i("freq",String.valueOf(freq) + "Hz");

所以是的,不完全确定窗口函数是否有很大作用,功率谱无论如何都包含谐波峰值,我不确定从哪里开始使用低通滤波器。

【问题讨论】:

【参考方案1】:

窗口函数可以帮助您增加一点结果。

窗口的目的是减少窗口两端的幅度分量,以避免出现杂散的高频,这是必要的,因为傅里叶变换假设信号是无限的,所以在一个窗口,两边重复无数次,导致边界不连续!

如果你应用一个窗口,这个问题会被最小化,但在某种程度上仍然会发生。

如果您正在使用吉他构建低通以过滤预期的最高调谐频率,则在应用窗口函数之前需要低通!

你需要考虑麦克风的频率响应,我相信这些移动麦克风很难捕捉到调好的吉他的低频,我们说的是 82.4Hz

寻找FFT的峰值不是做调谐器的好主意!

【讨论】:

关于移动设备板载麦克风频率响应的观点是有效的。对于大多数应用——尤其是语音电话——毕竟是主要应用——低频是不可取的,电话上的所有麦克风都有一个 HPF,其拐点可能约为 100Hz。在 ios 上可以禁用它,此外,所有设备的频率响应都被标准化并平坦到 40Hz。我怀疑 android 上的情况是否如此。 我目前的问题是我不清楚我将如何在我的音频数据上实现低通滤波器 - 我知道我可以如何用我的功率谱来做到这一点,删除上面的那些我我希望找到 - 但我不确定如何过滤 data[] 中的音频数据。就频率响应而言,这是我担心的问题,但我认为它在 50hz 左右还不错,峰值出现在正确的 bin 中(尽管谐波峰值通常是最大的)。【参考方案2】:

FFT 可以被认为是一系列带通滤波器,每个 bin 的幅度是窗口上的平均功率。 FFT 上游的 LPF 不会给您带来太多帮助 - 您可以直接丢弃更高阶的 FFT 箱 - 除非您需要特别陡峭的响应。

使用 FFT 实现吉他调音器的方法是有问题的(尽管 以这种方式实施成功的调谐器,它们并非不可克服)。

查找峰值箱是一种幼稚的方法,不会为您提供精确的结果。曾经。每个 bin 都是一个带通滤波器,因此您假设测量结果是 bin 中心频率。这就是问题所在:

等律中半音的频率呈几何级数(比率约为 1.06),而 FFT 箱的间距是线性的。如果我们假设 Fs 是 44.1k 并且使用 1024 点 FFT,则 bin 间距 44.1Hz。作为 E2(吉他的底弦约为 82Hz @A440),很明显,使用这种方法的调音器在很大程度上是无用的。即使为了实时响应(和大量处理)而交易了一个非常大的窗口大小,它仍然不是很准确。尝试调整电贝司(底弦:E1,~41Hz) 跨越分档的频率会发生什么情况?碰巧的是,所有八度音阶中 C 的频率都与 2 的幂相距不远。B - 吉他调音器确实需要在其上表现良好的音符也很接近。对于这些音符,基波的能量几乎均匀地分布在两个波段之间。它可能不再是最大的了。 基频甚至是峰值频率吗? (提示:通常不是)。 箱重叠。多少取决于使用的窗口函数。

如果您想坚持使用 FFT 解决方案,您可能希望使用 STFT。 DSPDimension.com 有一个很好的描述如何做到这一点。该页面缺少的一条信息是频率的定义是相位变化率:

F = dPhi/dt

因此,可以在知道两个连续结果窗口之间的相位差的情况下估计 F

请注意,加窗是采样,因此采样理论和奈奎斯特速率适用于可通过它实现的频率分辨率。吉他调音器至少需要 2048 点 FFT。

【讨论】:

我目前使用的是 8000hz 的采样率,我的功率谱阵列的大小为 2008,AFAIK 这意味着我们每个 bin 都略低于 4Hz,这对于吉他调音器来说已经足够接近了您开始在 D2 以下进行调整。我知道这并不理想,如果需要,我可以增加缓冲区大小来改善这一点。我知道这个项目总体上不是……明智的,但在这一点上,我必须坚持它并尽我所能,按照我设计的规范 - 大学项目。也意识到峰值通常不是吉他的基础,因此这篇文章,我正在寻找帮助过滤以获得更好的结果。【参考方案3】:

FFT 峰值幅度频率检测通常不适用于确定吉他音高,因为峰值频率通常不是音符音高的频率。尝试改用音高检测或估计算法。请参阅More accurate estimating guitar pitch frequency based on FFT(already) result 了解一些替代方案。

【讨论】:

以上是关于低通android PCM音频数据的主要内容,如果未能解决你的问题,请参考以下文章

Android音视频系列(七):PCM音频单声道与双声道的相互转换

Android麦克风数据采集格式和常见的编码格式

Android 如何使用 MediaRecorder 录制音频并输出为原始 PCM?

Android 音频开发——AudioTrack播放

Android 端音频变声方案

Android中PCM音频样本的上采样