分享
 
 
 

Wimamp 视觉效果插件 DIY

王朝vc·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

我想:在回答“你用什么播放器听 MP3”这个问题时,90%的人都会回答 Winamp!那么你一定用过 Winamp 的插件功能吧,正是多样化的插件使这个“老”播放器不断地焕发青春。不管新推出什么音频格式(MP4,VQF,RM...),只要插件一装就能播放。还有形形色色的可视插件,比如 Giess 等等,将音乐的节奏感表现的可谓淋漓尽致!

既然插件是用程序编写的,那么我们何不来一试身手,动手做它一个出来?!用过 Winamp 的人都知道,Winamp 插件是放在 Pulgin 文件夹中一个个的 DLL(动态链接库)文件,所以编写 Winamp 插件其实就是编写 Windows 的动态链接库。当然写的时候是要遵循一定的规范的(相关文档可以从 www.winamp.com 下载),在这方面,Winamp 作者 Justin Frankel 写的一个可视插件的例子可以作为我们很好的参考。下面我们就以这个例子(当然也是一个编写规范)为参考,认识一下 Winamp 可视插件的编写方法。

(下面的程序可从 Winamp 官方网站下载,文件名为 vis_minisdk.zip)

首先让我们看一下可视插件使用的数据结构(在文件 Vis.h 中)

// 注意:

// 任何呆在前台的插件窗口都应该将按键传送给其父(WinAMP 的)窗口,以确保

// 用户仍旧可以控制 WinAMP(除非用户按了 ESC 键或者插件所指定的键)。

// 在存储配置时,配置数据应当统一存放在 <dll directory>\plugin.ini 中。

// 请将这个插件例程看作一个框架。

typedef struct winampVisModule {

char *description; // 模块描述(出现在插件选择列表框下面得下拉列表框中)

HWND hwndParent; // 父窗口------------- (由主调应用填充)

HINSTANCE hDllInstance; // 此 DLL 的实例句柄 - (由主调应用填充)

int sRate; // 采样速率 ---------- (由主调应用填充)

int nCh; // 声道数 ------------ (由主调应用填充)

int latencyMs; // 从调用 RenderFrame 到真正绘制的潜伏时间(毫秒)

// (主调应用在获取数据的时候会查看这个值)

int delayMs; // 每两次调用之间的间隔时间(毫秒)

// 数据依照各自的 Nch(声道数) 条目被填充

int spectrumNch;

int waveformNch;

unsigned char spectrumData[2][576]; // 频谱数据

unsigned char waveformData[2][576]; // 波形数据

void (*Config)(struct winampVisModule *this_mod); // 模块配置函数

int (*Init)(struct winampVisModule *this_mod); // 初始化函数(创建窗口等等)。成功返回0

int (*Render)(struct winampVisModule *this_mod); // “表演”函数。成功返回0,如返回1表示插件应该终止

void (*Quit)(struct winampVisModule *this_mod); // 模块退出函数。完成之后调用

void *userData; // 用户数据 (可选)

} winampVisModule;

typedef struct {

int version; // VID_HDRVER (当前模块的版本)

char *description; // 插件的描述(出现在选择插件对话框的插件列表框中)

winampVisModule* (*getModule)(int); // 用来获取模块结构

} winampVisHeader;

// 定义导出标识

typedef winampVisHeader* (*winampVisGetHeaderType)();

// 当前模块的版本 (0x101 == 1.01)

#define VIS_HDRVER 0x101

上面列出的是一个编写可视插件必须包含的头文件,里面列出了可视插件用到的数据结构。在探讨具体插件程序之前,有一些概念必须搞清:一个可视插件中可以包含若干个模块(每一模块都是一种演示效果,可以在插件选择对话框中选择用哪个模块来演示),这些模块通过某种方法(后面将会看到)被 Winamp 获取,从而得到“表演”的机会。简而言之,Winamp 利用所有插件 DLL 中导出的一个统一名称的函数获得了一个插件头数据结构,然后通过此数据结构中的一个函数再去获取各个模块的信息(这个过程与 COM 的 QueryInterface() 用法有些神似,看来好的设计思想是相通的),进而利用多线程(通过 DLL View 观察得知)实现可视插件的展示。下面就是可视插件的源程序:

// Winamp 测试用可视插件 v1.0

// 版权所有 (C) 1997-1998, Justin Frankel/Nullsoft

// 基于此框架可自由的编写任何可视插件...

