65.9K
CodeProject 正在变化。 阅读更多。
Home

酷炫假音量表“DOS模式”,一个有趣的入门项目,适合想多了解VC++ IDE和Win32核心API的任何人

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (11投票s)

2017 年 9 月 29 日

CPOL

7分钟阅读

viewsIcon

16844

downloadIcon

219

这个简单的原生DLL(用VC++制作)使用Win32核心API从默认音频设备(扬声器或耳机)捕获实时音频信号。一个小的C#控制台EXE从DLL获取信号,并实时设置音量条的上升/下降。它很有趣,而且容易理解。

引言

你将在这个初学者+文章和代码中获得很多乐趣,特别是如果你以前没有做过任何硬件相关的编程。

如果你想尝试这段代码但又害怕VC++ IDE,请阅读我上一篇文章:一款超简单的VC++ IDE制作的DLL,特别是给初次尝试者。这应该会有帮助。

在这篇文章/代码中,我们在VC++ IDE(VS2013)中制作了一个简单的原生DLL。它直接使用Win32核心API,没有任何第三方中间件。这个DLL将“监听”PC的扬声器/耳机,并将信号数据缓存到一个小缓冲区中。然后,我们老式的C#控制台应用程序将获取这个缓冲信号,并呈现一个假音量表,向全世界展示我们有多酷!

我努力让代码尽可能简单。设计非常直接。本文/代码的主要目的是让你真正地去玩转Win32硬件核心API,并从中获得乐趣!

背景

Windows老程序员之间有一个秘密:Win32 API永不过时。

其中许多API都来自Windows XP甚至更早的版本,并且至今仍以几十年前相同的方式使用。想象一下!

大多数常用的Win32 API已经被微软深度封装。

.NET程序员可以很容易地通过那些方便的“using”/“import”关键字来使用它们。或者最多,你需要先“引用”它。

但是一些“核心”API没有被封装。至少不是微软封装的。也许是因为涉及大量的内存/指针操作,通常与硬件/性能/资源敏感的东西有关。这只是我的猜测。

要使用那些未封装的低级函数,你必须使用VC++ **(而且,你无法避免指针和指针的指针,哈哈)** 并遵循SDK/MSDN文档,或者依赖第三方来为你封装。第三方可以是开源社区,也可以是另一个软件供应商。

总之,今天我们将直接接触两个“核心”Win32 API(MMDevice & WASAPI)。它们自Windows Vista起就存在了,已经有10年了,而且一直很稳定。

注意:我标记了我们要使用的API。你可以在这里找到原始图表。

再次注意:不要被这个令人眼花缭乱的图表所迷惑!我一开始和你一样感到害怕,但一旦把它翻译成我们的示例代码,它就和弹指一样容易!(好吧,我的错,不是那么容易,但真的没那么难)。

Using the Code

设计很简单。两个线程:一个UI线程,一个工作线程(后端)。

一个解决方案,两个项目。

VolumeMeterConsole **是基于C#的,负责UI部分。它也是启动项目。

VolumeMeterDLL **是基于VC++的,它是本文/代码中的真正核心。所有Win32 API调用都在这里进行。

这个应用程序已经在Windows 7和8上进行了测试。要运行它,你可以直接点击VS IDE解决方案窗口中的绿色“启动”按钮。或者,如果你需要将结果部署到一台全新的PC上(也许是你女朋友的PC?),只需要两个最终文件。将它们放在同一个文件夹中。双击VolumeMeterConsole.exe并打开任何Youtube视频,瞧!

关注点

(1) 它很酷,但为什么是假的?

因为它很假。为了更容易理解,我没有使用真正的公式,即 **20 X LOG10(Sample Voltage/Max Voltage)** 来进行正确的DB电平计算。

相反,我只计算比例(样本字节值/最大字节值),**而且更进一步**,为了节省一些关于字节连接的样板代码(那些音频信号数据可能是16位、24位、32位),我只选取最高有效字节进行计算。

我承认这很粗糙,但它很快,而且从统计学上讲,结果并不太坏。

