Page 1 of 1

Controlling sound volume in C#

#1 PsychoCoder  Icon User is offline

  • Google.Sucks.Init(true);
  • member icon

Reputation: 1639
  • View blog
  • Posts: 19,853
  • Joined: 26-July 07

Post icon  Posted 09 March 2008 - 11:58 PM

In this tutorial I will be showing you how you would go about controlling your system volume in C#. The reason for this tutorial is simple, Ive been asked too many times how to do it, so I thought it appropriate to write a tutorial showing how to do this task. Before we get into any code, you need to understand that this tutorial in an advanced tutorial, using Win32 API calls, custom structures, and the like. If you are not familiar with those items, I suggest you study them before attempting this tutorial.

Controlling your system volume takes a lot of advanced coding, because you have to hook into the operating system in order to gain access to the controls needed, thats the reason for all the Win32 API's. The easiest way to do this is by using a mixer control, which is what we will be doing in this tutorial. This tutorial may be rather long, as this is a complete class library, with class files for:

  • All the Win32 API Calls
  • All the structures
  • All the constants needed
  • Class for doing the actual work


I will start by showing the other 3 classes first, so you can see first hand the items needed for taking control of the system's volume controls.

The first class we will see is the Win32 API class. In this class is where we will be storing all the Win32 calls needed for controlling the sound. Since each section is it's own class, we will need to reference certain Namespaces for each class. The Namespaces we need for the Win32 class are:


using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
//custom Namespaces
using VolumeControl.Library.Constants;
using VolumeControl.Library.Structs;




You will notice there are 2 custom Namespaces, those are our other class files we will be getting to shortly. Since our classes reference custom Namespaces, you should have guessed that all the class are a member of the VolumeControl.Library Namespace, but each also have an appendage to that, depending on which class it is. The Namespaces we create are:

  • VolumeControl.Library.Structs
  • VolumeControl.Library.Win32
  • VolumeControl.Library.Constants


The Namespace for the Win32 class is, you guessed it, VolumeControl.Library.Win32, I am going to make an assumption here, since you continued to read along in this tutorial I'm assuming you know about Win32 API calls, and how to look up their function if you needed to, so I am not going to go into great detail about what these API calls do. If you want to look them up, a good place to start is PInvoke.Net, they have pretty much every Win32 API in programming.

In this class we have Win32 API's for opening a mixer control, closing a mixer control, getting the details of the current mixer control, getting the ID of the mixer control, and more. Here are the Win32 API's we will be needing:

  • mixerClose
  • mixerGetControlDetailsA
  • mixerGetDevCapsA
  • mixerGetID
  • mixerGetLineControlsA
  • mixerGetLineInfoA
  • mixerGetNumDevs
  • mixerMessage
  • mixerOpen
  • mixerSetControlDetails


So now lets declare our Win32 API's:


[DllImport("winmm.dll", CharSet=CharSet.Ansi)] 
public static extern int mixerClose (int hmx);

[DllImport("winmm.dll", CharSet=CharSet.Ansi)]
public static extern int mixerGetControlDetailsA(int hmxobj, ref VolumeStructs.MixerDetails pmxcd, int fdwDetails);

[DllImport("winmm.dll", CharSet=CharSet.Ansi)]
public static extern int mixerGetDevCapsA(int uMxId, VolumeStructs.MixerCaps pmxcaps, int cbmxcaps);

[DllImport("winmm.dll", CharSet=CharSet.Ansi)]
public static extern int mixerGetID(int hmxobj, int pumxID, int fdwId);

[DllImport("winmm.dll", CharSet=CharSet.Ansi)]
public static extern int mixerGetLineControlsA(int hmxobj, ref VolumeStructs.LineControls pmxlc, int fdwControls);

[DllImport("winmm.dll", CharSet=CharSet.Ansi)]
public static extern int mixerGetLineInfoA(int hmxobj, ref VolumeStructs.MixerLine pmxl, int fdwInfo);

[DllImport("winmm.dll", CharSet=CharSet.Ansi)]
public static extern int mixerGetNumDevs();

[DllImport("winmm.dll", CharSet=CharSet.Ansi)]
public static extern int mixerMessage(int hmx, int uMsg, int dwParam1, int dwParam2);

[DllImport("winmm.dll", CharSet=CharSet.Ansi)]
public static extern int mixerOpen(out int phmx, int uMxId, int dwCallback, int dwInstance, int fdwOpen);

[DllImport("winmm.dll", CharSet=CharSet.Ansi)]
public static extern int mixerSetControlDetails(int hmxobj, ref VolumeStructs.MixerDetails pmxcd, int fdwDetails);




Now we will look at the structs we are going to be using for this class library. A struct is normally used to encapsulate small pieces of related data, such as we are doing in this library. First, as with any class, we need our Namespace references, here are the Namespaces you need to use in your structs class

using System;
using System.Runtime.InteropServices;
//custom namespaces
using VolumeControl.Library.Constants;



As you can see, we will be using the constants class in our structs class. Now for the structs we will be using. First is a struct to hold the data about the caps of the mixer

/// <summary>
/// struct for holding data for the mixer caps
/// </summary>
public struct MixerCaps
{
    public int wMid;
    public int wPid;
    public int vDriverVersion;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = VolumeConstants.MAXPNAMELEN)] public string szPname;
    public int fdwSupport;
    public int cDestinations;
}




Next is the struct to hold the data for the mixer control itself

/// <summary>
/// struct to hold data for the mixer control
/// </summary>
public struct Mixer
{
    public int cbStruct;
    public int dwControlID;
    public int dwControlType;
    public int fdwControl;
    public int cMultipleItems;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = VolumeConstants.MIXER_SHORT_NAME_CHARS)] public string szShortName;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = VolumeConstants.MIXER_LONG_NAME_CHARS)] public string szName;
    public int lMinimum;
    public int lMaximum;
    [MarshalAs(UnmanagedType.U4, SizeConst = 10)]  public int reserved;
}




