admin管理员组

文章数量:1532198

2024年6月27日发(作者:)

糟}{■ ≥蔫曩 III 

维普资讯

j 

编程语言 

ROGRAM AN(、【jAGE 

用 Vc 开谖 WINAMP的音效插件 一。, 。:  。。 。

_ 

一% 

√ 

_f' {廖艳轧荣华 

 .

。 

摘要本文阐述了WINAMP播放器软件的DSP插件制作技术,该插件是对WINAMP播 

∥ 谚 

黪 ~ 

放器功能的扩展,它可以将歌曲中的原唱歌声消除,只留下伴唱。 

关键词

一 

WINAMP,插件,VC++,MFC,DLL,DSP,双二次滤波算法 

_ 

誊一 

日IJ昌 

WINAMP是目前应用最广泛的音乐播放器软件之一,它 

5.可视化插件(Visualization plug—ins):它用来显示一 

些基于音乐的动态效果,增加软件的趣味性。使得音乐不但能 

让耳朵享受到,同时让眼睛也来享受一把。 

深受广大音乐爱好者的青睐。不仅是因为它强大的功能和漂亮 

的界面,最重要的还是该软件的设计结构,它拥有开放的非常 

WINAMP主程序根据输入插件注册的文件类型或者输入设 

备类型来调用相应的输入插件实现信号从媒体的输入,然后输 

入信号流被送到DSP(数字音频信号处理)插件进行进一步的 

处理,最后经过处理的信号送到输出插件,输出插件或者将音 

频信号从音箱还原,或者将音频信号以某种编码方式写入文 

件。这完全依赖输出插件的具体实现而定。 

本文详细介绍怎样来实现DSP插件(基于WINAMP2), 

灵活的插件接口。全世界的许多软件开发人员,同样也是音乐 

爱好者,他们为WINAMP编写了大量的非常实用的功能丰富 

的插件。使得该软件常能保持新鲜的活力。对当今网络流行的 

各种音乐文件格式提供很好的支持。 

事实上,WINAMP几乎是完全依赖于插件的支持而正常 

工作,WINAMP主体程序仅仅提供了一个载体,它将各种插 

件有机地结合起来,协调工作。 

并实现了一个消歌声插件。其它类型的插件可以通过访问 

WINAMP官方网站(http://www.winamp.corn)来了解,其他 

网站也可以找到一些相关的文章。 

二、体系结构 

WINAMP的插件是一些用来扩展WINAMP功能的文件, 

它以WIN32动态连接库的方式存在于WINAMP安装目录下和 

三、相关技术 

1.获取WINAMP2的SDK。 

plugins目录下,按照一定的格式命名,并导出指定的函数。 

WINAMP2支持主要五种不同类型的插件: 

1.输入插件(Input plug—ins):它用来扩展WINAMP 

的输入功能。通过它,WINAMP可以处理那些原本不能识别 

的文件格式。 

2.输出插件(Output plug—ins):它用来扩展WINAMP 

的输出功能,通过它,WINAMP可以将音频数据以一种不同 

在编写插件前,我们首先要从WINAMP网站下载插件 

SDK(Software Development Kit)。将下载文件解压缩之后得 

到相关的头文件,另外还有一些示例代码。仔细阅读这些示例 

对我们的开发将会有很大的帮助。DSP插件的相关头文件只有 

个,就是dsp.h。 

头文件里定义了两个结构和几个函数指针。这些结构用来 

定义插件的一些属性和功能。 

WINAMP的DSP插件DLL将导出一个知名的函数: 

winampDSPGetHeader2,这也是唯一个需要导出的函数。这个 

的方式输出到音频播放设备、文件、或者其它任何媒介。 

3.通用目的插件(General purpose plug—ins):如果你 

需要在WINAMP软件工作的后台做一些其它的与音频数据无 

关的事,那么这种插件正是你需要的。 

4.效果插件(DSP/effect plug—ins):在音频数据送到 

输出之前,WINAMP通过调用DPS插件来对音频做处理。 

函数将返回一个描述DLL模块的数据结构。每个插件DLL都 

可以包含一个或更多的功能相对独立的DSP插件。WINAMP 

主程序通过调用一个插件枚举函数来确定DLL内具体有多少 

个插件。对于DLL内的每一个DSP插件,都通过另一个结构 

} 

维普资讯

鼗露■ ,I薏稿:囊 

编程语言 

l’R0GRAM l ^NGUAGE 

来进行描述。 

一i 

# 

黛 

麓 

薯 嚣萼攀 

“i 一 

一勰 椒 -毋 一 瓤 

_ _ % 

另一个重要的结构是winampDSPHeader,这个结构用来描 

winampDSPModule结构用来描述一个DSP插件的信息。结 

述本DLL模块的信息,它的定义如下: 

构定义如下: 

typedefstruc:twinampDSPModule{ 

char:;:description; 

HWND hwndParent; 

HINSTANCE hDIIInstance; 

void( Config)(struct winampDSPModule this_mod): 

int(¥Init)(structwinampDSPModule¥thisj-nod): 

int( :ModifySamples)(struct winampDSPModule 

this

_

rood,short int¥samples,int numsamples,int bps,int 

nch,intsrate): 

void( Quit)(structwinampDSPModule: this_mod): 

void: userData; 

)winampDSPModule; 

其中: 

description:一个字符串指针,对插件作简要描述。 

hwndParent:父窗口句柄,当WINAMP装载该插件之 

后,它将填充这个成员,它其实就是WINAMP的主窗口。在 

这里一定要注意,我们在做开发的时候一定不能将这个结构定 

义为常量,因为主程序会在运行时修改某些值。 

hDllInstance:DLL的实例句柄,同上一个成员,在 

WINAMP装载该插件的时候,它瘵填充这个成员。 

 

Config:这是一个函数指针,如果需要的话,你可以指定 

_ 

个函数来对插件进行一些必要的参数配置。当在WINAMP 

插件管理窗口单击配置按钮的时候,该函数被调用。通常在这 

一 : 

个函数里应该显示一个参数配置对话框。如果你的插件不需要 

配置,那么该成员可以指定为NULL,这时,WINAMP插件管 

 

理窗口的配置按钮将显示为不可用状态。 

Init:这是~个函数指针。如果需要的话,你可以指定一 

 |

个函数来对插件进行工作前的初始化工作。如果你指定了这个 

函数,那么初始化成功后应该返回0。如果你的插件在工作的 

_ 

时候需要显示一个窗口界面,那么这个函数将是理想之处。如 

果你的插件不需要进行工作前的初始化,那么请将这个成员设 

为NULL。 

ModifySamples:一个函数指针,它指定一个DSP处理函 

数,这个函数是必需的,不能为空。这是DSP插件的核心函 

数,WINAMP主体程序在工作时将会反复调用这个函数来处 

理音频数据。这个函数的参数含义以及如何实现,将在本文的 

实现部分再作详细介绍。 

Quit:一个函数指针,当插件被WINAMP卸载的时候,它 

将被调用。在这个函数里,我们做一些最后的清理操作,如关 

闭工作窗口,释放申请的内存,保存工作参数等。 

userData:可选参数。在这里可以保存一些你认为需要的 

数据。 

、 

I_;=一 与满 

ytpedef struct{ 

intversion;//DSP DRVER 

char description;//description of library 

winampDSPModule:j:( getModule)(int): 

/module retrieval function 

)winampDSPHeader; 

其中: 

version:这是一个固定值,应填充常数DSP_HDRVER, 

它表示DSP插件的版本。常数DSP_HDRVER在头文件里被定 

义为0x20,表示本插件是用于WINAMP的。 

description:一个字符串,对DLL做一个简要描述,它将 

显示在插件管理窗口里。 

getModule:一个函数指针,WINAMP通过调用这个函数来 

枚举该DLL模块内的所有DSP插件。这个函数是必需的。我 

们将在本文的实现部分对它进行详细描述。 

DLL的导出函数的类型说明如下: 

,/exported symbols 

typedef winampDSPHeader ( winampDSPGetHeader- 

Type)(): 

2.消歌声原理 

通常,在立体声歌曲里,为了体现伴奏音乐的空间感,它 

在左右两个声道的分布是有比较大的区别的。但是人声却正好 

相反,为了使听众感觉歌声的定位准确,它在左右两个声道的 

分布基本是一致的,包括信号的幅度和相位。当然,在实际的 

歌曲中不可能完全做到,特别是当歌曲的录音质量比较差的时 

候。但是质量好的歌曲,其人声在左右声道的差别还是很小 

的。我们正是利用这一点,从歌曲里区分出人声和伴奏的。 

但是仅仅利用这一点还是不够的,因为在歌曲里,往往还 

有相当多的低频成份,而人耳有对低频声音的不敏感以及低频 

声音本身的定位困难的特点。所以往往在歌曲里,伴奏的低频 

音乐信号在左右声道中也是非常相似的。如果我们在进行消歌 

声的同时也大大消除了伴奏中的低频音乐,那么伴奏听起来就 

没有力度,会有一种虚无飘渺的感觉。通常的做法是,我们先 

将人声频段从背景音乐中分离出来,因为相对来说,人声的频 

段比起整个音乐的频段来说要窄得多。这样就可以尽可能地保 

留原来音乐的风格不变。 

人耳能听到的声音频率约为20—20KHz,对于现在流行的 

高品质音乐,信号一般都是包含声音的整个频段的,如CD音 

乐。但超过15KHz的声音,大多数人都很难听到了。 

人声的频率范围大约在300~3400Hz之间,男声和女声 

略有差别。利用这个特点,我们先通过带通滤波器将人声频段 

从歌曲中分离出来,然后将左右声道的人声相减,因为人声在 

一lll珏 

r ,_

蛙一 

羹 

… .= 

。 

一一

 w

维普资讯

謦 ≥ 

瘩骘 萼謦蠢 

蠢 

辨黔譬纛勰 

。 t一 舞_ 垮薯 。 二 _ 

∥ 一 

编程语言 

PROGRAM I_AN( {AGE 

f:滤波器的截止频率。r:信号的采样频率。Q:滤波 

器的品质因数。 

左右声道的分量一致,这样该频段内的人声就相互抵消了。分 

离出人声后的左右声道音乐由于缺少关键的中频段,所以如果 

直接播放出来的话会显得很空洞。这样,我们将消除了人声之 

后的中频段再分别加到左右两个声道上去,这样处理之后。人 

四、实现 

声基本上消除了,同时也最大地保留了伴奏音乐的本来面目。 

J 

|.P=—= ..; 

入 

▲ 

▲一. 

混 r 

、 

输出 

● 

自道 左声il 

二一垒 ^,t减法 卜t —’l 

 

 

r一 

、 

r 

f道 右声娃 

. 

_ =・ 一.1 

; 

消歌声处理流程图 

3.双二次滤波算法 

双二次滤波算法是一种简单而快速的数字信号滤波算法。 

通过计算出的不同的滤波器参数。它可以完成低通滤波,高通 

滤波,带通,带阻等多种运算。双二次滤波器算法的基本公式 

如下: 

y =aoxn+alx ~I+a2xn—blYn—l—b2yn 2 

其中: 

Y :第n次输出。x :第n次输入。 

x n--:第n一1次输入…X z:第n一2次输入。 

Y 一第n一1次输出…Y::第n一2次输出。 

ao,aI,a2,bl,b2:滤波器系数。 

●高通滤波器系数按如下方式计算: 

2nf sirrn 1 

, , 

%=

号(1+c0s(t,),al:一s(1+c0s(t,),龟=% 

bl=一2scosoJ,b2=(1一a) 

●低通滤波器系数按如下方式计算: 

2nf sinm 1 

了’ 酉 

Oo:

号(1一co鲫), =s(1一c0s(t,), =Oo 

bl=一2scosoJ,b2:(1一a)s 

其中: 

为了方便起见,我们使用了VC6开发环境。并且使用了 

MFC库来编写。 

1.生成程序框架 

用向导生成一个基于MFC的DLL工程的基本框架。我们 

在这个框架的基础上进行进一步的开发。我们需要修改一下工 

程的设置,以便进行调试并看到WINAMP加载我们的控件之 

后的效果。 

打开工程属性对话。在LINK页,将工程的输出文件路径 

改到WINAMP2的安装目录下的plugins目录下,并修改输出 

文件的文件名为dsp_if.lter,dll。比如,你的WINAMP安装在C:、 

Program Files、winamp、下,则设置输出文件为:C:、Program 

Files、winamp、plugins\dsp filter,dll。切换到Debug标签下,为 

调试器设置可执行文件的路径,浏览到WINAMP的主执行文 

件winamp,exe,选中即可。这样设置之后,当我们编写好代 

码之后就可以调试了,也可以直接运行查看结果了。 

2,双二次滤波算法封装类 

我们首先对双二次滤波算法进行C++类封装。从菜单选 

择插入新类,取名为Cfilter。 

向Cfilter类添加几个函数和变量,其中粗体部分为薪加 

的: 

class CFilter 

f 

public: 

CFilter《): 

virtual~CFilter《): 

//设置滤波器参数 

voidSetFilterParament(intfrequency,floatQ=1.Of): 

//滤波函数 

vitrual void Filter(short pData,int num,int nch,int srate): 

protected: 

//计算滤波系数 

vitrual void CalcFilterParament《)=0: 

int_sr;//采样频率 

int.

_

f;//截止频率 

float ://品质因数 

float

_

aO,

_

al,-a2://滤波器参数 

float_j)1,一b2: 

private: 

//历史值 

int

_

xnl 

_

xn2

__

1 

_

ynl 

_

yn2 

int xnl-2.._xn2 。_ynl-2,_yn2.2: 

) 

constfloatPI=3.1415926f;// 值 

鼍 

¨1  。

维普资讯

j 

襄藕嘲— ■囊薯譬 

¨1 

¨ 

i? 

 =

+ 

,一 

, 

编程语言 

PR0GRAM t ANGU^G 

。 一 

//截断函数 

//template<class T> 

inline int B0UND(intx,int min,int max) 

f 

return(x<min)7min:((×>max)?max:x): 

} 

这个类是封装的基本双二次滤波算法,我们将计算滤波系 

数的成员函数定义为纯虚函数。因为它不知道如何计算滤波算 

法系数。这样,我们就不能对这个类进行实例化,我们需要从 

这个类派生出具体的滤波器类,因为只有一个具体的滤波器才 

知道怎样来计算它的系数。 

我们需要这个类里的几个关键函数。SetFilterParament函 

数用来设置滤波器参数,在这个函数里,我们应该将参数保留 

下来,并调用滤波系数计算函数去计算实际工作时的滤波系 

数。Filter函数是滤波算法的核心函数r因为不同的滤波器仅 

仅表现为滤波器系数的差别,而算法都是一样的,所以应该在 

基类里实现它。另外我们还需要在类的构造函数里添加成员变 

量的初始化代码。 

部分源代码如下: 

//////////////////////////////////// 

//COnstructiOn/Destruction 

/////////////////////////////////// 

CFilter::CFilter() 

( 

//初始化参数 

_

xnl_1=0,_xn2j=0,_ynlj=0,_yn2_1=O: 

_

xnl-2=0._xn2_2=0,_ynl-2=0,_yn2_2=o= 

_

a0=1.Of._al=1.Of,

_

a2=1.Of; 

_j)1=1.0f._j)2=1.0f: 

