SoundTouch音频处理库源码分析及算法提取(4)

来源:互联网 发布:模拟退火算法实例ppt 编辑:程序博客网 时间:2024/05/17 22:01

SoundTouch构造流程初始化的一点补充。
在SoundTouch类构造函数中,我们留意到有这么一个函数calcEffectiveRateAndTempo()
SoundTouch::SoundTouch()
{
    // Initialize rate transposer and tempo changer instances
    pRateTransposer = RateTransposer::newInstance();
    pTDStretch = TDStretch::newInstance();
    setOutPipe(pTDStretch);
    rate = tempo = 0;
    virtualPitch =
    virtualRate =
    virtualTempo = 1.0;
    calcEffectiveRateAndTempo();
    channels = 0;
    bSrateSet = FALSE;
}
在SoundTouch类的6个成员函数void setRate(float newRate),void setRateChange(float newRate),void setTempo(float newTempo),void setTempoChange(float newTempo),void setPitch(float newPitch),void setPitchOctaves(float newPitch)分别调用。不难想象,应该是对音频处理参数的一些处理,通过对calcEffectiveRateAndTempo的进一步分析,他的实现如下。
// Calculates 'effective' rate and tempo values from the
// nominal control values.
void SoundTouch::calcEffectiveRateAndTempo()
{
    float oldTempo = tempo;
    float oldRate = rate;
    tempo = virtualTempo / virtualPitch;
    rate = virtualPitch * virtualRate;
    if (!TEST_FLOAT_EQUAL(rate,oldRate)) pRateTransposer->setRate(rate);
    if (!TEST_FLOAT_EQUAL(tempo, oldTempo)) pTDStretch->setTempo(tempo);
#ifndef PREVENT_CLICK_AT_RATE_CROSSOVER
    if (rate <= 1.0f)
    {
        if (output != pTDStretch)
        {
            FIFOSamplePipe *tempoOut;
            assert(output == pRateTransposer);
            // move samples in the current output buffer to the output of pTDStretch
            tempoOut = pTDStretch->getOutput();
            tempoOut->moveSamples(*output);
            // move samples in pitch transposer's store buffer to tempo changer's input
            pTDStretch->moveSamples(*pRateTransposer->getStore());
            output = pTDStretch;
        }
    }
    else
#endif
    {
        if (output != pRateTransposer)
        {
            FIFOSamplePipe *transOut;
            assert(output == pTDStretch);
            // move samples in the current output buffer to the output of pRateTransposer
            transOut = pRateTransposer->getOutput();
            transOut->moveSamples(*output);
            // move samples in tempo changer's input to pitch transposer's input
            pRateTransposer->moveSamples(*pTDStretch->getInput());
            output = pRateTransposer;
        }
    }
}
主要还是完成了pRateTransposer,pTDStretch两个类的一些参数设置。从而对于整个声音的处理流程大概也有了一个初步的认识。
1、创建一个数字低通滤波器AAFilter,通过加入hamming window来截取sample。
我们分析一下他是如何创建这个低通数字滤波器,主要实现还是在RateTransposer类的构造函数中,构造一个AAFilter类来实现。pAAFilter = new AAFilter(32);
RateTransposer::RateTransposer() : FIFOProcessor(&outputBuffer)
{
    numChannels = 2;
    bUseAAFilter = TRUE;
    fRate = 0;
    // Instantiates the anti-alias filter with default tap length
    // of 32
    pAAFilter = new AAFilter(32);
}
我们看一下AAFilter类定义,比较简单,也很好理解。class FIRFilter *pFIR就和前面分析的一样,指向根据CPU派生出支持相应增强指令集优化的类。同样他们只是简单的override数据处理的函数。double cutoffFreq;就是低通截止频率。calculateCoeffs()就是我们应该重点理解的类函数,数字滤波器的主要参数就靠它来实现
class AAFilter
{
protected:
    class FIRFilter *pFIR;
    /// Low-pass filter cut-off frequency, negative = invalid
    double cutoffFreq;
    /// num of filter taps
    uint length;
    /// Calculate the FIR coefficients realizing the given cutoff-frequency
    void calculateCoeffs();
public:
    AAFilter(uint length);
    ~AAFilter();
    /// Sets new anti-alias filter cut-off edge frequency, scaled to sampling
    /// frequency (nyquist frequency = 0.5). The filter will cut off the
    /// frequencies than that.
    void setCutoffFreq(double newCutoffFreq);
    /// Sets number of FIR filter taps, i.e. ~filter complexity
    void setLength(uint newLength);
    uint getLength() const;
    /// Applies the filter to the given sequence of samples.
    /// Note : The amount of outputted samples is by value of 'filter length'
    /// smaller than the amount of input samples.
    uint evaluate(SAMPLETYPE *dest,
                  const SAMPLETYPE *src,
                  uint numSamples,
                  uint numChannels) const;
};
先看一下AAFilter的构造函数,先创建一个FIR滤波器的实例,接着让截取频率等于0.5,需要注意的是,这个是一个角频率。然后设置滤波器的窗体宽度。
AAFilter::AAFilter(uint len)
{
    pFIR = FIRFilter::newInstance();
    cutoffFreq = 0.5;
    setLength(len);
}

