1019 lines
34 KiB
C
1019 lines
34 KiB
C
/*
|
|
* 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 */
|