struct for holding data for the details of the mixer control

/// <summary>
/// struct for holding data about the details of the mixer control
/// </summary>
public struct MixerDetails
{
    public int cbStruct;
    public int dwControlID;
    public int cChannels;
    public int item;
    public int cbDetails;
    public IntPtr paDetails;
}



Now a struct for an unsigned mixer control

/// <summary>
/// struct to hold data for an unsigned mixer control details
/// </summary>
public struct UnsignedMixerDetails
{
    public int dwValue;
}



A struct for holding data about the mixer line being used

/// <summary>
/// struct to hold data for the mixer line
/// </summary>
public struct MixerLine
{
    public int cbStruct;
    public int dwDestination;
    public int dwSource;
    public int dwLineID;
    public int fdwLine;
    public int dwUser;
    public int dwComponentType;
    public int cChannels;
    public int cConnections;
    public int cControls;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = VolumeConstants.MIXER_SHORT_NAME_CHARS)] public string szShortName;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = VolumeConstants.MIXER_LONG_NAME_CHARS)] public string szName;
    public int dwType;
    public int dwDeviceID;
    public int wMid;
    public int wPid;
    public int vDriverVersion;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = VolumeConstants.MAXPNAMELEN)] public string szPname;
}



A struct for holding the data for the mixer line controls being used

/// <summary>
/// struct for holding data for the mixer line controls
/// </summary>
public struct LineControls
{
    public int cbStruct;
    public int dwLineID;
    public int dwControl;
    public int cControls;
    public int cbmxctrl;
    public IntPtr pamxctrl;
}



Ok, we have our Win32 API's and structs out of the way, now lets look at the constants needed for using a mixer control to take control of the volume control. Lets take a look at the constants for accomplishing this task:

/// <summary>
/// Class to hold all the constants needed for controlling the system sound
/// </summary>
public static class VolumeConstants
{
    public const int MMSYSERR_NOERROR = 0;
    public const int MAXPNAMELEN = 32;
    public const int MIXER_LONG_NAME_CHARS = 64;
    public const int MIXER_SHORT_NAME_CHARS = 16;
    public const int MIXER_GETLINEINFOF_COMPONENTTYPE = 0x3;
    public const int MIXER_GETCONTROLDETAILSF_VALUE = 0x0;
    public const int MIXER_GETLINECONTROLSF_ONEBYTYPE = 0x2;
    public const int MIXER_SETCONTROLDETAILSF_VALUE = 0x0;
    public const int MIXERLINE_COMPONENTTYPE_DST_FIRST = 0x0;
    public const int MIXERLINE_COMPONENTTYPE_SRC_FIRST = 0x1000;        
    public const int MIXERCONTROL_CT_CLASS_FADER = 0x50000000;
    public const int MIXERCONTROL_CT_UNITS_UNSIGNED = 0x30000;
    public const int MIXERCONTROL_CONTROLTYPE_FADER = (MIXERCONTROL_CT_CLASS_FADER | MIXERCONTROL_CT_UNITS_UNSIGNED);
    public const int MIXERCONTROL_CONTROLTYPE_VOLUME = (MIXERCONTROL_CONTROLTYPE_FADER + 1);
    public const int MIXERLINE_COMPONENTTYPE_DST_SPEAKERS = (MIXERLINE_COMPONENTTYPE_DST_FIRST + 4);
    public const int MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE = (MIXERLINE_COMPONENTTYPE_SRC_FIRST + 3);
    public const int MIXERLINE_COMPONENTTYPE_SRC_LINE = (MIXERLINE_COMPONENTTYPE_SRC_FIRST + 2);
}



NOTE: You will notice that the first 3 class are all declared as static, this is done so we don't have to create an instance of the class to use the items it contains. As with structs, there is a raging debate over using static classes, as far as I'm concerned the jury is still out on this.

Now on to the class that actually does all the work. In this class we have 4 methods, 1 for getting the mixer control, 1 for setting the mixer control, 1 for getting the current volume level, and finally 1 for setting the volume level. First the method for getting the mixer control. In this method we will be creating a new mixer control, we will retrieve it and use the method for setting its properties to set all the information we need for the mixer.

In this class you will be using many methods of the Marshal Class, located in the System.Runtime.InteropServices Namespace. We will be allocating memory blocks for our mixer control using the AllocCoTaskMem Method, we will be grabbing the size of our unmanaged type using the Marshal.SizeOf Method and more.

This is one of the reasons I said at the start of this tutorial that it is an advanced tutorial, and requires a firm grasp of the language, and the objects used, to understand what is going on. Before we can write any code that is going to work, we need to make sure we have references to the proper Namespaces, here are the Namespaces you will need to reference:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;
//custom Namespaces
using VolumeControl.Library.Constants;
using VolumeControl.Library.Structs;
using VolumeControl.Library.Win32;



Now for the first method in our class, the GetMixer method:


/// <summary>
/// method to retrieve a mixer control
/// </summary>
/// <param name="i"></param>
/// <param name="type"></param>
/// <param name="ctrlType"></param>
/// <param name="mixerControl"></param>
/// <param name="currVolume"></param>
/// <returns></returns>
private static bool GetMixer(int i, int type, int ctrlType, out VolumeStructs.Mixer mixerControl, out int currVolume)
{
    //create our method level variables
    int details;
    bool bReturn;
    currVolume = -1;

    //create our struct objects
    VolumeStructs.LineControls lineControls = new VolumeStructs.LineControls();
    VolumeStructs.MixerLine line = new VolumeStructs.MixerLine();
    VolumeStructs.MixerDetails mcDetails = new VolumeStructs.MixerDetails();
    VolumeStructs.UnsignedMixerDetails detailsUnsigned = new VolumeStructs.UnsignedMixerDetails();

    //create a new mixer control
    mixerControl = new VolumeStructs.Mixer();
   
    //set the properties of out mixerline object
    line.cbStruct = Marshal.SizeOf(line);
    line.dwComponentType = type;
    //get the line info and assign it to our details variable
    details = PCWin32.mixerGetLineInfoA(i, ref line, VolumeConstants.MIXER_GETLINEINFOF_COMPONENTTYPE);

    //make sure we didnt receive any errors
    if (VolumeConstants.MMSYSERR_NOERROR == details)
    {
        int mcSize = 152;
        //get the size of the unmanaged type
        int control = Marshal.SizeOf(typeof(VolumeStructs.Mixer));
        //allocate a block of memory
        lineControls.pamxctrl = Marshal.AllocCoTaskMem(mcSize);
        //get the size of the line controls
        lineControls.cbStruct = Marshal.SizeOf(lineControls);

        //set properties for our mixer control
        lineControls.dwLineID = line.dwLineID;
        lineControls.dwControl = ctrlType;
        lineControls.cControls = 1;
        lineControls.cbmxctrl = mcSize;

        // Allocate a buffer for the control
        mixerControl.cbStruct = mcSize;

        // Get the control
        details = PCWin32.mixerGetLineControlsA(i, ref lineControls, VolumeConstants.MIXER_GETLINECONTROLSF_ONEBYTYPE);

        //once again check to see if we received any errors
        if (VolumeConstants.MMSYSERR_NOERROR == details)
        {
            bReturn = true;
            //Copy the control into the destination structure
            mixerControl = (VolumeStructs.Mixer)Marshal.PtrToStructure(lineControls.pamxctrl, typeof(VolumeStructs.Mixer));
        }
        else
        {
            bReturn = false;
        }

        int mcDetailsSize = Marshal.SizeOf(typeof(VolumeStructs.MixerDetails));
        int mcDetailsUnsigned = Marshal.SizeOf(typeof(VolumeStructs.UnsignedMixerDetails));
        mcDetails.cbStruct = mcDetailsSize;
        mcDetails.dwControlID = mixerControl.dwControlID;
        mcDetails.paDetails = Marshal.AllocCoTaskMem(mcDetailsUnsigned);
        mcDetails.cChannels = 1;
        mcDetails.item = 0;
        mcDetails.cbDetails = mcDetailsUnsigned;
        details = PCWin32.mixerGetControlDetailsA(i, ref mcDetails, VolumeConstants.MIXER_GETCONTROLDETAILSF_VALUE);
        detailsUnsigned = (VolumeStructs.UnsignedMixerDetails)Marshal.PtrToStructure(mcDetails.paDetails, typeof(VolumeStructs.UnsignedMixerDetails));
        currVolume = detailsUnsigned.dwValue;
        return bReturn;
    }

    bReturn = false;
    return bReturn;
}



Next we will be looking at the SetMixer method. This is the method that does all the work for changing the volume level. It is called from the SetVolume method. We will be passing this method the mixer control we're using, the level we want the volume set at:

/// <summary>
/// method for setting the value for a volume control
/// </summary>
/// <param name="i"></param>
/// <param name="mixerControl"></param>
/// <param name="volumeLevel"></param>
/// <returns>true/false</returns>
private static bool SetMixer(int i, VolumeStructs.Mixer mixerControl, int volumeLevel)
{
    //method level variables
    bool bReturn;
    int details;

    //create our struct object for controlling the system sound
    VolumeStructs.MixerDetails mixerDetails = new VolumeStructs.MixerDetails();
    VolumeStructs.UnsignedMixerDetails volume = new VolumeStructs.UnsignedMixerDetails();

    //set out mixer control properties
    mixerDetails.item = 0;
    //set the id of the mixer control
    mixerDetails.dwControlID = mixerControl.dwControlID;
    //return the size of the mixer details struct
    mixerDetails.cbStruct = Marshal.SizeOf(mixerDetails);
    //return the volume
    mixerDetails.cbDetails = Marshal.SizeOf(volume);

    //Allocate a buffer for the mixer control value buffer
    mixerDetails.cChannels = 1;
    volume.dwValue = volumeLevel;

    //Copy the data into the mixer control value buffer
    mixerDetails.paDetails = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(VolumeStructs.UnsignedMixerDetails)));
    Marshal.StructureToPtr(volume, mixerDetails.paDetails, false);

    //Set the control value
    details = PCWin32.mixerSetControlDetails(i, ref mixerDetails, VolumeConstants.MIXER_SETCONTROLDETAILSF_VALUE);

    //Check to see if any errors were returned
    if (VolumeConstants.MMSYSERR_NOERROR == details)
    {
        bReturn = true;
    }
    else
    {
        bReturn = false;
    }
    return bReturn;

}



Now before you can set the volume you need to know what the current volume is, that is the job of our GetVolume method. Here we will call our GetMixer method which will allow us to grab the current volume level. Remember to always close your mixer object when you are done using it to free up those resources, and to prevent any memory leaks.

NOTE: .Net languages are managed languages and do a pretty good job managing resources and memory usage, but here we are dealing with an unmanaged type so we need to ensure we are closing items when we dont need them anymore.

Now for the GetVolume method:

/// <summary>
/// method for retrieving the current volume from the system
/// </summary>
/// <returns>int value</returns>
public static int GetVolume()
{
    //method level variables
    int currVolume;
    int mixerControl;

    //create a new volume control
    VolumeStructs.Mixer mixer = new VolumeStructs.Mixer();
    
    //open the mixer
    PCWin32.mixerOpen(out mixerControl, 0, 0, 0, 0);

    //set the type to volume control type
    int type = VolumeConstants.MIXERCONTROL_CONTROLTYPE_VOLUME;

    //get the mixer control and get the current volume level
    GetMixer(mixerControl, VolumeConstants.MIXERLINE_COMPONENTTYPE_DST_SPEAKERS, type, out mixer, out currVolume);

    //close the mixer control since we are now done with it
    PCWin32.mixerClose(mixerControl);

    //return the current volume to the calling method
    return currVolume;
}