_

sr=44100; 

j=1000; 

=1.Of; 

} 

¨_一 

CFilter::~CFilter() 

( 

} 

//设置滤波参数 

void C Filter::SetFilterParament(int frequency,float Q) 

( 

j=frequency; 

=Q: 

CalcFilterParament(): 

} 

//滤波函数 

voidCFilter::Filter(short pData,intnum,intnch.intsrate) 

( 

//如果采样率改变,应重新计算参数 

_f(srate!=_sr) 

( 

与 

r srate; 

CalcFilterParament(): 

} 

iflnch==2)//双声道 

( 

short I pData; 

short r=pData-i-1: 

inti=num: 

while(i一一>0) 

( - 

//计算输出 

intlyn=(int)(_a0 :( ”-i-_a1%xn1j-i-_a2 _xn2_l 

一 

1¥

_

ynl_1一_!)2 _yn2.1-): 

intryn:(int)(_a0 ( r)-i-.al¥_xnl -i-_a2 :_xn2_2 

-b1 _ynl 一_!)2’ _yn2-2): 

//更新历史数据 

xn2

_

l=

__

xnlj:_xnl_1=; I: 

yn2_1=_ynlj:_ynl_1=lyn; 

_

xn2-2=_xnl_2:_xnl-2= r: 

_

yn2-2:_ynl_2:_ynl_2=ryn; 

//截断至16位 

lyn=BOUND(1yn,-32768,32767): 

ryn=BOUND(ryn,一32768,32767): 

I:lyn; 

r=ryn: 

I-i-=2: 

r-i-=2: 

} 

) 

