过滤音频信号时出现奇怪的噪音

信息处理 低通滤波器 过滤
2022-01-31 22:52:02

我正在使用 Naudio 开源库,并且正在尝试进行一些简单的过滤。问题是我听到一些“咔哒”声,不是太大声。该库为我提供了使用至少两个缓冲区的可能性,因此计算时间不会在它们之间引入延迟。因为在大多数情况下,我都在处理立体声信号,我将它分成两个数组,并且我彼此独立计算。我想知道当我在缓冲区上使用过滤器时是否有什么特别之处。我首先使用了一个低通双二阶滤波器,如下所示:

       //generate coeff
       //sincerely, I don't know what's up with q
       //I have taken into consideration some values
       //to see if the noise disappears   
       double w0 = 2 * Math.PI * cutoffFrequency / _sampleRate;
       double cosw0 = Math.Cos(w0);
       double alpha = Math.Sin(w0) / (2 * q);
       _b0 = (1 - cosw0) / 2;
       _b1 = 1 - cosw0;
       _b2 = (1 - cosw0) / 2;
       _a0 = 1 + alpha;
       _a1 = -2 * cosw0;
       _a2 = 1 - alpha; 
       for (int i = 2; i < length; i++)  
       {
            output[i] = (float)((_b0 / _a0) * input[i] + (_b1 / _a0) * input[i - 1] + (_b2 / _a0) * input[i - 2]- (_a1 / _a0) * output[i - 1] - (_a2 / _a0) * output[i - 2]);
       }
       output[1] = (float)(
                (_b0 / _a0) * input[1] + (_b1 / _a0) * input[0] + (_b2 / _a0) * input[0]
                - (_a1 / _a0) * output[0] - (_a2 / _a0) * output[0]);
        output[0] = (float)(
                (_b0 / _a0) * input[0] + (_b1 / _a0) * 0 + (_b2 / _a0) * 0
                - (_a1 / _a0) * 0 - (_a2 / _a0) * 0);

我认为我所有的问题都来自前两个样本(输出 0:1),我尝试了所有组合:输出[-1]=0,输出[-1]=输出[0],但没有任何效果。当 "i" 为 0 或 1 时 output[i-1], output[i-2] 应该有什么值?

当我使用 LowPass Windowed-Sinc 滤波器时,我遇到了同样的噪音(咔哒声),就像这样:

//计算系数

        int i;

        int m = length;
        double PI = Math.PI;
        length=101;
        for (i = 0; i < length; i++)
        {
            if (i - m / 2 == 0)
            {
                _h[i] = 2 * PI * _cutOffFrecv;
            }
            else
            { 
                //!=0
                _h[i] = Math.Sin(2 * PI * _cutOffFrecv * (i - m / 2)) / (i - m / 2);
            }
            _h[i] = _h[i] * (0.54 - 0.46 * Math.Cos(2 * PI * i / m));
        }
        //normalize the low-pass filter kernel for unity gain at DC
        double s = 0;
        for (i = 0; i < m; i++)
        {
            s = s + _h[i];
        }
        for (i = 0; i < m; i++)
        {
            _h[i] = _h[i] / s;
        }
        //convolve the input & kernel
        //_kernelSize=101
        //most often length is 6615 or 6614 for each channel
        //in these examples I compute only one channel
        for (j = 0; j < length; j++)
        {
            output[j]=0;
            for (i = 0; i < _kernelSize; i++)
            {
                if (j >= i)
                {
                    output[j] =(float)(output[j]+ _h[i] * input[j - i]);
                }
            }
        }

问题肯定不在于拆分信号或组合通道,因为我已经在没有任何过滤器的情况下对此进行了测试,一切正常。我还尝试模拟由处理算法产生的一些延迟(但不改变信号)并且没有出错。我非常确定问题来自过滤。我写的所有东西都用在缓冲区上。

2个回答

您使用这些功能的上下文尚不清楚,但在我看来,您的问题是“边缘效应”。

