/* Advanced Linux Sounds Architecture support for EsounD 7-19-98: Nick Lopez( kimo_sabe@usa.net ) - it starts! */ #include "esd.h" #include /* debugging messages for audio device */ static int driver_trace = 0; #if defined(DRIVER_NEWALSA) # include #else # include #endif #if (SND_LIB_MINOR > 4) # define ALSA_5_API #endif static snd_pcm_t *alsa_sound_handle; #ifdef ALSA_5_API static snd_pcm_format_t alsa_format; static snd_pcm_channel_info_t alsa_pinfo; static int alsa_direction = SND_PCM_OPEN_PLAYBACK; static int alsa_mode = SND_PCM_MODE_STREAM; static int alsa_channel = SND_PCM_CHANNEL_PLAYBACK; #endif /* so that EsounD can use other cards besides the first */ #ifndef ALSACARD # define ALSACARD 0 #endif #ifndef ALSADEVICE # define ALSADEVICE 0 #endif /* some identifiers changed names */ #ifndef SND_PCM_OPEN_RECORD #define SND_PCM_OPEN_RECORD SND_PCM_OPEN_CAPTURE #endif #ifndef snd_pcm_record_format #define snd_pcm_record_format snd_pcm_capture_format #endif #define ARCH_esd_audio_open static void alsa_print_error (int code, int card, int device) { if( driver_trace ) { perror( "snd_ctl_open" ); if( device >= 0 ) { fprintf (stderr, "card %d pcm device %d open failed: %s\n", card, device, snd_strerror( code ) ); } else { fprintf( stderr, "card %d open failed: %s\n", card, snd_strerror( code ) ); } } } int esd_audio_open() { int mask, card=ALSACARD, device=ALSADEVICE; int nbr_cards, ret; char buf[256]; struct snd_ctl_hw_info hw_info; #ifdef ALSA_5_API snd_pcm_channel_params_t params; snd_pcm_channel_setup_t setup; #else int alsa_direction = SND_PCM_OPEN_PLAYBACK; snd_pcm_format_t alsa_format; snd_pcm_playback_params_t params; #endif snd_ctl_t *ctl_handle; if ( esd_audio_device ) { card = atoi( esd_audio_device ); } if( driver_trace ) { fprintf( stderr, "Using ALSA %s\n", SND_LIB_VERSION_STR ); } /* if recording, set for full duplex mode */ if ( (esd_audio_format & ESD_MASK_FUNC) == ESD_RECORD ) { alsa_direction = SND_PCM_OPEN_DUPLEX; #ifdef ALSA_5_API /* alsa_channel = SND_PCM_CHANNEL_CAPTURE; */ #endif } #if 0 /* single card code, just in case anyone needs it */ if ( ret = snd_pcm_open( &alsa_sound_handle, ALSACARD, ALSADEVICE, alsa_direction ) < 0) { perror( "snd_pcm_open" ); fprintf( stderr, "open failed: %s\n", snd_strerror( ret ) ); esd_audio_close(); esd_audio_fd = -1; return ( -1 ); } #else /* multiple card code, open the first available. someone check it? */ mask = snd_cards_mask(); if ( !mask ) { fprintf( stderr, "audio_alsa: no cards found!" ); esd_audio_close(); esd_audio_fd = -1; return ( -1 ); } alsa_sound_handle = NULL; #ifdef ALSA_5_API nbr_cards = snd_cards(); #else nbr_cards = SND_CARDS; #endif if( driver_trace ) { fprintf( stderr, "esd: Found %d card(s)\n", nbr_cards ); } for ( ; ( card < nbr_cards ) && (alsa_sound_handle == NULL); card++ ) { if( driver_trace ) { fprintf( stderr, "esd: trying alsa card %d\n", card ); } /* open sound card */ ret = snd_ctl_open( &ctl_handle, card ); if ( ret < 0 ) { alsa_print_error( ret, card, -1 ); continue; } if (driver_trace ) { fprintf( stderr, "opened alsa card %d\n", card ); } /* get info on sound card */ ret = snd_ctl_hw_info( ctl_handle, &hw_info ); if ( ret < 0 ) { alsa_print_error( ret, card, -1 ); continue; } ret = snd_ctl_close( ctl_handle ); if ( ret < 0 ) { alsa_print_error( ret, card, -1 ); continue; } /* search for available pcm device on card */ for ( device=0; (device < hw_info.pcmdevs) && (alsa_sound_handle == NULL); device++ ) { ret = snd_pcm_open( &alsa_sound_handle, card, device, alsa_direction ); if ( ret < 0 ) { alsa_print_error( ret, card, device ); alsa_sound_handle = NULL; continue; } } device--; if ( (alsa_sound_handle != NULL) && driver_trace ) { fprintf( stderr, "opened alsa card %d pcm device %d\n", card, device ); } } card--; if ( alsa_sound_handle == NULL ) { fprintf( stderr, "Couldn't open any alsa card! Last card tried was %d\n" ,card ); fprintf( stderr, "Error opening card %d: %s\n", card, snd_strerror( ret ) ); esd_audio_close(); esd_audio_fd = -1; return ( -1 ); } #endif /* Multiple cards */ #ifdef ALSA_5_API memset(&alsa_pinfo, 0, sizeof(alsa_pinfo)); ret = snd_pcm_channel_info( alsa_sound_handle, &alsa_pinfo ); if ( ret ) { fprintf( stderr, "error: %s: in snd_pcm_channel_info\n", snd_strerror(ret) ); esd_audio_close(); esd_audio_fd = -1; return( -1 ); } memset(¶ms, 0, sizeof(params)); params.buf.stream.queue_size = ESD_BUF_SIZE; params.buf.stream.fill = SND_PCM_FILL_NONE; params.channel = alsa_channel; params.mode = alsa_mode; params.start_mode = SND_PCM_START_FULL; params.stop_mode = SND_PCM_STOP_STOP; memset(&alsa_format, 0, sizeof(alsa_format)); #endif /* set the sound driver audio format for playback */ alsa_format.format = ( (esd_audio_format & ESD_MASK_BITS) == ESD_BITS16 ) ? /* 16 bit */ SND_PCM_SFMT_S16 : /* 8 bit */ SND_PCM_SFMT_U8; alsa_format.rate = esd_audio_rate; #ifdef ALSA_5_API alsa_format.voices = ( ( esd_audio_format & ESD_MASK_CHAN) == ESD_STEREO ) ? 2 : 1; /* Use supported interleave */ if(alsa_pinfo.flags & SND_PCM_CHNINFO_INTERLEAVE ) { alsa_format.interleave = 1; if( driver_trace) fprintf( stderr, "using interleave mode\n"); } if(alsa_pinfo.flags & SND_PCM_CHNINFO_NONINTERLEAVE ) { alsa_format.interleave = 0; if( driver_trace) fprintf( stderr, "esd: using noninterleave mode\n"); } memcpy(¶ms.format, &alsa_format, sizeof(alsa_format)); snd_pcm_channel_flush( alsa_sound_handle, alsa_channel ); ret = snd_pcm_channel_params( alsa_sound_handle, ¶ms ); if ( ret ) { fprintf( stderr, "error: %s: in snd_pcm_channel_params\n", snd_strerror(ret) ); esd_audio_close(); esd_audio_fd = -1; return( -1 ); } ret = snd_pcm_channel_prepare( alsa_sound_handle, alsa_channel ); if ( ret ) { fprintf( stderr, "error: %s: in snd_pcm_channel_prepare\n", snd_strerror(ret) ); esd_audio_close(); esd_audio_fd = -1; return(-1); } memset(&setup, 0, sizeof(setup)); setup.mode = alsa_mode; setup.channel = alsa_channel; ret = snd_pcm_channel_setup( alsa_sound_handle, &setup ); if( ret ) { fprintf( stderr, "error: %s: in snd_pcm_channel_setup\n", snd_strerror(ret) ); esd_audio_close(); esd_audio_fd = -1; return(-1); } if ( params.format.rate != esd_audio_rate || params.format.voices != 2 || params.format.format != SND_PCM_SFMT_S16 ) { fprintf( stderr, "set format didn't work."); esd_audio_close(); esd_audio_fd = -1; return(-1); } #else alsa_format.channels = ( ( esd_audio_format & ESD_MASK_CHAN) == ESD_STEREO ) ? 2 : 1; if( alsa_direction == SND_PCM_OPEN_DUPLEX || alsa_direction == SND_PCM_OPEN_PLAYBACK ) { if ( ( ret = snd_pcm_playback_format( alsa_sound_handle, &alsa_format ) ) < 0 ) { fprintf( stderr, "set playback format failed: %s\n", snd_strerror( ret ) ); esd_audio_close(); esd_audio_fd = -1; return ( -1 ); } } if( alsa_direction == SND_PCM_OPEN_DUPLEX || alsa_direction == SND_PCM_OPEN_RECORD ) { if ( ( ret = snd_pcm_record_format( alsa_sound_handle, &alsa_format ) ) < 0 ) { fprintf( stderr, "set record format failed: %s\n", snd_strerror( ret ) ); esd_audio_close(); esd_audio_fd = -1; return ( -1 ); } } params.fragment_size = frag_size; params.fragments_max = frag_count; params.fragments_room = 1; ret = snd_pcm_playback_params( alsa_sound_handle, ¶ms ); if ( ret ) { printf( "error: %s: in snd_pcm_playback_params\n", snd_strerror(ret) ); } if ( alsa_format.rate != esd_audio_rate || alsa_format.channels != 2 || alsa_format.format != SND_PCM_SFMT_S16 ) fprintf( stderr, "set format didn't work."); #endif /* ALSA_5_API */ /* shouldn't use non-blocking mode, because you have to sit in a loop rewriting data until success (eating cpu time in the process). This wasn't being done, and didn't work on my machine. Or you could use select(man page 2), I guess. */ #if 0 ret = snd_pcm_block_mode( alsa_sound_handle, 1 ); if ( ret ) printf( "error: %s: in snd_pcm_block_mode\n", snd_strerror(ret)); #endif /* no descriptor for ALSAlib */ #ifdef ALSA_5_API return ( esd_audio_fd = snd_pcm_file_descriptor( alsa_sound_handle, alsa_channel ) ); #else return ( esd_audio_fd = snd_pcm_file_descriptor(alsa_sound_handle) ); #endif } #define ARCH_esd_audio_close void esd_audio_close() { snd_pcm_close( alsa_sound_handle ); } #define ARCH_esd_audio_pause void esd_audio_pause() { /* apparently this gets rid of pending data, which isn't the effect we're going for, namely, play the data in the buffers and stop */ /* snd_pcm_drain_playback( handle ); */ } #define ARCH_esd_audio_read int esd_audio_read( void *buffer, int buf_size ) { return (snd_pcm_read( alsa_sound_handle, buffer, buf_size )); } #define ARCH_esd_audio_write int esd_audio_write( void *buffer, int buf_size ) { ssize_t nwrite=0, pos=0; int counter = 0; #ifdef ALSA_5_API snd_pcm_channel_status_t status; int ret; #endif while (counter < 100 && (nwrite = snd_pcm_write( alsa_sound_handle, buffer+pos, buf_size-pos )) != buf_size-pos ) { if ( nwrite > 0 ) { pos += nwrite; if ( nwrite < 32 ) { usleep(100); } } else if ( nwrite < 0 ) { if ( errno == EAGAIN ) { usleep(100); counter++; } else { #ifdef ALSA_5_API status.channel = alsa_channel; ret = snd_pcm_channel_status( alsa_sound_handle, &status ); if( ret ) { if( driver_trace ) fprintf( stderr, "error: %s: in snd_pcm_channel_status\n", snd_strerror(ret) ); return(-1); } if( status.status == SND_PCM_STATUS_UNDERRUN ) { counter++; snd_pcm_channel_flush( alsa_sound_handle, alsa_channel ); snd_pcm_playback_prepare( alsa_sound_handle ); usleep(100); } else { return -1; } #else return -1; #endif /* ALSA_5_API */ } } } return pos; } #define ARCH_esd_audio_flush void esd_audio_flush() { fsync( esd_audio_fd ); /*snd_pcm_flush_playback( handle );*/ }