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

来源:互联网 发布:c语言程序员 编辑:程序博客网 时间:2024/06/07 14:26

SoundTouch音频处理库初始化流程剖析

定义一个变量SoundTouch m_SoundTouch;

 

SoundTouch的派生关系

FIFOSamplePipe->FIFOProcessor->SoundTouch (流程[1])

因此首先构造基类FIFOSamplePipe,接着派生出FIFOProcessor,然后才以FIFOProcessor派生出SoundTouch。这里不得不提一下老外的C++水平真的很高,在这里基本上把类的继承发挥到了极致。能够分析这样的代码简直就是一种享受。先看一下基类FIFOSamplePipe,如下定义:

class FIFOSamplePipe

{

public:

    // virtual default destructor

    virtual ~FIFOSamplePipe() {}

 

    /// Returns a pointer to the beginning of the output samples.

    /// This function is provided for accessing the output samples directly.

    /// Please be careful for not to corrupt the book-keeping!

    ///

    /// When using this function to output samples, also remember to 'remove' the

    /// output samples from the buffer by calling the

    /// 'receiveSamples(numSamples)' function

    virtual SAMPLETYPE *ptrBegin() = 0;

 

    /// Adds 'numSamples' pcs of samples from the 'samples' memory position to

    /// the sample buffer.

    virtual void putSamples(const SAMPLETYPE *samples,  ///< Pointer to samples.

                            uint numSamples             ///< Number of samples to insert.

                            ) = 0;

 

 

    // Moves samples from the 'other' pipe instance to this instance.

    void moveSamples(FIFOSamplePipe &other  ///< Other pipe instance where from the receive the data.

         )

    {

        int oNumSamples = other.numSamples();

 

        putSamples(other.ptrBegin(), oNumSamples);

        other.receiveSamples(oNumSamples);

    };

 

    /// Output samples from beginning of the sample buffer. Copies requested samples to

    /// output buffer and removes them from the sample buffer. If there are less than

    /// 'numsample' samples in the buffer, returns all that available.

    ///

    /// /return Number of samples returned.

    virtual uint receiveSamples(SAMPLETYPE *output, ///< Buffer where to copy output samples.

                                uint maxSamples                 ///< How many samples to receive at max.

                                ) = 0;

 

    /// Adjusts book-keeping so that given number of samples are removed from beginning of the

    /// sample buffer without copying them anywhere.

    ///

    /// Used to reduce the number of samples in the buffer when accessing the sample buffer directly

    /// with 'ptrBegin' function.

    virtual uint receiveSamples(uint maxSamples   ///< Remove this many samples from the beginning of

 

pipe.

                                ) = 0;

 

    /// Returns number of samples currently available.

    virtual uint numSamples() const = 0;

 

    // Returns nonzero if there aren't any samples available for outputting.

    virtual int isEmpty() const = 0;

 

    /// Clears all the samples.

    virtual void clear() = 0;

}

 

这里没有实现FIFOSamplePipe类的构造函数,因此系统隐性的调用了默认的自动生成的FIFOSamplePipe()。当然他应该没有做任何的初始化,同样也不需要做任何的初始化。通过定义virtual ~FIFOSamplePipe() {}虚析构函数,使得new一个子类,例如:FIFOSamplePipe* a = new FIFOProcessor,当a销毁的时候都会执行子类FIFOProcessor的析构函数,保证不管多少层继承都会一次过全部销毁,这是作为一个基类的特点。类的继承和多态果然是C++最为强悍的一部分,有助于编写重复性很高的类。通过看这个基类的声明,我们可以留意到除了定义大多数虚函数之外,他唯独实现了moveSamples这个函数,也就是子类如果没有override moveSamples,都将调用这个方法。他做的处理也相对来说很简单,根据注释,我们不难理解,正是这个函数实现了各个派生类之间的数据共享传递的接口。

// Moves samples from the 'other' pipe instance to this instance.

moveSamples(FIFOSamplePipe &other  ///< Other pipe instance where from the receive the data.

)

{

        int oNumSamples = other.numSamples();

 

        putSamples(other.ptrBegin(), oNumSamples);

        other.receiveSamples(oNumSamples);

};

在创建SoundTouch类之前,经过(流程[1])的前面两个步骤,他们都隐形的调用了默认的析构函数,由于基类FIFOSamplePipe没有实现构造函数,我们可以默认他不做任何的初始化,然后FIFOProcessor简单的把成员变量FIFOSamplePipe *output;一个指向基类的指针赋值简单做了一下初始化,让他指向NULL

FIFOProcessor()

{

output = NULL;

}

现在回到SoundTouch的构造函数,在构造完前面两个类之后,他终于可以调用自己的默认构造函数

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类的成员变量

class SoundTouch : public FIFOProcessor

{

private:

    /// Rate transposer class instance

    class RateTransposer *pRateTransposer;

 

    /// Time-stretch class instance

    class TDStretch *pTDStretch;

 

