2 * Alsa MIXER Wine Driver for Linux
3 * Very loosely based on wineoss mixer driver
5 * Copyright 2007 Maarten Lankhorst
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 #include "wine/port.h"
35 #ifdef HAVE_SYS_IOCTL_H
36 # include <sys/ioctl.h>
39 #define NONAMELESSUNION
40 #define NONAMELESSSTRUCT
51 #include "wine/unicode.h"
52 #include "wine/debug.h"
54 WINE_DEFAULT_DEBUG_CHANNEL(mixer);
58 #define WINE_MIXER_MANUF_ID 0xAA
59 #define WINE_MIXER_PRODUCT_ID 0x55
60 #define WINE_MIXER_VERSION 0x0100
63 * In windows it seems to be required for all controls to have a volume switch
64 * In alsa that's optional
66 * I assume for playback controls, that there is always a playback volume switch available
69 * For capture controls, it is needed that there is a capture switch and a volume switch,
70 * It doesn't matter wether it is a playback volume switch or a capture volume switch.
71 * The code will first try to get/adjust capture volume, if that fails it tries playback volume
72 * It is not pretty, but under my 3 test cards it seems that there is no other choice:
73 * Most capture controls don't have a capture volume setting
75 * MUX means that only capture source can be exclusively selected,
76 * MIXER means that multiple sources can be selected simultaneously.
79 static const char * getMessage(UINT uMsg)
82 #define MSG_TO_STR(x) case x: return #x;
84 MSG_TO_STR(DRVM_INIT);
85 MSG_TO_STR(DRVM_EXIT);
86 MSG_TO_STR(DRVM_ENABLE);
87 MSG_TO_STR(DRVM_DISABLE);
88 MSG_TO_STR(MXDM_GETDEVCAPS);
89 MSG_TO_STR(MXDM_GETLINEINFO);
90 MSG_TO_STR(MXDM_GETNUMDEVS);
91 MSG_TO_STR(MXDM_OPEN);
92 MSG_TO_STR(MXDM_CLOSE);
93 MSG_TO_STR(MXDM_GETLINECONTROLS);
94 MSG_TO_STR(MXDM_GETCONTROLDETAILS);
95 MSG_TO_STR(MXDM_SETCONTROLDETAILS);
99 sprintf(str, "UNKNOWN(%08x)", uMsg);
103 /* A simple declaration of a line control
104 * These are each of the channels that show up
106 typedef struct line {
107 /* Name we present to outside world */
108 WCHAR name[MAXPNAMELEN];
114 snd_mixer_elem_t *elem;
121 WCHAR mixername[MAXPNAMELEN];
124 LPDRVCALLBACK callback;
125 DWORD_PTR callbackpriv;
131 #define MAX_MIXERS 32
133 static int cards = 0;
134 static mixer mixdev[MAX_MIXERS];
135 static HANDLE thread;
136 static int elem_callback(snd_mixer_elem_t *elem, unsigned int mask);
137 static DWORD WINAPI ALSA_MixerPollThread(LPVOID lParam);
138 static CRITICAL_SECTION elem_crst;
139 static int msg_pipe[2];
142 /* found channel names in alsa lib, alsa api doesn't have another way for this
143 * map name -> componenttype, worst case we get a wrong componenttype which is
147 static const struct mixerlinetype {
148 const char *name; DWORD cmpt;
150 { "Master", MIXERLINE_COMPONENTTYPE_DST_SPEAKERS, },
151 { "Capture", MIXERLINE_COMPONENTTYPE_DST_WAVEIN, },
152 { "PCM", MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT, },
153 { "PC Speaker", MIXERLINE_COMPONENTTYPE_SRC_PCSPEAKER, },
154 { "Synth", MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER, },
155 { "Headphone", MIXERLINE_COMPONENTTYPE_DST_HEADPHONES, },
156 { "Mic", MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE, },
157 { "Aux", MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED, },
158 { "CD", MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC, },
159 { "Line", MIXERLINE_COMPONENTTYPE_SRC_LINE, },
160 { "Phone", MIXERLINE_COMPONENTTYPE_SRC_TELEPHONE, },
163 /* Map name to MIXERLINE_COMPONENTTYPE_XXX */
164 static int getcomponenttype(const char *name)
167 for (x=0; x< sizeof(converttable)/sizeof(converttable[0]); ++x)
168 if (!strcasecmp(name, converttable[x].name))
170 TRACE("%d -> %s\n", x, name);
171 return converttable[x].cmpt;
173 WARN("Unknown mixer name %s, probably harmless\n", name);
174 return MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED;
177 /* Is this control suited for showing up? */
178 static int blacklisted(snd_mixer_elem_t *elem)
180 const char *name = snd_mixer_selem_get_name(elem);
183 if (!snd_mixer_selem_has_playback_volume(elem) &&
184 (!snd_mixer_selem_has_capture_volume(elem) ||
185 !snd_mixer_selem_has_capture_switch(elem)))
188 TRACE("%s: %x\n", name, blisted);
192 /* get amount of channels for elem */
193 /* Officially we should keep capture/playback seperated,
194 * but that's not going to work in the alsa api */
195 static int chans(mixer *mmixer, snd_mixer_elem_t * elem, DWORD capt)
199 if (capt && snd_mixer_selem_has_capture_volume(elem)) {
200 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
201 if (snd_mixer_selem_has_capture_channel(elem, chn))
204 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
205 if (snd_mixer_selem_has_playback_channel(elem, chn))
209 FIXME("Mixer channel %s was found for %s, but no channels were found? Wrong selection!\n", snd_mixer_selem_get_name(elem), (snd_mixer_selem_has_playback_volume(elem) ? "playback" : "capture"));
213 static void ALSA_MixerInit(void)
217 for (x = 0; x < MAX_MIXERS; ++x)
220 char cardind[6], cardname[10];
221 BOOL hascapt=0, hasmast=0;
225 snd_mixer_elem_t *elem, *mastelem = NULL, *captelem = NULL;
226 snd_ctl_card_info_t *info = NULL;
227 snd_ctl_card_info_alloca(&info);
228 mixdev[mixnum].lines = NULL;
229 mixdev[mixnum].callback = 0;
231 snprintf(cardind, sizeof(cardind), "%d", x);
232 card = snd_card_get_index(cardind);
235 snprintf(cardname, sizeof(cardname), "hw:%d", card);
237 err = snd_ctl_open(&ctl, cardname, 0);
240 WARN("Cannot open card: %s\n", snd_strerror(err));
244 err = snd_ctl_card_info(ctl, info);
247 WARN("Cannot get card info: %s\n", snd_strerror(err));
252 MultiByteToWideChar(CP_UNIXCP, 0, snd_ctl_card_info_get_name(info), -1, mixdev[mixnum].mixername, sizeof(mixdev[mixnum].mixername)/sizeof(WCHAR));
255 err = snd_mixer_open(&mixdev[mixnum].mix,0);
258 WARN("Error occured opening mixer: %s\n", snd_strerror(err));
262 err = snd_mixer_attach(mixdev[mixnum].mix, cardname);
266 err = snd_mixer_selem_register(mixdev[mixnum].mix, NULL, NULL);
270 err = snd_mixer_load(mixdev[mixnum].mix);
274 mixdev[mixnum].chans = 0;
275 mixdev[mixnum].dests = 1; /* Master, Capture will be enabled if needed */
277 for (elem = snd_mixer_first_elem(mixdev[mixnum].mix); elem; elem = snd_mixer_elem_next(elem))
278 if (!strcasecmp(snd_mixer_selem_get_name(elem), "Master"))
283 else if (!strcasecmp(snd_mixer_selem_get_name(elem), "Capture"))
288 else if (!blacklisted(elem))
290 if (snd_mixer_selem_has_capture_switch(elem))
292 ++mixdev[mixnum].chans;
293 mixdev[mixnum].dests = 2;
295 if (snd_mixer_selem_has_playback_volume(elem))
296 ++mixdev[mixnum].chans;
299 /* If there is only 'Capture' and 'Master', this device is not worth it */
300 if (!mixdev[mixnum].chans)
302 WARN("No channels found, skipping device!\n");
303 snd_mixer_close(mixdev[mixnum].mix);
307 /* If there are no 'Capture' and 'Master', something is wrong */
308 if (hasmast != 1 || hascapt != 1)
311 FIXME("Should have found 1 channel for 'Master', but instead found %d\n", hasmast);
313 FIXME("Should have found 1 channel for 'Capture', but instead found %d\n", hascapt);
317 mixdev[mixnum].chans += 2; /* Capture/Master */
318 mixdev[mixnum].lines = calloc(sizeof(MIXERLINEW), mixdev[mixnum].chans);
320 if (!mixdev[mixnum].lines)
324 mline = &mixdev[mixnum].lines[0];
325 MultiByteToWideChar(CP_UNIXCP, 0, snd_mixer_selem_get_name(mastelem), -1, mline->name, sizeof(mline->name)/sizeof(WCHAR));
326 mline->component = getcomponenttype("Master");
329 mline->elem = mastelem;
330 mline->chans = chans(&mixdev[mixnum], mastelem, 0);
333 * Note: since mmixer->dests = 1, it means only playback control is visible
334 * This makes sense, because if there are no capture sources capture control
335 * can't do anything and should be invisible */
338 MultiByteToWideChar(CP_UNIXCP, 0, snd_mixer_selem_get_name(captelem), -1, mline->name, sizeof(mline->name)/sizeof(WCHAR));
339 mline->component = getcomponenttype("Capture");
342 mline->elem = captelem;
343 mline->chans = chans(&mixdev[mixnum], captelem, 1);
345 snd_mixer_elem_set_callback(mastelem, &elem_callback);
346 snd_mixer_elem_set_callback_private(mastelem, &mixdev[mixnum]);
348 if (mixdev[mixnum].dests == 2)
350 snd_mixer_elem_set_callback(captelem, &elem_callback);
351 snd_mixer_elem_set_callback_private(captelem, &mixdev[mixnum]);
355 for (elem = snd_mixer_first_elem(mixdev[mixnum].mix); elem; elem = snd_mixer_elem_next(elem))
356 if (elem != mastelem && elem != captelem && !blacklisted(elem))
358 const char * name = snd_mixer_selem_get_name(elem);
359 DWORD comp = getcomponenttype(name);
360 snd_mixer_elem_set_callback(elem, &elem_callback);
361 snd_mixer_elem_set_callback_private(elem, &mixdev[mixnum]);
363 if (snd_mixer_selem_has_playback_volume(elem))
365 mline = &mixdev[mixnum].lines[y++];
366 mline->component = comp;
367 MultiByteToWideChar(CP_UNIXCP, 0, name, -1, mline->name, MAXPNAMELEN);
368 mline->capt = mline->dst = 0;
370 mline->chans = chans(&mixdev[mixnum], elem, 0);
372 if (snd_mixer_selem_has_capture_switch(elem))
374 mline = &mixdev[mixnum].lines[y++];
375 mline->component = comp;
376 MultiByteToWideChar(CP_UNIXCP, 0, name, -1, mline->name, MAXPNAMELEN);
377 mline->capt = mline->dst = 1;
379 mline->chans = chans(&mixdev[mixnum], elem, 1);
383 TRACE("%s: Amount of controls: %i/%i, name: %s\n", cardname, mixdev[mixnum].dests, mixdev[mixnum].chans, debugstr_w(mixdev[mixnum].mixername));
388 WARN("Error occured initialising mixer: %s\n", snd_strerror(err));
389 if (mixdev[mixnum].lines)
390 free(mixdev[mixnum].lines);
391 snd_mixer_close(mixdev[mixnum].mix);
395 InitializeCriticalSection(&elem_crst);
396 elem_crst.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": ALSA_MIXER.elem_crst");
400 static void ALSA_MixerExit(void)
406 WARN("Callback thread still alive, terminating uncleanly, refcnt: %d\n", refcnt);
407 /* Least we can do is making sure we're not in 'foreign' code */
408 EnterCriticalSection(&elem_crst);
409 TerminateThread(thread, 1);
413 TRACE("Cleaning up\n");
415 elem_crst.DebugInfo->Spare[0] = 0;
416 DeleteCriticalSection(&elem_crst);
417 for (x = 0; x < cards; ++x)
419 snd_mixer_close(mixdev[x].mix);
420 free(mixdev[x].lines);
425 static mixer* MIX_GetMix(UINT wDevID)
429 if (wDevID < 0 || wDevID >= cards)
431 WARN("Invalid mixer id: %d\n", wDevID);
435 mmixer = &mixdev[wDevID];
439 /* Since alsa doesn't tell what exactly changed, just assume all affected controls changed */
440 static int elem_callback(snd_mixer_elem_t *elem, unsigned int type)
442 mixer *mmixer = snd_mixer_elem_get_callback_private(elem);
445 if (type != SND_CTL_EVENT_MASK_VALUE)
450 EnterCriticalSection(&elem_crst);
452 if (!mmixer->callback)
455 for (x=0; x<mmixer->chans; ++x)
457 if (elem != mmixer->lines[x].elem)
460 TRACE("Found changed control %s\n", debugstr_w(mmixer->lines[x].name));
461 mmixer->callback(mmixer->hmx, MM_MIXM_LINE_CHANGE, mmixer->callbackpriv, x, 0);
465 LeaveCriticalSection(&elem_crst);
470 static DWORD WINAPI ALSA_MixerPollThread(LPVOID lParam)
472 struct pollfd *pfds = NULL;
473 int x, y, err, mcnt, count = 1;
475 TRACE("%p\n", lParam);
477 for (x = 0; x < cards; ++x)
478 count += snd_mixer_poll_descriptors_count(mixdev[x].mix);
480 TRACE("Counted %d descriptors\n", count);
481 pfds = HeapAlloc(GetProcessHeap(), 0, count * sizeof(struct pollfd));
485 WARN("Out of memory\n");
489 pfds[0].fd = msg_pipe[0];
490 pfds[0].events = POLLIN;
493 for (x = 0; x < cards; ++x)
494 y += snd_mixer_poll_descriptors(mixdev[x].mix, &pfds[y], count - y);
496 while ((err = poll(pfds, (unsigned int) count, -1)) >= 0 || errno == EINTR || errno == EAGAIN)
498 if (pfds[0].revents & POLLIN)
502 for (x = y = 0; x < cards; ++x)
504 int j, max = snd_mixer_poll_descriptors_count(mixdev[x].mix);
505 for (j = 0; j < max; ++j)
506 if (pfds[mcnt+j].revents)
508 y += snd_mixer_handle_events(mixdev[x].mix);
514 TRACE("Handled %d events\n", y);
518 TRACE("Shutting down\n");
520 HeapFree(GetProcessHeap(), 0, pfds);
522 y = read(msg_pipe[0], &x, sizeof(x));
528 static DWORD MIX_Open(UINT wDevID, LPMIXEROPENDESC desc, DWORD_PTR flags)
530 mixer *mmixer = MIX_GetMix(wDevID);
532 return MMSYSERR_BADDEVICEID;
534 flags &= CALLBACK_TYPEMASK;
540 case CALLBACK_FUNCTION:
544 FIXME("Unhandled callback type: %08lx\n", flags & CALLBACK_TYPEMASK);
545 return MIXERR_INVALVALUE;
548 mmixer->callback = (LPDRVCALLBACK)desc->dwCallback;
549 mmixer->callbackpriv = desc->dwInstance;
550 mmixer->hmx = (HDRVR)desc->hmx;
553 if (InterlockedIncrement(&refcnt) == 1)
555 if (pipe(msg_pipe) >= 0)
557 thread = CreateThread(NULL, 0, ALSA_MixerPollThread, NULL, 0, NULL);
562 msg_pipe[0] = msg_pipe[1] = -1;
566 msg_pipe[0] = msg_pipe[1] = -1;
569 return MMSYSERR_NOERROR;
572 static DWORD MIX_Close(UINT wDevID)
575 mixer *mmixer = MIX_GetMix(wDevID);
577 return MMSYSERR_BADDEVICEID;
579 EnterCriticalSection(&elem_crst);
580 mmixer->callback = 0;
581 LeaveCriticalSection(&elem_crst);
583 if (!InterlockedDecrement(&refcnt))
585 if (write(msg_pipe[1], &x, sizeof(x)) > 0)
587 TRACE("Shutting down thread...\n");
588 WaitForSingleObject(thread, INFINITE);
593 return MMSYSERR_NOERROR;
596 static DWORD MIX_GetDevCaps(UINT wDevID, LPMIXERCAPS2W caps, DWORD_PTR parm2)
598 mixer *mmixer = MIX_GetMix(wDevID);
602 return MMSYSERR_INVALPARAM;
605 return MMSYSERR_BADDEVICEID;
607 memset(&capsW, 0, sizeof(MIXERCAPS2W));
609 capsW.wMid = WINE_MIXER_MANUF_ID;
610 capsW.wPid = WINE_MIXER_PRODUCT_ID;
611 capsW.vDriverVersion = WINE_MIXER_VERSION;
613 lstrcpynW(capsW.szPname, mmixer->mixername, sizeof(capsW.szPname)/sizeof(WCHAR));
614 capsW.cDestinations = mmixer->dests;
615 memcpy(caps, &capsW, min(parm2, sizeof(capsW)));
616 return MMSYSERR_NOERROR;
621 /**************************************************************************
622 * mxdMessage (WINEALSA.3)
624 DWORD WINAPI ALSA_mxdMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser,
625 DWORD_PTR dwParam1, DWORD_PTR dwParam2)
629 TRACE("(%04X, %s, %08lX, %08lX, %08lX);\n", wDevID, getMessage(wMsg),
630 dwUser, dwParam1, dwParam2);
634 case DRVM_INIT: ALSA_MixerInit(); ret = MMSYSERR_NOERROR; break;
635 case DRVM_EXIT: ALSA_MixerExit(); ret = MMSYSERR_NOERROR; break;
636 /* All taken care of by driver initialisation */
637 /* Unimplemented, and not needed */
640 ret = MMSYSERR_NOERROR; break;
643 ret = MIX_Open(wDevID, (LPMIXEROPENDESC) dwParam1, dwParam2); break;
646 ret = MIX_Close(wDevID); break;
648 case MXDM_GETDEVCAPS:
649 ret = MIX_GetDevCaps(wDevID, (LPMIXERCAPS2W)dwParam1, dwParam2); break;
651 case MXDM_GETNUMDEVS:
655 WARN("unknown message %s!\n", getMessage(wMsg));
656 return MMSYSERR_NOTSUPPORTED;
659 TRACE("Returning %08X\n", ret);
662 TRACE("(%04X, %04X, %08lX, %08lX, %08lX);\n", wDevID, wMsg, dwUser, dwParam1, dwParam2);
664 return MMSYSERR_NOTENABLED;