winealsa: Improve sound mixer detection

Maarten Lankhorst m.b.lankhorst at gmail.com
Wed May 2 13:09:49 CDT 2007


The previous mixer code depended on the existence of 1 'Master', and 1
'Capture' control. According to some feedback in wine-devel this is not
always the case, the new code will try the following order for loading:
Destination waveout: Master, Headphone PCM
Destination wavein: Capture

If multiple of those controls are found, the first one in the list will
be the 'destination', the rest will be added as 'source'. If nothing is
found for destination waveout then this device is disabled.
If no wavein destination is found, or no controls exists with capture
switch, then that part will be disabled altogether.
-------------- next part --------------
>From 3f3eec1e53e468dc6043a0e03265fb3920cd211a Mon Sep 17 00:00:00 2001
From: Maarten Lankhorst <m.b.lankhorst at gmail.com>
Date: Sat, 28 Apr 2007 14:33:36 +0200
Subject: [PATCH] winealsa: Improve sound mixer detection

---
 dlls/winealsa.drv/mixer.c |  237 +++++++++++++++++++++++++++------------------
 1 files changed, 142 insertions(+), 95 deletions(-)

diff --git a/dlls/winealsa.drv/mixer.c b/dlls/winealsa.drv/mixer.c
index ad87916..c1dab51 100644
--- a/dlls/winealsa.drv/mixer.c
+++ b/dlls/winealsa.drv/mixer.c
@@ -251,6 +251,10 @@ static void fillcontrols(mixer *mmixer)
         int x;
         long min, max;
 
+        TRACE("Filling control %d\n", id);
+        if (id == 1 && !mline->elem)
+            continue;
+
         if (mline->capt && snd_mixer_selem_has_capture_volume(mline->elem))
             snd_mixer_selem_get_capture_volume_range(mline->elem, &min, &max);
         else
@@ -325,29 +329,100 @@ static int chans(mixer *mmixer, snd_mixer_elem_t * elem, DWORD capt)
     return ret;
 }
 
