views:

333

answers:

3

First, note that I know there are a few questions like this already posted; however they don't seem to address the problem adequately. I have a C# application, with all the pInvoke hooks to talk to the waveXXX API, and I'm able to do capture and play back of audio with that. I'm also able to adjust speaker (WaveOut) volume with that API.
The problem is that for whatever reason, that API does not allow me to adjust microphone (WaveIn) volume. So, I managed to find some mixer code that I've also pulled in and access through pInvoke and that allows me to adjust microphone volume, but only on my W7 PC. The mixer code I started with comes from here: http://social.msdn.microsoft.com/Forums/en-US/isvvba/thread/05dc2d35-1d45-4837-8e16-562ee919da85 and it works, but is written to adjust speaker volume. I added the SetMicVolume method shown here...

    public static void SetMicVolume(int mxid, int percentage)
    {
        bool rc;
        int mixer, vVolume;
        MIXERCONTROL volCtrl = new MIXERCONTROL();
        int currentVol;
        mixerOpen(out mixer, mxid, 0, 0, MIXER_OBJECTF_WAVEIN);
        int type = MIXERCONTROL_CONTROLTYPE_VOLUME;
        rc = GetVolumeControl(mixer, MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE, type, out volCtrl, out currentVol);
        if (rc == false)
        {
            mixerClose(mixer);
            mixerOpen(out mixer, 0, 0, 0, 0);
            rc = GetVolumeControl(mixer, MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE, type, out volCtrl, out currentVol);
            if (rc == false)    
                throw new Exception("SetMicVolume/GetVolumeControl() failed");
        }
        vVolume = ((int)((float)(volCtrl.lMaximum - volCtrl.lMinimum) / 100.0F) * percentage);
        rc = SetVolumeControl(mixer, volCtrl, vVolume);
        if (rc == false)
            throw new Exception("SetMicVolume/SetVolumeControl() failed");
        mixerClose(mixer);
    }

Note the "second attempt" to call GetVolumeControl(). This is done because on XP, in the first call to GetVolumeControl (refer to site above for that code), the call to mixerGetLineControlsA() fails with XP systems returning MIXERR_INVALCONTROL. Then, with this second attempt using mixerOpen(out mixer, 0, 0, 0, 0), the code doesn't return a failure but the mic gain is unaffected. Note, as I said above, this works on W7 (the second attempt is never executed because it doesn't fail using mixerOpen(out mixer, mxid, 0, 0, MIXER_OBJECTF_WAVEIN)).

I admit to not having a good grasp on the mixer API, so that's what I'm looking into now; however if anyone has a clue why this would work on W7, but not XP, I'd sure like to hear it. Meanwhile, if I figure it out before I get a response, I'll post my own answer...

A: 

I tried doing exactly this a while ago when I was writing .NET Voice Recorder using NAudio, and found it extremely hard. You probably have to end up writing two lots of code, one for XP and one for Vista/Win 7. I am using NAudio for the mixer interop.

This is what I ended up with (still doesn't work everywhere)

    private void TryGetVolumeControl()
    {
        int waveInDeviceNumber = waveIn.DeviceNumber;
        if (Environment.OSVersion.Version.Major >= 6) // Vista and over
        {
            var mixerLine = new MixerLine((IntPtr)waveInDeviceNumber, 0, MixerFlags.WaveIn);
            foreach (var control in mixerLine.Controls)
            {
                if (control.ControlType == MixerControlType.Volume)
                {
                    volumeControl = control as UnsignedMixerControl;
                    MicrophoneLevel = desiredVolume;
                    break;
                }
            }
        }
        else
        {
            var mixer = new Mixer(waveInDeviceNumber);
            foreach (var destination in mixer.Destinations)
            {
                if (destination.ComponentType == MixerLineComponentType.DestinationWaveIn)
                {
                    foreach (var source in destination.Sources)
                    {
                        if (source.ComponentType == MixerLineComponentType.SourceMicrophone)
                        {
                            foreach (var control in source.Controls)
                            {
                                if (control.ControlType == MixerControlType.Volume)
                                {
                                    volumeControl = control as UnsignedMixerControl;
                                    MicrophoneLevel = desiredVolume;
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        }

    }
Mark Heath
Mark, thanks for the quick response. Hadn't heard of NAudio prior to this (which kinda surprises me because the last few months I've been doing a lot of 'googling' for various audio issues on the pc).It looks pretty mature. I'll take a look at that; however, I'd sure like to find a solution just using the basic mixer API just so I can avoid pulling in a big package just to do mic-volume-control.
Ed
Mark, I've been browsing the NAudio package (looks really clean); but I don't see any calls to mixerOpen(). If that observation is correct, how is NAudio using the mixer?
Ed
you don't need to open the mixer to change the controls on it
Mark Heath
But don't all the control calls need the mixer handle that is returned by mixeropen()?
Ed
no, they can use an Id.
Mark Heath
A: 

The following code seems to work ok for me now (updated 6/29/2010). Note that my test cases are my two PCs, one being W7 the other being XP, so it's not conclusive. I've verified that this does not work for all machines, but for those on which they do, it seems to be fine.

    public static bool setMicVolume(int mxid, int percentage)
    {
        if (mixerdisabled)
            return(false);

        bool rc;
        int mixer, vVolume, ctrltype, comptype;
        MIXERCONTROL volCtrl = new MIXERCONTROL();
        int currentVol;
        int mr = mixerOpen(out mixer, mxid, 0, 0, MIXER_OBJECTF_WAVEIN);
        if (mr != MMSYSERR_NOERROR)
        {
            Warning("mixerOpen() failed: " + mr.ToString());
            mixerdisabled = true;
            return(false);
        }
        ctrltype = MIXERCONTROL_CONTROLTYPE_VOLUME;
        comptype = MIXERLINE_COMPONENTTYPE_DST_WAVEIN;
        rc = GetVolumeControl(mixer, comptype, ctrltype, out volCtrl, out currentVol);
        if (rc == false)
        {
            Warning("SetMicVolume/GetVolumeControl() failed");
            mixerdisabled = true;
            mixerClose(mixer);
            return(false);
        }
        vVolume = ((int)((float)(volCtrl.lMaximum - volCtrl.lMinimum) / 100.0F) * percentage);
        rc = SetVolumeControl(mixer, volCtrl, vVolume);
        if (rc == false)
        {
            Warning("SetMicVolume/SetVolumeControl() failed");
            mixerdisabled = true;
            mixerClose(mixer);
            return (false);
        }
        mixerClose(mixer);
        return (true);
    }

Notice that the main difference is that I'm using a component type of 'MIXERLINE_COMPONENTTYPE_DST_WAVEIN' instead of 'MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE'. Don't really understand this, so if anyone wants to chime in with an explanation (or to tell me that this isn't going to work generically), I'm welcoming the replies!

Ed
Note that I've already had an XP machine that this failed on, so its still not perfect...
Ed
A: 

What is de mxid Parameter?

Ricardo C.
Its the same ID that would be passed to waveInGetDevCaps()...In the nutshell, you can get a list of the valid audio devices by starting with a devid of 0 and calling waveInGetDevCaps() until it returns an error...for(devid=0;;devid++){ if (waveInGetDevCaps(devid,...) != NOERROR) break; ...} So, after you decide which device you want to open, you can use that same devid value in the call to setMicVolume().HTH...
Ed