/* * Sample Wine Driver for Advanced Linux Sound System (ALSA) * Based on version of the ALSA API * * This file performs the initialisation and scanning of the sound subsystem. * * Copyright 2002 Eric Pouech * 2002 Marco Pietrobono * 2003 Christian Costa : WaveIn support * 2006-2007 Maarten Lankhorst * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include "config.h" #include "wine/port.h" #include #include #include #include #ifdef HAVE_UNISTD_H # include #endif #include #include #include #ifdef HAVE_SYS_IOCTL_H # include #endif #ifdef HAVE_SYS_MMAN_H # include #endif #include "windef.h" #include "winbase.h" #include "wingdi.h" #include "winerror.h" #include "winuser.h" #include "winnls.h" #include "winreg.h" #include "mmddk.h" /* ksmedia.h defines KSDATAFORMAT_SUBTYPE_PCM and KSDATAFORMAT_SUBTYPE_IEEE_FLOAT * However either all files that use it will define it, or no files will * The only way to solve this is by adding initguid.h here, and include the guid that way */ #include "initguid.h" #include "alsa.h" #include "wine/library.h" #include "wine/unicode.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(wave); #ifdef HAVE_ALSA /*---------------------------------------------------------------------------- ** ALSA_TestDeviceForWine ** ** Test to see if a given device is sufficient for Wine. */ static int ALSA_TestDeviceForWine(int card, int device, snd_pcm_stream_t streamtype) { snd_pcm_t *pcm = NULL; char pcmname[256]; int retcode; snd_pcm_hw_params_t *hwparams; const char *reason = NULL; unsigned int rrate; hwparams = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_hw_params_sizeof() ); /* Note that the plug: device masks out a lot of info, we want to avoid that */ sprintf(pcmname, "hw:%d,%d", card, device); retcode = snd_pcm_open(&pcm, pcmname, streamtype, SND_PCM_NONBLOCK); if (retcode < 0) { /* Note that a busy device isn't automatically disqualified */ if (retcode == (-1 * EBUSY)) retcode = 0; goto exit; } retcode = snd_pcm_hw_params_any(pcm, hwparams); if (retcode < 0) { reason = "Could not retrieve hw_params"; goto exit; } /* set the count of channels */ retcode = snd_pcm_hw_params_set_channels(pcm, hwparams, 2); if (retcode < 0) { reason = "Could not set channels"; goto exit; } rrate = 44100; retcode = snd_pcm_hw_params_set_rate_near(pcm, hwparams, &rrate, 0); if (retcode < 0) { reason = "Could not set rate"; goto exit; } if (rrate == 0) { reason = "Rate came back as 0"; goto exit; } /* write the parameters to device */ retcode = snd_pcm_hw_params(pcm, hwparams); if (retcode < 0) { reason = "Could not set hwparams"; goto exit; } retcode = 0; exit: if (pcm) snd_pcm_close(pcm); HeapFree( GetProcessHeap(), 0, hwparams ); if (retcode != 0 && retcode != (-1 * ENOENT)) TRACE("Discarding card %d/device %d: %s [%d(%s)]\n", card, device, reason, retcode, snd_strerror(retcode)); return retcode; } /*---------------------------------------------------------------------------- ** ALSA_RegGetString ** Retrieve a string from a registry key */ static int ALSA_RegGetString(HKEY key, const char *value, char **bufp) { DWORD rc; DWORD type; DWORD bufsize; *bufp = NULL; rc = RegQueryValueExA(key, value, NULL, &type, NULL, &bufsize); if (rc != ERROR_SUCCESS) return(rc); if (type != REG_SZ) return 1; *bufp = HeapAlloc(GetProcessHeap(), 0, bufsize); if (! *bufp) return 1; rc = RegQueryValueExA(key, value, NULL, NULL, (LPBYTE)*bufp, &bufsize); return rc; } /*---------------------------------------------------------------------------- ** ALSA_RegGetBoolean ** Get a string and interpret it as a boolean */ /* Possible truths: Y(es), T(rue), 1, E(nabled) */ #define IS_OPTION_TRUE(ch) ((ch) == 'y' || (ch) == 'Y' || (ch) == 't' || (ch) == 'T' || (ch) == '1' || (ch) == 'e' || (ch) == 'E') static int ALSA_RegGetBoolean(HKEY key, const char *value, BOOL *answer) { DWORD rc; char *buf = NULL; rc = ALSA_RegGetString(key, value, &buf); if (buf) { *answer = FALSE; if (IS_OPTION_TRUE(*buf)) *answer = TRUE; HeapFree(GetProcessHeap(), 0, buf); } return rc; } /*---------------------------------------------------------------------------- ** ALSA_RegGetBoolean ** Get a string and interpret it as a DWORD */ static int ALSA_RegGetInt(HKEY key, const char *value, DWORD *answer) { DWORD rc; char *buf = NULL; rc = ALSA_RegGetString(key, value, &buf); if (buf) { *answer = atoi(buf); HeapFree(GetProcessHeap(), 0, buf); } return rc; } /* return a string duplicated on the win32 process heap, free with HeapFree */ static char* ALSA_strdup(const char *s) { char *result = HeapAlloc(GetProcessHeap(), 0, strlen(s)+1); if (!result) return NULL; strcpy(result, s); return result; } #define ALSA_RETURN_ONFAIL(mycall) \ { \ int rc; \ {rc = mycall;} \ if ((rc) < 0) \ { \ ERR("%s failed: %s(%d)\n", #mycall, snd_strerror(rc), rc); \ return(rc); \ } \ } /*---------------------------------------------------------------------------- ** ALSA_ComputeCaps ** ** Given an ALSA PCM, figure out our HW CAPS structure info. ** ctl can be null, pcm is required, as is all output parms. ** */ static int ALSA_ComputeCaps(snd_ctl_t *ctl, snd_pcm_t *pcm, WORD *channels, DWORD *flags, DWORD *formats, DWORD *supports) { snd_pcm_hw_params_t *hw_params; snd_pcm_format_mask_t *fmask; snd_pcm_access_mask_t *acmask; unsigned int ratemin = 0; unsigned int ratemax = 0; unsigned int chmin = 0; unsigned int chmax = 0; int rc, dir = 0; hw_params = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_hw_params_sizeof() ); fmask = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_format_mask_sizeof() ); acmask = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_access_mask_sizeof() ); if ((rc = snd_pcm_hw_params_any(pcm, hw_params)) < 0) goto done; snd_pcm_hw_params_get_format_mask(hw_params, fmask); if ((rc = snd_pcm_hw_params_get_access_mask(hw_params, acmask)) < 0) goto done; if ((rc = snd_pcm_hw_params_get_rate_min(hw_params, &ratemin, &dir)) < 0) goto done; if ((rc = snd_pcm_hw_params_get_rate_max(hw_params, &ratemax, &dir)) < 0) goto done; if ((rc = snd_pcm_hw_params_get_channels_min(hw_params, &chmin)) < 0) goto done; if ((rc = snd_pcm_hw_params_get_channels_max(hw_params, &chmax)) < 0) goto done; #define X(r,v) \ if ( (r) >= ratemin && ( (r) <= ratemax || ratemax == -1) ) \ { \ if (snd_pcm_format_mask_test( fmask, SND_PCM_FORMAT_U8)) \ { \ if (chmin <= 1 && 1 <= chmax) \ *formats |= WAVE_FORMAT_##v##M08; \ if (chmin <= 2 && 2 <= chmax) \ *formats |= WAVE_FORMAT_##v##S08; \ } \ if (snd_pcm_format_mask_test( fmask, SND_PCM_FORMAT_S16_LE)) \ { \ if (chmin <= 1 && 1 <= chmax) \ *formats |= WAVE_FORMAT_##v##M16; \ if (chmin <= 2 && 2 <= chmax) \ *formats |= WAVE_FORMAT_##v##S16; \ } \ } X(11025,1); X(22050,2); X(44100,4); X(48000,48); X(96000,96); #undef X if (chmin > 1) FIXME("Device has a minimum of %d channels\n", chmin); *channels = chmax; /* FIXME: is sample accurate always true ? ** Can we do WAVECAPS_PITCH, WAVECAPS_SYNC, or WAVECAPS_PLAYBACKRATE? */ *supports |= WAVECAPS_SAMPLEACCURATE; /* FIXME: NONITERLEAVED and COMPLEX are not supported right now */ if ( snd_pcm_access_mask_test( acmask, SND_PCM_ACCESS_MMAP_INTERLEAVED ) ) *supports |= WAVECAPS_DIRECTSOUND; /* check for volume control support */ if (ctl) { if (snd_ctl_name(ctl)) { snd_hctl_t *hctl; if (snd_hctl_open(&hctl, snd_ctl_name(ctl), 0) >= 0) { snd_hctl_load(hctl); if (!ALSA_CheckSetVolume( hctl, NULL, NULL, NULL, NULL, NULL, NULL, NULL )) { *supports |= WAVECAPS_VOLUME; if (chmin <= 2 && 2 <= chmax) *supports |= WAVECAPS_LRVOLUME; } snd_hctl_free(hctl); snd_hctl_close(hctl); } } } *flags = DSCAPS_CERTIFIED | DSCAPS_CONTINUOUSRATE; *flags |= DSCAPS_SECONDARYMONO | DSCAPS_SECONDARYSTEREO; *flags |= DSCAPS_SECONDARY8BIT | DSCAPS_SECONDARY16BIT; if (*formats & (WAVE_FORMAT_1M08 | WAVE_FORMAT_2M08 | WAVE_FORMAT_4M08 | WAVE_FORMAT_48M08 | WAVE_FORMAT_96M08 | WAVE_FORMAT_1M16 | WAVE_FORMAT_2M16 | WAVE_FORMAT_4M16 | WAVE_FORMAT_48M16 | WAVE_FORMAT_96M16) ) *flags |= DSCAPS_PRIMARYMONO; if (*formats & (WAVE_FORMAT_1S08 | WAVE_FORMAT_2S08 | WAVE_FORMAT_4S08 | WAVE_FORMAT_48S08 | WAVE_FORMAT_96S08 | WAVE_FORMAT_1S16 | WAVE_FORMAT_2S16 | WAVE_FORMAT_4S16 | WAVE_FORMAT_48S16 | WAVE_FORMAT_96S16) ) *flags |= DSCAPS_PRIMARYSTEREO; if (*formats & (WAVE_FORMAT_1M08 | WAVE_FORMAT_2M08 | WAVE_FORMAT_4M08 | WAVE_FORMAT_48M08 | WAVE_FORMAT_96M08 | WAVE_FORMAT_1S08 | WAVE_FORMAT_2S08 | WAVE_FORMAT_4S08 | WAVE_FORMAT_48S08 | WAVE_FORMAT_96S08) ) *flags |= DSCAPS_PRIMARY8BIT; if (*formats & (WAVE_FORMAT_1M16 | WAVE_FORMAT_2M16 | WAVE_FORMAT_4M16 | WAVE_FORMAT_48M16 | WAVE_FORMAT_96M16 | WAVE_FORMAT_1S16 | WAVE_FORMAT_2S16 | WAVE_FORMAT_4S16 | WAVE_FORMAT_48S16 | WAVE_FORMAT_96S16) ) *flags |= DSCAPS_PRIMARY16BIT; rc = 0; done: if (rc < 0) ERR("failed: %s(%d)\n", snd_strerror(rc), rc); HeapFree( GetProcessHeap(), 0, hw_params ); HeapFree( GetProcessHeap(), 0, fmask ); HeapFree( GetProcessHeap(), 0, acmask ); return rc; } /*---------------------------------------------------------------------------- ** ALSA_AddCommonDevice ** ** Perform Alsa initialization common to both capture and playback ** ** Side Effect: ww->pcname and ww->ctlname may need to be freed. ** ** Note: this was originally coded by using snd_pcm_name(pcm), until ** I discovered that with at least one version of alsa lib, ** the use of a pcm named default:0 would cause snd_pcm_name() to fail. ** So passing the name in is logically extraneous. Sigh. */ static int ALSA_AddCommonDevice(snd_ctl_t *ctl, snd_pcm_t *pcm, const char *pcmname, WINE_WAVEDEV *ww) { snd_pcm_info_t *infop; int rc; infop = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_info_sizeof() ); if ((rc = snd_pcm_info(pcm, infop)) < 0) { HeapFree( GetProcessHeap(), 0, infop ); return rc; } if (pcm && pcmname) ww->pcmname = ALSA_strdup(pcmname); else { HeapFree( GetProcessHeap(), 0, infop ); return -1; } if (ctl && snd_ctl_name(ctl)) ww->ctlname = ALSA_strdup(snd_ctl_name(ctl)); strcpy(ww->interface_name, "winealsa: "); memcpy(ww->interface_name + strlen(ww->interface_name), ww->pcmname, min(strlen(ww->pcmname), sizeof(ww->interface_name) - strlen("winealsa: "))); strcpy(ww->ds_desc.szDrvname, "winealsa.drv"); memcpy(ww->ds_desc.szDesc, snd_pcm_info_get_name(infop), min( (sizeof(ww->ds_desc.szDesc) - 1), strlen(snd_pcm_info_get_name(infop))) ); ww->ds_caps.dwMinSecondarySampleRate = DSBFREQUENCY_MIN; ww->ds_caps.dwMaxSecondarySampleRate = DSBFREQUENCY_MAX; ww->ds_caps.dwPrimaryBuffers = 1; HeapFree( GetProcessHeap(), 0, infop ); return 0; } /*---------------------------------------------------------------------------- ** ALSA_FreeDevice */ static void ALSA_FreeDevice(WINE_WAVEDEV *ww) { HeapFree(GetProcessHeap(), 0, ww->pcmname); ww->pcmname = NULL; HeapFree(GetProcessHeap(), 0, ww->ctlname); ww->ctlname = NULL; } /*---------------------------------------------------------------------------- ** ALSA_AddDeviceToArray ** ** Dynamically size one of the wavein or waveout arrays of devices, ** and add a fully configured device node to the array. ** */ static int ALSA_AddDeviceToArray(WINE_WAVEDEV *ww, WINE_WAVEDEV **array, DWORD *count, DWORD *alloced, int isdefault) { int i = *count; if (*count >= *alloced) { (*alloced) += WAVEDEV_ALLOC_EXTENT_SIZE; if (! (*array)) *array = HeapAlloc(GetProcessHeap(), 0, sizeof(*ww) * (*alloced)); else *array = HeapReAlloc(GetProcessHeap(), 0, *array, sizeof(*ww) * (*alloced)); if (!*array) { return -1; } } /* If this is the default, arrange for it to be the first element */ if (isdefault && i > 0) { (*array)[*count] = (*array)[0]; i = 0; } (*array)[i] = *ww; (*count)++; return 0; } /*---------------------------------------------------------------------------- ** ALSA_AddPlaybackDevice ** ** Add a given Alsa device to Wine's internal list of Playback ** devices. */ static int ALSA_AddPlaybackDevice(snd_ctl_t *ctl, snd_pcm_t *pcm, const char *pcmname, int isdefault) { WINE_WAVEDEV wwo; int rc; memset(&wwo, '\0', sizeof(wwo)); rc = ALSA_AddCommonDevice(ctl, pcm, pcmname, &wwo); if (rc) return(rc); MultiByteToWideChar(CP_ACP, 0, wwo.ds_desc.szDesc, -1, wwo.outcaps.szPname, sizeof(wwo.outcaps.szPname)/sizeof(WCHAR)); wwo.outcaps.szPname[sizeof(wwo.outcaps.szPname)/sizeof(WCHAR) - 1] = '\0'; wwo.outcaps.wMid = MM_CREATIVE; wwo.outcaps.wPid = MM_CREATIVE_SBP16_WAVEOUT; wwo.outcaps.vDriverVersion = 0x0100; rc = ALSA_ComputeCaps(ctl, pcm, &wwo.outcaps.wChannels, &wwo.ds_caps.dwFlags, &wwo.outcaps.dwFormats, &wwo.outcaps.dwSupport); if (rc) { WARN("Error calculating device caps for pcm [%s]\n", wwo.pcmname); ALSA_FreeDevice(&wwo); return(rc); } rc = ALSA_AddDeviceToArray(&wwo, &WOutDev, &ALSA_WodNumDevs, &ALSA_WodNumMallocedDevs, isdefault); if (rc) ALSA_FreeDevice(&wwo); return (rc); } /*---------------------------------------------------------------------------- ** ALSA_AddCaptureDevice ** ** Add a given Alsa device to Wine's internal list of Capture ** devices. */ static int ALSA_AddCaptureDevice(snd_ctl_t *ctl, snd_pcm_t *pcm, const char *pcmname, int isdefault) { WINE_WAVEDEV wwi; int rc; memset(&wwi, '\0', sizeof(wwi)); rc = ALSA_AddCommonDevice(ctl, pcm, pcmname, &wwi); if (rc) return(rc); MultiByteToWideChar(CP_ACP, 0, wwi.ds_desc.szDesc, -1, wwi.incaps.szPname, sizeof(wwi.incaps.szPname) / sizeof(WCHAR)); wwi.incaps.szPname[sizeof(wwi.incaps.szPname)/sizeof(WCHAR) - 1] = '\0'; wwi.incaps.wMid = MM_CREATIVE; wwi.incaps.wPid = MM_CREATIVE_SBP16_WAVEOUT; wwi.incaps.vDriverVersion = 0x0100; rc = ALSA_ComputeCaps(ctl, pcm, &wwi.incaps.wChannels, &wwi.ds_caps.dwFlags, &wwi.incaps.dwFormats, &wwi.dwSupport); if (rc) { WARN("Error calculating device caps for pcm [%s]\n", wwi.pcmname); ALSA_FreeDevice(&wwi); return(rc); } rc = ALSA_AddDeviceToArray(&wwi, &WInDev, &ALSA_WidNumDevs, &ALSA_WidNumMallocedDevs, isdefault); if (rc) ALSA_FreeDevice(&wwi); return(rc); } /*---------------------------------------------------------------------------- ** ALSA_CheckEnvironment ** ** Given an Alsa style configuration node, scan its subitems ** for environment variable names, and use them to find an override, ** if appropriate. ** This is essentially a long and convolunted way of doing: ** getenv("ALSA_CARD") ** getenv("ALSA_CTL_CARD") ** getenv("ALSA_PCM_CARD") ** getenv("ALSA_PCM_DEVICE") ** ** The output value is set with the atoi() of the first environment ** variable found to be set, if any; otherwise, it is left alone */ static void ALSA_CheckEnvironment(snd_config_t *node, int *outvalue) { snd_config_iterator_t iter; for (iter = snd_config_iterator_first(node); iter != snd_config_iterator_end(node); iter = snd_config_iterator_next(iter)) { snd_config_t *leaf = snd_config_iterator_entry(iter); if (snd_config_get_type(leaf) == SND_CONFIG_TYPE_STRING) { const char *value; if (snd_config_get_string(leaf, &value) >= 0) { char *p = getenv(value); if (p) { *outvalue = atoi(p); return; } } } } } /*---------------------------------------------------------------------------- ** ALSA_DefaultDevices ** ** Jump through Alsa style hoops to (hopefully) properly determine ** Alsa defaults for CTL Card #, as well as for PCM Card + Device #. ** We'll also find out if the user has set any of the environment ** variables that specify we're to use a specific card or device. ** ** Parameters: ** directhw Whether to use a direct hardware device or not; ** essentially switches the pcm device name from ** one of 'default:X' or 'plughw:X' to "hw:X" ** defctlcard If !NULL, will hold the ctl card number given ** by the ALSA config as the default ** defpcmcard If !NULL, default pcm card # ** defpcmdev If !NULL, default pcm device # ** fixedctlcard If !NULL, and the user set the appropriate ** environment variable, we'll set to the ** card the user specified. ** fixedpcmcard If !NULL, and the user set the appropriate ** environment variable, we'll set to the ** card the user specified. ** fixedpcmdev If !NULL, and the user set the appropriate ** environment variable, we'll set to the ** device the user specified. ** ** Returns: 0 on success, < 0 on failiure */ static int ALSA_DefaultDevices(int directhw, long *defctlcard, long *defpcmcard, long *defpcmdev, int *fixedctlcard, int *fixedpcmcard, int *fixedpcmdev) { snd_config_t *configp; char pcmsearch[256]; ALSA_RETURN_ONFAIL(snd_config_update()); if (defctlcard) if (snd_config_search(snd_config, "defaults.ctl.card", &configp) >= 0) snd_config_get_integer(configp, defctlcard); if (defpcmcard) if (snd_config_search(snd_config, "defaults.pcm.card", &configp) >= 0) snd_config_get_integer(configp, defpcmcard); if (defpcmdev) if (snd_config_search(snd_config, "defaults.pcm.device", &configp) >= 0) snd_config_get_integer(configp, defpcmdev); if (fixedctlcard) { if (snd_config_search(snd_config, "ctl.hw.@args.CARD.default.vars", &configp) >= 0) ALSA_CheckEnvironment(configp, fixedctlcard); } if (fixedpcmcard) { sprintf(pcmsearch, "pcm.%s.@args.CARD.default.vars", directhw ? "hw" : "plughw"); if (snd_config_search(snd_config, pcmsearch, &configp) >= 0) ALSA_CheckEnvironment(configp, fixedpcmcard); } if (fixedpcmdev) { sprintf(pcmsearch, "pcm.%s.@args.DEV.default.vars", directhw ? "hw" : "plughw"); if (snd_config_search(snd_config, pcmsearch, &configp) >= 0) ALSA_CheckEnvironment(configp, fixedpcmdev); } return 0; } /*---------------------------------------------------------------------------- ** ALSA_ScanDevices ** ** Iterate through all discoverable ALSA cards, searching ** for usable PCM devices. ** ** Parameters: ** directhw Whether to use a direct hardware device or not; ** essentially switches the pcm device name from ** one of 'default:X' or 'plughw:X' to "hw:X" ** defctlcard Alsa's notion of the default ctl card. ** defpcmcard . pcm card ** defpcmdev . pcm device ** fixedctlcard If not -1, then gives the value of ALSA_CTL_CARD ** or equivalent environment variable ** fixedpcmcard If not -1, then gives the value of ALSA_PCM_CARD ** or equivalent environment variable ** fixedpcmdev If not -1, then gives the value of ALSA_PCM_DEVICE ** or equivalent environment variable ** ** Returns: 0 on success, < 0 on failiure */ static int ALSA_ScanDevices(int directhw, long defctlcard, long defpcmcard, long defpcmdev, int fixedctlcard, int fixedpcmcard, int fixedpcmdev) { int card = fixedpcmcard; int scan_devices = (fixedpcmdev == -1); /*------------------------------------------------------------------------ ** Loop through all available cards **----------------------------------------------------------------------*/ if (card == -1) snd_card_next(&card); for (; card != -1; snd_card_next(&card)) { char ctlname[256]; snd_ctl_t *ctl; int rc; int device; /*-------------------------------------------------------------------- ** Try to open a ctl handle; Wine doesn't absolutely require one, ** but it does allow for volume control and for device scanning **------------------------------------------------------------------*/ sprintf(ctlname, "hw:%d", fixedctlcard == -1 ? card : fixedctlcard); rc = snd_ctl_open(&ctl, ctlname, SND_CTL_NONBLOCK); if (rc < 0) { ctl = NULL; WARN("Unable to open an alsa ctl for [%s] (pcm card %d): %s; not scanning devices\n", ctlname, card, snd_strerror(rc)); if (fixedpcmdev == -1) fixedpcmdev = 0; } /*-------------------------------------------------------------------- ** Loop through all available devices on this card **------------------------------------------------------------------*/ device = fixedpcmdev; if (device == -1) snd_ctl_pcm_next_device(ctl, &device); for (; device != -1; snd_ctl_pcm_next_device(ctl, &device)) { char defaultpcmname[256]; char plugpcmname[256]; char hwpcmname[256]; char *pcmname = NULL; snd_pcm_t *pcm; sprintf(defaultpcmname, "default:%d", card); sprintf(plugpcmname, "plughw:%d,%d", card, device); sprintf(hwpcmname, "hw:%d,%d", card, device); /*---------------------------------------------------------------- ** See if it's a valid playback device **--------------------------------------------------------------*/ if (ALSA_TestDeviceForWine(card, device, SND_PCM_STREAM_PLAYBACK) == 0) { /* If we can, try the default:X device name first */ if (! scan_devices && ! directhw) { pcmname = defaultpcmname; rc = snd_pcm_open(&pcm, pcmname, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); } else rc = -1; if (rc < 0) { pcmname = directhw ? hwpcmname : plugpcmname; rc = snd_pcm_open(&pcm, pcmname, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); } if (rc >= 0) { if (defctlcard == card && defpcmcard == card && defpcmdev == device) ALSA_AddPlaybackDevice(ctl, pcm, pcmname, TRUE); else ALSA_AddPlaybackDevice(ctl, pcm, pcmname, FALSE); snd_pcm_close(pcm); } else { TRACE("Device [%s/%s] failed to open for playback: %s\n", directhw || scan_devices ? "(N/A)" : defaultpcmname, directhw ? hwpcmname : plugpcmname, snd_strerror(rc)); } } /*---------------------------------------------------------------- ** See if it's a valid capture device **--------------------------------------------------------------*/ if (ALSA_TestDeviceForWine(card, device, SND_PCM_STREAM_CAPTURE) == 0) { /* If we can, try the default:X device name first */ if (! scan_devices && ! directhw) { pcmname = defaultpcmname; rc = snd_pcm_open(&pcm, pcmname, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); } else rc = -1; if (rc < 0) { pcmname = directhw ? hwpcmname : plugpcmname; rc = snd_pcm_open(&pcm, pcmname, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); } if (rc >= 0) { if (defctlcard == card && defpcmcard == card && defpcmdev == device) ALSA_AddCaptureDevice(ctl, pcm, pcmname, TRUE); else ALSA_AddCaptureDevice(ctl, pcm, pcmname, FALSE); snd_pcm_close(pcm); } else { TRACE("Device [%s/%s] failed to open for capture: %s\n", directhw || scan_devices ? "(N/A)" : defaultpcmname, directhw ? hwpcmname : plugpcmname, snd_strerror(rc)); } } if (! scan_devices) break; } if (ctl) snd_ctl_close(ctl); /*-------------------------------------------------------------------- ** If the user has set env variables such that we're pegged to ** a specific card, then break after we've examined it **------------------------------------------------------------------*/ if (fixedpcmcard != -1) break; } return 0; } /*---------------------------------------------------------------------------- ** ALSA_PerformDefaultScan ** Perform the basic default scanning for devices within ALSA. ** The hope is that this routine implements a 'correct' ** scanning algorithm from the Alsalib point of view. ** ** Note that Wine, overall, has other mechanisms to ** override and specify exact CTL and PCM device names, ** but this routine is imagined as the default that ** 99% of users will use. ** ** The basic algorithm is simple: ** Use snd_card_next to iterate cards; within cards, use ** snd_ctl_pcm_next_device to iterate through devices. ** ** We add a little complexity by taking into consideration ** environment variables such as ALSA_CARD (et all), and by ** detecting when a given device matches the default specified ** by Alsa. ** ** Parameters: ** directhw If !0, indicates we should use the hw:X ** PCM interface, rather than first try ** the 'default' device followed by the plughw ** device. (default and plughw do fancy mixing ** and audio scaling, if they are available). ** devscan If TRUE, we should scan all devices, not ** juse use device 0 on each card ** ** Returns: ** 0 on success ** ** Effects: ** Invokes the ALSA_AddXXXDevice functions on valid ** looking devices */ static int ALSA_PerformDefaultScan(int directhw, BOOL devscan) { long defctlcard = -1, defpcmcard = -1, defpcmdev = -1; int fixedctlcard = -1, fixedpcmcard = -1, fixedpcmdev = -1; int rc; /* FIXME: We should dlsym the new snd_names_list/snd_names_list_free 1.0.9 apis, ** and use them instead of this scan mechanism if they are present */ rc = ALSA_DefaultDevices(directhw, &defctlcard, &defpcmcard, &defpcmdev, &fixedctlcard, &fixedpcmcard, &fixedpcmdev); if (rc) return(rc); if (fixedpcmdev == -1 && ! devscan) fixedpcmdev = 0; return(ALSA_ScanDevices(directhw, defctlcard, defpcmcard, defpcmdev, fixedctlcard, fixedpcmcard, fixedpcmdev)); } /*---------------------------------------------------------------------------- ** ALSA_AddUserSpecifiedDevice ** Add a device given from the registry */ static int ALSA_AddUserSpecifiedDevice(const char *ctlname, const char *pcmname) { int rc; int okay = 0; snd_ctl_t *ctl = NULL; snd_pcm_t *pcm = NULL; if (ctlname) { rc = snd_ctl_open(&ctl, ctlname, SND_CTL_NONBLOCK); if (rc < 0) ctl = NULL; } rc = snd_pcm_open(&pcm, pcmname, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); if (rc >= 0) { ALSA_AddPlaybackDevice(ctl, pcm, pcmname, FALSE); okay++; snd_pcm_close(pcm); } rc = snd_pcm_open(&pcm, pcmname, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); if (rc >= 0) { ALSA_AddCaptureDevice(ctl, pcm, pcmname, FALSE); okay++; snd_pcm_close(pcm); } if (ctl) snd_ctl_close(ctl); return (okay == 0); } /*---------------------------------------------------------------------------- ** ALSA_WaveInit ** Initialize the Wine Alsa sub system. ** The main task is to probe for and store a list of all appropriate playback ** and capture devices. ** Key control points are from the registry key: ** [Software\Wine\Alsa Driver] ** AutoScanCards Whether or not to scan all known sound cards ** and add them to Wine's list (default yes) ** AutoScanDevices Whether or not to scan all known PCM devices ** on each card (default no) ** UseDirectHW Whether or not to use the hw:X device, ** instead of the fancy default:X or plughw:X device. ** The hw:X device goes straight to the hardware ** without any fancy mixing or audio scaling in between. ** DeviceCount If present, specifies the number of hard coded ** Alsa devices to add to Wine's list; default 0 ** DevicePCMn Specifies the Alsa PCM devices to open for ** Device n (where n goes from 1 to DeviceCount) ** DeviceCTLn Specifies the Alsa control devices to open for ** Device n (where n goes from 1 to DeviceCount) ** ** Using AutoScanCards no, and then Devicexxx info ** is a way to exactly specify the devices used by Wine. ** */ LONG ALSA_WaveInit(void) { DWORD rc; BOOL AutoScanCards = TRUE; BOOL AutoScanDevices = FALSE; BOOL UseDirectHW = FALSE; DWORD DeviceCount = 0; HKEY key = 0; int i; if (!wine_dlopen("libasound.so.2", RTLD_LAZY|RTLD_GLOBAL, NULL, 0)) { ERR("Error: ALSA lib needs to be loaded with flags RTLD_LAZY and RTLD_GLOBAL.\n"); return -1; } /* @@ Wine registry key: HKCU\Software\Wine\Alsa Driver */ rc = RegOpenKeyExA(HKEY_CURRENT_USER, "Software\\Wine\\Alsa Driver", 0, KEY_QUERY_VALUE, &key); if (rc == ERROR_SUCCESS) { ALSA_RegGetBoolean(key, "AutoScanCards", &AutoScanCards); ALSA_RegGetBoolean(key, "AutoScanDevices", &AutoScanDevices); ALSA_RegGetBoolean(key, "UseDirectHW", &UseDirectHW); ALSA_RegGetInt(key, "DeviceCount", &DeviceCount); } if (AutoScanCards) rc = ALSA_PerformDefaultScan(UseDirectHW, AutoScanDevices); for (i = 0; i < DeviceCount; i++) { char *ctl_name = NULL; char *pcm_name = NULL; char value[30]; sprintf(value, "DevicePCM%d", i + 1); if (ALSA_RegGetString(key, value, &pcm_name) == ERROR_SUCCESS) { sprintf(value, "DeviceCTL%d", i + 1); ALSA_RegGetString(key, value, &ctl_name); ALSA_AddUserSpecifiedDevice(ctl_name, pcm_name); } HeapFree(GetProcessHeap(), 0, ctl_name); HeapFree(GetProcessHeap(), 0, pcm_name); } if (key) RegCloseKey(key); return (rc); } #endif /* HAVE_ALSA */