#include <windows.h>

#include "vis.h"

char szAppName[] = "SimpleVis"; // 窗口类名

// 有关配置的声明

int config_x=50, config_y=50; // 窗口在屏幕上的横纵坐标

void config_read(struct winampVisModule *this_mod); // 读配置

void config_write(struct winampVisModule *this_mod); // 写配置

void config_getinifn(struct winampVisModule *this_mod, char *ini_file); // 生成一个 .ini 文件名

// 在需要的时候返回一个 winampVisModule,用在下面的 hdr 中。WinAMP 可由此得知插件中的模块数。

winampVisModule *getModule(int which);

// "成员"函数

void config(struct winampVisModule *this_mod); // 模块配置函数

int init(struct winampVisModule *this_mod); // 模块初始化函数

int render1(struct winampVisModule *this_mod); // 模块1 的“表演”函数

int render2(struct winampVisModule *this_mod); // 模块2 的“表演”函数

int render3(struct winampVisModule *this_mod); // 模块3 的“表演”函数

void quit(struct winampVisModule *this_mod); // 模块结束的清理函数

// 插件窗口的窗口处理函数

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);

HWND hMainWnd; // 主窗口句柄

// 双缓冲数据

HDC memDC; // 内存DC

HBITMAP memBM, // 内存位图 (for memDC)

oldBM; // old bitmap (from memDC)

// 模块头部。包括模块版本,插件描述(出现在选择插件对话框的插件列表框中)和模块接口函数的地址

winampVisHeader hdr = { VIS_HDRVER, "Nullsoft Test Visualization Library v1.0", getModule };

// 第一模块 (示波器)

winampVisModule mod1 =

{

"Oscilliscope",

NULL, // hwndParent

NULL, // hDllInstance

0, // sRate

0, // nCh

25, // latencyMS

25, // delayMS

0, // spectrumNch

2, // waveformNch

{ 0, }, // spectrumData

{ 0, }, // waveformData

config,

init,

render1,

quit

};

// 第二模块 (光谱分析)

winampVisModule mod2 =

{

"Spectrum Analyser",

NULL, // hwndParent

NULL, // hDllInstance

0, // sRate

0, // nCh

25, // latencyMS

25, // delayMS

2, // spectrumNch

0, // waveformNch

{ 0, }, // spectrumData

{ 0, }, // waveformData

config,

init,

render2,

quit

};

// 第三模块 (VU meter)

winampVisModule mod3 =

{

"VU Meter",

NULL, // hwndParent

NULL, // hDllInstance

0, // sRate

0, // nCh

25, // latencyMS

25, // delayMS

0, // spectrumNch

2, // waveformNch

{ 0, }, // spectrumData

{ 0, }, // waveformData

config,

init,

render3,

quit

};

// 这是插件 DLL 中仅有的一个导出函数,用来返回插件头结构体指针,从而进一步得

// 知插件中各个模块的信息。

// 如果你正在编译 C++ 程序,extern "C" { 就是必要的,所以我们使用了 #ifdef。

#ifdef __cplusplus

extern "C" {

#endif

__declspec( dllexport ) winampVisHeader *winampVisGetHeader()

{

return &hdr;

}

#ifdef __cplusplus

}

#endif

// 在得到插件头结构体指针后用来获取模块信息。

// 如果一个不存在的模块被请求就返回 NULL,否则依据 'which' 来返回各可用模块(结构体指针)中的一个。

winampVisModule *getModule(int which)

{

switch (which)

{

case 0: return &mod1;

case 1: return &mod2;

case 2: return &mod3;

default:return NULL;

}

}

// 模块配置函数。通过 this_mod 可得知要配置哪个模块。

// 允许你的所有模块共用一个配置函数。

// (当然你不一非定要使用这个名字,你可以建立 config1(), config2(), 等等...)

void config(struct winampVisModule *this_mod)

{

MessageBox(this_mod->hwndParent,"This module is Copyright (c) 1997-1998, Justin Frankel/Nullsoft\n"

"-- This is just a demonstration module, it really isn't\n"

" supposed to be enjoyable --","Configuration",MB_OK);

}

// 模块初始化函数。注册窗口类,创建窗口等等。

// 这是所有模块都要做的工作,但是你也可以建立 init1() 和 init2()...

// 成功返回 0,失败返回1。

int init(struct winampVisModule *this_mod)

