|  | /* | 
|  | * Sample Wine Driver for Advanced Linux Sound System (ALSA) | 
|  | *      Based on version <final> 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" | 
|  |  | 
|  | #ifdef HAVE_ALSA | 
|  |  | 
|  | #include "wine/port.h" | 
|  |  | 
|  | #include <stdlib.h> | 
|  | #include <stdarg.h> | 
|  | #include <stdio.h> | 
|  | #include <string.h> | 
|  | #ifdef HAVE_UNISTD_H | 
|  | # include <unistd.h> | 
|  | #endif | 
|  | #include <errno.h> | 
|  | #include <limits.h> | 
|  | #include <fcntl.h> | 
|  | #ifdef HAVE_SYS_IOCTL_H | 
|  | # include <sys/ioctl.h> | 
|  | #endif | 
|  | #ifdef HAVE_SYS_MMAN_H | 
|  | # include <sys/mman.h> | 
|  | #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); | 
|  |  | 
|  | /*---------------------------------------------------------------------------- | 
|  | **  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_RegGetInt | 
|  | **  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; | 
|  |  | 
|  | *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_UNIXCP, 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_UNIXCP, 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 convoluted 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 failure | 
|  | */ | 
|  | 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 failure | 
|  | */ | 
|  | 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"); | 
|  | 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 */ |