Hi,
I have a SIS SI7012 (Intel 810 chipset) built-in audio card, which turns out to be capable of only 48000Hz sample rate in hardware. The ALSA winmm driver in Wine is hardcoded to use the first hardware pcm device ("hw:0") directly, and therefore fails for any wave format request with a sample rate different than 48000Hz. I have lifted some code from dsound and put together a patch which adds the option to read the device name from the Wine configuration file. As a result I have now got Winamp working (hurray for me!).
HOWEVER there are still issues - the "default" ALSA device doesn't like the way Wine accesses it through mmap, and also it seems that it is not possible to set the buffer size and period size the way it is done now. Other programs that use ALSA, like aplay and mplayer, don't have this problem. My knowledge of Wine and ALSA is pretty meagre, so can anyone can provide any insight as to how to fix this issue?
My config file now looks like this [alsa] ;"Device" = "hw:0" "Device" = "default" "UseMMAP" = "N"
And this is the patch, not really useful for anyone else but me right now:
---- Patch start -----
diff -u -r wine-20021125-orig/dlls/winmm/winealsa/Makefile.in wine-20021125/dlls/winmm/winealsa/Makefile.in --- wine-20021125-orig/dlls/winmm/winealsa/Makefile.in 2002-06-28 19:31:01.000000000 +0100 +++ wine-20021125/dlls/winmm/winealsa/Makefile.in 2002-12-08 14:06:03.000000000 +0000 @@ -3,7 +3,7 @@ SRCDIR = @srcdir@ VPATH = @srcdir@ MODULE = winealsa.drv -IMPORTS = winmm user32 kernel32 ntdll +IMPORTS = winmm user32 kernel32 ntdll advapi32 EXTRALIBS = @ALSALIBS@
LDDLLFLAGS = @LDDLLFLAGS@ diff -u -r wine-20021125-orig/dlls/winmm/winealsa/alsa.c wine-20021125/dlls/winmm/winealsa/alsa.c --- wine-20021125-orig/dlls/winmm/winealsa/alsa.c 2002-07-19 01:30:16.000000000 +0100 +++ wine-20021125/dlls/winmm/winealsa/alsa.c 2002-12-08 14:52:38.000000000 +0000 @@ -25,12 +25,93 @@ #include "winbase.h" #include "wingdi.h" #include "winuser.h" +#include "winreg.h" #include "mmddk.h" #include "alsa.h" +#include "wine/debug.h"
#ifdef HAVE_ALSA
+WINE_DEFAULT_DEBUG_CHANNEL(wave); + static struct WINE_ALSA* alsa = NULL; + +/* configuration variables for the ALSA driver */ +#define ALSA_DEVICE_NAME_DEFAULT "hw:0" +#define ALSA_USE_MMAP_DEFAULT 1 +char alsa_device_name[MAX_PATH+1]; +int alsa_use_mmap = 1; + +/* + * Get a config key from either the app-specific or the default config + */ +inline static DWORD get_config_key( HKEY defkey, HKEY appkey, const char *name, + char *buffer, DWORD size ) +{ + if (appkey && !RegQueryValueExA( appkey, name, 0, NULL, buffer, &size )) return 0; + return RegQueryValueExA( defkey, name, 0, NULL, buffer, &size ); +} + + +/* + * Setup the alsa options. + */ +inline static void setup_alsa_options(void) +{ + char buffer[MAX_PATH+1]; + HKEY hkey, appkey = 0; + + buffer[MAX_PATH]='\0'; + + if (RegCreateKeyExA( HKEY_LOCAL_MACHINE, "Software\Wine\Wine\Config\alsa", 0, NULL, + REG_OPTION_VOLATILE, KEY_ALL_ACCESS, NULL, &hkey, NULL )) + { + ERR("Cannot create config registry key\n" ); + ExitProcess(1); + } + + if (GetModuleFileNameA( 0, buffer, MAX_PATH )) + { + HKEY tmpkey; + + if (!RegOpenKeyA( HKEY_LOCAL_MACHINE, "Software\Wine\Wine\Config\AppDefaults", &tmpkey )) + { + char appname[MAX_PATH+16]; + char *p = strrchr( buffer, '\' ); + if (p!=NULL) { + appname[MAX_PATH]='\0'; + strncpy(appname,p+1,MAX_PATH); + strcat(appname,"\alsa"); + TRACE("appname = [%s] \n",appname); + if (RegOpenKeyA( tmpkey, appname, &appkey )) appkey = 0; + RegCloseKey( tmpkey ); + } + } + } + + /* get options */ + strncpy(alsa_device_name, ALSA_DEVICE_NAME_DEFAULT, MAX_PATH); + if (!get_config_key( hkey, appkey, "Device", buffer, MAX_PATH )) { + alsa_device_name[MAX_PATH] = '\0'; + strncpy(alsa_device_name, buffer, MAX_PATH); + if (strncmp(alsa_device_name, ALSA_DEVICE_NAME_DEFAULT, MAX_PATH)) { + WARN("alsa_device_name = %s (default=" ALSA_DEVICE_NAME_DEFAULT ")\n", + alsa_device_name); + } + } + + alsa_use_mmap = ALSA_USE_MMAP_DEFAULT; + if (!get_config_key( hkey, appkey, "UseMMAP", buffer, MAX_PATH )) { + alsa_use_mmap = strcmp(buffer, "N"); + if (alsa_use_mmap != ALSA_USE_MMAP_DEFAULT) { + WARN("alsa_use_mmap = %d (default=%d)\n", alsa_use_mmap, + ALSA_USE_MMAP_DEFAULT); + } + } + + if (appkey) RegCloseKey( appkey ); + RegCloseKey( hkey ); +}
/************************************************************************** * ALSA_drvOpen [internal] @@ -39,7 +120,7 @@ { if (alsa) return 0; - + /* I know, this is ugly, but who cares... */ alsa = (struct WINE_ALSA*)1; return 1; @@ -71,7 +152,8 @@
switch(wMsg) { #ifdef HAVE_ALSA - case DRV_LOAD: ALSA_WaveInit(); + case DRV_LOAD: setup_alsa_options(); + ALSA_WaveInit(); return 1; case DRV_FREE: return 1; case DRV_OPEN: return ALSA_drvOpen((LPSTR)dwParam1); diff -u -r wine-20021125-orig/dlls/winmm/winealsa/alsa.h wine-20021125/dlls/winmm/winealsa/alsa.h --- wine-20021125-orig/dlls/winmm/winealsa/alsa.h 2002-08-29 02:51:32.000000000 +0100 +++ wine-20021125/dlls/winmm/winealsa/alsa.h 2002-12-08 14:19:06.000000000 +0000 @@ -29,3 +29,6 @@ #endif
extern LONG ALSA_WaveInit(void); +/* config options */ +extern char alsa_device_name[]; +extern int alsa_use_mmap; Only in wine-20021125/dlls/winmm/winealsa: alsa.o diff -u -r wine-20021125-orig/dlls/winmm/winealsa/audio.c wine-20021125/dlls/winmm/winealsa/audio.c --- wine-20021125-orig/dlls/winmm/winealsa/audio.c 2002-10-25 20:09:02.000000000 +0100 +++ wine-20021125/dlls/winmm/winealsa/audio.c 2002-12-08 14:32:16.000000000 +0000 @@ -217,13 +217,13 @@ } \ } while(0)
- EXIT_ON_ERROR( snd_ctl_open(&ctl,"hw:0",0) , "ctl open failed" ); + EXIT_ON_ERROR( snd_ctl_open(&ctl,alsa_device_name,0) , "ctl open failed" ); EXIT_ON_ERROR( snd_ctl_card_info(ctl, cardinfo), "card info failed"); EXIT_ON_ERROR( snd_ctl_elem_list(ctl, elemlist), "elem list failed");
nCtrls = snd_ctl_elem_list_get_count(elemlist);
- EXIT_ON_ERROR( snd_hctl_open(&hctl,"hw:0",0), "hctl open failed"); + EXIT_ON_ERROR( snd_hctl_open(&hctl,alsa_device_name,0), "hctl open failed"); EXIT_ON_ERROR( snd_hctl_load(hctl), "hctl load failed" );
elem=snd_hctl_first_elem(hctl); @@ -404,8 +404,9 @@
wwo = &WOutDev[0];
+ TRACE("ALSA_WaveInit device name = %s", alsa_device_name); /* FIXME: use better values */ - wwo->device = "hw:0,0"; + wwo->device = alsa_device_name; wwo->caps.wMid = 0x0002; wwo->caps.wPid = 0x0104; strcpy(wwo->caps.szPname, "SB16 Wave Out"); @@ -495,7 +496,7 @@ snd_pcm_hw_params_get_access_mask(hw_params, acmask);
/* FIXME: NONITERLEAVED and COMPLEX are not supported right now */ - if ( snd_pcm_access_mask_test( acmask, SND_PCM_ACCESS_MMAP_INTERLEAVED ) ) + if ( alsa_use_mmap && snd_pcm_access_mask_test( acmask, SND_PCM_ACCESS_MMAP_INTERLEAVED ) ) wwo->caps.dwSupport |= WAVECAPS_DIRECTSOUND; }
@@ -1114,7 +1115,7 @@ } while(0)
access = SND_PCM_ACCESS_MMAP_INTERLEAVED; - if ( ( err = snd_pcm_hw_params_set_access(pcm, hw_params, access ) ) < 0) { + if (!alsa_use_mmap || ((err = snd_pcm_hw_params_set_access(pcm, hw_params, access)) < 0)) { WARN("mmap not available. switching to standard write.\n"); access = SND_PCM_ACCESS_RW_INTERLEAVED; EXIT_ON_ERROR( snd_pcm_hw_params_set_access(pcm, hw_params, access ), MMSYSERR_INVALPARAM, "unable to set access for playback"); @@ -1128,11 +1129,13 @@ format = (wwo->format.wBitsPerSample == 16) ? SND_PCM_FORMAT_S16_LE : SND_PCM_FORMAT_U8; EXIT_ON_ERROR( snd_pcm_hw_params_set_format(pcm, hw_params, format), MMSYSERR_INVALPARAM, "unable to set required format");
+#if 0 EXIT_ON_ERROR( snd_pcm_hw_params_set_buffer_size_near(pcm, hw_params, buffer_size), MMSYSERR_NOMEM, "unable to get required buffer"); buffer_size = snd_pcm_hw_params_get_buffer_size(hw_params);
EXIT_ON_ERROR( snd_pcm_hw_params_set_period_size_near(pcm, hw_params, buffer_size/num_periods, 0), MMSYSERR_ERROR, "unable to set required period size"); period_size = snd_pcm_hw_params_get_period_size(hw_params, 0); +#endif
rate = snd_pcm_hw_params_set_rate_near(pcm, hw_params, wwo->format.wf.nSamplesPerSec, 0); if (rate < 0) { @@ -1147,6 +1150,11 @@ }
EXIT_ON_ERROR( snd_pcm_hw_params(pcm, hw_params), MMSYSERR_INVALPARAM, "unable to set hw params for playback"); + +#if 1 + buffer_size = snd_pcm_hw_params_get_buffer_size(hw_params); + period_size = snd_pcm_hw_params_get_period_size(hw_params, 0); +#endif
snd_pcm_sw_params_current(pcm, sw_params); EXIT_ON_ERROR( snd_pcm_sw_params_set_start_threshold(pcm, sw_params, dwFlags & WAVE_DIRECTSOUND ? INT_MAX : 1 ), MMSYSERR_ERROR, "unable to set start threshold");