{

int width = (this_mod == &mod3)?256:288; // mod1 和 mod2 的宽高相等

int height = (this_mod == &mod3)?32:256; // 但是 mod3 的与另外两个不同

config_read(this_mod); // 读取配置信息

{ // 注册窗口类

WNDCLASS wc;

memset(&wc,0,sizeof(wc));

wc.lpfnWndProc = WndProc; // 窗口处理过程

wc.hInstance = this_mod->hDllInstance; // DLL 的实例句柄

wc.lpszClassName = szAppName; // 窗口类名

if (!RegisterClass(&wc))

{

MessageBox(this_mod->hwndParent,"Error registering window class","blah",MB_OK);

return 1;

}

}

hMainWnd = CreateWindowEx(

WS_EX_TOOLWINDOW|WS_EX_APPWINDOW, // 这些扩展风格建立一个好看的小窗口外框,

// 但是窗口在任务栏上有一个按钮

szAppName, // 窗口类名

this_mod->description, // 窗口标题使用模块描述

WS_VISIBLE|WS_SYSMENU, // 使窗口可见并且有一个关闭按钮

config_x,config_y, // 窗口的屏幕位置 (从配置中读取)

width,height, // 窗口的宽高 (需要在后面调整客户区尺寸)

this_mod->hwndParent, // 父窗口句柄 (WinAMP 主窗口)

NULL, // 无菜单

this_mod->hDllInstance, // DLL 的实例句柄

0); // 无窗口创建数据

if (!hMainWnd)

{

MessageBox(this_mod->hwndParent,"Error creating window","blah",MB_OK);

return 1;

}

SetWindowLong(hMainWnd,GWL_USERDATA,(LONG)this_mod); // 将窗口用户数据设为模块结构体指针

{ // 调整窗口尺寸以使得客户区等于宽乘高

RECT r;

GetClientRect(hMainWnd,&r);

SetWindowPos(hMainWnd,0,0,0,width*2-r.right,height*2-r.bottom,SWP_NOMOVE|SWP_NOZORDER);

}

// 创建双缓冲

memDC = CreateCompatibleDC(NULL);

memBM = CreateCompatibleBitmap(memDC,width,height);

oldBM = SelectObject(memDC,memBM);

// 显示窗口

ShowWindow(hMainWnd,SW_SHOWNORMAL);

return 0;

}

// 示波器模块的“表演”函数。成功返回0,如果插件应该被终止则返回1。

int render1(struct winampVisModule *this_mod)

{

int x, y;

// 清除背景

Rectangle(memDC,0,0,288,256);

// 绘制示波器

for (y = 0; y < this_mod->nCh; y ++) //有几个声道就花几条波形图

{

MoveToEx(memDC,0,(y*256)>>(this_mod->nCh-1),NULL);

for (x = 0; x < 288; x ++)

{

LineTo(memDC,x,(y*256 + this_mod->waveformData[y][x]^128)>>(this_mod->nCh-1));

}

}

{ // 将双缓冲复制到窗口中

HDC hdc = GetDC(hMainWnd);

BitBlt(hdc,0,0,288,256,memDC,0,0,SRCCOPY);

ReleaseDC(hMainWnd,hdc);

}

return 0;

}

// 频谱分析模块的“表演”函数。成功返回0,如果插件应该被终止则返回1。

int render2(struct winampVisModule *this_mod)

{

int x, y;

// 清除背景

Rectangle(memDC,0,0,288,256);

// 绘制分析仪

for (y = 0; y < this_mod->nCh; y ++) //有几个声道就花几条波形图

{

for (x = 0; x < 288; x ++)

{

MoveToEx(memDC,x,(y*256+256)>>(this_mod->nCh-1),NULL);

LineTo(memDC,x,(y*256 + 256 - this_mod->spectrumData[y][x])>>(this_mod->nCh-1));

}

}

{ // 将双缓冲复制到窗口中

HDC hdc = GetDC(hMainWnd);

BitBlt(hdc,0,0,288,256,memDC,0,0,SRCCOPY);

ReleaseDC(hMainWnd,hdc);

}

return 0;

}

// VU 表模块的“表演”函数。成功返回0,如果插件应该被终止则返回1。

int render3(struct winampVisModule *this_mod)