The final method we have is the method for setting the volume to a specified level. Here we will create a new mixer control (as we do in every method in this class, because we make sure to close it as soon as we're done with it). We will then open the mixer, then we will check the value that is being passed for the volume level. If a value greater than the maximum level we will set the level at the maximum value, same with the minimum level, if the value provided is lower than the minimum value we will set the volume level at the minimum level. Once we are done we, once again, close the mixer to free up those resources:

/// <summary>
/// method for setting the volume to a specific level
/// </summary>
/// <param name="volumeLevel">volume level we wish to set volume to</param>
public static void SetVolume(int volumeLevel)
{
    try
    {
        //method level variables
        int currVolume;
        int mixerControl;

        //create a new volume control
        VolumeStructs.Mixer volumeControl = new VolumeStructs.Mixer();

        //open the mixer control
        PCWin32.mixerOpen(out mixerControl, 0, 0, 0, 0);

        //set the type to volume control type
        int controlType = VolumeConstants.MIXERCONTROL_CONTROLTYPE_VOLUME;

        //get the current mixer control and get the current volume
        GetMixer(mixerControl, VolumeConstants.MIXERLINE_COMPONENTTYPE_DST_SPEAKERS, controlType, out volumeControl, out currVolume);

        //now check the volume level. If the volume level
        //is greater than the max then set the volume to
        //the max level. If it's less than the minimum level
        //then set it to the minimun level
        if (volumeLevel > volumeControl.lMaximum)
        {
            volumeLevel = volumeControl.lMaximum;
        }
        else if (volumeLevel < volumeControl.lMinimum)
        {
            volumeLevel = volumeControl.lMinimum;
        }

        //set the volume
        SetMixer(mixerControl, volumeControl, volumeLevel);

        //now re-get the mixer control
        GetMixer(mixerControl, VolumeConstants.MIXERLINE_COMPONENTTYPE_DST_SPEAKERS, controlType, out volumeControl, out currVolume);

        //make sure the volume level is equal to the current volume
        if (volumeLevel != currVolume)
        {
            throw new Exception("Cannot Set Volume");
        }

        //close the mixer control as we are finished with it
        PCWin32.mixerClose(mixerControl);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }            
}



Now that we have created this class library you're probably wondering how you actually use it. Well here is an example of how you would use this in, say the click event of a button on your form. When we click the button we will first display the current volume level, we will then set a new volume level, then we will re-display the current volume level:

private void button1_Click(object sender, System.EventArgs e)
{
    //first we will display the current volume
    Label1.Text = VolumeControl.GetVolume.ToString();

    //now we will change the volume level
    VolumeControl.SetVolume(50);

    //now we will display the new volume level
    Label1.Text = VolumeControl.GetVolume().ToString();
}



There you have it! A way to create a Windows mixer control and control the volume of the computer the application is running on. I truly hope you found this tutorial useful and informative, I know I learned a lot when creating this class library. I am including the solution for this class library, all the license information and headers must remain in tact because it is convered by the GNU - General Public License, but you are free to modify and distribute as you wish. Thank you for reading.

Attached File  PC_VolumeControl.zip (33.98K)
Number of downloads: 14030

Happy Coding!

Is This A Good Question/Topic? 1
  • +

Replies To: Controlling sound volume in C#

#2 brianlang75  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 23-March 09

Posted 23 March 2009 - 02:44 PM

I can't seem to get this to work, and the zip file is not valid (it can't be opened in WinZip or in WinRAR).
Was This Post Helpful? 0
  • +
  • -

#3 crepitus  Icon User is offline

  • D.I.C Regular
  • member icon

Reputation: 84
  • View blog
  • Posts: 383
  • Joined: 08-September 09

Posted 13 September 2009 - 04:19 AM

Note, there's a winmm.net library that has the p/invoke definitions already declared.
Was This Post Helpful? 0
  • +
  • -

#4 inadilemma  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 05-October 09

Posted 05 October 2009 - 08:25 AM

View Postcrepitus, on 13 Sep, 2009 - 03:19 AM, said:

Note, there's a winmm.net library that has the p/invoke definitions already declared.


Nice work. Actually I am trying for long to make this work and have seen this solution using winmm in several sites. But unfortunately in my case the getvolume always gives the max 999 value. Stuck with this for a few days. Can you please help what's going on. I am pretty much confident that my code is bug free.
Was This Post Helpful? 0
  • +
  • -

#5 efficacious  Icon User is offline

  • D.I.C Head

Reputation: 0
  • View blog
  • Posts: 55
  • Joined: 09-November 09

Posted 19 November 2009 - 11:42 AM

I found this tutorial kinda of confusing.. I think its mostly because of the fact that all of the code is seperated. I'm not able to see how it all goes together...

I downloaded the zip file but it only contains a file called "PC_VolumeControl" and it doesn't have an extension so I am unable to open it....

I watch alot of movies online and the biggest problem I face with it is the volume level differences.. So I've been trying to write a program that will take notice of decibal changes in the volume output and adjust the volume accordingly.

This post has been edited by efficacious: 19 November 2009 - 11:44 AM

Was This Post Helpful? 0
  • +
  • -

#6 PaulM44  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 03-December 09

Posted 03 December 2009 - 10:26 AM

I managed to get most of this working and can get the volume fine. However, there seems to be a problem with the call to mixerSetControlDetails in SetMixer. It returns an error indicating an invalid parameter. Any idea why?
Was This Post Helpful? 0
  • +
  • -

#7 KarlJay  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 3
  • Joined: 02-May 09

Posted 21 December 2009 - 03:29 AM

View Postefficacious, on 19 Nov, 2009 - 10:42 AM, said:

I watch alot of movies online and the biggest problem I face with it is the volume level differences.. So I've been trying to write a program that will take notice of decibal changes in the volume output and adjust the volume accordingly.


