WinAMP 之所以經久不衰,關鍵在於其開放的插件架構——無論新音訊格式(MP4、VQF、RM)還是炫酷視覺化效果(G-Force、MilkDrop),皆透過放置於 Plugins 目錄下的 DLL 實現。本文以官方 SDK 範例 vis_minisdk.zip 為基礎,深入剖析視覺化插件的資料結構、生命週期與繪圖流程,帶你從零打造屬於自己的視覺化模組。
每個視覺化 DLL 必須導出統一名稱的函式 winampVisGetHeader(),傳回 winampVisHeader*,WinAMP 据此發現並載入模組。
typedef struct winampVisModule {
char *description; // 模組說明(顯示於插件選單)
HWND hwndParent; // 父視窗(WinAMP 主視窗,由主程式填入)
HINSTANCE hDllInstance; // DLL 實例句柄(由主程式填入)
int sRate; // 取樣率(由主程式填入)
int nCh; // 聲道數(由主程式填入)
int latencyMs; // Render 呼叫至實際繪製的延遲(毫秒)
int delayMs; // 兩次 Render 間隔(毫秒,控制幀率)
int spectrumNch; // 頻譜資料聲道數
int waveformNch; // 波形資料聲道數
unsigned char spectrumData[2][576]; // 頻譜資料 [聲道][頻帶]
unsigned char waveformData[2][576]; // 波形資料 [聲道][取樣點]
void (*Config)(struct winampVisModule *); // 設定對話框
int (*Init)(struct winampVisModule *); // 初始化(建立視窗等),成功傳回 0
int (*Render)(struct winampVisModule *); // 每幀繪製,傳回 1 表示請求卸載
void (*Quit)(struct winampVisModule *); // 清理資源
void *userData; // 自訂資料指標
} winampVisModule;
typedef struct {
int version; // VIS_HDRVER (0x101)
char *description; // 插件整體說明
winampVisModule* (*getModule)(int); // 取得第 N 個模組
} winampVisHeader;
Plugins 目錄下所有 *.dll。GetProcAddress("winampVisGetHeader") 取得標頭。version 相容性,讀取 description 顯示於插件清單。getModule(0) 取得第一個模組指標。Init() → 進入多執行緒迴圈反覆呼叫 Render() → 使用者切換/關閉時呼叫 Quit()。int Init(winampVisModule *this_mod) {
// 讀取儲存的視窗位置
config_read(this_mod);
// 註冊視窗類別
WNDCLASS wc = {0};
wc.lpfnWndProc = VisWndProc;
wc.hInstance = this_mod->hDllInstance;
wc.lpszClassName = szAppName;
RegisterClass(&wc);
// 建立視窗(WS_POPUP | WS_VISIBLE,不在工作列顯示)
this_mod->hwndParent = CreateWindowEx(
WS_EX_TOPMOST, szAppName, this_mod->description,
WS_POPUP | WS_VISIBLE, config_x, config_y, 320, 240,
this_mod->hwndParent, NULL, this_mod->hDllInstance, NULL);
// 將 this_mod 指標附加至視窗,供 WndProc 使用
SetWindowLongPtr(this_mod->hwndParent, GWLP_USERDATA, (LONG_PTR)this_mod);
return 0; // 成功
}
此函式在獨立執行緒中以固定間隔(delayMs)被呼叫,必須極度精簡:
int Render(winampVisModule *this_mod) {
if (!this_mod->hwndParent) return 0;
HDC hdc = GetDC(this_mod->hwndParent);
RECT rc; GetClientRect(this_mod->hwndParent, &rc);
// 範例:繪製簡單頻譜柱狀圖
int nCh = this_mod->spectrumNch;
int bands = 576 / nCh; // 實際有效頻帶數
int barW = max(1, rc.right / bands);
for (int ch = 0; ch < nCh; ch++) {
for (int i = 0; i < bands; i++) {
int val = this_mod->spectrumData[ch][i];
int h = (val * rc.bottom) / 255;
int x = i * barW;
int y = rc.bottom - h;
// 依聲道顏色區分
COLORREF clr = (ch == 0) ? RGB(0,255,0) : RGB(0,0,255);
HBRUSH br = CreateSolidBrush(clr);
RECT r = {x, y, x+barW, rc.bottom};
FillRect(hdc, &r, br);
DeleteObject(br);
}
}
ReleaseDC(this_mod->hwndParent, hdc);
return 0; // 繼續下一幀
}
若需提供使用者設定(如顏色、靈敏度),實作 Config 顯示模態對話框,並將設定寫入 plugin.ini(WinAMP 指定統一設定檔)。
視窗程序必須將未處理的鍵盤訊息轉發給父視窗(WinAMP),確保全域熱鍵(如空白鍵播放/暫停)仍能運作:
LRESULT CALLBACK VisWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
winampVisModule *mod = (winampVisModule*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
if (msg == WM_KEYDOWN && (wp == VK_ESCAPE || wp == 'Q')) {
// ESC 或自訂鍵關閉視覺化
PostMessage(mod->hwndParent, WM_WA_VIS_END, 0, 0);
return 0;
}
// 其他鍵轉發給 WinAMP
return SendMessage(mod->hwndParent, msg, wp, lp);
}
vis_YourName.dll(慣例以 vis_ 開頭)Plugins\ 資料夾,重新啟動 WinAMP 即可於 Options → Visualization 選單中看到。BitBlt 至螢幕,消除閃爍。Init 建立渲染環境,Render 中提交繪圖指令,適合複雜 3D 視覺化。getModule(index) 提供多種視覺化風格(如「波形」「頻譜」「粒子」),使用者可在下拉選單切換。WinAMP 視覺化插件開發雖屬經典技術,但其「標準介面 + 多執行緒 Render 迴圈 + 共享記憶體資料交換」的架構思想,對現代音訊視覺化、即時資料儀表板開發仍具參考價值。掌握 winampVisModule 結構與四大回調函式,即可自由發揮創意,打造屬於你的聲音視覺藝術。