{

int x, y;

// 清除背景

Rectangle(memDC,0,0,256,32);

// 绘制 VU 表

for (y = 0; y < 2; y ++)

{

int last=this_mod->waveformData[y][0];

int total=0;

for (x = 1; x < 576; x ++)

{

total += abs(last - this_mod->waveformData[y][x]);

last = this_mod->waveformData[y][x];

}

total /= 288;

if (total > 127)

total = 127;

if (y)

Rectangle(memDC,128,0,128+total,32);

else

Rectangle(memDC,128-total,0,128,32);

}

{ // 将双缓冲复制到窗口中

HDC hdc = GetDC(hMainWnd);

BitBlt(hdc,0,0,256,32,memDC,0,0,SRCCOPY);

ReleaseDC(hMainWnd,hdc);

}

return 0;

}

// 模块清除函数 (对应于 init())。销毁窗口,取消窗口类的注册。

void quit(struct winampVisModule *this_mod)

{

config_write(this_mod); // 写入配置

SelectObject(memDC,oldBM); // 删除双缓冲

DeleteObject(memDC);

DeleteObject(memBM);

DestroyWindow(hMainWnd); // 销毁窗口

UnregisterClass(szAppName,this_mod->hDllInstance); // 取消窗口类的注册

}

// 插件窗口的窗口处理函数

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

{

switch (message)

{

case WM_CREATE: return 0;

case WM_ERASEBKGND: return 0;

case WM_PAINT:

{ // 以双缓冲的信息更新窗口显示

PAINTSTRUCT ps;

RECT r;

HDC hdc = BeginPaint(hwnd,&ps);

GetClientRect(hwnd,&r);

BitBlt(hdc,0,0,r.right,r.bottom,memDC,0,0,SRCCOPY);

EndPaint(hwnd,&ps);

}

return 0;

case WM_DESTROY: PostQuitMessage(0); return 0;

case WM_KEYDOWN: // 将键盘消息传递给 WinAMP 主窗口 (使其被处理)

case WM_KEYUP:

{ // 从窗口的用户数据中得到 this_mod

winampVisModule *this_mod = (winampVisModule *) GetWindowLong(hwnd,GWL_USERDATA);

PostMessage(this_mod->hwndParent,message,wParam,lParam);

}

return 0;

case WM_MOVE:

{ // 从配置中得到 config_x 和 config_y

RECT r;

GetWindowRect(hMainWnd,&r);

config_x = r.left;

config_y = r.top;

}

return 0;

}

return DefWindowProc(hwnd,message,wParam,lParam);

}

// 生成一个 <dll directory>\plugin.ini 形式的 .ini 文件名

void config_getinifn(struct winampVisModule *this_mod, char *ini_file)

{

char *p;

GetModuleFileName(this_mod->hDllInstance,ini_file,MAX_PATH);

p=ini_file+strlen(ini_file);

while (p >= ini_file && *p != '\\')

p--;

if (++p >= ini_file)

*p = 0;

strcat(ini_file,"plugin.ini");

}

void config_read(struct winampVisModule *this_mod)

{

char ini_file[MAX_PATH];

config_getinifn(this_mod,ini_file);

config_x = GetPrivateProfileInt(this_mod->description,"Screen_x",config_x,ini_file);

config_y = GetPrivateProfileInt(this_mod->description,"Screen_y",config_y,ini_file);

}

void config_write(struct winampVisModule *this_mod)

{

char string[32];

char ini_file[MAX_PATH];

config_getinifn(this_mod,ini_file);

wsprintf(string,"%d",config_x);

WritePrivateProfileString(this_mod->description,"Screen_x",string,ini_file);

wsprintf(string,"%d",config_y);

WritePrivateProfileString(this_mod->description,"Screen_y",string,ini_file);

}

正如我们前面所说,插件程序与主程序之间一定要有相互合作的规范才能正常的运作,比如约定好的函数名等等。从上面的程序中可以看出,Winamp 用约定好的函数名调用插件 DLL 中唯一的一个导出函数 winampVisGetHeader() 来获取一个指向插件头结构体 winampVisHeader 的指针,而后者包含的一个指针函数(*getModule)(int) 就可以根据给定参数向 Winamp 暴露模块的接口。^_^这样一来,插件的全部细节就都出现在 Winamp 面前了。因为 getModule 函数对没有实现的模块返回 NULL,所以 Winamp 就可以通过返回值确定可视插件中模块的数目了。其实上面的程序就是一个可视插件的编写框架,当明确了其中的规范之后就可以把精力放在“表演”函数的编写和具体实现上了。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有