else_f(nch==1)//单声道 

( 

short m=pData; 

int;=num: 

while(i一一>0) 

( 

intmyn=(int)(_a0 ( m)+-a1 _xnlj-i-_a2 n2J 

_j)1¥_ynlj一_b2 _yn2_1): 

if(myn>32767)myn:32767; 

elseif(myn<-32768)myn=~32768; 

_

xn2_1 _xnl_1:_xnl_1= m: 

_

yn2

_

l=

_

ynlJ:_ynlj=myn; 

m=myn; 

m++: 

} 

} 

} 

下面,我们要从这个滤波器的基类派生出有实际意义的几 

个滤波器。 

(1)高通滤波器类的说明: 

#include Filter.h 

//高通滤波器 

classCHighPass ter:publicCFilter 

海 

嚣孽叠 ;薯 

维普资讯

_¨ 

{ 

public: 

virtual void CalcFilterParament(): 

}: 

这个类很简单,我们只需要实现计算滤波系数的函数即 

可。 

//计算高通滤波器参数 

void CHighPass ter::CalcFilterParament{) 

{ 

floatomega=(2.Of PI:l=j)/一sr: 

floatsin

_

omega=sinf(omega): 

floatcos

_

omega=cosf(omega): 

floatalpha=sin

_

omega/(2.Of% ): 

floatscalar=1.Of/(1.0f+alpha): 

_

a0=0.5f:l=(1.0f+cos__omega): scalar; 

_

al=一(1.0f+cos_omega) scalar; 

_

a2=j0: 

_b1=一2.Of===cos omega scalar; 

一b2=(1.Of—alpha):=:scalar; 

} 