+static void filllines(mixer *mmixer, snd_mixer_elem_t *mastelem, snd_mixer_elem_t *captelem, int capt)
+{
+    snd_mixer_elem_t *elem;
+    line *mline = mmixer->lines;
+
+    /* Master control */
+    MultiByteToWideChar(CP_UNIXCP, 0, snd_mixer_selem_get_name(mastelem), -1, mline->name, sizeof(mline->name)/sizeof(WCHAR));
+    mline->component = getcomponenttype(snd_mixer_selem_get_name(mastelem));
+    mline->dst = 0;
+    mline->capt = 0;
+    mline->elem = mastelem;
+    mline->chans = chans(mmixer, mastelem, 0);
+
+    snd_mixer_elem_set_callback(mastelem, &elem_callback);
+    snd_mixer_elem_set_callback_private(mastelem, &mmixer);
+
+    /* Capture control
+     * Note: since mmixer->dests = 1, it means only playback control is visible
+     * This makes sense, because if there are no capture sources capture control
+     * can't do anything and should be invisible */
+
+    /* Control 1 is reserved for capture even when not enabled */
+    ++mline;
+    if (capt)
+    {
+        MultiByteToWideChar(CP_UNIXCP, 0, snd_mixer_selem_get_name(captelem), -1, mline->name, sizeof(mline->name)/sizeof(WCHAR));
+        mline->component = getcomponenttype(snd_mixer_selem_get_name(captelem));
+        mline->dst = 1;
+        mline->capt = 1;
+        mline->elem = captelem;
+        mline->chans = chans(mmixer, captelem, 1);
+
+        snd_mixer_elem_set_callback(captelem, &elem_callback);
+        snd_mixer_elem_set_callback_private(captelem, &mmixer);
+    }
+
+    for (elem = snd_mixer_first_elem(mmixer->mix); elem; elem = snd_mixer_elem_next(elem))
+        if (elem != mastelem && elem != captelem && !blacklisted(elem))
+        {
+            const char * name = snd_mixer_selem_get_name(elem);
+            DWORD comp = getcomponenttype(name);
+
+            if (snd_mixer_selem_has_playback_volume(elem))
+            {
+                (++mline)->component = comp;
+                MultiByteToWideChar(CP_UNIXCP, 0, name, -1, mline->name, MAXPNAMELEN);
+                mline->capt = mline->dst = 0;
+                mline->elem = elem;
+                mline->chans = chans(mmixer, elem, 0);
+            }
+            else if (!capt)
+                continue;
+
+            if (capt && snd_mixer_selem_has_capture_switch(elem))
+            {
+                (++mline)->component = comp;
+                MultiByteToWideChar(CP_UNIXCP, 0, name, -1, mline->name, MAXPNAMELEN);
+                mline->capt = mline->dst = 1;
+                mline->elem = elem;
+                mline->chans = chans(mmixer, elem, 1);
+            }
+
+            snd_mixer_elem_set_callback(elem, &elem_callback);
+            snd_mixer_elem_set_callback_private(elem, &mmixer);
+        }
+}
+
+/* Windows api wants to have a 'master' device to which all slaves are attached
+ * There are 2 ones in this code:
+ * - 'Master', fall back to 'Headphone' if unavailable, and if that's not available 'PCM'
+ * - 'Capture'
+ * Capture might not always be available, so should be prepared to be without if needed
+ */
+
 static void ALSA_MixerInit(void)
 {
     int x, mixnum = 0;
 
     for (x = 0; x < MAX_MIXERS; ++x)
     {
-        int card, err, y;
+        int card, err, capcontrols = 0;
         char cardind[6], cardname[10];
-        BOOL hascapt=0, hasmast=0;
-        line *mline;
 
         snd_ctl_t *ctl;
-        snd_mixer_elem_t *elem, *mastelem = NULL, *captelem = NULL;
+        snd_mixer_elem_t *elem, *mastelem = NULL, *headelem = NULL, *captelem = NULL, *pcmelem = NULL;
         snd_ctl_card_info_t *info = NULL;
         snd_ctl_card_info_alloca(&info);
-        mixdev[mixnum].lines = NULL;
-        mixdev[mixnum].callback = 0;
-        mixdev[mixnum].controls = NULL;
 
+        memset(&mixdev[mixnum], 0, sizeof(*mixdev));
         snprintf(cardind, sizeof(cardind), "%d", x);
         card = snd_card_get_index(cardind);
         if (card < 0)
             continue;
+
         snprintf(cardname, sizeof(cardname), "hw:%d", card);
 
         err = snd_ctl_open(&ctl, cardname, 0);
@@ -368,7 +443,7 @@ static void ALSA_MixerInit(void)
         MultiByteToWideChar(CP_UNIXCP, 0, snd_ctl_card_info_get_name(info), -1, mixdev[mixnum].mixername, sizeof(mixdev[mixnum].mixername)/sizeof(WCHAR));
         snd_ctl_close(ctl);
 
-        err = snd_mixer_open(&mixdev[mixnum].mix,0);
+        err = snd_mixer_open(&mixdev[mixnum].mix, 0);
         if (err < 0)
         {
             WARN("Error occurred opening mixer: %s\n", snd_strerror(err));
@@ -387,116 +462,84 @@ static void ALSA_MixerInit(void)
         if (err < 0)
             goto eclose;
 
-        mixdev[mixnum].chans = 0;
-        mixdev[mixnum].dests = 1; /* Master, Capture will be enabled if needed */
-
+        /* First, lets see what's available..
+         * If there are multiple Master or Captures, all except 1 will be added as slaves
+         */
         for (elem = snd_mixer_first_elem(mixdev[mixnum].mix); elem; elem = snd_mixer_elem_next(elem))
-            if (!strcasecmp(snd_mixer_selem_get_name(elem), "Master"))
-            {
+            if (!strcasecmp(snd_mixer_selem_get_name(elem), "Master") && !mastelem)
                 mastelem = elem;
-                ++hasmast;
-            }
-            else if (!strcasecmp(snd_mixer_selem_get_name(elem), "Capture"))
-            {
+            else if (!strcasecmp(snd_mixer_selem_get_name(elem), "Capture") && !captelem)
                 captelem = elem;
-                ++hascapt;
-            }
             else if (!blacklisted(elem))
             {
                 if (snd_mixer_selem_has_capture_switch(elem))
+                    ++capcontrols;
+                if (snd_mixer_selem_has_playback_volume(elem))
                 {
-                    ++mixdev[mixnum].chans;
-                    mixdev[mixnum].dests = 2;
+                    if (!strcasecmp(snd_mixer_selem_get_name(elem), "Headphone") && !headelem)
+                        headelem = elem;
+                    else if (!strcasecmp(snd_mixer_selem_get_name(elem), "PCM") && !pcmelem)
+                        pcmelem = elem;
+                    else
+                        ++(mixdev[mixnum].chans);
                 }
-                if (snd_mixer_selem_has_playback_volume(elem))
-                    ++mixdev[mixnum].chans;
             }
 
+        /* Add master channel, uncounted channels and an extra for capture  */
+        mixdev[mixnum].chans += !!mastelem + !!headelem + !!pcmelem + 1;
+
         /* If there is only 'Capture' and 'Master', this device is not worth it */
-        if (!mixdev[mixnum].chans)
+        if (mixdev[mixnum].chans == 2)
         {
             WARN("No channels found, skipping device!\n");
-            snd_mixer_close(mixdev[mixnum].mix);
-            continue;
+            goto close;
         }
 
-        /* If there are no 'Capture' and 'Master', something is wrong */
-        if (hasmast != 1 || hascapt != 1)
+        /* Master element can't have a capture control in this code, so
+         * if Headphone or PCM is promoted to master, unset its capture control */
+        if (headelem && !mastelem)
         {
-            if (hasmast != 1)
-                FIXME("Should have found 1 channel for 'Master', but instead found %d\n", hasmast);
-            if (hascapt != 1)
-                FIXME("Should have found 1 channel for 'Capture', but instead found %d\n", hascapt);
-            goto eclose;
+            /* Using 'Headphone' as master device */
+            mastelem = headelem;
+            capcontrols -= !!snd_mixer_selem_has_capture_switch(mastelem);
+        }
+        else if (pcmelem && !mastelem)
+        {
+            /* Use 'PCM' as master device */
+            mastelem = pcmelem;
+            capcontrols -= !!snd_mixer_selem_has_capture_switch(mastelem);
+        }
+        else if (!mastelem)
+        {
+            /* If there is nothing sensible that can act as 'Master' control, something is wrong */
+            FIXME("No master control found, disabling mixer\n");
+            goto close;
+        }
+
+        if (!captelem || !capcontrols)
+        {
+            /* Can't enable capture, so disabling it
+             * Note: capture control will still exist because
+             * dwLineID 0 and 1 are reserved for Master and Capture
+             */
+            WARN("No use enabling capture part of mixer, capture control found: %s, amount of capture controls: %d\n",
+                 (!captelem ? "no" : "yes"), capcontrols);
+            capcontrols = 0;
+            mixdev[mixnum].dests = 1;
+        }
+        else
+        {
+            mixdev[mixnum].chans += capcontrols;
+            mixdev[mixnum].dests = 2;
         }
 
-        mixdev[mixnum].chans += 2; /* Capture/Master */
         mixdev[mixnum].lines = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(line) * mixdev[mixnum].chans);
         mixdev[mixnum].controls = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(control) * CONTROLSPERLINE*mixdev[mixnum].chans);
         err = -ENOMEM;
         if (!mixdev[mixnum].lines || !mixdev[mixnum].controls)
-            goto eclose;
-
-        /* Master control */
-        mline = &mixdev[mixnum].lines[0];
-        MultiByteToWideChar(CP_UNIXCP, 0, snd_mixer_selem_get_name(mastelem), -1, mline->name, sizeof(mline->name)/sizeof(WCHAR));
-        mline->component = getcomponenttype("Master");
-        mline->dst = 0;
-        mline->capt = 0;
-        mline->elem = mastelem;
-        mline->chans = chans(&mixdev[mixnum], mastelem, 0);
-
-        /* Capture control
-         * Note: since mmixer->dests = 1, it means only playback control is visible
-         * This makes sense, because if there are no capture sources capture control
-         * can't do anything and should be invisible */
-
-        mline++;
-        MultiByteToWideChar(CP_UNIXCP, 0, snd_mixer_selem_get_name(captelem), -1, mline->name, sizeof(mline->name)/sizeof(WCHAR));
-        mline->component = getcomponenttype("Capture");
-        mline->dst = 1;
-        mline->capt = 1;
-        mline->elem = captelem;
-        mline->chans = chans(&mixdev[mixnum], captelem, 1);
-
-        snd_mixer_elem_set_callback(mastelem, &elem_callback);
-        snd_mixer_elem_set_callback_private(mastelem, &mixdev[mixnum]);
-
-        if (mixdev[mixnum].dests == 2)
-        {
-            snd_mixer_elem_set_callback(captelem, &elem_callback);
-            snd_mixer_elem_set_callback_private(captelem, &mixdev[mixnum]);
-        }
-
-        y=2;
-        for (elem = snd_mixer_first_elem(mixdev[mixnum].mix); elem; elem = snd_mixer_elem_next(elem))
-            if (elem != mastelem && elem != captelem && !blacklisted(elem))
-            {
-                const char * name = snd_mixer_selem_get_name(elem);
-                DWORD comp = getcomponenttype(name);
-                snd_mixer_elem_set_callback(elem, &elem_callback);
-                snd_mixer_elem_set_callback_private(elem, &mixdev[mixnum]);
-
-                if (snd_mixer_selem_has_playback_volume(elem))
-                {
-                    mline = &mixdev[mixnum].lines[y++];
-                    mline->component = comp;
-                    MultiByteToWideChar(CP_UNIXCP, 0, name, -1, mline->name, MAXPNAMELEN);
-                    mline->capt = mline->dst = 0;
-                    mline->elem = elem;
-                    mline->chans = chans(&mixdev[mixnum], elem, 0);
-                }
-                if (snd_mixer_selem_has_capture_switch(elem))
-                {
-                    mline = &mixdev[mixnum].lines[y++];
-                    mline->component = comp;
-                    MultiByteToWideChar(CP_UNIXCP, 0, name, -1, mline->name, MAXPNAMELEN);
-                    mline->capt = mline->dst = 1;
-                    mline->elem = elem;
-                    mline->chans = chans(&mixdev[mixnum], elem, 1);
-                }
-            }
+            goto close;
 
+        filllines(&mixdev[mixnum], mastelem, captelem, capcontrols);
         fillcontrols(&mixdev[mixnum]);
 
         TRACE("%s: Amount of controls: %i/%i, name: %s\n", cardname, mixdev[mixnum].dests, mixdev[mixnum].chans, debugstr_w(mixdev[mixnum].mixername));
@@ -505,12 +548,16 @@ static void ALSA_MixerInit(void)
 
         eclose:
         WARN("Error occurred initialising mixer: %s\n", snd_strerror(err));
+        close:
         HeapFree(GetProcessHeap(), 0, mixdev[mixnum].lines);
         HeapFree(GetProcessHeap(), 0, mixdev[mixnum].controls);
         snd_mixer_close(mixdev[mixnum].mix);
     }
     cards = mixnum;
 
+    /* There is no trouble with already assigning callbacks without initialising critsect:
+     * Callbacks only occur when snd_mixer_handle_events is called (only happens in thread)
+     */
     InitializeCriticalSection(&elem_crst);
     elem_crst.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": ALSA_MIXER.elem_crst");
     TRACE("\n");
-- 
1.4.4.2



More information about the wine-patches mailing list