新书推介:《语义网技术体系》
作者:瞿裕忠,胡伟,程龚
   XML论坛     W3CHINA.ORG讨论区     计算机科学论坛     SOAChina论坛     Blog     开放翻译计划     新浪微博  
 
  • 首页
  • 登录
  • 注册
  • 软件下载
  • 资料下载
  • 核心成员
  • 帮助
  •   Add to Google

    >> 本版讨论高级C/C++编程、代码重构(Refactoring)、极限编程(XP)、泛型编程等话题
    [返回] 计算机科学论坛计算机技术与应用『 C/C++编程思想 』 → WinAMP 插件DIY 查看新帖用户列表

      发表一个新主题  发表一个新投票  回复主题  (订阅本版) 您是本帖的第 3309 个阅读者浏览上一篇主题  刷新本主题   树形显示贴子 浏览下一篇主题
     * 贴子主题: WinAMP 插件DIY 举报  打印  推荐  IE收藏夹 
       本主题类别:     
     卷积内核 帅哥哟,离线,有人找我吗?
      
      
      威望:8
      头衔:总统
      等级:博士二年级(版主)
      文章:3942
      积分:27590
      门派:XML.ORG.CN
      注册:2004/7/21

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给卷积内核发送一个短消息 把卷积内核加入好友 查看卷积内核的个人资料 搜索卷积内核在『 C/C++编程思想 』的所有贴子 访问卷积内核的主页 引用回复这个贴子 回复这个贴子 查看卷积内核的博客楼主
    发贴心情 WinAMP 插件DIY

    我想:在回答“你用什么播放器听 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 键或者插件所指定的键)。
    // 在存储配置时,配置数据应当统一存放在 \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 获取,从而得到“表演”的机会。


       收藏   分享  
    顶(0)
      




    ----------------------------------------------
    事业是国家的,荣誉是单位的,成绩是领导的,工资是老婆的,财产是孩子的,错误是自己的。

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/11/14 11:45:00
     
     卷积内核 帅哥哟,离线,有人找我吗?
      
      
      威望:8
      头衔:总统
      等级:博士二年级(版主)
      文章:3942
      积分:27590
      门派:XML.ORG.CN
      注册:2004/7/21

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给卷积内核发送一个短消息 把卷积内核加入好友 查看卷积内核的个人资料 搜索卷积内核在『 C/C++编程思想 』的所有贴子 访问卷积内核的主页 引用回复这个贴子 回复这个贴子 查看卷积内核的博客2
    发贴心情 
    简而言之,Winamp 利用所有插件 DLL 中导出的一个统一名称的函数获得了一个插件头数据结构,然后通过此数据结构中的一个函数再去获取各个模块的信息(这个过程与 COM 的 QueryInterface() 用法有些神似,看来好的设计思想是相通的),进而利用多线程(通过 DLL View 观察得知)实现可视插件的展示。下面就是可视插件的源程序:

    // Winamp 测试用可视插件 v1.0
    // 版权所有 (C) 1997-1998, Justin Frankel/Nullsoft
    // 基于此框架可自由的编写任何可视插件...

    #include

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

    ----------------------------------------------
    事业是国家的,荣誉是单位的,成绩是领导的,工资是老婆的,财产是孩子的,错误是自己的。

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/11/14 11:46:00
     
     卷积内核 帅哥哟,离线,有人找我吗?
      
      
      威望:8
      头衔:总统
      等级:博士二年级(版主)
      文章:3942
      积分:27590
      门派:XML.ORG.CN
      注册:2004/7/21

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给卷积内核发送一个短消息 把卷积内核加入好友 查看卷积内核的个人资料 搜索卷积内核在『 C/C++编程思想 』的所有贴子 访问卷积内核的主页 引用回复这个贴子 回复这个贴子 查看卷积内核的博客3
    发贴心情 
    // 这是插件 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 ++) file://有几个声道就花几条波形图
      {
       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 ++) file://有几个声道就花几条波形图
      {
       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;
    }

    ----------------------------------------------
    事业是国家的,荣誉是单位的,成绩是领导的,工资是老婆的,财产是孩子的,错误是自己的。

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/11/14 11:46:00
     
     卷积内核 帅哥哟,离线,有人找我吗?
      
      
      威望:8
      头衔:总统
      等级:博士二年级(版主)
      文章:3942
      积分:27590
      门派:XML.ORG.CN
      注册:2004/7/21

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给卷积内核发送一个短消息 把卷积内核加入好友 查看卷积内核的个人资料 搜索卷积内核在『 C/C++编程思想 』的所有贴子 访问卷积内核的主页 引用回复这个贴子 回复这个贴子 查看卷积内核的博客4
    发贴心情 
    // 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);
    }

    // 生成一个 \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);
     onfig_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 就可以通过返回值确定可视插件中模块的数目了。其实上面的程序就是一个可视插件的编写框架,当明确了其中的规范之后就可以把精力放在“表演”函数的编写和具体实现上了。

    ----------------------------------------------
    事业是国家的,荣誉是单位的,成绩是领导的,工资是老婆的,财产是孩子的,错误是自己的。

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/11/14 11:46:00
     
     GoogleAdSense
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 C/C++编程思想 』的所有贴子 访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2024/11/23 4:26:58

    本主题贴数4,分页: [1]

    管理选项修改tag | 锁定 | 解锁 | 提升 | 删除 | 移动 | 固顶 | 总固顶 | 奖励 | 惩罚 | 发布公告
    W3C Contributing Supporter! W 3 C h i n a ( since 2003 ) 旗 下 站 点
    苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
    141.602ms