(2)低通滤波器类的说明: 

#include Filter.h 

//低通滤波器 

class CLowPassFilter:public CFilter 

{ 

public: 

vitrual void CalcFilterParament(): 

}: 

同高通滤波器一样,我们只需要实现计算滤波系数的函数 

即可: 

//计算低通滤波器参数 

void CLowPassFilter::CalcFilterParament{) 

{ 

floatomega=(2.Of¥PI j)/_sr: 

floatsin

_

omega=sinf(omega): 

floatcos

_

omega=cosf(omega): 

floatalpha=sin

_

omega/l2.Of¥ ): 

floatscalar=1.Of/(1.0f+alpha): ・ 

aO=0.5f}(1.Of—cos_omega):=:scalar; 

_

al=(1.0f—cos_omega) scalar; 

a2=j0 

_b1=一2.Of cos_omega scalar; 

-j)2=《1.Of—alpha) scalar;  ’

} 

带通滤波器有点特殊,它是一个高通滤波器和一个低通滤 

波器的串联,我们让它同时继承高通滤波器和低通滤波器两个 

类,类说明如下: 

#include HighPassFilter.h 

#include LowPassFilter.h 

//带通滤波器 

class CBandPassFiIter: 

曩  ̄.411■穗 

编程语言 

PR0(jRAM l ANt;ljA(j 

public CHighPassFlIter, 

public CLowPassFilter 

{ 

public: 

//滤波函数 

void Filter(Short pData,intnum,intnch.intsrate): 

//设置滤波参数 

void SetFilterParament(int hf,int If,float hQ=1.Of,float 

IQ=1.0f): 

}: 

因为是两上滤波器的串联应用,所以这里我们重写了基类 

酌Filter函数,部分源代码如下: 

//滤波函数 

VoidCBandPassFiIter::Filter(short pData,intnum,intnch 

int srate) 

{ 

//一个高通和一个低能滤波器串联操作 

CHighPassFiIter::Filter(pData,num,nch,srate) 

CLowPassFilter::Filter(pData,num,nch,srate) 

} 

void CBandPassFiIter::SetFilterParament(int hf,int If,float 

hQ,floatIQ) 

{ 

CHighPassFiIter::SetFilterParament{hf,hQ): 

CLowPassFilter::SetFilterParament(If.IQ): 

} 

3.主界面 

在窗13上,设置了一个开关,还有几个调节频段和平衡的 

控件。见下图。图下面则显示一些统计数据。篇幅所限这里不 

再列举那些跟界面控制相关的代码了。 

插件运行时的主界面 

4.实现DSP插件规定的接口 

DSP插件需要实现的接13其实在前面已经说得很清楚了。 

我们自顶向下来一步步实现它。首先,我们来实现插件必需 

导出的函数:winampDSPGetHeader2。这个函数很简单,它要 

一 .. 

_ 

。 

甄胃囊菇。0麓囊蠢鬃 。 

维普资讯

— 

编程语言 

l,R0 ;RAM l。AN(jUAG 

鼍 謦1 

做的就是返回代表本插件的一个数据结构。我们将插件接口有 

关的代码全部放在一个单独的源文件内,Module.cpp。文件的 

开始应该包含dsp.h头文件,我们假设你已经将dsp.h头文件 

拷贝到了当前工程的源代码目录下。 

GetModule是另一个跟整个插件DLL有关的重要函数,它 

的作用就是被WINAMP主程序调用来枚举DLL内的所有插件 

信息。这个函数有一个参数,它是一个整数索引号。WINAMP 

主程序将以从0开始的索引号来反复地调用这个函数。这个函 

数的任务也很简单,就是返回第0个DSP插件信息描述结 

构、第1个DSP插件信息描述结构,等等。那么如果没有那 

么多插件怎么办?该函数应该在当索引值超过插件数时返回 

NULL。这时,WINAMP就知道枚举结束了。这个函数的指针 

从模块信息数据结构中传递给WINAMP主程序。 

Init函数用来对插件进行初始化,它的工作是新建一个工 

作对话框(见主界面部分)。相应地,我们要实现Quit函 

数,在这里将对话框删除。 

DoFflter函数用来实现音频数据的处理,它直接调用对话 

框类的相应函数来实现的。具体的处理过程是在对话框类的成 

员函数Filter内完成的。这个函数有几个比较重要的参数。这 

些参数是直接从WINAMP主程序里传人的。其中: 

samples:指向音频信号数据缓冲区(16Bit),它是混合 

数据,即多个声道混杂在一起的。例如,对于双声道信号,第 

1个数据应该是声道1的第一个数据,第2个数据则是声道2 

的第一个数据,第3个数据是声道1的第二个数据,以此类 

推。 

numsamples:每个声道的数据量,如果是多个声道,那 

么,总的数据量应该是它乘以声道数。 

bps:信号的比特数,8Bit或者16Bit。 

neh:音频信息的声道数, 立体声信号的声道数应该是 

2。 

srate:信号的采样频率,指每个声道每秒钟的采样数。 

当数据处理完成之后,函数返回处理后的数据量,通常就 

是传人函数的参数numsamples的值。然而,你也可以返回其 

它值,但不应大于numsamples的2倍,也不应小于它的一 

半。WINAMP在准备好数据调用你的处理函数的时候。对音 

频数据的缓冲区是有一定的余量的,为输人数据量的2倍。于 

是你有机会去修改音频数据的总量。你可以试试会产生什么样 

的效果。 

Config函数原本是用来对控件进行配置的。在这里,我们 

不需要进行配置,因为所有的参数都可以在工作界面窗口内实 

时地进行调整。然而,我们还是利用了一下这个函数,它做了 

件跟配置完全不同的另一件事:显示插件的About对话 

 ̄2006.5 

与‘ 

框;) 

以上几个函数都有一个共同的参数:this_mod。它既是代 

表本插件的数据结构,也就是我们从GetModule函数中返回 

的。 

Iint,Quit,DoFiher,Config,四个函数共同完成一个DSP 

插件的完整功能。它们的指针放在表示插件信息的结构modl 

中由函数GetModule传递到WINAMP主程序内。 

下面是接口实现的关键代码: 

//Module.cpp一一一一实现DSPplug—ins接口 

#include dsp.h 

#include FilterDIg.h 

winampDSPModule GetModule《int which): 

//插件枚举函数 

void Config l struct winampDSPModule: this

_

rood) 