    /// Virtual pitch parameter. Effective rate & tempo are calculated from these parameters.

    float virtualRate;

 

    /// Virtual pitch parameter. Effective rate & tempo are calculated from these parameters.

    float virtualTempo;

 

    /// Virtual pitch parameter. Effective rate & tempo are calculated from these parameters.

    float virtualPitch;

 

    /// Flag: Has sample rate been set?

    BOOL  bSrateSet;

 

    /// Calculates effective rate & tempo valuescfrom 'virtualRate', 'virtualTempo' and

    /// 'virtualPitch' parameters.

    void calcEffectiveRateAndTempo();

 

protected :

    /// Number of channels

    uint  channels;

 

    /// Effective 'rate' value calculated from 'virtualRate', 'virtualTempo' and 'virtualPitch'

    float rate;

 

    /// Effective 'tempo' value calculated from 'virtualRate', 'virtualTempo' and 'virtualPitch'

    float tempo;

...

根据构造函数他实例化pRateTransposer,pTDStretch这两个类。

首先看一下RateTransposer类的成员函数newInstance,通过一个宏定义INTEGER_SAMPLESnew一个定点还是浮点处理的类,马上就可以判断不管RateTransposerInteger还是RateTransposerFloat都应该直接从RateTransposer派生。(假设INTEGER_SAMPLES被定义)他将构造一个RateTransposerInteger

RateTransposer *RateTransposer::newInstance()

{

#ifdef INTEGER_SAMPLES

    return ::new RateTransposerInteger;

#else

    return ::new RateTransposerFloat;

#endif

}

看一下RateTransposerInteger类的定义,不出所料果然由RateTransposer派生

class RateTransposer : public FIFOProcessor

{

protected:

...

    FIFOSampleBuffer storeBuffer;

 

    /// Buffer for keeping samples between transposing & anti-alias filter

    FIFOSampleBuffer tempBuffer;

 

    /// Output sample buffer

    FIFOSampleBuffer outputBuffer;

...

上诉两个类他们和基类之间存在这样的关系:

FIFOSamplePipe->FIFOProcessor->RateTransposer->RateTransposerInteger

这里的构造过程不同的是:RateTransposer::RateTransposer() : FIFOProcessor(&outputBuffer)

RateTransposer构造函数指明了父类FIFOProcessor的构造形式FIFOProcessor(&outputBuffer)

FIFOProcessor(FIFOSamplePipe *pOutput   ///< Output pipe.

)

{

output = pOutput;

}

RateTransposer把类成员变量outputBuffer作为传递函数参数,这里可能大家就会很奇怪,代码里面根本还没有实例化RateTransposer类,他怎么可能存在一个FIFOSampleBuffer outputBuffer;其实正是体现了c++的多态性,这里传入的实际上是一个__vfptr数组,这个数组就是指向实例化各个派生类的这个变量的指针数组。这下子明白了。__vfptr[0]不一定有值,但是__vfptr肯定是一个存在的值。构造完FIFOProcessor,此时要构造RateTransposer,他有三个FIFOSampleBuffer类定义。

...

class FIFOSampleBuffer : public FIFOSamplePipe

...

与基类的继承关系

FIFOSamplePipe->FIFOSampleBuffer

/// Constructor

FIFOSampleBuffer(int numChannels = 2     ///< Number of channels, 1=mono, 2=stereo.

                                              ///< Default is stereo.

);

他没有定义不带参的构造函数,因此这个带参数的构造函数将以默认的方式给调用

FIFOSampleBuffer::FIFOSampleBuffer(int numChannels)

{

    assert(numChannels > 0);

    sizeInBytes = 0; // reasonable initial value

    buffer = NULL;

    bufferUnaligned = NULL;

    samplesInBuffer = 0;

    bufferPos = 0;

    channels = (uint)numChannels;

    ensureCapacity(32);     // allocate initial capacity

}

FIFOSampleBuffer的构造函数将被调用三次。

现在终于可以执行RateTransposer的构造函数

// Constructor

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

};

在其构造函数中初始化了一个指向class FIRFilter的指针

AAFilter::AAFilter(uint len)

{

    pFIR = FIRFilter::newInstance();

    cutoffFreq = 0.5;

    setLength(len);

}

首先我们看看FIRFilter类成员函数newInstance(),嘿嘿,在这里我们发现了一个非常有用的函数detectCPUextensions();通过这个函数我们可以判断cpu到底支持什么类型的多媒体指令集。根据注释我们也可以很快理解。detectCPUextensions收藏了。他的实现就在Cpu_detect_x86_win.cpp的实现中。美中不足的是,他只能检测x86结构体系的CPU。可能我多想了。根据本人电脑的配置(采用的赛扬cpu),所以只支持mmx指令。

FIRFilter * FIRFilter::newInstance()

{

    uint uExtensions;

    uExtensions = detectCPUextensions();

    // Check if MMX/SSE/3DNow! instruction set extensions supported by CPU

#ifdef ALLOW_MMX

    // MMX routines available only with integer sample types

    if (uExtensions & SUPPORT_MMX)

    {

        return ::new FIRFilterMMX;

    }

    else

#endif // ALLOW_MMX

#ifdef ALLOW_SSE

    if (uExtensions & SUPPORT_SSE)

    {

        // SSE support

        return ::new FIRFilterSSE;

    }

    else

#endif // ALLOW_SSE

#ifdef ALLOW_3DNOW

    if (uExtensions & SUPPORT_3DNOW)

    {

        // 3DNow! support

        return ::new FIRFilter3DNow;

    }

    else

#endif // ALLOW_3DNOW

    {

        // ISA optimizations not supported, use plain C version

        return ::new FIRFilter;

    }

}

为此他将通过这个判断构造返回一个FIRFilterMMX

if (uExtensions & SUPPORT_MMX)