That's the exact same problem I'm having, the commercials are so freakin loud it's unreal. I think the only way to do this is to make something like the VU meter as seen in WinAmp that shows the volume. You'd have to tie into the program that's running the movie (realplayer etc)
then rather than display the VU bars, drop the volume if it goes too high.

The problem is that how will you be able to tell when the volume is higher than you want? The IPod has a volume limit, and I've seen where you can set the volume per song, one of mine was VERY low, set it high, then it was usable.

I don't know if there is anyway for the computer to know the volume of a source as it's playing it. My over-the-air TV does the same thing, some high-end TV's offer this for commercials.

Maybe write a program that displays all the values in the classes that MS offers for audio control and see what does what.

Post the answer if you find it...
Was This Post Helpful? 0
  • +
  • -

#8 shamirk  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 2
  • Joined: 25-May 11

Posted 25 May 2011 - 09:07 AM

View PostPsychoCoder, on 09 March 2008 - 11:58 PM, said:

In this tutorial I will be showing you how you would go about controlling your system volume in C#. The reason for this tutorial is simple, Ive been asked too many times how to do it, so I thought it appropriate to write a tutorial showing how to do this task. Before we get into any code, you need to understand that this tutorial in an advanced tutorial, using Win32 API calls, custom structures, and the like. If you are not familiar with those items, I suggest you study them before attempting this tutorial.

Controlling your system volume takes a lot of advanced coding, because you have to hook into the operating system in order to gain access to the controls needed, thats the reason for all the Win32 API's. The easiest way to do this is by using a mixer control, which is what we will be doing in this tutorial. This tutorial may be rather long, as this is a complete class library, with class files for:

  • All the Win32 API Calls
  • All the structures
  • All the constants needed
  • Class for doing the actual work


I will start by showing the other 3 classes first, so you can see first hand the items needed for taking control of the system's volume controls.

The first class we will see is the Win32 API class. In this class is where we will be storing all the Win32 calls needed for controlling the sound. Since each section is it's own class, we will need to reference certain Namespaces for each class. The Namespaces we need for the Win32 class are:


using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
//custom Namespaces
using VolumeControl.Library.Constants;
using VolumeControl.Library.Structs;




You will notice there are 2 custom Namespaces, those are our other class files we will be getting to shortly. Since our classes reference custom Namespaces, you should have guessed that all the class are a member of the VolumeControl.Library Namespace, but each also have an appendage to that, depending on which class it is. The Namespaces we create are:

  • VolumeControl.Library.Structs
  • VolumeControl.Library.Win32
  • VolumeControl.Library.Constants


The Namespace for the Win32 class is, you guessed it, VolumeControl.Library.Win32, I am going to make an assumption here, since you continued to read along in this tutorial I'm assuming you know about Win32 API calls, and how to look up their function if you needed to, so I am not going to go into great detail about what these API calls do. If you want to look them up, a good place to start is PInvoke.Net, they have pretty much every Win32 API in programming.

In this class we have Win32 API's for opening a mixer control, closing a mixer control, getting the details of the current mixer control, getting the ID of the mixer control, and more. Here are the Win32 API's we will be needing:

  • mixerClose
  • mixerGetControlDetailsA
  • mixerGetDevCapsA
  • mixerGetID
  • mixerGetLineControlsA
  • mixerGetLineInfoA
  • mixerGetNumDevs
  • mixerMessage
  • mixerOpen
  • mixerSetControlDetails


So now lets declare our Win32 API's:


[DllImport("winmm.dll", CharSet=CharSet.Ansi)] 
public static extern int mixerClose (int hmx);

[DllImport("winmm.dll", CharSet=CharSet.Ansi)]
public static extern int mixerGetControlDetailsA(int hmxobj, ref VolumeStructs.MixerDetails pmxcd, int fdwDetails);

[DllImport("winmm.dll", CharSet=CharSet.Ansi)]
public static extern int mixerGetDevCapsA(int uMxId, VolumeStructs.MixerCaps pmxcaps, int cbmxcaps);

[DllImport("winmm.dll", CharSet=CharSet.Ansi)]
public static extern int mixerGetID(int hmxobj, int pumxID, int fdwId);

[DllImport("winmm.dll", CharSet=CharSet.Ansi)]
public static extern int mixerGetLineControlsA(int hmxobj, ref VolumeStructs.LineControls pmxlc, int fdwControls);

[DllImport("winmm.dll", CharSet=CharSet.Ansi)]
public static extern int mixerGetLineInfoA(int hmxobj, ref VolumeStructs.MixerLine pmxl, int fdwInfo);

[DllImport("winmm.dll", CharSet=CharSet.Ansi)]
public static extern int mixerGetNumDevs();

[DllImport("winmm.dll", CharSet=CharSet.Ansi)]
public static extern int mixerMessage(int hmx, int uMsg, int dwParam1, int dwParam2);

[DllImport("winmm.dll", CharSet=CharSet.Ansi)]
public static extern int mixerOpen(out int phmx, int uMxId, int dwCallback, int dwInstance, int fdwOpen);

[DllImport("winmm.dll", CharSet=CharSet.Ansi)]
public static extern int mixerSetControlDetails(int hmxobj, ref VolumeStructs.MixerDetails pmxcd, int fdwDetails);




Now we will look at the structs we are going to be using for this class library. A struct is normally used to encapsulate small pieces of related data, such as we are doing in this library. First, as with any class, we need our Namespace references, here are the Namespaces you need to use in your structs class

using System;
using System.Runtime.InteropServices;
//custom namespaces
using VolumeControl.Library.Constants;



As you can see, we will be using the constants class in our structs class. Now for the structs we will be using. First is a struct to hold the data about the caps of the mixer

/// <summary>
/// struct for holding data for the mixer caps
/// </summary>
public struct MixerCaps
{
    public int wMid;
    public int wPid;
    public int vDriverVersion;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = VolumeConstants.MAXPNAMELEN)] public string szPname;
    public int fdwSupport;
    public int cDestinations;
}