简单比较

  • 所有数字计算:123456789/1000000000 约等于 0.12
  • 最高有效数字计算:100000000/1000000000 = 0.1

(现在你知道我有多懒了……)

使用的方法也在代码注释中有记录。顺便说一句,请阅读代码中的注释。它确实包含了一些好东西……

(2) 在C#控制台应用程序中,为什么不使用Await/Async进行多线程编程?

因为我不想引入额外的干扰,尤其是那些混乱的Task<type>。这个初学者+文章/代码的主要重点是VC++ IDE调用Win32 API。仅此而已。

await/async/Task<type> 确实有它们的作用,特别是当涉及到现代异步.NET编程,以及一些炫酷的进度条和“取消”功能时。

(3) 为什么又说它是假的?

DLL部分不是C++。

我没有包含类/构造函数/析构函数。它只是瀑布式的纯C代码。一点COM调用,即使不是必需的,我也不会费心。如果一个初学者文章包含了太多东西,你知道会发生什么:人们会放弃。

话虽如此,这段代码不应该被用作你下一个酷炫、铁打、工业级项目的模板。代码很脆弱,因为它几乎没有资源释放和故障转移功能(那些trycatchfinally的东西,你知道的)。

代码牺牲了这些功能,以换取某种“精简”的特性。这就是为什么它又显得很假。

(4) 如何调试它?

控制台EXE(C#)部分的调试很简单。这里就不多解释了。我们来谈谈DLL调试。

解决方案/项目配置为使编译后的DLL和EXE始终输出到一个“bin”文件夹。

在DLL项目属性页中,对于“**调试**”,请按如下方式配置:

当你实际开始调试DLL时,不要点击顶部的绿色“**启动**”按钮。而是右键单击DLL项目,选择“**调试**”,然后选择“**启动新实例**”,现在你就可以在DLL代码中设置“**断点**”,单步进入、单步跳出、按F11进行调试了。

(5) 源代码中的SDF文件是什么?而且它本身就有30MB!

如果你浏览解决方案文件夹,你会发现我们所有的源代码文件都很小。是的,它们只是纯文本文件,几百字节而已,没什么大不了的。但你会看到VolumeMeter.sdf,30多MB!

这个SDF文件是一个紧凑的数据库,由VC++ IDE创建。它是为了构建智能感知。因为VC++很老派,所以需要这个功能来从.H/#include中提取信息,在你编码时为你提供花哨的彩色代码和下拉列表。

如果你不喜欢这30MB,你可以取消它。在项目选项中:文本编辑器 -> C/C++ -> 高级。将[禁用数据库]设置为“**True**”。好吧,这样你可以节省30MB,但会失去编码时的智能感知。你自己决定。

(6) “核心”和“内核”有什么区别?

嗯,我们确实接触了Win32“Core”API,但这个核心不是内核。

kernel”在Windows操作系统或任何通用操作系统中都有特定的含义。通常,它指的是直接操作硬件(内存/CPU/硬盘/设备)的OS部分。许多设备驱动程序都是在“内核模式”下编程的。

更具体地说,我们的代码使用的是“用户模式”的Win32核心API。

如果你对这个“内核”话题非常感兴趣,可以在这里阅读一些有趣的资料(我还没有深入研究)。

(7) 你是自己全部弄明白的吗?

当然不是!

我做了不少搜索和研究,发现一篇MSDN文章对我非常有帮助。

他的(Matthew Van Eerde)代码可以在Github上找到:https://github.com/mvaneerde/blog/tree/master/loopback-capture

我从中学到了很多。很感激。还记得我提到的“铁打”、“工业级”代码吗?他的代码可以作为起点。

还有很多音频DSP文章可以阅读。如果你深入研究,这一点也不枯燥。如果不深入,那就算了,没什么大不了的。

你可以在这里找到一个好的起点。

(8) 让我们看看它有多有趣(我在Youtube上展示了结果)

希望你和我一样喜欢这个~~~~

历史

  • 2017年9月29日:初始版本
© . All rights reserved.