//显示关于对话《利用配置函数) 

int Ink《structwinampDSPModule: this_rood): 

//插件初始化 

void Quit《struct winampDSPModule{this_rood): 

//插件退出前的清理 

int DoFilter《struct winampDSPModule:#this

rnod, 

short int samples,int numsamples, 

intbps,intnch,intsrate)://音频数据处理 

//插件DLL的信息描述数据结构 

winampDSPHeader hdr=( 

DSP 

HDRVER .

消歌声插件forWlNAMP2 , 

GetModule 

): 

//DLL的唯一导出函数,它返回DLL的信息描述数据结构 

指针 

extern C 

__

declspec《dllexport)winampDSPHeader*winampDSPGet- 

Header2《) 

( 

return&hdr; 

) 

//消歌声插件信息描述数据结构 

winampDSPModule orod 1= 

( 

消歌声处理 , 

NUL L. //hwndParent 

NULL, //hDIIInstance 

Config, 

Init, 

DoFilter, 

Quit, 

NULL 

) 

winampDSPModule=::GetModule《int index) 

( 

switchlindex) 

( 

 ^

~ 

一 

囊 

著■ 旗裁 

§麓 鼙蓍 j 

鼍 一 一艚

蜜胃l簟 誓■■囊, 

维普资讯

一 

辑; 羹一。 毫 

0 

caseO:return&modl: 

default:return N U LL; 

} 

