Introduction
In this article, I'll show you how to use Windows Mixer
from C#.
For some time, I was trying to get information about how to program the mixer
from C#. I didn't have too much luck, and the few examples found were in C++, so… I took the hard and fun way... doing it myself.
This library is part of my Audio library to control Wave Audio (Playback
/Recording
), Mixer
, Playback
/Recording
compressed files using ACM, basic speech recognition and some other stuff that I'll be releasing in future articles.
AudioMixer Namespace
Hierarchical Objects View
The hierarchical view shows how the classes are organized.
The main object Mixers
contains two Mixer
objects, Playback
and Recording
; those will work with default devices, but can be changed by setting the property Mixers.Playback.DeviceId
or Mixers.Recording.DeviceId
.
I made it as simple as possible by hiding all the flat Win32 API implementation from the developer and creating a hierarchical set of objects.
Every mixer
is autonomous, meaning you can have Playback mixer
set to one soundcard and Recording mixer
to another one. Also each one contains two arrays or MixerLines
(Lines
, UserLines
).
Lines
object will contain all lines inside the mixer
, for example all the lines that don't have controls associated with it or lines that are not source lines.
UserLines
will contains all lines that the developer can interact with, having controls like Volume
, Mute
, Fadder
, Bass
, etc. (basically it is the same as Lines
object but a filter was applied to it).
Every Line
will contain a collection of controls, like Mute
, Volume
, Bass
, Fader
, etc.
If you are interested in knowing more about how Windows Mixer
works, here is an excellent link with all the information about it.
Let's See Some Code
To Get the Audio Devices in the Computer
foreach(MixerDetail mixerDetail in mMixers.Devices)
{
...
...
}
To Get the Input Devices
foreach(MixerDetail mixerDetail in mMixers.Recording.Devices)
{
...
...
}
Change Output Mixer Device
Mixers mixers = new Mixers();
foreach(MixerDetail mixerDetail in mixers.Playback.Devices)
{
if (mixerDetail.MixerName == "Sound Blaster Live")
mixers.Playback.DeviceId = mixerDetail.DeviceId;
}
Change Output Mixer Device to the Default Device
Mixers mixers = new Mixers();
mixers.Playback.DeviceId = -1;
or
mixers.Playback.DeviceId = mixers.Playback.DeviceIdDefault;
Getting Playback Speaker Master Volume
mixers.Playback.Lines.GetMixerFirstLineByComponentType(
MIXERLINE_COMPONENTTYPE.DST_SPEAKERS).Volume;
Setting Playback Speaker Master Volume for the Left Channel Only
MixerLine line = mixers.Playback.Lines.GetMixerFirstLineByComponentType(
MIXERLINE_COMPONENTTYPE.DST_SPEAKERS);
line.Channel = Channel.Left;
line.Volume = 32000;
Selecting Microphone as Default Input
mixers.Recording.Lines.GetMixerFirstLineByComponentType(
MIXERLINE_COMPONENTTYPE.SRC_MICROPHONE).Selected = true;
Getting Callback Notifications When a Line has Changed
mMixers = new Mixers();
mMixers.Playback.MixerLineChanged +=
new WaveLib.AudioMixer.Mixer.MixerLineChangeHandler(mMixer_MixerLineChanged);
mMixers.Recording.MixerLineChanged +=
new WaveLib.AudioMixer.Mixer.MixerLineChangeHandler(mMixer_MixerLineChanged);
private void mMixer_MixerLineChanged(Mixer mixer, MixerLine line)
{
Console.WriteLine("Mixer: " + mixer.DeviceDetail.MixerName);
Console.WriteLine("Mixer Type: " + mixer.MixerType);
Console.WriteLine("Mixer Line: " + line.Name);
}
Getting and Setting Rare Controls
Specific controls like Fadder, Microphone Boost, bass, treble, etc. can be accessed via the MixerControl
object using ValueAsSigned
, ValueAsUnsigned
and ValueAsBoolean
properties, but they are not implemented as standard properties because they don't belong to all controls.
x86 vs x64
Finally I got a core 2 duo and now I could compile the library under x86 and x64. At first I thought there won't be too much difference, but the big problem is that the IntPtr
pointers are platform specific, so whereas a x86 takes 4 bytes, a x64 takes 8 bytes, that makes things really messy and there is no simple fix in some cases, for example in the following struct I could not find a way to make it work for x86 and x64 in C#. Looks like the .NET Framework is missing something to work with unions inside structs... I had to declare two structs and work with them separately.
Before
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Auto)]
public struct MIXERCONTROLDETAILS
{
[FieldOffset(0)] public UInt32 cbStruct;
[FieldOffset(4)] public UInt32 dwControlID;
[FieldOffset(8)] public UInt32 cChannels;
[FieldOffset(12)] public IntPtr hwndOwner;
[FieldOffset(12)] public UInt32 cMultipleItems;
[FieldOffset(16)] public UInt32 cbDetails;
[FieldOffset(20)] public IntPtr paDetails;
}
After
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode, Pack = 2)]
public struct MIXERCONTROLDETAILS
{
[FieldOffset(0)] public UInt32 cbStruct;
[FieldOffset(4)] public UInt32 dwControlID;
[FieldOffset(8)] public UInt32 cChannels;
[FieldOffset(12)] public IntPtr hwndOwner;
[FieldOffset(12)] public UInt32 cMultipleItems;
[FieldOffset(16)] public UInt32 cbDetails;
[FieldOffset(20)] public IntPtr paDetails;
}
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode, Pack = 2)]
public struct MIXERCONTROLDETAILS64
{
[FieldOffset(0)] public UInt32 cbStruct;
[FieldOffset(4)] public UInt32 dwControlID;
[FieldOffset(8)] public UInt32 cChannels;
[FieldOffset(12)] public IntPtr hwndOwner;
[FieldOffset(12)] public UInt32 cMultipleItems;
[FieldOffset(20)] public UInt32 cbDetails;
[FieldOffset(24)] public IntPtr paDetails;
}
I was hoping to declare a dynamic size for the FieldOffset
and then I could have fixed the problem but it doesn't compile on .NET.
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode, Pack = 2)]
public struct MIXERCONTROLDETAILS
{
[FieldOffset(0)] public UInt32 cbStruct;
[FieldOffset(4)] public UInt32 dwControlID;
[FieldOffset(8)] public UInt32 cChannels;
[FieldOffset(12)] public IntPtr hwndOwner;
[FieldOffset(12)] public UInt32 cMultipleItems;
[FieldOffset(12 + IntPtr.Size)] public UInt32 cbDetails;
[FieldOffset(12 + IntPtr.Size + 4)] public IntPtr paDetails;
}
Notes
All the current functionality is tested and working, I tried not to include bugs, but they are there and I find them every once in a while. If you find bugs, I'll be grateful to get feedback to update the article.
For now I don't need anything else from the library, but if you think of something that is not included in it which could make it better, just let me know and I'll try to include it.
If you have a problem with it, feel free to write me an email.
I started with programming about 19 years ago as a teenager, from my old Commodore moving to PC/Server environment Windows/UNIX SQLServer/Oracle doing gwBasic, QBasic, Turbo Pascal, Assembler, Turbo C, BC, Summer87, Clipper, Fox, SQL, C/C++, Pro*C, VB3/5/6, Java, and today loving C#.
Currently working as SDE on Failover Clustering team for Microsoft.
Passion for most programming languages and my kids Aidan&Nadia.