Next is the struct to hold the data for the mixer control itself

/// <summary>
/// struct to hold data for the mixer control
/// </summary>
public struct Mixer
{
    public int cbStruct;
    public int dwControlID;
    public int dwControlType;
    public int fdwControl;
    public int cMultipleItems;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = VolumeConstants.MIXER_SHORT_NAME_CHARS)] public string szShortName;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = VolumeConstants.MIXER_LONG_NAME_CHARS)] public string szName;
    public int lMinimum;
    public int lMaximum;
    [MarshalAs(UnmanagedType.U4, SizeConst = 10)]  public int reserved;
}




struct for holding data for the details of the mixer control

/// <summary>
/// struct for holding data about the details of the mixer control
/// </summary>
public struct MixerDetails
{
    public int cbStruct;
    public int dwControlID;
    public int cChannels;
    public int item;
    public int cbDetails;
    public IntPtr paDetails;
}



Now a struct for an unsigned mixer control

/// <summary>
/// struct to hold data for an unsigned mixer control details
/// </summary>
public struct UnsignedMixerDetails
{
    public int dwValue;
}



A struct for holding data about the mixer line being used

/// <summary>
/// struct to hold data for the mixer line
/// </summary>
public struct MixerLine
{
    public int cbStruct;
    public int dwDestination;
    public int dwSource;
    public int dwLineID;
    public int fdwLine;
    public int dwUser;
    public int dwComponentType;
    public int cChannels;
    public int cConnections;
    public int cControls;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = VolumeConstants.MIXER_SHORT_NAME_CHARS)] public string szShortName;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = VolumeConstants.MIXER_LONG_NAME_CHARS)] public string szName;
    public int dwType;
    public int dwDeviceID;
    public int wMid;
    public int wPid;
    public int vDriverVersion;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = VolumeConstants.MAXPNAMELEN)] public string szPname;
}



A struct for holding the data for the mixer line controls being used

/// <summary>
/// struct for holding data for the mixer line controls
/// </summary>
public struct LineControls
{
    public int cbStruct;
    public int dwLineID;
    public int dwControl;
    public int cControls;
    public int cbmxctrl;
    public IntPtr pamxctrl;
}



Ok, we have our Win32 API's and structs out of the way, now lets look at the constants needed for using a mixer control to take control of the volume control. Lets take a look at the constants for accomplishing this task:

/// <summary>
/// Class to hold all the constants needed for controlling the system sound
/// </summary>
public static class VolumeConstants
{
    public const int MMSYSERR_NOERROR = 0;
    public const int MAXPNAMELEN = 32;
    public const int MIXER_LONG_NAME_CHARS = 64;
    public const int MIXER_SHORT_NAME_CHARS = 16;
    public const int MIXER_GETLINEINFOF_COMPONENTTYPE = 0x3;
    public const int MIXER_GETCONTROLDETAILSF_VALUE = 0x0;
    public const int MIXER_GETLINECONTROLSF_ONEBYTYPE = 0x2;
    public const int MIXER_SETCONTROLDETAILSF_VALUE = 0x0;
    public const int MIXERLINE_COMPONENTTYPE_DST_FIRST = 0x0;
    public const int MIXERLINE_COMPONENTTYPE_SRC_FIRST = 0x1000;        
    public const int MIXERCONTROL_CT_CLASS_FADER = 0x50000000;
    public const int MIXERCONTROL_CT_UNITS_UNSIGNED = 0x30000;
    public const int MIXERCONTROL_CONTROLTYPE_FADER = (MIXERCONTROL_CT_CLASS_FADER | MIXERCONTROL_CT_UNITS_UNSIGNED);
    public const int MIXERCONTROL_CONTROLTYPE_VOLUME = (MIXERCONTROL_CONTROLTYPE_FADER + 1);
    public const int MIXERLINE_COMPONENTTYPE_DST_SPEAKERS = (MIXERLINE_COMPONENTTYPE_DST_FIRST + 4);
    public const int MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE = (MIXERLINE_COMPONENTTYPE_SRC_FIRST + 3);
    public const int MIXERLINE_COMPONENTTYPE_SRC_LINE = (MIXERLINE_COMPONENTTYPE_SRC_FIRST + 2);
}



NOTE: You will notice that the first 3 class are all declared as static, this is done so we don't have to create an instance of the class to use the items it contains. As with structs, there is a raging debate over using static classes, as far as I'm concerned the jury is still out on this.

Now on to the class that actually does all the work. In this class we have 4 methods, 1 for getting the mixer control, 1 for setting the mixer control, 1 for getting the current volume level, and finally 1 for setting the volume level. First the method for getting the mixer control. In this method we will be creating a new mixer control, we will retrieve it and use the method for setting its properties to set all the information we need for the mixer.

In this class you will be using many methods of the Marshal Class, located in the System.Runtime.InteropServices Namespace. We will be allocating memory blocks for our mixer control using the AllocCoTaskMem Method, we will be grabbing the size of our unmanaged type using the Marshal.SizeOf Method and more.

This is one of the reasons I said at the start of this tutorial that it is an advanced tutorial, and requires a firm grasp of the language, and the objects used, to understand what is going on. Before we can write any code that is going to work, we need to make sure we have references to the proper Namespaces, here are the Namespaces you will need to reference:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;
//custom Namespaces
using VolumeControl.Library.Constants;
using VolumeControl.Library.Structs;
using VolumeControl.Library.Win32;



Now for the first method in our class, the GetMixer method:


/// <summary>
/// method to retrieve a mixer control
/// </summary>
/// <param name="i"></param>
/// <param name="type"></param>
/// <param name="ctrlType"></param>
/// <param name="mixerControl"></param>
/// <param name="currVolume"></param>
/// <returns></returns>
private static bool GetMixer(int i, int type, int ctrlType, out VolumeStructs.Mixer mixerControl, out int currVolume)
{
    //create our method level variables
    int details;
    bool bReturn;
    currVolume = -1;

    //create our struct objects
    VolumeStructs.LineControls lineControls = new VolumeStructs.LineControls();
    VolumeStructs.MixerLine line = new VolumeStructs.MixerLine();
    VolumeStructs.MixerDetails mcDetails = new VolumeStructs.MixerDetails();
    VolumeStructs.UnsignedMixerDetails detailsUnsigned = new VolumeStructs.UnsignedMixerDetails();

    //create a new mixer control
    mixerControl = new VolumeStructs.Mixer();
   
    //set the properties of out mixerline object
    line.cbStruct = Marshal.SizeOf(line);
    line.dwComponentType = type;
    //get the line info and assign it to our details variable
    details = PCWin32.mixerGetLineInfoA(i, ref line, VolumeConstants.MIXER_GETLINEINFOF_COMPONENTTYPE);

    //make sure we didnt receive any errors
    if (VolumeConstants.MMSYSERR_NOERROR == details)
    {
        int mcSize = 152;
        //get the size of the unmanaged type
        int control = Marshal.SizeOf(typeof(VolumeStructs.Mixer));
        //allocate a block of memory
        lineControls.pamxctrl = Marshal.AllocCoTaskMem(mcSize);
        //get the size of the line controls
        lineControls.cbStruct = Marshal.SizeOf(lineControls);

        //set properties for our mixer control
        lineControls.dwLineID = line.dwLineID;
        lineControls.dwControl = ctrlType;
        lineControls.cControls = 1;
        lineControls.cbmxctrl = mcSize;

        // Allocate a buffer for the control
        mixerControl.cbStruct = mcSize;

        // Get the control
        details = PCWin32.mixerGetLineControlsA(i, ref lineControls, VolumeConstants.MIXER_GETLINECONTROLSF_ONEBYTYPE);

        //once again check to see if we received any errors
        if (VolumeConstants.MMSYSERR_NOERROR == details)
        {
            bReturn = true;
            //Copy the control into the destination structure
            mixerControl = (VolumeStructs.Mixer)Marshal.PtrToStructure(lineControls.pamxctrl, typeof(VolumeStructs.Mixer));
        }
        else
        {
            bReturn = false;
        }

        int mcDetailsSize = Marshal.SizeOf(typeof(VolumeStructs.MixerDetails));
        int mcDetailsUnsigned = Marshal.SizeOf(typeof(VolumeStructs.UnsignedMixerDetails));
        mcDetails.cbStruct = mcDetailsSize;
        mcDetails.dwControlID = mixerControl.dwControlID;
        mcDetails.paDetails = Marshal.AllocCoTaskMem(mcDetailsUnsigned);
        mcDetails.cChannels = 1;
        mcDetails.item = 0;
        mcDetails.cbDetails = mcDetailsUnsigned;
        details = PCWin32.mixerGetControlDetailsA(i, ref mcDetails, VolumeConstants.MIXER_GETCONTROLDETAILSF_VALUE);
        detailsUnsigned = (VolumeStructs.UnsignedMixerDetails)Marshal.PtrToStructure(mcDetails.paDetails, typeof(VolumeStructs.UnsignedMixerDetails));
        currVolume = detailsUnsigned.dwValue;
        return bReturn;
    }

    bReturn = false;
    return bReturn;
}



Next we will be looking at the SetMixer method. This is the method that does all the work for changing the volume level. It is called from the SetVolume method. We will be passing this method the mixer control we're using, the level we want the volume set at:

/// <summary>
/// method for setting the value for a volume control
/// </summary>
/// <param name="i"></param>
/// <param name="mixerControl"></param>
/// <param name="volumeLevel"></param>
/// <returns>true/false</returns>
private static bool SetMixer(int i, VolumeStructs.Mixer mixerControl, int volumeLevel)
{
    //method level variables
    bool bReturn;
    int details;

    //create our struct object for controlling the system sound
    VolumeStructs.MixerDetails mixerDetails = new VolumeStructs.MixerDetails();
    VolumeStructs.UnsignedMixerDetails volume = new VolumeStructs.UnsignedMixerDetails();

    //set out mixer control properties
    mixerDetails.item = 0;
    //set the id of the mixer control
    mixerDetails.dwControlID = mixerControl.dwControlID;
    //return the size of the mixer details struct
    mixerDetails.cbStruct = Marshal.SizeOf(mixerDetails);
    //return the volume
    mixerDetails.cbDetails = Marshal.SizeOf(volume);

    //Allocate a buffer for the mixer control value buffer
    mixerDetails.cChannels = 1;
    volume.dwValue = volumeLevel;

    //Copy the data into the mixer control value buffer
    mixerDetails.paDetails = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(VolumeStructs.UnsignedMixerDetails)));
    Marshal.StructureToPtr(volume, mixerDetails.paDetails, false);

    //Set the control value
    details = PCWin32.mixerSetControlDetails(i, ref mixerDetails, VolumeConstants.MIXER_SETCONTROLDETAILSF_VALUE);

    //Check to see if any errors were returned
    if (VolumeConstants.MMSYSERR_NOERROR == details)
    {
        bReturn = true;
    }
    else
    {
        bReturn = false;
    }
    return bReturn;

}



Now before you can set the volume you need to know what the current volume is, that is the job of our GetVolume method. Here we will call our GetMixer method which will allow us to grab the current volume level. Remember to always close your mixer object when you are done using it to free up those resources, and to prevent any memory leaks.

NOTE: .Net languages are managed languages and do a pretty good job managing resources and memory usage, but here we are dealing with an unmanaged type so we need to ensure we are closing items when we dont need them anymore.

Now for the GetVolume method:

/// <summary>
/// method for retrieving the current volume from the system
/// </summary>
/// <returns>int value</returns>
public static int GetVolume()
{
    //method level variables
    int currVolume;
    int mixerControl;

    //create a new volume control
    VolumeStructs.Mixer mixer = new VolumeStructs.Mixer();
    
    //open the mixer
    PCWin32.mixerOpen(out mixerControl, 0, 0, 0, 0);

    //set the type to volume control type
    int type = VolumeConstants.MIXERCONTROL_CONTROLTYPE_VOLUME;

    //get the mixer control and get the current volume level
    GetMixer(mixerControl, VolumeConstants.MIXERLINE_COMPONENTTYPE_DST_SPEAKERS, type, out mixer, out currVolume);

    //close the mixer control since we are now done with it
    PCWin32.mixerClose(mixerControl);

    //return the current volume to the calling method
    return currVolume;
}



The final method we have is the method for setting the volume to a specified level. Here we will create a new mixer control (as we do in every method in this class, because we make sure to close it as soon as we're done with it). We will then open the mixer, then we will check the value that is being passed for the volume level. If a value greater than the maximum level we will set the level at the maximum value, same with the minimum level, if the value provided is lower than the minimum value we will set the volume level at the minimum level. Once we are done we, once again, close the mixer to free up those resources:

/// <summary>
/// method for setting the volume to a specific level
/// </summary>
/// <param name="volumeLevel">volume level we wish to set volume to</param>
public static void SetVolume(int volumeLevel)
{
    try
    {
        //method level variables
        int currVolume;
        int mixerControl;

        //create a new volume control
        VolumeStructs.Mixer volumeControl = new VolumeStructs.Mixer();

        //open the mixer control
        PCWin32.mixerOpen(out mixerControl, 0, 0, 0, 0);

        //set the type to volume control type
        int controlType = VolumeConstants.MIXERCONTROL_CONTROLTYPE_VOLUME;

        //get the current mixer control and get the current volume
        GetMixer(mixerControl, VolumeConstants.MIXERLINE_COMPONENTTYPE_DST_SPEAKERS, controlType, out volumeControl, out currVolume);

        //now check the volume level. If the volume level
        //is greater than the max then set the volume to
        //the max level. If it's less than the minimum level
        //then set it to the minimun level
        if (volumeLevel > volumeControl.lMaximum)
        {
            volumeLevel = volumeControl.lMaximum;
        }
        else if (volumeLevel < volumeControl.lMinimum)
        {
            volumeLevel = volumeControl.lMinimum;
        }

        //set the volume
        SetMixer(mixerControl, volumeControl, volumeLevel);

        //now re-get the mixer control
        GetMixer(mixerControl, VolumeConstants.MIXERLINE_COMPONENTTYPE_DST_SPEAKERS, controlType, out volumeControl, out currVolume);

        //make sure the volume level is equal to the current volume
        if (volumeLevel != currVolume)
        {
            throw new Exception("Cannot Set Volume");
        }

        //close the mixer control as we are finished with it
        PCWin32.mixerClose(mixerControl);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }            
}



Now that we have created this class library you're probably wondering how you actually use it. Well here is an example of how you would use this in, say the click event of a button on your form. When we click the button we will first display the current volume level, we will then set a new volume level, then we will re-display the current volume level:

private void button1_Click(object sender, System.EventArgs e)
{
    //first we will display the current volume
    Label1.Text = VolumeControl.GetVolume.ToString();

    //now we will change the volume level
    VolumeControl.SetVolume(50);

    //now we will display the new volume level
    Label1.Text = VolumeControl.GetVolume().ToString();
}



There you have it! A way to create a Windows mixer control and control the volume of the computer the application is running on. I truly hope you found this tutorial useful and informative, I know I learned a lot when creating this class library. I am including the solution for this class library, all the license information and headers must remain in tact because it is convered by the GNU - General Public License, but you are free to modify and distribute as you wish. Thank you for reading.

Attachment attachment

Happy Coding!


HI it is very nice and helpfull code! many thanks for you.
i have question - i encounter a problem to Volume value to my application by rolling the scroll bar of the Volume Control. can you direct me how can i retrieve the value? (by your code it causing a collision of Get() and Set().
Was This Post Helpful? 0
  • +
  • -

#9 milkamal  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 26-October 11

Posted 26 October 2011 - 02:59 PM

[/quote]

HIWhen i use the code with a simple form application, i can see the label getting the volume level which we set. But actual volume on the speaker is not getting controlled. I am using Win 7 ? Is there anything missing in my code ?

[/quote]


Attached File  VolControl.zip (66.07K)
Number of downloads: 1228
Was This Post Helpful? 0
  • +
  • -

#10 likenoother31  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 4
  • Joined: 22-December 09

Posted 25 June 2012 - 12:56 PM

Hello. I am using this tutorial and it is working perfectly on a 32-bit system running XP. But I also need it to work on my Windows 7 64-bit machine. I am assuming there are some constants and field offsets that will need to be changed to make it work for a 64-bit computer, but I am unsure of which ones need to be changed, and what to change them to exactly. Could you please help me with this.
Was This Post Helpful? 0
  • +
  • -

#11 ADNS  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 10-August 13

Posted 10 August 2013 - 06:57 PM

Hi,
Probably won't get answer on this, let's try.
To be honest i'm weekend programer, somehow it is working but...
I used this library in simple form app - win 7, VS 2010, C# - don't know what else to say.
I got a throw in SetVolume function "Cannot Set Volume" every time i call it. I figure it out that volume is set to desired volume - 1 so i've changed the 152th line in VolumeControl.sc to "volume.dwValue = volumeLevel + 1";
It now works fine but i still really don't know what couse the issue.

Secoundly and essentially the main problem is that i see the volume level is apply only to my application.
I want to change the volume of master system track so that will affects every working process.
Is there an easy tweek i can do to achieve that?

Sorry if my quastions seems stupid and nooby but i don't know at this point how it is all really working.
I will be very glad for any advise/tip. ;)
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1