在设置宽度的类成员函数SetLength中,调用了类成员函数calculateCoeffs();
// Sets number of FIR filter taps
void AAFilter::setLength(uint newLength)
{
    length = newLength;
    calculateCoeffs();
}
现在重点介绍一下类成员函数calculateCoeffs(),他就是整个数字滤波器参数实现的核心。源代码如下:
// Calculates coefficients for a low-pass FIR filter using Hamming window
void AAFilter::calculateCoeffs()
{
    uint i;
    double cntTemp, temp, tempCoeff,h, w;
    double fc2, wc;
    double scaleCoeff, sum;
    double *work;
    SAMPLETYPE *coeffs;
    assert(length >= 2);
    assert(length % 4 == 0);
    assert(cutoffFreq >= 0);
    assert(cutoffFreq <= 0.5);
    work = new double[length];
    coeffs = new SAMPLETYPE[length];

    fc2 = 2.0 * cutoffFreq;
    wc = PI * fc2;
    tempCoeff = TWOPI / (double)length;
    sum = 0;
    for (i = 0; i < length; i ++)
    {
        cntTemp = (double)i - (double)(length / 2);
        temp = cntTemp * wc;
        if (temp != 0)
        {
            h = fc2 * sin(temp) / temp;                     // sinc function
        }
        else
        {
            h = 1.0;
        }
        w = 0.54 + 0.46 * cos(tempCoeff * cntTemp);       // hamming window
        temp = w * h;
        work[i] = temp;
        // calc net sum of coefficients
        sum += temp;
    }
...
类函数的前半部分通过assert进行一些必要的判断,例如长度一定要大于2且一定要是4的倍数,才能保证length/2是一个整数,同时保证截取频率在0和0.5之间。接着采用汉明窗作为窗。注意到0.54 + 0.46 * cos(2 * pi * cntTemp / N)和汉明窗函数0.54 - 0.46*cos(2*pi*n/(N-1))形式上有点不一致,其实也不难理解:
i = (0 .. length-1) 且 cntTemp = i - (length/ 2);
  0.54 + 0.46 * cos(2 * pi * cntTemp / N)
= 0.54 - 0.46 * cos(2 * pi * cntTemp / N + pi)
= 0.54 - 0.46 * cos(2 * pi * cntTemp / N + pi * N / N)
= 0.54 - 0.46 * cos(2 * pi * (cntTemp + N / 2) / N)
= 0.54 - 0.46 * cos(2 * pi * n / N) where n = 0..N-1
仅仅是一个cos(x) = -cos(x+pi)的变化,很简单却又让人很容易惯性思维,不容易想明白。至于为什么用N不用N-1,我相信以下这段话,可以很清楚明白的表达,在这里,要谢谢一个哈理工老师的指教。“这个N-1如果用N,对称中心N/2不是整数,就不是一个采样点(因为是偶对称,并且N要取奇数----低通滤波器理论上只能这么选取参数”注意到我们在长度中i是从0开始的,到length结束,而length前面通过assert判断一定要大于2且一定是四
的倍数,他不是一个奇数,因此(length-1)/2一定不是一个整数。所以这里可以理解为我们的滤波器是有length+1的长度。
......
    // ensure the sum of coefficients is larger than zero
    assert(sum > 0);

    // ensure we've really designed a lowpass filter...
    assert(work[length/2] > 0);
    assert(work[length/2 + 1] > -1e-6);
    assert(work[length/2 - 1] > -1e-6);

    // Calculate a scaling coefficient in such a way that the result can be
    // divided by 16384
    scaleCoeff = 16384.0f / sum;

    for (i = 0; i < length; i ++)
    {
        // scale & round to nearest integer
        temp = work[i] * scaleCoeff;
        temp += (temp >= 0) ? 0.5 : -0.5;
        // ensure no overfloods
        assert(temp >= -32768 && temp <= 32767);
        coeffs[i] = (SAMPLETYPE)temp;
    }

    // Set coefficients. Use divide factor 14 => divide result by 2^14 = 16384
    pFIR->setCoefficients(coeffs, length, 14);

    delete[] work;
    delete[] coeffs;
}
类函数的后半部分,assert用来验证这个低通滤波器是否真的有效,剩下的主要是做一个定点的处理2^14=16384,相当于右移了14位(放大16384倍,结果再左移14位变回来,可以增加精度),同时还assert(temp >= -32768 && temp <= 32767);来验证temp作为一个十六位整数一定不溢出。最后做的事情,就是把低通滤波器参数传递进去FIRxxx的类。然后FIRxxx类就可以抽象成一个数字低通滤波器。至此,所有的初始化工作完毕,可以进入数据的具体处理流程。

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/suhetao/archive/2010/08/31/5853502.aspx

原创粉丝点击