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");