欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  数据库

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

程序员文章站 2022-06-16 14:58:02
...

SoundTouch音频处理库初始化流程剖析 定义一个变量SoundTouch m_SoundTouch; SoundTouch的派生关系 FIFOSamplePipe-FIFOProcessor-SoundTouch (流程[1]) 因此首先构造基类FIFOSamplePipe,接着派生出FIFOProcessor,然后才以FIFOProcessor派生出SoundTouch。

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, ///

uint numSamples ///

) = 0;

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

void moveSamples(FIFOSamplePipe &other ///

)

{

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, ///

uint maxSamples ///

) = 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 ///

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

)

{

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_SAMPLES来new一个定点还是浮点处理的类,马上就可以判断不管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 = 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 ///

///

);

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

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的核心算法。不同指令集的实现,可以参考FIRFilter3DNow,FIRFilterSSE,默认是FIRFilter的evaluateFilterStereo函数的实现。

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

for (i = 0; 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

{

__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时,应使用FIRFilter的evaluateFilterStere函数。执行完这里,终于可以真正意义上构造我们的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();下回分晓。

http://blog.csdn.net/suhetao/archive/2010/08/28/5845667.aspx