X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/d5dc920668a85c56c4d3e54a6898bbd43bcb64a1..5371d722ecd94db9d5b3b21f4b91d073a38bd73b:/src/sound.c diff --git a/src/sound.c b/src/sound.c index 93e456cbc4..8aaef9d746 100644 --- a/src/sound.c +++ b/src/sound.c @@ -1,11 +1,12 @@ /* sound.c -- sound support. - Copyright (C) 1998, 1999, 2001 Free Software Foundation. + Copyright (C) 1998, 1999, 2001, 2002, 2003, 2004, + 2005, 2006, 2007, 2008 Free Software Foundation, Inc. This file is part of GNU Emacs. GNU Emacs is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2, or (at your option) +the Free Software Foundation; either version 3, or (at your option) any later version. GNU Emacs is distributed in the hope that it will be useful, @@ -72,6 +73,14 @@ Boston, MA 02110-1301, USA. */ #ifdef HAVE_SOUNDCARD_H #include #endif +#ifdef HAVE_ALSA +#ifdef ALSA_SUBDIR_INCLUDE +#include +#else +#include +#endif /* ALSA_SUBDIR_INCLUDE */ +#endif /* HAVE_ALSA */ + /* END: Non Windows Includes */ #else /* WINDOWSNT */ @@ -88,7 +97,6 @@ Boston, MA 02110-1301, USA. */ #endif /* WINDOWSNT */ /* BEGIN: Common Definitions */ -#define abs(X) ((X) < 0 ? -(X) : (X)) /* Symbols. */ @@ -108,7 +116,8 @@ enum sound_attr SOUND_ATTR_SENTINEL }; -static void sound_perror P_ ((char *)); +static void alsa_sound_perror P_ ((char *, int)) NO_RETURN; +static void sound_perror P_ ((char *)) NO_RETURN; static void sound_warning P_ ((char *)); static int parse_sound P_ ((Lisp_Object, Lisp_Object *)); @@ -120,6 +129,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. */ @@ -226,6 +238,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)); @@ -279,7 +295,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)); @@ -451,13 +467,12 @@ static Lisp_Object sound_cleanup (arg) Lisp_Object arg; { - if (current_sound_device) - { - if (current_sound_device->close) - current_sound_device->close (current_sound_device); - if (current_sound->fd > 0) - emacs_close (current_sound->fd); - } + if (current_sound_device->close) + current_sound_device->close (current_sound_device); + if (current_sound->fd > 0) + emacs_close (current_sound->fd); + free (current_sound_device); + free (current_sound); return Qnil; } @@ -604,13 +619,19 @@ wav_play (s, sd) { char *buffer; int nbytes; - int blksize = 2048; + int blksize = sd->period_size ? sd->period_size (sd) : 2048; + int data_left = header->data_length; buffer = (char *) alloca (blksize); lseek (s->fd, sizeof *header, SEEK_SET); - - while ((nbytes = emacs_read (s->fd, buffer, blksize)) > 0) - sd->write (sd, buffer, nbytes); + while (data_left > 0 + && (nbytes = emacs_read (s->fd, buffer, blksize)) > 0) + { + /* Don't play possible garbage at the end of file */ + if (data_left < nbytes) nbytes = data_left; + data_left -= nbytes; + sd->write (sd, buffer, nbytes); + } if (nbytes < 0) sound_perror ("Error reading sound file"); @@ -633,7 +654,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 +711,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 +890,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 +932,372 @@ 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; + + + err = snd_pcm_open (&p->handle, file, SND_PCM_STREAM_PLAYBACK, 0); + if (err < 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; + int fact = snd_pcm_format_size (sd->format, 1) * sd->channels; + return p->period_size * (fact > 0 ? fact : 1); +} + +static void +alsa_configure (sd) + struct sound_device *sd; +{ + int val, err, dir; + unsigned uval; + struct alsa_params *p = (struct alsa_params *) sd->data; + snd_pcm_uframes_t buffer_size; + + xassert (p->handle != 0); + + err = snd_pcm_hw_params_malloc (&p->hwparams); + if (err < 0) + alsa_sound_perror ("Could not allocate hardware parameter structure", err); + + err = snd_pcm_sw_params_malloc (&p->swparams); + if (err < 0) + alsa_sound_perror ("Could not allocate software parameter structure", err); + + err = snd_pcm_hw_params_any (p->handle, p->hwparams); + if (err < 0) + alsa_sound_perror ("Could not initialize hardware parameter structure", err); + + err = snd_pcm_hw_params_set_access (p->handle, p->hwparams, + SND_PCM_ACCESS_RW_INTERLEAVED); + if (err < 0) + alsa_sound_perror ("Could not set access type", err); + + val = sd->format; + err = snd_pcm_hw_params_set_format (p->handle, p->hwparams, val); + if (err < 0) + alsa_sound_perror ("Could not set sound format", err); + + uval = sd->sample_rate; + err = snd_pcm_hw_params_set_rate_near (p->handle, p->hwparams, &uval, 0); + if (err < 0) + alsa_sound_perror ("Could not set sample rate", err); + + val = sd->channels; + err = snd_pcm_hw_params_set_channels (p->handle, p->hwparams, val); + if (err < 0) + alsa_sound_perror ("Could not set channel count", err); + + err = snd_pcm_hw_params (p->handle, p->hwparams); + if (err < 0) + alsa_sound_perror ("Could not set parameters", 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); + + 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; + + err = snd_pcm_prepare (p->handle); + if (err < 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, vol; + snd_mixer_selem_get_playback_volume_range (e, &pmin, &pmax); + 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) + { + snd_pcm_uframes_t frames = (nbytes - nwritten)/fact; + if (frames == 0) break; + + err = snd_pcm_writei (p->handle, buffer + nwritten, frames); + if (err < 0) + { + 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; + snd_pcm_close (handle); + + 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 */ @@ -980,7 +1385,7 @@ do_play_sound (psz_file, ui_volume) DEFUN ("play-sound-internal", Fplay_sound_internal, Splay_sound_internal, 1, 1, 0, doc: /* Play sound SOUND. -Internal use only, use `play-sound' instead.\n */) +Internal use only, use `play-sound' instead. */) (sound) Lisp_Object sound; { @@ -990,8 +1395,6 @@ Internal use only, use `play-sound' instead.\n */) #ifndef WINDOWSNT Lisp_Object file; struct gcpro gcpro1, gcpro2; - struct sound_device sd; - struct sound s; Lisp_Object args[2]; #else /* WINDOWSNT */ int len = 0; @@ -1009,77 +1412,68 @@ Internal use only, use `play-sound' instead.\n */) #ifndef WINDOWSNT file = Qnil; GCPRO2 (sound, file); - bzero (&sd, sizeof sd); - bzero (&s, sizeof s); - current_sound_device = &sd; - current_sound = &s; + current_sound_device = (struct sound_device *) xmalloc (sizeof (struct sound_device)); + bzero (current_sound_device, sizeof (struct sound_device)); + current_sound = (struct sound *) xmalloc (sizeof (struct sound)); + bzero (current_sound, sizeof (struct sound)); record_unwind_protect (sound_cleanup, Qnil); - s.header = (char *) alloca (MAX_SOUND_HEADER_BYTES); + current_sound->header = (char *) alloca (MAX_SOUND_HEADER_BYTES); if (STRINGP (attrs[SOUND_FILE])) { /* Open the sound file. */ - s.fd = openp (Fcons (Vdata_directory, Qnil), - attrs[SOUND_FILE], Qnil, &file, Qnil); - if (s.fd < 0) + current_sound->fd = openp (Fcons (Vdata_directory, Qnil), + attrs[SOUND_FILE], Qnil, &file, Qnil); + if (current_sound->fd < 0) sound_perror ("Could not open sound file"); /* Read the first bytes from the file. */ - s.header_size = emacs_read (s.fd, s.header, MAX_SOUND_HEADER_BYTES); - if (s.header_size < 0) + current_sound->header_size + = emacs_read (current_sound->fd, current_sound->header, + MAX_SOUND_HEADER_BYTES); + if (current_sound->header_size < 0) sound_perror ("Invalid sound file header"); } else { - s.data = attrs[SOUND_DATA]; - s.header_size = min (MAX_SOUND_HEADER_BYTES, SBYTES (s.data)); - bcopy (SDATA (s.data), s.header, s.header_size); + current_sound->data = attrs[SOUND_DATA]; + current_sound->header_size = min (MAX_SOUND_HEADER_BYTES, SBYTES (current_sound->data)); + bcopy (SDATA (current_sound->data), current_sound->header, current_sound->header_size); } /* Find out the type of sound. Give up if we can't tell. */ - find_sound_type (&s); + find_sound_type (current_sound); /* Set up a device. */ if (STRINGP (attrs[SOUND_DEVICE])) { int len = SCHARS (attrs[SOUND_DEVICE]); - sd.file = (char *) alloca (len + 1); - strcpy (sd.file, SDATA (attrs[SOUND_DEVICE])); + current_sound_device->file = (char *) alloca (len + 1); + strcpy (current_sound_device->file, SDATA (attrs[SOUND_DEVICE])); } if (INTEGERP (attrs[SOUND_VOLUME])) - sd.volume = XFASTINT (attrs[SOUND_VOLUME]); + current_sound_device->volume = XFASTINT (attrs[SOUND_VOLUME]); else if (FLOATP (attrs[SOUND_VOLUME])) - sd.volume = XFLOAT_DATA (attrs[SOUND_VOLUME]) * 100; + current_sound_device->volume = XFLOAT_DATA (attrs[SOUND_VOLUME]) * 100; args[0] = Qplay_sound_functions; 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 (&sd); +#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. */ - sd.open (&sd); + current_sound_device->open (current_sound_device); /* Play the sound. */ - s.play (&s, &sd); - - /* Close the input file, if any. */ - if (!STRINGP (s.data)) - { - emacs_close (s.fd); - s.fd = -1; - } - - /* Close the device. */ - sd.close (&sd); + current_sound->play (current_sound, current_sound_device); /* Clean up. */ - current_sound_device = NULL; - current_sound = NULL; UNGCPRO; #else /* WINDOWSNT */