    {

        return ::new FIRFilterMMX;

    }

查看FIRFilterMMX的类定义class FIRFilterMMX : public FIRFilter,他从FIRFilter派生。成员函数uint FIRFilterMMX::evaluateFilterStereo引起了我的高度注意,主要的算法采用MMX指令集来完成某些声音计算。这个就是我们需要的Rate的核心算法。不同指令集的实现,可以参考FIRFilter3DNowFIRFilterSSE,默认是FIRFilterevaluateFilterStereo函数的实现。

// mmx-optimized version of the filter routine for stereo sound

uint FIRFilterMMX::evaluateFilterStereo(short *dest, const short *src, uint numSamples) const

{

    // Create stack copies of the needed member variables for asm routines :

    uint i, j;

    __m64 *pVdest = (__m64*)dest;

 

    if (length < 2) return 0;

 

    for (i = 0; i < (numSamples - length) / 2; i ++)

    {

        __m64 accu1;

        __m64 accu2;

        const __m64 *pVsrc = (const __m64*)src;

        const __m64 *pVfilter = (const __m64*)filterCoeffsAlign;

 

        accu1 = accu2 = _mm_setzero_si64();

        for (j = 0; j < lengthDiv8 * 2; j ++)

        {

            __m64 temp1, temp2;

 

            temp1 = _mm_unpacklo_pi16(pVsrc[0], pVsrc[1]);  // = l2 l0 r2 r0

            temp2 = _mm_unpackhi_pi16(pVsrc[0], pVsrc[1]);  // = l3 l1 r3 r1

 

            accu1 = _mm_add_pi32(accu1, _mm_madd_pi16(temp1, pVfilter[0]));  // += l2*f2+l0*f0

 

r2*f2+r0*f0

            accu1 = _mm_add_pi32(accu1, _mm_madd_pi16(temp2, pVfilter[1]));  // += l3*f3+l1*f1

 

r3*f3+r1*f1

 

            temp1 = _mm_unpacklo_pi16(pVsrc[1], pVsrc[2]);  // = l4 l2 r4 r2

 

            accu2 = _mm_add_pi32(accu2, _mm_madd_pi16(temp2, pVfilter[0]));  // += l3*f2+l1*f0

 

r3*f2+r1*f0

            accu2 = _mm_add_pi32(accu2, _mm_madd_pi16(temp1, pVfilter[1]));  // += l4*f3+l2*f1

 

r4*f3+r2*f1

 

            // accu1 += l2*f2+l0*f0 r2*f2+r0*f0

            //       += l3*f3+l1*f1 r3*f3+r1*f1

 

            // accu2 += l3*f2+l1*f0 r3*f2+r1*f0

            //          l4*f3+l2*f1 r4*f3+r2*f1

 

            pVfilter += 2;

            pVsrc += 2;

        }

        // accu >>= resultDivFactor

        accu1 = _mm_srai_pi32(accu1, resultDivFactor);

        accu2 = _mm_srai_pi32(accu2, resultDivFactor);

 

        // pack 2*2*32bits => 4*16 bits

        pVdest[0] = _mm_packs_pi32(accu1, accu2);

        src += 4;

        pVdest ++;

    }

 

   _m_empty();  // clear emms state

 

    return (numSamples & 0xfffffffe) - length;

}

因此,如果把SoundTouch移植到arm等没有多媒体指令集的CPU时,应使用FIRFilterevaluateFilterStere函数。执行完这里,终于可以真正意义上构造我们的RateTransposerInteger()。在构造函数中:

RateTransposerInteger::RateTransposerInteger() : RateTransposer()

{

    // Notice: use local function calling syntax for sake of clarity,

    // to indicate the fact that C++ constructor can't call virtual functions.

    RateTransposerInteger::resetRegisters();

    RateTransposerInteger::setRate(1.0f);

}进行了一些必要的初始化。至此pRateTransposer = RateTransposer::newInstance();实例化完毕。至于pTDStretch = TDStretch::newInstance();下回分晓。

 

原创粉丝点击