* sound.c (alsa_sound_perror, alsa_open, alsa_period_size)

(alsa_configure, alsa_close, alsa_choose_format, alsa_write)
	(snd_error_quiet, alsa_init): New functions.
	(vox_init): Return 0 if unable to open device.
	(Fplay_sound_internal): Test for alsa first and use vox (oss) as
	a fallback.
	(struct sound_device): Add period_size.
	(wav_play, au_play): Use period_size if set.
This commit is contained in:
Jan Djärv 2006-05-18 17:06:01 +00:00
parent 4900d996ca
commit 2d2643f653

View file

@ -73,6 +73,10 @@ Boston, MA 02110-1301, USA. */
#ifdef HAVE_SOUNDCARD_H
#include <soundcard.h>
#endif
#ifdef HAVE_ALSA
#include <asoundlib.h>
#endif
/* END: Non Windows Includes */
#else /* WINDOWSNT */
@ -121,6 +125,9 @@ static int parse_sound P_ ((Lisp_Object, Lisp_Object *));
#ifndef DEFAULT_SOUND_DEVICE
#define DEFAULT_SOUND_DEVICE "/dev/dsp"
#endif
#ifndef DEFAULT_ALSA_SOUND_DEVICE
#define DEFAULT_ALSA_SOUND_DEVICE "default"
#endif
/* Structure forward declarations. */
@ -227,6 +234,10 @@ struct sound_device
void (* choose_format) P_ ((struct sound_device *sd,
struct sound *s));
/* Return a preferred data size in bytes to be sent to write (below)
each time. 2048 is used if this is NULL. */
int (* period_size) P_ ((struct sound_device *sd));
/* Write NYBTES bytes from BUFFER to device SD. */
void (* write) P_ ((struct sound_device *sd, const char *buffer,
int nbytes));
@ -280,7 +291,7 @@ static void vox_open P_ ((struct sound_device *));
static void vox_configure P_ ((struct sound_device *));
static void vox_close P_ ((struct sound_device *sd));
static void vox_choose_format P_ ((struct sound_device *, struct sound *));
static void vox_init P_ ((struct sound_device *));
static int vox_init P_ ((struct sound_device *));
static void vox_write P_ ((struct sound_device *, const char *, int));
static void find_sound_type P_ ((struct sound *));
static u_int32_t le2hl P_ ((u_int32_t));
@ -604,7 +615,7 @@ wav_play (s, sd)
{
char *buffer;
int nbytes;
int blksize = 2048;
int blksize = sd->period_size ? sd->period_size (sd) : 2048;
buffer = (char *) alloca (blksize);
lseek (s->fd, sizeof *header, SEEK_SET);
@ -633,7 +644,8 @@ enum au_encoding
AU_ENCODING_32,
AU_ENCODING_IEEE32,
AU_ENCODING_IEEE64,
AU_COMPRESSED = 23
AU_COMPRESSED = 23,
AU_ENCODING_ALAW_8 = 27
};
@ -689,7 +701,7 @@ au_play (s, sd)
SBYTES (s->data) - header->data_offset);
else
{
int blksize = 2048;
int blksize = sd->period_size ? sd->period_size (sd) : 2048;
char *buffer;
int nbytes;
@ -868,16 +880,33 @@ vox_choose_format (sd, s)
/* Initialize device SD. Set up the interface functions in the device
structure. */
static void
static int
vox_init (sd)
struct sound_device *sd;
{
char *file;
int fd;
/* Open the sound device. Default is /dev/dsp. */
if (sd->file)
file = sd->file;
else
file = DEFAULT_SOUND_DEVICE;
fd = emacs_open (file, O_WRONLY, 0);
if (fd >= 0)
emacs_close (fd);
else
return 0;
sd->fd = -1;
sd->open = vox_open;
sd->close = vox_close;
sd->configure = vox_configure;
sd->choose_format = vox_choose_format;
sd->write = vox_write;
sd->period_size = NULL;
return 1;
}
/* Write NBYTES bytes from BUFFER to device SD. */
@ -893,6 +922,359 @@ vox_write (sd, buffer, nbytes)
sound_perror ("Error writing to sound device");
}
#ifdef HAVE_ALSA
/***********************************************************************
ALSA Driver Interface
***********************************************************************/
/* This driver is available on GNU/Linux. */
static void
alsa_sound_perror (msg, err)
char *msg;
int err;
{
error ("%s: %s", msg, snd_strerror (err));
}
struct alsa_params
{
snd_pcm_t *handle;
snd_pcm_hw_params_t *hwparams;
snd_pcm_sw_params_t *swparams;
snd_pcm_uframes_t period_size;
};
/* Open device SD. If SD->file is non-null, open that device,
otherwise use a default device name. */
static void
alsa_open (sd)
struct sound_device *sd;
{
char *file;
struct alsa_params *p;
int err;
/* Open the sound device. Default is "default". */
if (sd->file)
file = sd->file;
else
file = DEFAULT_ALSA_SOUND_DEVICE;
p = xmalloc (sizeof (*p));
p->handle = NULL;
p->hwparams = NULL;
p->swparams = NULL;
sd->fd = -1;
sd->data = p;
if ((err = snd_pcm_open (&p->handle, file, SND_PCM_STREAM_PLAYBACK, 0)) < 0)
alsa_sound_perror (file, err);
}
static int
alsa_period_size (sd)
struct sound_device *sd;
{
struct alsa_params *p = (struct alsa_params *) sd->data;
return p->period_size;
}
static void
alsa_configure (sd)
struct sound_device *sd;
{
int val, err, dir;
struct alsa_params *p = (struct alsa_params *) sd->data;
snd_pcm_uframes_t buffer_size;
xassert (p->handle != 0);
if ((err = snd_pcm_hw_params_malloc (&p->hwparams)) < 0)
alsa_sound_perror ("Could not allocate hardware parameter structure", err);
if ((err = snd_pcm_sw_params_malloc (&p->swparams)) < 0)
alsa_sound_perror ("Could not allocate software parameter structure", err);
if ((err = snd_pcm_hw_params_any (p->handle, p->hwparams)) < 0)
alsa_sound_perror ("Could not initialize hardware parameter structure", err);
if ((err = snd_pcm_hw_params_set_access (p->handle, p->hwparams,
SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
alsa_sound_perror ("Could not set access type", err);
val = sd->format;
if ((err = snd_pcm_hw_params_set_format (p->handle, p->hwparams, val)) < 0)
alsa_sound_perror ("Could not set sound format", err);
val = sd->sample_rate;
if ((err = snd_pcm_hw_params_set_rate_near (p->handle, p->hwparams, &val, 0))
< 0)
alsa_sound_perror ("Could not set sample rate", err);
val = sd->channels;
if ((err = snd_pcm_hw_params_set_channels (p->handle, p->hwparams, val)) < 0)
alsa_sound_perror ("Could not set channel count", err);
err = snd_pcm_hw_params_get_period_size (p->hwparams, &p->period_size, &dir);
if (err < 0)
alsa_sound_perror ("Unable to get period size for playback", err);
err = snd_pcm_hw_params_get_buffer_size (p->hwparams, &buffer_size);
if (err < 0)
alsa_sound_perror("Unable to get buffer size for playback", err);
if ((err = snd_pcm_hw_params (p->handle, p->hwparams)) < 0)
alsa_sound_perror ("Could not set parameters", err);
err = snd_pcm_sw_params_current (p->handle, p->swparams);
if (err < 0)
alsa_sound_perror ("Unable to determine current swparams for playback",
err);
/* Start the transfer when the buffer is almost full */
err = snd_pcm_sw_params_set_start_threshold (p->handle, p->swparams,
(buffer_size / p->period_size)
* p->period_size);
if (err < 0)
alsa_sound_perror ("Unable to set start threshold mode for playback", err);
/* Allow the transfer when at least period_size samples can be processed */
err = snd_pcm_sw_params_set_avail_min (p->handle, p->swparams, p->period_size);
if (err < 0)
alsa_sound_perror ("Unable to set avail min for playback", err);
/* Align all transfers to 1 period */
err = snd_pcm_sw_params_set_xfer_align (p->handle, p->swparams,
p->period_size);
if (err < 0)
alsa_sound_perror ("Unable to set transfer align for playback", err);
err = snd_pcm_sw_params (p->handle, p->swparams);
if (err < 0)
alsa_sound_perror ("Unable to set sw params for playback\n", err);
snd_pcm_hw_params_free (p->hwparams);
p->hwparams = NULL;
snd_pcm_sw_params_free (p->swparams);
p->swparams = NULL;
if ((err = snd_pcm_prepare (p->handle)) < 0)
alsa_sound_perror ("Could not prepare audio interface for use", err);
if (sd->volume > 0)
{
int chn;
snd_mixer_t *handle;
snd_mixer_elem_t *e;
char *file = sd->file ? sd->file : DEFAULT_ALSA_SOUND_DEVICE;
if (snd_mixer_open (&handle, 0) >= 0)
{
if (snd_mixer_attach (handle, file) >= 0
&& snd_mixer_load (handle) >= 0
&& snd_mixer_selem_register (handle, NULL, NULL) >= 0)
for (e = snd_mixer_first_elem (handle);
e;
e = snd_mixer_elem_next (e))
{
if (snd_mixer_selem_has_playback_volume (e))
{
long pmin, pmax;
snd_mixer_selem_get_playback_volume_range (e, &pmin, &pmax);
long vol = pmin + (sd->volume * (pmax - pmin)) / 100;
for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++)
snd_mixer_selem_set_playback_volume (e, chn, vol);
}
}
snd_mixer_close(handle);
}
}
}
/* Close device SD if it is open. */
static void
alsa_close (sd)
struct sound_device *sd;
{
struct alsa_params *p = (struct alsa_params *) sd->data;
if (p)
{
if (p->hwparams)
snd_pcm_hw_params_free (p->hwparams);
if (p->swparams)
snd_pcm_sw_params_free (p->swparams);
if (p->handle)
{
snd_pcm_drain(p->handle);
snd_pcm_close (p->handle);
}
free (p);
}
}
/* Choose device-dependent format for device SD from sound file S. */
static void
alsa_choose_format (sd, s)
struct sound_device *sd;
struct sound *s;
{
struct alsa_params *p = (struct alsa_params *) sd->data;
if (s->type == RIFF)
{
struct wav_header *h = (struct wav_header *) s->header;
if (h->precision == 8)
sd->format = SND_PCM_FORMAT_U8;
else if (h->precision == 16)
sd->format = SND_PCM_FORMAT_S16_LE;
else
error ("Unsupported WAV file format");
}
else if (s->type == SUN_AUDIO)
{
struct au_header *header = (struct au_header *) s->header;
switch (header->encoding)
{
case AU_ENCODING_ULAW_8:
sd->format = SND_PCM_FORMAT_MU_LAW;
break;
case AU_ENCODING_ALAW_8:
sd->format = SND_PCM_FORMAT_A_LAW;
break;
case AU_ENCODING_IEEE32:
sd->format = SND_PCM_FORMAT_FLOAT_BE;
break;
case AU_ENCODING_IEEE64:
sd->format = SND_PCM_FORMAT_FLOAT64_BE;
break;
case AU_ENCODING_8:
sd->format = SND_PCM_FORMAT_S8;
break;
case AU_ENCODING_16:
sd->format = SND_PCM_FORMAT_S16_BE;
break;
case AU_ENCODING_24:
sd->format = SND_PCM_FORMAT_S24_BE;
break;
case AU_ENCODING_32:
sd->format = SND_PCM_FORMAT_S32_BE;
break;
default:
error ("Unsupported AU file format");
}
}
else
abort ();
}
/* Write NBYTES bytes from BUFFER to device SD. */
static void
alsa_write (sd, buffer, nbytes)
struct sound_device *sd;
const char *buffer;
int nbytes;
{
struct alsa_params *p = (struct alsa_params *) sd->data;
/* The the third parameter to snd_pcm_writei is frames, not bytes. */
int fact = snd_pcm_format_size (sd->format, 1) * sd->channels;
int nwritten = 0;
int err;
while (nwritten < nbytes)
{
if ((err = snd_pcm_writei (p->handle,
buffer + nwritten,
(nbytes - nwritten)/fact)) < 0)
{
fprintf(stderr, "Err %d/%s\n", err, snd_strerror(err));
if (err == -EPIPE)
{ /* under-run */
err = snd_pcm_prepare (p->handle);
if (err < 0)
alsa_sound_perror ("Can't recover from underrun, prepare failed",
err);
}
else if (err == -ESTRPIPE)
{
while ((err = snd_pcm_resume (p->handle)) == -EAGAIN)
sleep(1); /* wait until the suspend flag is released */
if (err < 0)
{
err = snd_pcm_prepare (p->handle);
if (err < 0)
alsa_sound_perror ("Can't recover from suspend, "
"prepare failed",
err);
}
}
else
alsa_sound_perror ("Error writing to sound device", err);
}
else
nwritten += err * fact;
}
}
static void
snd_error_quiet (file, line, function, err, fmt)
const char *file;
int line;
const char *function;
int err;
const char *fmt;
{
}
/* Initialize device SD. Set up the interface functions in the device
structure. */
static int
alsa_init (sd)
struct sound_device *sd;
{
char *file;
snd_pcm_t *handle;
int err;
/* Open the sound device. Default is "default". */
if (sd->file)
file = sd->file;
else
file = DEFAULT_ALSA_SOUND_DEVICE;
snd_lib_error_set_handler ((snd_lib_error_handler_t) snd_error_quiet);
err = snd_pcm_open (&handle, file, SND_PCM_STREAM_PLAYBACK, 0);
snd_lib_error_set_handler (NULL);
if (err < 0)
return 0;
sd->fd = -1;
sd->open = alsa_open;
sd->close = alsa_close;
sd->configure = alsa_configure;
sd->choose_format = alsa_choose_format;
sd->write = alsa_write;
sd->period_size = alsa_period_size;
return 1;
}
#endif /* HAVE_ALSA */
/* END: Non Windows functions */
#else /* WINDOWSNT */
@ -1056,10 +1438,11 @@ Internal use only, use `play-sound' instead.\n */)
args[1] = sound;
Frun_hook_with_args (2, args);
/* There is only one type of device we currently support, the VOX
sound driver. Set up the device interface functions for that
device. */
vox_init (current_sound_device);
#ifdef HAVE_ALSA
if (!alsa_init (current_sound_device))
#endif
if (!vox_init (current_sound_device))
error ("No usable sound device driver found");
/* Open the device. */
current_sound_device->open (current_sound_device);