Introduction
You will have lots of fun on this beginner+ article & code especially if you haven't done any hardware related programming before.
If you are curious to try this code but feel scared on VC++ IDE, just read my last article: A super easy DLL made in VC++ IDE, especially for the first time tasters. It should help.
In this article/code, we are making a simple native DLL in VC++ IDE (VS2013). It directly uses Win32 Core APIs, without any third-party middle man in between. This DLL will "listen" to the PC's speaker/headphone and cache the signal data in a little buffer. Then our old fashioned C# console app will fetch this buffered signal and present a dummy volume meter to show the whole world how cool we are!
I strive to keep the code as simple as possible. The design is very straight forward. The main purpose of this article/code is to make you really mess around with Win32 hardware core APIs and have some fun!
Background
There is a secret only shared within long-time Windows programmers: Win32 APIs last FOREVER.
Lots of those APIs are from Windows XP or even earlier, and are still being used the same way as decades ago. Imagine that!
Most common Win32 APIs are already deeply wrapped by Microsoft.
.NET programmers can easily use them by those convenient "using
"/"import
" keywords. Or at most, you have to make "reference" of it before anything else.
But some "core" APIs are not wrapped. At least not by Microsoft. Maybe it's because there are too many memory/pointer operations involved, usually related to hardware/performance/resource sensitive stuff. Just my guess.
To use those unwrapped low level functions, you either have to use VC++ (and no, you can't avoid pointers and pointers' pointers, LOL) and follow the SDK/MSDN documentation or rely on 3rd party does the wrapping for you. 3rd party can be either an open source community or just another software vendor.
Anyway, today we are going to directly touch two "core" Win32 APIs (MMDevice & WASAPI). They are there since Windows Vista, 10 years ago, and stabilized ever since.
NOTE: I marked APIs we are going to use. You can find the original chart here.
NOTE AGAIN: Don't be fooled by this DIZZY chart! I first got scared the same way as you are, but once it's translated into our sample code, it's just as easy as a finger snap! (ok, my bad, not THAT easy, but not that hard, really).
Using the Code
The design is simple. Two threads: one UI thread, one worker thread (backend):
One solution, two projects:
VolumeMeterConsole
is C# based, which is doing the UI part. It's also the startup project.
VolumeMeterDLL
is VC++ based, it's the real deal in this article/code. All Win32 API calls happen here.
This app has been tested on Windows 7 & 8. To run it, you can either directly hit the green "start" in the VS IDE solution window. Or, if you ever need to deploy the result to a brand new PC (your boy/girl friend's PC maybe?), only two final files are needed. Put them into the same folder. Double click the VolumeMeterConsole.exe and open any Youtube video, ta-da---!
Points of Interest
(1) It Is Cool, but Why Is It Dummy?
Because it is dummy. To make things easier to understand, I didn't use the real formula, 20 X LOG10(Sample Voltage/Max Voltage) for proper DB level calculation.
Instead, I only calculate the ratio (sample's bytes value/max bytes value), and even further, to save some boilerplate code effort on bytes concatenation (those audio signal data could have 16bit, 24bit, 32bit difference), I just pick the most significant byte to do the calculation.
I admit it's rough, but it's quick and statistically speaking, the result isn't too bad.
Just comparing:
- all digits calc: 123456789/1000000000 approx.= 0.12
- the most significant digit calc: 100000000/1000000000 = 0.1
(Now you know how lazy I am...)
The method used is also documented in code comments. By the way, do read comments in the code. It does have some goodies...
(2) In C# Console App, Why Not Use Await/Async for Multi-Thread Programming?
Because I don't want to introduce extra distractions, especially that spaghetti Task<type>
thingy. The main focus for this beginner+ article/code is on Win32 API calling from VC++ IDE. That's all.
await
/async
/Task<type>
do have their places, especially if it comes to modern asynchronous .NET programming with some cool progress bar and "cancel
" functions involved.
(3) Why Is It Dummy Again?
The DLL part is not C++.
I didn't put class/constructor/destructor in. It's just water-fall-style plain C code. A little COM calling, even that if it's not necessary, I wouldn't even bother. If one beginner article fits too many things, you know what will happen: people will just give up.
That said, this code should not be used as a template for your next cool, iron clad, industry strength project. The code is fragile because it has nearly no resource-freeing and fail-over features (those try
...catch
...finally
stuff, you know).
The code sacrifices those features to gain some kind of "mean and lean". That's why it's dummy again.
(4) How to Debug It?
The console EXE (C#) part debugging is easy. I won't explain it here. Let's talk about DLL debugging.
The solution/projects are configured so the compiled DLL and EXE are always output in one "bin" folder:
and in DLL project property page, for the "Debugging", configure it like this:
When you actually start to debug the DLL, don't click the green "start" button on top. Instead, right click the DLL project, hit "Debug" and hit "Start new instance" and now you can do "break point", step-in, step-out, F11 to your DLL code.
(5) What Is That SDF File in Source Code? and It's 30 MB by Itself!
If you browse your solution folder, you can see all our source code files are very small. Yeah, they are just pure text files, several hundreds bytes here and there, no big deal. But you can see VolumeMeter.sdf, 30+MB!
This SDF file is a compact database, created by VC++ IDE. It's for building intellisense. Because VC++ is old fashioned, it needs such a feature to pull info from .H/#include to give you those fancy colored code and dropdown list while you are coding.
If you don't like this 30MB, you can cancel it. In project options: Text Editor-->C/C++-->Advanced. [Disable Database] to check "True". Well, you save 30MB in this case, but lose intellisense in coding. Your call.
(6) What's the Difference Between "Core" and "Kernel"?
Well, we do touch the Win32 "Core
" APIs, but this core is not kernel.
"kernel
" in Windows OS, or any general OS, has specific meaning. Usually, it refers to the part of the OS which is directly operating the hardware (memory/CPU/HD/devices). Lots of device drivers are programmed in "kernel mode".
More specifically speaking, our code is using "user mode" win32 core APIs.
Some fancy reading here if you are really interested in this "kernel" topic (I'm not there yet).
(7) Did You Figure This Out All by Yourself?
Of course NOT!
I did quite a bit of searching and researching, and found one MSDN article that is really helpful for my case.
His (Matthew Van Eerde) code is available at Github @ https://github.com/mvaneerde/blog/tree/master/loopback-capture
I learned a lot from it. Appreciate it. Remember I mentioned "iron clad", "industrial strength" code? His code could be used as a start point for that.
Lots of audio DSP articles reading as well. If you get into this, it's not boring at all. If not, well then don't bother, no big deal.
A good start point is can be found here.
(8) Let's See How Fun It Is (I Put the Result in Action on Youtube)
Hope you enjoy this just as I did~~~~
History
- 29th September, 2017: Initial version