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");
Nikolay Stefanov wrote:
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"
I assume you're using ALSA 0.9 (not 0.5) and that winamp uses the dsound interface
I don't think this is the right think to do: - from hard coding the driver name, winealsa should enumerate all installed alsa drivers, and be prepared to open such a device - from the mmap configuration, this should be identified at run time, not by an obscure configuration option
A+
On Sunday 08 December 2002, Eric Pouech wrote:
I assume you're using ALSA 0.9 (not 0.5) and that winamp uses the dsound interface
Correct, however the result is the same (doesn't work) with both waveout and dsound interface, because of said hw limitations.
I don't think this is the right think to do:
- from hard coding the driver name, winealsa should enumerate all
installed alsa drivers, and be prepared to open such a device
Just to clarify - the device name *was* hardcoded to "hw,0", the patch makes it possible to specify it in the configuration file.
- from the mmap configuration, this should be identified at run time,
not by an obscure configuration option
Well, it is identified at run time by the current code, and the driver tries to use it, but it doesn't work. So I added the option as a way to tell winealsa not to use the mmap interface. Here is the error that I get if mmap is enabled:
trace:wave:wodPlayer_WriteMaxFrags Writing wavehdr 0x4122c260.0[32768] wine: pcm_plugin.c:491: snd_pcm_plugin_mmap_commit: Assertion `size == 0' failed.
Those ALSA assertions are pretty annoying, no way to inspect the error or to bail out :-(
Nikolay
I've fixed the problem - turns out that ALSA driver doesn't like it when the buffer and period sizes are set directly. I modified the setup code to use buffer and period times instead, with some values that I copied from the OGLE alsa driver. Also it seems that the driver crashes if these are set before the sample rate, if you are wondering about the change of order. Everything seems to work normally now, I tested it with WinAmp and some games.
What do you think, is this is a driver issue or a wine issue? None of the other projects that I checked (ogle, mplayer) set the sizes directly, they use time values instead.
Lastly, is there any special reason why the dsound implementation is not using mmap_write, like the wave one does?
Nikolay
PS: Thanks to Michal Miroslaw for the patch, it's a lot simpler than what I did initially.
------ patch against 20021125 ---------
diff -ur sources/wine-20021125-orig/dlls/winmm/winealsa/audio.c sources/wine-20021125/dlls/winmm/winealsa/audio.c --- sources/wine-20021125-orig/dlls/winmm/winealsa/audio.c 2002-10-25 20:09:02.000000000 +0100 +++ sources/wine-20021125/dlls/winmm/winealsa/audio.c 2002-12-09 21:36:45.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,"hw",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,"hw",0), "hctl open failed"); EXIT_ON_ERROR( snd_hctl_load(hctl), "hctl load failed" );
elem=snd_hctl_first_elem(hctl); @@ -405,7 +405,7 @@ wwo = &WOutDev[0];
/* FIXME: use better values */ - wwo->device = "hw:0,0"; + wwo->device = "hw"; wwo->caps.wMid = 0x0002; wwo->caps.wPid = 0x0104; strcpy(wwo->caps.szPname, "SB16 Wave Out"); @@ -1033,8 +1033,9 @@ snd_pcm_access_t access; snd_pcm_format_t format; int rate; - int buffer_size = 0x4000; /* in frames (1 frame = sizeof(sample) * n.channels) */ - int num_periods = 32; + unsigned int buffer_time = 500000; + unsigned int period_time = 10000; + int buffer_size; snd_pcm_uframes_t period_size; int flags; snd_pcm_t * pcm; @@ -1128,12 +1129,6 @@ 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");
- 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); - rate = snd_pcm_hw_params_set_rate_near(pcm, hw_params, wwo->format.wf.nSamplesPerSec, 0); if (rate < 0) { ERR("Rate %ld Hz not available for playback: %s\n", wwo->format.wf.nSamplesPerSec, snd_strerror(rate)); @@ -1145,8 +1140,14 @@ snd_pcm_close(pcm); return WAVERR_BADFORMAT; } + + EXIT_ON_ERROR( snd_pcm_hw_params_set_buffer_time_near(pcm, hw_params, buffer_time, 0), MMSYSERR_INVALPARAM, "unable to set buffer time"); + EXIT_ON_ERROR( snd_pcm_hw_params_set_period_time_near(pcm, hw_params, period_time, 0), MMSYSERR_INVALPARAM, "unable to set period time");
EXIT_ON_ERROR( snd_pcm_hw_params(pcm, hw_params), MMSYSERR_INVALPARAM, "unable to set hw params for playback"); + + period_size = snd_pcm_hw_params_get_period_size(hw_params, 0); + buffer_size = snd_pcm_hw_params_get_buffer_size(hw_params);
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");
Nikolay Stefanov wrote:
I've fixed the problem - turns out that ALSA driver doesn't like it when the buffer and period sizes are set directly. I modified the setup code to use buffer and period times instead, with some values that I copied from the OGLE alsa driver. Also it seems that the driver crashes if these are set before the sample rate, if you are wondering about the change of order. Everything seems to work normally now, I tested it with WinAmp and some games.
What do you think, is this is a driver issue or a wine issue? None of the other projects that I checked (ogle, mplayer) set the sizes directly, they use time values instead.
If all other ALSA users do it this way, then Wine is certainly incorrect.
Lastly, is there any special reason why the dsound implementation is not using mmap_write, like the wave one does?
at the time this has been written, it has been tried to do so, but differencies in mapping between ALSA and way dsound does the mapping made it quite difficult (search in wine-devel archives for the detail)
Nikolay
PS: Thanks to Michal Miroslaw for the patch, it's a lot simpler than what I did initially.
looks way better than previous ok you can safely submit to wine-patches for inclusion A+
Well, I use something easier - change all hw:x to hw (default ALSA card). And select this default using ALSA_CARD environment variable. Patch against current CVS follows.
Index: audio.c =================================================================== RCS file: /home/wine/wine/dlls/winmm/winealsa/audio.c,v retrieving revision 1.8 diff -u -r1.8 audio.c --- audio.c 25 Oct 2002 19:09:02 -0000 1.8 +++ audio.c 8 Dec 2002 22:33:18 -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,"hw",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,"hw",0), "hctl open failed"); EXIT_ON_ERROR( snd_hctl_load(hctl), "hctl load failed" );
elem=snd_hctl_first_elem(hctl); @@ -405,7 +405,7 @@ wwo = &WOutDev[0];
/* FIXME: use better values */ - wwo->device = "hw:0,0"; + wwo->device = "hw"; wwo->caps.wMid = 0x0002; wwo->caps.wPid = 0x0104; strcpy(wwo->caps.szPname, "SB16 Wave Out");