From: Jeremy White <jwhite@codeweavers.com>
To: alsa-devel@lists.sourceforge.net
Cc: Robert Reif <reif@earthlink.net>
Subject: RFC: Appropriate device detection for Wine
Date: Mon, 06 Jun 2005 22:52:09 -0500 [thread overview]
Message-ID: <42A519E9.2000103@codeweavers.com> (raw)
[-- Attachment #1: Type: text/plain, Size: 1103 bytes --]
Hi All,
I was hoping to get some advice on the proper use of Alsalib
within Wine, particularly with respect to
device detection and enumeration.
Wine has a need to provide a list of all available
PCM (playback and capture) devices back to our
clients (a Windows program).
Right now, Wine has a brute force
scan process (iterate 0-5 in cards, always use dev 0).
I've implemented a program that represents what I think
is the 'right' way for Wine to perform this scan.
Note that this code is just for the default case where
the user hasn't explicitly specified a device
name; Wine will still allow the user to override this
scan with an explicit device name such as file:/tmp/blat
The basic approach I take is as follows:
Use snd_card_next to iterate cards;
within cards, use snd_ctl_pcm_next_device
to iterate devices.
I jump through a few hoops to detect and respect
a specification of ALSA_CTL_CARD or ALSA_CARD, etc,
Now I've deluded myself into thinking this is a
careful, rigorous implementation.
Thoughts? Comments? Tactfully worded puncturing of
my delusions <grin>?
Thanks,
Jeremy
[-- Attachment #2: alsascan.c --]
[-- Type: text/x-csrc, Size: 14613 bytes --]
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <alsa/asoundlib.h>
/* Macros to mock up Wine compatibility */
#define ERR printf
#define WARN printf
#define TRACE printf
#define TRUE 1
#define FALSE 0
#define ALSA_RETURN_ONFAIL(mycall) \
{ \
int rc = mycall; \
if ((rc) < 0) \
{ \
ERR("%s failed: %s(%d)\n", #mycall, snd_strerror(rc), rc); \
return(rc); \
} \
}
/*----------------------------------------------------------------------------
** ALSA_AddPlaybackDevice
**
** Add a given Alsa device to Wine's internal list of Playback
** devices.
**
** Parameters:
** ctl Control handle; can be NULL
** pcm PCM handle, must be ! NULL
** isdefault Flag, if !0, indicates this device matches Alsa's
** definition of the default PCM device
**
**--------------------------------------------------------------------------*/
static int ALSA_AddPlaybackDevice(snd_ctl_t *ctl, snd_pcm_t *pcm, int isdefault)
{
snd_pcm_info_t *infop;
ALSA_RETURN_ONFAIL(snd_pcm_info_malloc(&infop));
ALSA_RETURN_ONFAIL(snd_pcm_info(pcm, infop));
/* FIXME: Add real implementation here */
printf("Playback [pcm %s/ctl %s|id %s|", snd_pcm_name(pcm), ctl ? snd_ctl_name(ctl) : "(none)", snd_pcm_info_get_id(infop));
printf("name %s|%s", snd_pcm_info_get_name(infop), isdefault ? "Default" : "Not default");
printf("]\n");
snd_pcm_info_free(infop);
return 0;
}
/*----------------------------------------------------------------------------
** ALSA_AddCaptureDevice
**
** Add a given Alsa device to Wine's internal list of Capture
** devices.
**
** Parameters:
** ctl Control handle; can be NULL
** pcm PCM handle, must be ! NULL
** isdefault Flag, if !0, indicates this device matches Alsa's
** definition of the default PCM device
**
**--------------------------------------------------------------------------*/
static int ALSA_AddCaptureDevice(snd_ctl_t *ctl, snd_pcm_t *pcm, int isdefault)
{
snd_pcm_info_t *infop;
ALSA_RETURN_ONFAIL(snd_pcm_info_malloc(&infop));
ALSA_RETURN_ONFAIL(snd_pcm_info(pcm, infop));
/* FIXME: Add real implementation here */
printf("Capture [pcm %s/ctl %s|id %s|", snd_pcm_name(pcm), ctl ? snd_ctl_name(ctl) : "(none)", snd_pcm_info_get_id(infop));
printf("name %s|%s", snd_pcm_info_get_name(infop), isdefault ? "Default" : "Not default");
printf("]\n");
snd_pcm_info_free(infop);
return 0;
}
/*----------------------------------------------------------------------------
** 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 convolunted 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.
**--------------------------------------------------------------------------*/
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:
** plugflag Whether to use the plugin device or not;
** essentially switches the pcm device name from
** "hw" to "plughw".
** 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 failiure
**--------------------------------------------------------------------------*/
static int ALSA_DefaultDevices(int plugflag,
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", plugflag ? "plughw" : "hw");
if (snd_config_search(snd_config, pcmsearch, &configp) >= 0)
ALSA_CheckEnvironment(configp, fixedpcmcard);
}
if (fixedpcmdev)
{
sprintf(pcmsearch, "pcm.%s.@args.DEV.default.vars", plugflag ? "plughw" : "hw");
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:
** plugflag Whether to use the plugin device or not;
** essentially switches the pcm device name from
** "hw" to "plughw".
** 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 failiure
**--------------------------------------------------------------------------*/
static int ALSA_ScanDevices(int plugflag,
long defctlcard, long defpcmcard, long defpcmdev,
int fixedctlcard, int fixedpcmcard, int fixedpcmdev)
{
int card = fixedpcmcard;
/*------------------------------------------------------------------------
** 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, 0);
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 pcmname[256];
snd_pcm_t *pcm;
/*----------------------------------------------------------------
** See if it's a valid playback device
**--------------------------------------------------------------*/
sprintf(pcmname, "%s:%d,%d", plugflag ? "plughw" : "hw", card, device);
rc = snd_pcm_open(&pcm, pcmname, SND_PCM_STREAM_PLAYBACK, 0);
if (rc >= 0)
{
if (defctlcard == card && defpcmcard == card && defpcmdev == device)
ALSA_AddPlaybackDevice(ctl, pcm, TRUE);
else
ALSA_AddPlaybackDevice(ctl, pcm, FALSE);
snd_pcm_close(pcm);
}
else
{
TRACE("Device [%s] not suitable for playback: %s\n",
pcmname, snd_strerror(rc));
}
/*----------------------------------------------------------------
** See if it's a valid capture device
**--------------------------------------------------------------*/
rc = snd_pcm_open(&pcm, pcmname, SND_PCM_STREAM_CAPTURE, 0);
if (rc >= 0)
{
if (defctlcard == card && defpcmcard == card && defpcmdev == device)
ALSA_AddCaptureDevice(ctl, pcm, TRUE);
else
ALSA_AddCaptureDevice(ctl, pcm, FALSE);
snd_pcm_close(pcm);
}
else
{
TRACE("Device [%s] not suitable for capture: %s\n",
pcmname, snd_strerror(rc));
}
/*----------------------------------------------------------------
** If the user has set environment variables such that
** we're pegged to a specific device (or if we've got no
** ctl device), then break out of the device scan loop
**--------------------------------------------------------------*/
if (fixedpcmdev != -1)
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:
** plugflag If !0, indicates we should use the plug:hw
** PCM interface, rather than the hw interface.
** The plug:hw interface performs automatic
** rate conversion and a bunch of other magic
** things that can be quite handy.
**
** Returns:
** 0 on succes
**
** Side Effects:
** Invokes the ALSA_AddXXXDevice functions on valid
** looking devices
**--------------------------------------------------------------------------*/
static int ALSA_PerformDefaultScan(int plugflag)
{
long defctlcard = -1, defpcmcard = -1, defpcmdev = -1;
int fixedctlcard = -1, fixedpcmcard = -1, fixedpcmdev = -1;
int rc;
rc = ALSA_DefaultDevices(plugflag, &defctlcard, &defpcmcard, &defpcmdev,
&fixedctlcard, &fixedpcmcard, &fixedpcmdev);
if (rc)
return(rc);
return(ALSA_ScanDevices(plugflag, defctlcard, defpcmcard, defpcmdev, fixedctlcard, fixedpcmcard, fixedpcmdev));
}
int main(void)
{
ALSA_PerformDefaultScan(0);
ALSA_PerformDefaultScan(1);
ALSA_RETURN_ONFAIL(snd_config_update_free_global());
return 0;
}
next reply other threads:[~2005-06-07 3:52 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2005-06-07 3:52 Jeremy White [this message]
2005-06-07 7:32 ` RFC: Appropriate device detection for Wine Jaroslav Kysela
2005-06-07 12:27 ` Jeremy White
2005-06-07 12:48 ` Jaroslav Kysela
2005-06-07 14:25 ` Jeremy White
2005-06-13 2:43 ` RFC: Appropriate device detection for Wine (more advice needed) Jeremy White
2005-06-13 7:24 ` Clemens Ladisch
2005-06-13 12:23 ` Jeremy White
2005-06-13 12:35 ` Takashi Iwai
2005-06-13 14:00 ` Jeremy White
2005-06-13 14:23 ` Takashi Iwai
2005-06-13 15:01 ` Jeremy White
2005-06-13 15:11 ` Takashi Iwai
2005-06-14 3:22 ` Jeremy White
2005-06-14 8:30 ` Takashi Iwai
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=42A519E9.2000103@codeweavers.com \
--to=jwhite@codeweavers.com \
--cc=alsa-devel@lists.sourceforge.net \
--cc=reif@earthlink.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox