/* -*- Mode: c; tab-width: 4; c-basic-offset: 4; -*- * * ALSA 0.9 support for Esound * by Santiago Otero (siryurian@terra.es) * * * 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 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ /* Debug flag */ static int alsadbg = 0; /* Error flag */ static int alsaerr = 0; #include /* FULL DUPLEX => two handlers */ static snd_pcm_t *alsa_playback_handle = NULL; static snd_pcm_t *alsa_capture_handle = NULL; static snd_output_t *output = NULL; #define ACS SND_PCM_ACCESS_RW_INTERLEAVED #define BUFFERSIZE 16384 void print_state(void) { int state; int err; snd_pcm_status_t *status; snd_pcm_status_alloca(&status); if ( (err = snd_pcm_status(alsa_playback_handle, status)) < 0) { fprintf(stderr, "Error getting status:%s\n", snd_strerror(err)); return; } state = snd_pcm_status_get_state( status ); switch(state) { case SND_PCM_STATE_OPEN : fprintf(stderr, "SND_PCM_STATE_OPEN\n"); break; case SND_PCM_STATE_SETUP: fprintf(stderr, "SND_PCM_STATE_SETUP\n"); break; case SND_PCM_STATE_PREPARED: fprintf(stderr,"SND_PCM_STATE_PREPARED\n"); break; case SND_PCM_STATE_RUNNING: fprintf(stderr, "SND_PCM_STATE_RUNNING\n"); break; case SND_PCM_STATE_XRUN: fprintf(stderr, "SND_PCM_STATE_XRUN\n"); break; case SND_PCM_STATE_DRAINING: fprintf(stderr, "SND_PCM_STATE_DRAINING\n"); break; case SND_PCM_STATE_PAUSED: fprintf(stderr, "SND_PCM_STATE_PAUSED\n"); break; case SND_PCM_STATE_SUSPENDED: fprintf(stderr, "SND_PCM_STATE_SUSPENDED\n"); break; default: fprintf(stderr, "WARNING: unknown state %d\n", state); } } snd_pcm_t* initAlsa(char *dev, int format, int channels, int speed, int mode) { snd_pcm_t *handle; snd_pcm_hw_params_t *hwparams; int err; unsigned int periods; #ifdef DRIVER_ALSA_09_NEW_PCM_API int t_dir=0; unsigned int t_speed=(unsigned int)speed; snd_pcm_uframes_t t_bufsize=BUFFERSIZE; #endif snd_config_update_free_global(); err = snd_pcm_open(&handle, dev, mode, SND_PCM_NONBLOCK); if (err < 0) { if (alsadbg) fprintf(stderr, "%s\n", snd_strerror(err)); alsaerr = -2; return NULL; } snd_pcm_nonblock(handle, 0); snd_pcm_hw_params_alloca(&hwparams); err = snd_pcm_hw_params_any(handle, hwparams); if (err < 0) { if (alsadbg) fprintf(stderr, "%s\n", snd_strerror(err)); alsaerr = -1; return handle; } err = snd_pcm_hw_params_set_access(handle, hwparams, ACS); if (err < 0) { if (alsadbg) fprintf(stderr, "%s\n", snd_strerror(err)); alsaerr = -1; return handle; } err = snd_pcm_hw_params_set_format(handle, hwparams, format); if (err < 0) { if (alsadbg) fprintf(stderr, "%s\n", snd_strerror(err)); alsaerr = -1; return handle; } err = snd_pcm_hw_params_set_channels(handle, hwparams, channels); if (err < 0) { if (alsadbg) fprintf(stderr, "%s\n", snd_strerror(err)); alsaerr = -1; return handle; } #ifndef DRIVER_ALSA_09_NEW_PCM_API err = snd_pcm_hw_params_set_rate_near(handle, hwparams, speed, 0); #else err = snd_pcm_hw_params_set_rate_near(handle, hwparams, &t_speed, &t_dir); #endif if (err < 0) { if (alsadbg) fprintf(stderr, "%s\n", snd_strerror(err)); alsaerr = -1; return handle; } #ifndef DRIVER_ALSA_09_NEW_PCM_API if (err != speed) { #else if (t_speed != speed) { #endif if (alsadbg) fprintf(stderr, "Rate not avaliable %i != %i\n", speed, err); alsaerr = -1; return handle; } err = snd_pcm_hw_params_set_periods_integer(handle, hwparams); if (err < 0) { if (alsadbg) fprintf(stderr, "%s\n", snd_strerror(err)); alsaerr = -1; return handle; } periods = 2; err = snd_pcm_hw_params_set_periods_min(handle, hwparams, &periods, NULL); if (err < 0) { if (alsadbg) fprintf(stderr, "%s\n", snd_strerror(err)); alsaerr = -1; return handle; } periods = 64; err = snd_pcm_hw_params_set_periods_max(handle, hwparams, &periods, NULL); if (err < 0) { if (alsadbg) fprintf(stderr, "%s\n", snd_strerror(err)); alsaerr = -1; return handle; } #ifndef DRIVER_ALSA_09_NEW_PCM_API err = snd_pcm_hw_params_set_buffer_size_near(handle, hwparams, BUFFERSIZE); #else err = snd_pcm_hw_params_set_buffer_size_near(handle, hwparams, &t_bufsize); #endif if (err < 0) { if (alsadbg) fprintf(stderr, "Buffersize:%s\n", snd_strerror(err)); alsaerr = -1; return handle; } err = snd_pcm_hw_params(handle, hwparams); if (err < 0) { if (alsadbg) fprintf(stderr, "%s\n", snd_strerror(err)); alsaerr = -1; return handle; } if (alsadbg) snd_pcm_dump(handle, output); alsaerr = 0; return handle; } #define ARCH_esd_audio_devices const char *esd_audio_devices(void) { int card, err; static char *all_alsa_cards = NULL; char *alsa_card_tmp; snd_ctl_t *handle; snd_ctl_card_info_t *info; snd_ctl_card_info_alloca(&info); if(all_alsa_cards) { free(all_alsa_cards); all_alsa_cards = NULL; } card = -1; if(snd_card_next(&card) < 0 || card < 0) { /* No cards found */ } else { while( card >= 0 ) { char name[32]; sprintf( name, "hw:%d", card ); err = snd_ctl_open(&handle, name, 0); if( err < 0 ) { fprintf( stderr, "audio_alsa: Error: control open (%i): %s\n", card, snd_strerror(err)); continue; } err = snd_ctl_card_info(handle, info); if( err < 0 ) { fprintf( stderr, "audio_alsa: Error: control hardware info (%i): %s\n", card, snd_strerror(err)); snd_ctl_close(handle); continue; } alsa_card_tmp = malloc(strlen(snd_ctl_card_info_get_name(info))+20); sprintf( alsa_card_tmp, "hw:%d (%s)\n", card, snd_ctl_card_info_get_name(info) ); if(all_alsa_cards) { all_alsa_cards = realloc(all_alsa_cards, strlen(all_alsa_cards)+strlen(alsa_card_tmp)+30); strcat(all_alsa_cards, " "); strcat(all_alsa_cards, alsa_card_tmp); free(alsa_card_tmp); } else { all_alsa_cards = alsa_card_tmp; } snd_ctl_close(handle); if(snd_card_next(&card) < 0) { break; } } } if(all_alsa_cards) { return(all_alsa_cards); } else { return("No available cards found"); } } static void noerr(const char *file, int line, const char *func, int err, const char *fmt, ...) { } #define ARCH_esd_audio_open int esd_audio_open(void) { int channels; int format; char *dev; char *debug; debug = getenv("ESD_DEBUG"); if (debug && *debug) { alsadbg = atoi(debug); if (alsadbg < 0) alsadbg = 0; } else { /* suppress verbose error messages from alsa-lib */ snd_lib_error_set_handler(noerr); } if (alsadbg) fprintf(stderr, "esd_audio_open\n"); if ((esd_audio_format & ESD_MASK_BITS) == ESD_BITS16) format = SND_PCM_FORMAT_S16; else format = SND_PCM_FORMAT_U8; if ((esd_audio_format & ESD_MASK_CHAN) == ESD_STEREO) channels = 2; else channels = 1; snd_output_stdio_attach(&output, stderr, 0); if(esd_audio_device) { dev = (char*) malloc(strlen(esd_audio_device)+1); strcpy(dev, esd_audio_device); } else { /* bind to alsa default setting */ dev = strdup ("default"); } if (alsadbg) fprintf(stderr, "dev=%s\n",dev); alsa_playback_handle = initAlsa(dev, format, channels, esd_audio_rate, SND_PCM_STREAM_PLAYBACK); if(alsaerr) { if(alsaerr == -1) snd_pcm_close(alsa_playback_handle); if (alsadbg) fprintf(stderr, "Error opening device for playback\n"); esd_audio_fd = -1; free(dev); return alsaerr; } if (alsadbg) fprintf(stderr, "Device open for playback\n"); if ( (esd_audio_format & ESD_MASK_FUNC) == ESD_RECORD ) { alsa_capture_handle = initAlsa(dev, format, channels, esd_audio_rate, SND_PCM_STREAM_CAPTURE); if (alsaerr) { if (alsaerr==-1) snd_pcm_close(alsa_capture_handle); if (alsadbg) fprintf(stderr, "Error opening device for capture\n"); snd_pcm_close(alsa_playback_handle); esd_audio_fd = -1; free(dev); return alsaerr; } if (alsadbg) fprintf(stderr, "Device open for capture\n"); } esd_audio_fd = 0; free(dev); if (alsadbg) print_state(); return 0; } #define ARCH_esd_audio_close void esd_audio_close(void) { if (alsadbg) { fprintf(stderr, "esd_audio_close\n"); print_state(); } if (alsa_playback_handle != NULL) { snd_pcm_hw_free( alsa_playback_handle ); snd_pcm_close( alsa_playback_handle ); } if (alsa_capture_handle != NULL) { snd_pcm_hw_free( alsa_capture_handle ); snd_pcm_close(alsa_capture_handle); } alsa_playback_handle = NULL; alsa_capture_handle = NULL; } #define ARCH_esd_audio_pause void esd_audio_pause(void) { return; } #define ARCH_esd_audio_read int esd_audio_read( void *buffer, int buf_size ) { int err; int len = snd_pcm_bytes_to_frames(alsa_capture_handle, buf_size); while ( ( err = snd_pcm_readi( alsa_capture_handle, buffer, len)) < 0) { if (alsadbg) { fprintf(stderr, "esd_audio_read\n"); print_state(); } if (err == -EPIPE) { if (alsadbg) fprintf(stderr, "EPIPE\n"); if (( err = snd_pcm_prepare( alsa_capture_handle ) )< 0) { if (alsadbg) fprintf(stderr, "%s\n", snd_strerror(err)); return -1; } continue; } else if ( err == -ESTRPIPE) { if (alsadbg) fprintf(stderr, "ESTRPIPE\n"); while (( err = snd_pcm_resume(alsa_capture_handle)) == -EAGAIN) sleep(1); if (err < 0) { if (alsadbg) fprintf(stderr, "Preparing...\n"); if (snd_pcm_prepare( alsa_capture_handle) < 0) return -1; } continue; } err = snd_pcm_prepare(alsa_capture_handle) ; if (err < 0) { if (alsadbg) fprintf(stderr, "%s\n", snd_strerror(err)); return -1; } } return ( snd_pcm_frames_to_bytes(alsa_capture_handle, err) ); } #define ARCH_esd_audio_write int esd_audio_write( void *buffer, int buf_size ) { int err; int len = snd_pcm_bytes_to_frames(alsa_playback_handle, buf_size); while (len > 0) { while ( ( err = snd_pcm_writei( alsa_playback_handle, buffer, len)) < 0) { if (alsadbg) { fprintf(stderr, "esd_audio_write\n"); print_state(); } if (err == -EPIPE) { if (alsadbg) fprintf(stderr, "EPIPE\n"); } else if (err == -ESTRPIPE) { if (alsadbg) fprintf(stderr, "ESTRPIPE\n"); while (( err = snd_pcm_resume(alsa_playback_handle)) == -EAGAIN) sleep(1); } err = snd_pcm_prepare(alsa_playback_handle) ; if (err < 0) { if (alsadbg) fprintf(stderr, "%s\n", snd_strerror(err)); return -1; } continue; } len -= err; buffer += snd_pcm_frames_to_bytes(alsa_playback_handle, err); } return buf_size; } #define ARCH_esd_audio_flush void esd_audio_flush(void) { if (alsadbg) { fprintf(stderr, "esd_audio_flush\n"); print_state(); } if (alsa_playback_handle != NULL) snd_pcm_drain( alsa_playback_handle ); if (alsadbg) print_state(); }