


























实现实时监听Windows桌面的系统声音并且分析音频,实现音频可视化(频谱)。
Github仓库地址:https://github.com/ShenyfZero9211/MyIrrlicht
在虚幻引擎(Unreal)统治高精渲染、unity 垄断跨平台开发、Godot引领开源引擎大道的今天,为什么我仍要执拗地使用 Irrlicht(鬼火) 引擎?
本项目最大的突破在于摆脱了虚拟声卡依赖,实现了原生的系统回环捕获。
通过 wasapi_bridge.c 直接调用 Windows Core Audio API,实现了 AUDCLNT_STREAMFLAGS_LOOPBACK 模式。
graph LR SystemAudio[系统音频输出] -->|Loopback| WASAPI[WASAPI Bridge] WASAPI -->|环形缓冲区| FFT[FFT 频谱分析] FFT -->|对数频带| Visualizer[3D 渲染器] Visualizer -->|Render| Screen[屏幕显示] WASAPI -->|PCM| WAV[WAV 实时录制]
传统的 FFT 线性分布(每隔几赫兹一个条)不符合人类听觉。我们实现了等音程对数分布:
为了解决大动态音乐导致的视觉“触顶”,我们引入了双曲正切函数:
$$H_{target} = H_{max} \times \tanh(\frac{Amplitude}{Threshold})$$
这使得频谱柱在极大音量下也会平滑地趋近于上限值,而非突进式的崩溃,带来了如模拟器材般的“温暖限幅”感。
由于现代电子乐的高频能量通常较低,我们设计了补偿算法:
tilt = base ^ (index / total * power)视觉上的“灵动”来自于对真实的模拟。
PeakGravity 参数进行自由落体。在 Windows 环境下使用 i686-w64-mingw32-g++ (基于 GCC) 编译原本为 MSVC 设计的 Irrlicht,面临的首要挑战是符号导出逻辑的差异。
__declspec(dllexport) 的不一致性MSVC 对 dllexport 的处理非常霸道,而 GCC 在某些情况下会因为类的内联函数、静态成员而在导出时产生 multiple definition 或符号丢失错误。
IrrCompileConfig.h 中,我们重构了导出宏:#if defined(_IRR_WINDOWS_API_) && defined(__GNUC__)
#define IRRLICHT_API __attribute__((visibility("default"))) __declspec(dllexport)
#elif defined(_IRR_WINDOWS_API_)
#define IRRLICHT_API __declspec(dllexport)
#endif
结合 -fvisibility=hidden 编译选项,确保只有被标记的接口进入导出表,从而大幅减小符号表体积。为了确保 MinGW 生成的 DLL 能够与各种编译器(甚至是老版本的 C++ APP)具有较好的二进制兼容性,我们在 Makefile 中显式指定了 --out-implib,并优化了 libgcc_s_dw2-1.dll 的静态链接策略,使得最后的运行环境极致纯净。
很多人认为 DirectX 9 在 Win10 之后已经“过时”,但在极低延迟的桌面可视化领域,DX9 的轻快响应依然是不可替代的。
在现代 Windows 中,所有窗口渲染都受 DWM 接管。传统的 Present 调用在某些高刷显示器上会产生严重的肉眼可见撕裂。
CD3D9Driver.cpp 中,我们改进了窗口模式下的 D3DPRESENT_PARAMETERS 配置,引入了更智能的 BackBufferCount 策略,并确保 PresentationInterval 能够与系统 DWM 的合成帧率完美同步。传统引擎通常在 while(device->run()) 中进行大量的系统消息轮询。
CIrrDeviceWin32.cpp 中,我们大幅精简了对过时消息(如死掉的游戏手柄支持)的处理逻辑,改为优先响应由 WASAPI 采集线程 触发的数据同步信号。这使得频谱可视化的刷新在音频包到达的瞬间即可完成。在
SimpleAudioVisual项目的工程实践中,如何处理多线程实时性、配置的热更新以及跨环境的兼容性是决定项目成熟度的三大关键点。本篇将对这三个维度的工程细节进行深入拆解。
为了实现“改完配置即见效果”的开发体验,我们没有引入复杂的看门狗服务,而是利用了 Windows 原生的文件属性 API。
通过 GetFileAttributesExA 获取文件的 FILE_ATTRIBUTE_DATA,并将其中的 ftLastWriteTime 与内存中保存的上次修改时间进行 CompareFileTime。如果文件时间戳变大,则触发配置重新加载。
// [main.cpp] 每 500ms 执行一次检测
bool checkConfigUpdate() {
WIN32_FILE_ATTRIBUTE_DATA data;
if (GetFileAttributesExA(g_ConfigPath.c_str(), GetFileExInfoStandard, &data)) {
// 比较当前文件时间戳与记录的上次写入时间
if (CompareFileTime(&data.ftLastWriteTime, &g_lastConfigWriteTime) > 0) {
std::cout << "[System] Config change detected, reloading..." << std::endl;
// 重新加载并更新全局状态
loadSettings(g_ConfigPath.c_str());
g_lastConfigWriteTime = data.ftLastWriteTime; // 更新时间戳
return true; // 告知外部逻辑可能需要重置 UI 或材质颜色
}
}
return false;
}
MinGW 编译出的程序通常对环境有较强的依赖。如果不进行孤立化处理,程序往往在开发机运行正常,而在目标机由于缺失 libgcc_s_dw2-1.dll 而报错。
我们为 SimpleAudioVisual 挑选了以下核心 DLL,将其与 EXE 放置于同一目录,实现了绿色发布:
通过 build/ 文件夹的构建逻辑,我们强制程序寻找当前路径下的依赖,彻底隔离了全局 PATH 环境。
在音频可视化场景中,音频采集(生产者)和渲染循环(消费者)往往不在同一节奏。
我们的音频桥接器 (wasapi_bridge.c) 使用了环形缓冲区 (Ring Buffer)。必须确保在写入(Push)和读取(FFT 分析)时数据是一致的。
我们选择了 CriticalSection 而非 Mutex,是因为其完全在用户态执行,在无竞争时开销极低。
// [wasapi_bridge.c] 生产者:音频采集线程
static void ring_push_sample(float sample) {
if (!g_state.pcmRing || !g_state.ringLockReady) return;
// 进入临界区,锁定环形缓冲区
EnterCriticalSection(&g_state.ringLock);
g_state.pcmRing[g_state.ringWritePos] = sample;
g_state.ringWritePos = (g_state.ringWritePos + 1) % g_state.ringCapacity;
// 更新计数器(消费者根据此值决定读取量)
if (g_state.unreadCount < g_state.ringCapacity) {
g_state.unreadCount++;
}
// 离开临界区
LeaveCriticalSection(&g_state.ringLock);
}
// [wasapi_bridge.c] 消费者:渲染主线程请求 FFT 分析
__declspec(dllexport) void wasapi_get_fft(float* bins, int nBoxes) {
// ... 前置准备 ...
EnterCriticalSection(&g_state.ringLock);
// 从 pcmRing 中拷贝最新的 2048 个采样进行 FFT 变换
int count = wasapi_get_recent_pcm(pcm_buffer, FFT_SIZE);
LeaveCriticalSection(&g_state.ringLock);
// ... 后续 FFT 计算与对数平滑处理 ...
}
通过该机制,即使在渲染线程因为复杂的 3D 物效出现掉帧或卡顿时,后台的 WASAPI 采集线程依然能稳定填充缓冲区。在下一帧渲染开始时,消费者能立刻拿到最实时的音频切片,从而实现听感与视觉的高度同步。
隐藏,分别是[start recording]和[end recording],截取当前系统的音频流,存储在根目录下的captured_audio目录下,音频质量完好无损,媲美原声。settings.cfg配置文件,可实现软件的自动热更新,即保存触发初始化。build/SimpleAudioVisual 文件夹,实现了真正的“零配置可运行”。这是一个极小的开发实例,但是它极具价值。之后我会基于核心技术:音频回环驱动 (WASAPI Bridge)封装成单独适用开发的DLL版本,供Processing、Love2D等框架调用。
Github仓库地址:https://github.com/ShenyfZero9211/MyIrrlicht
“Hope is a good thing ,maybe the best of things,and no good thing ever dies...”
—— SharpEye · Yifan
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。