//全局工作对话框指针,当插件被初始化时,工作对话框创 

//建,插件被卸载时,对话框被删除 

CfilterDIg g

_

pDIg=NULL; 

//数据缓冲区 

short bufl【65536】://带通滤波缓冲器 

short buf2【65536 J://低通滤波缓冲器 

short buf3【65536l://高通滤波缓冲器 

//消人声函数 

void CFilterDIg::Filter{short★pData,int num,int nch,int 

srate) 

{ 

//更新统计数据 

nch=nch; 

m-srate=srate; 

_

bytes 4-=num: 

_

calls 4-4-: 

//应该在双声道下工作 

if{nch==2&&m-j)Enable) 

{ 

//将音频数据分解成高频段,低频段,及包含人声的中频段 

memcpy{bufl,pData,num nch sizeof{short)): 

memcpy{buf2,pData,num%nch¥sizeof{short)): 

memcpy{buf3,pData,num nch sizeof{short)): 

_

filterB.Filter{bufl,num,nch,srate): 

m_f,ilterL.Filter{buf2,num,nch,srate): 

_

filterH.Filter{buf3,num,nch,srate): 

short a=bufl: 

short¥b=buf2;。 

short¥C=buf3; 

short out:pData; 

for{inti=O:i<num:++i) 

{ 

//对人声频段做差运算 

intisb={int){a【0】★mJs—a【1】-k m』s 4-0.5f): 

//合成新的左右声道 

intI=isb 4-b【0】4-C【0】: 

int r:isb 4-b【1】4-C【1】: 

//截断至16位 

I:BOUND{I,.-32768,32767): 

r=BOUND{r,一32768,32767): 

//输出 

out【0】=I: 

out【1】=r: 

a 4-=2: 

b 4-=2: 

C 4-=2: 

out 4-=2: 

} 

} 

} 

编程语言 

PROGRAM l AN(itj^【jE 

//显示关于对话框 

void Config{struct winampDSPModule: this_mod) 

{ 

::MessageBox{this_mod一>hwndParent, 使用双二 

次滤波器的消歌声程序\n 

Copyright{C)2005 , 消歌声插件 ,MB_OK l 

MBJCONWARNING): 

} 

//初始化函数{显示调节对话框) 

intInit{structwinampDSPM0dule this_mod) 

{ 

AFX_MANAG E

_

STATE{AfxGetStaticModuleState{)): 

ifIg_pDIg!=NULL)deletegJoDIg; 

gdoDIg=new CFilterDIg I CWnd::FromHandle 

{this_mod一>hwndParent)): 

gdoDIg一>Create{): 

gdoDIg一>ShowWindow(SW_SHOW): 

return O: 

} 

//退出处理 

void Quit{struct winampDSPModule%this_mod) 

{ 

AFX

_

MANAGE

_

STATE{AfxGetStaticModuleState{)): 

.f{gjoDIg) 

gdoDIg一>DestroyWindqw{): 

gdoDIg=NULL; 

} 

//音频处理函数 

int DoFilter{structwinampDSPModule this__mod. 

short int samples,int numsamples, 

int bps,int nch,int srate) 

{ 

ifIbps==16) 

{ 

.f{gJoDIg!=NULL) 

gJoDIg一>Filter{samples.numsamples,nch,srate): 

} 

return numsamples; 

} 