在评估卷积或双二阶时,您需要访问当前缓冲区之外的样本。您的两个实现将这些样本评估为零。这是不正确的。例如,对于双二阶,每次处理一个音频块时,都需要存储 and 的最后 2 个input[]output[]并重复使用它们来代替input[-1], input[-2], output[-1], output[-2]. 即使你处理的数据是小块的,你也必须像处理它一样处理它;因此,您的过滤器的状态变量不得在每个缓冲区的边界处重置为零。

关于双二阶滤波器,我已经按照您所说的保存了以前的值,但没有任何改变。这是代码:

    public void DoFiltering(float[] input, float[] output, int length, bool rightChannel)
    {


        for (int i = 2; i < length; i++)
        {
            output[i] = (float)(
                (_b0 / _a0) * input[i] + (_b1 / _a0) * input[i - 1] + (_b2 / _a0) * input[i - 2]
                - (_a1 / _a0) * output[i - 1] - (_a2 / _a0) * output[i - 2]);
        }

        if (rightChannel)
        {
            //output[-1] = _outR_Channel[0]
            //output[-2]= _outL_Channel[1]
            //the same thing for left channel
            //and for input
            output[1] = (float)(
                (_b0 / _a0) * input[1] + (_b1 / _a0) * input[0] + (_b2 / _a0) * _inR_Channel[0]
                - (_a1 / _a0) * output[0] - (_a2 / _a0) * _outR_Channel[0]);
            output[0] = (float)(
                (_b0 / _a0) * input[0] + (_b1 / _a0) * _inR_Channel[0] + (_b2 / _a0) * _inR_Channel[1]
                - (_a1 / _a0) * _outR_Channel[0] - (_a2 / _a0) * _outR_Channel[1]);
            //save the last two values in order to use them in the next step
            _outR_Channel[0] = output[length - 1];
            _outR_Channel[1] = output[length - 2];
            _inR_Channel[0] = input[length - 1];
            _inR_Channel[1] = input[length - 2];
            // (output[length-2],output[length-1]) <=> (output[-2],output[-1]) <=> (_output[1],_output[0])
        }
        else
        {

            output[1] = (float)(
                (_b0 / _a0) * input[1] + (_b1 / _a0) * input[0] + (_b2 / _a0) * _inL_Channel[0]
                - (_a1 / _a0) * output[0] - (_a2 / _a0) * _outL_Channel[0]);
            output[0] = (float)(
                (_b0 / _a0) * input[0] + (_b1 / _a0) * _inL_Channel[0] + (_b2 / _a0) * _inL_Channel[1]
                - (_a1 / _a0) * _outL_Channel[0] - (_a2 / _a0) * _outL_Channel[1]);

            _outL_Channel[0] = output[length - 1];
            _outL_Channel[1] = output[length - 2];
            _inL_Channel[0] = input[length - 1];
            _inL_Channel[1] = input[length - 2];
        }

我已经检查了几次索引,看看我是否没有犯任何错误。我确信拆分通道程序是正确的,因为我已经独立测试过它并且没有任何过滤器。我想知道我应该如何更改 window-sinc 过滤器。假设我有一个“n”个元素的输入,将它向左移动“k”个位置并用以前的输入值(“k”值)填充它,可以吗?输出呢?在这种情况下,我将获得 n+k 个输出数组,但我只需要 n 个值。如果我从 k 到 k+n-1 取样可以吗?我也在考虑考虑下一个值,输入应该是:[k 个以前的值][当前数据 - n 个长度][k 个下一个值]。对于输出,我将具有相同的结构,但值不同。那会让人头疼,因为我必须使用另一种双缓冲或三重缓冲技术。我想指出一个事实,Naudio 让我有机会更改缓冲区长度。事实上,我可以更改直接影响缓冲区长度的延迟时间。如果我增加它,比如说 400 或 500 毫秒,那么点击就不会那么频繁了。

你知道我应该如何解决这个问题吗?我很确定这应该是一个解决方案。