5.实际运行 

完成插件的代码编写之后,编译工程。这样插件DLL应 

该已经生成,并且已位于正确的位置了。然后启动WINAMP 

播放器,或者直接利用开发环境的运行命令。因为我们已经设 

置好了WINAMP播放器为调试主文件。 

注意,WINAMP出现在屏幕上以后,WINAMP并不会自 

动加载我们编写的插件,需要对WINAMP设置一下,指定运 

行插件才行。 

选择WINAMP主菜单一>选项一>参数设置...,或者直接 

按快捷键CTRL+P,打开WINAMP参数设置对话框。左边的 

树上选中插件下的音效处理,特效。这时,右边列表里已经出 

现了一条名为“消歌声插件for WINAMP2【dsp_ifher.dll】.'的 

维普资讯

囊■ 弩曩■薯簟 

编程语言 

PROGRAM l ANG JA(; 

仿函数配接器的扩展和实现 

张斌 

摘 要 

C++标准库的STL中用到了仿函数(也称为函数对象)的概念,实际上,其中 

所有的算法都提供了一个用仿函数作为其参数的重载版本,从而决定其行为。 

对于每个普通的一元和二元运算符,标准库都提供了一个仿函数,对于组合而 

言,标准库还提供了几个标准的仿函数和配接器,然而’C++标准库并没有 

提供足够的组合型配接器,用于支持仿函数之间的组合。本文将对一些常用的 

仿函数组合进行扩展,并具体实现出几个自定义仿函数和几个常用的组合型配 

接器。 

关键词 

仿函数(functors),函数对象(function objects),函数配接器(function 

adapters),组合型配接器(compose adapters) 

在标准c++中,其强大的重载功能几乎无所不能,所谓 

仿函数,就是运用c++强大的运算符重载功能,在一个类中 

把重载了operator()运算符的函数作为类的成员函数,并通 

过该类的一个对象,可以象调用普通函数那样对其调用。从形 

实现。然而,C++标准库并没有提供足够的组合型配接器, 

用于支持仿函数之间的组合,在SGI STL和Boost中提供了一 

些常用的组合,如f(g(x))、f(g(x,Y))、f(g.(x),h 

(x))、f(g(x),h(y))等。 

本文将讨论几个常用的仿函数和组合型配接器的实现方法 

及具体实现: 

自定义仿函数:pow_functor,sin functor,cos_functor,自定 

义仿函数配接器:f(g(x),h(y))、z(f(g(x),h 

(y)))、z(f(g(x)),f(h(x))其中:z,f,g,h, 

分别为任意函数,x,Y为任意类型的参数。 

式上看,仿函数的定义比起普通的函数定义要显得复杂,但在 

功能上却有其独到的特点。第一,因为是通过类的对象来引 

用,因此仿函数可以拥有内部状态。第二,因为定义为类的成 

员,因此仿函数是有类型的。第三,仿函数在执行速度上比一 

般函数指针要快。基于其自身优越的性能和特点,仿函数在 

C++标准库的STL中得到了广泛地应用。 

般而言,几乎所有的函数行为都可以由仿函数的组合而 

\ 

选项,它就是刚刚写的插件。如果没有看到它,那么就要检查 

插件的开发,完全跟DSP插件类似。另外其它版本的插件开 

发与本文介绍的2.x版本的开发基本一致,可以通过访问 

下你的工程,同时要确定一下你的WINAMP版本是否为 

“2.x”。一定要选择与你的WINAMP版本相应的SDK来编写 

http://www.winamp.com/网站,阅读相关的资料来进行开 

发。 

你的插件。 

在插件列表内选中我们的插件后,插件的界面就出来了。 

放一首歌,可以慢慢调整一下高频值和低频值,还有平衡,使 

得消声效果最好。 

参考文献 

1.http://www.winamp.com/nsdn/winamp/sdk/,Winamp 

Software Development Kit,NullSofl 

五、总结 

本文介绍了怎样开发WINAMP播放器软件的插件,并详 

2.Phil Burk.使用双二次共振滤波器.游戏编程精粹 

(3).人民邮电出版社,2003年7月 

(收稿日期:2006年3月26日) 

细讲解了DSP插件,一个消歌声插件的开发过程。其它类型 

毫■囊藿按_巧与 

本文标签: 插件函数进行滤波实现