Crossfire Client, Trunk  R18925
/home/leaf/crossfire/client/trunk/sound-src/alsa9.c
Go to the documentation of this file.
00001 static char *rcsid_sound_src_alsa9_c =
00002     "$Id: alsa9.c 13995 2010-10-14 04:01:47Z kbulgrien $";
00003 /*
00004     Crossfire client, a client program for the crossfire program.
00005 
00006     Copyright (C) 2001-2005,  Mark Wedel & Crossfire Development Team
00007 
00008     This program is free software; you can redistribute it and/or modify
00009     it under the terms of the GNU General Public License as published by
00010     the Free Software Foundation; either version 2 of the License, or
00011     (at your option) any later version.
00012 
00013     This program is distributed in the hope that it will be useful,
00014     but WITHOUT ANY WARRANTY; without even the implied warranty of
00015     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00016     GNU General Public License for more details.
00017 
00018     You should have received a copy of the GNU General Public License
00019     along with this program; if not, write to the Free Software
00020     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
00021 
00022     The author can be reached via e-mail to crossfire-devel@real-time.com
00023 */
00024 
00032 #define SOUND_DEBUG
00033 
00034 /* Debugs the actual writing of data - this generally isn't that interesting,
00035  * and generates a lot of messages which tends to obscure the more interesting
00036  * ones.
00037  */
00038 /*#define SOUND_DEBUG_WRITES */
00039 
00040 #include <config.h>
00041 
00042 #include <stdio.h>
00043 #include <sys/types.h>
00044 #include <sys/stat.h>
00045 #include <time.h>
00046 
00047 #ifdef HAVE_FCNTL_H
00048 #include <fcntl.h>
00049 #endif
00050 
00051 #ifdef HAVE_UNISTD_H
00052 #include <unistd.h>
00053 #endif
00054 
00055 #include <math.h>
00056 
00057 #ifdef HAVE_SYS_IOCTL_H
00058 #include <sys/ioctl.h>
00059 #endif
00060 
00061 #ifdef HAVE_SYS_SELECT_H
00062 #include <sys/select.h>
00063 #endif
00064 
00065 #ifdef HAVE_STRING_H
00066 #include <string.h>
00067 #endif
00068 
00069 #include <stdlib.h>
00070 #include <ctype.h>
00071 #include <errno.h>
00072 
00073 #include "newclient.h"
00074 #include "client-types.h"
00075 #include "def_sounds.h"
00076 #include "sndproto.h"
00077 #include "common.h"
00078 
00079 #include <alsa/asoundlib.h>
00080 #include <alsa/pcm_plugin.h>
00081 
00082 
00083 #define ALSA_PCM_NEW_HW_PARAMS_API
00084 #define AUDIODEV "default:0,0"
00085 snd_pcm_t *handle = NULL;
00086 int sndbuf_pos=0;
00087 
00088 #define ALSA9_ERROR(str,err) { \
00089                 fprintf(stderr,"ALSA9 Error: %s %s\n",str,snd_strerror(err)); }
00090 
00091 #define SOUND_DECREASE 0.1
00092 
00093 /* mixer variables */
00094 int *sounds_in_buffer=NULL;
00095 int current_buffer=0; /* Next buffer we will write out */
00096 int first_free_buffer=0; /* So we know when to stop playing sounds */
00097 
00098 int soundfd=0;
00099 
00100 sound_settings settings = { 0, 1, 0, 8000, 200, 2048, 4, AUDIODEV };
00101 
00102 snd_pcm_hw_params_t *params;
00103 static snd_pcm_uframes_t chunk_size = 0;
00104 int err = 0;
00105 
00106 void alsa_audio_close(void) { snd_pcm_close(handle); }
00107 
00113 int init_audio(void) {
00114     snd_pcm_sw_params_t *sw_params;
00115     unsigned int format;
00116 
00117     printf("cfsndserv for the ALSA9 sound system\n");
00118     printf("supports sound effects only (no background music).\n");
00119     fflush(stdout);
00120 
00121 #ifdef SOUND_DEBUG
00122     fprintf(stderr, "init_audio: ALSA9\n");
00123     fflush(stderr);
00124 #endif
00125 
00126     /* open the PCM device */
00127     if ((err = snd_pcm_open(&handle,AUDIODEV,SND_PCM_STREAM_PLAYBACK,0)) <0) {
00128         ALSA9_ERROR("init_audio(): ",err);
00129         exit(1);
00130     }
00131 
00132     atexit(alsa_audio_close);
00133 
00134     /* allocate and zero out params */
00135     snd_pcm_hw_params_alloca(&params);
00136 
00137     if ((err = snd_pcm_hw_params_any(handle,params)) <0) {
00138         ALSA9_ERROR("init_audio(): ",err);
00139         exit(1);
00140     }
00141 
00142     /* set the access mode (interleaved) */
00143     if ((err = snd_pcm_hw_params_set_access(handle,params,SND_PCM_ACCESS_RW_INTERLEAVED)) <0) {
00144         ALSA9_ERROR("init_audio(): ",err);
00145         exit(1);
00146     }
00147 
00148     /* set the format */
00149 
00150    if (settings.bit8)
00151        format = settings.sign?SND_PCM_FORMAT_S8:SND_PCM_FORMAT_U8;
00152    else
00153        format = SND_PCM_FORMAT_U16;
00154 
00155 
00156   if ((err = snd_pcm_hw_params_set_format(handle,params,format))<0) {
00157         ALSA9_ERROR("init_audio(): ",err);
00158         exit(1);
00159   }
00160 
00161   /* set the number of channels */
00162   if ((err = snd_pcm_hw_params_set_channels(handle,params,settings.stereo?2:1))<0) {
00163         ALSA9_ERROR("init_audio(): ",err);
00164         exit(1);
00165    }
00166 
00167   stereo = settings.stereo?1:0;
00168 
00169   /* set the rate (our frequency, or closest match) */
00170   unsigned int r = (unsigned int)settings.frequency;
00171   if (r == 0) r = 41100;
00172   int dir = 0;
00173   frequency = snd_pcm_hw_params_set_rate_near(handle,params,&r,&dir);
00174 
00175   /* get sample size */
00176   sample_size = (snd_pcm_format_physical_width(format) * (settings.stereo?2:1)) / 8;
00177   #ifdef SOUND_DEBUG
00178         printf("init_audio(): sample_size = %d\n",sample_size);
00179     fflush(stdout);
00180   #endif
00181 
00182 
00183     /* apply the settings */
00184     if ((err = snd_pcm_hw_params(handle,params))<0) {
00185         ALSA9_ERROR("init_audio(): ",err);
00186         exit(1);
00187     }
00188 
00189     err=snd_pcm_nonblock(handle, 1);
00190     if (err < 0) {
00191         ALSA9_ERROR("nonblock setting error: %s", err);
00192         exit(1);
00193     }
00194 
00195     if ((err = snd_pcm_sw_params_malloc (&sw_params)) < 0) {
00196             fprintf (stderr, "cannot allocate software parameters structure (%s)\n",
00197                                  snd_strerror (err));
00198             exit (1);
00199     }
00200     if ((err = snd_pcm_sw_params_current (handle, sw_params)) < 0) {
00201             fprintf (stderr, "cannot initialize software parameters structure (%s)\n",
00202                                  snd_strerror (err));
00203             exit (1);
00204     }
00205     if ((err = snd_pcm_sw_params_set_avail_min (handle, sw_params, 4096)) < 0) {
00206             fprintf (stderr, "cannot set minimum available count (%s)\n",
00207                                  snd_strerror (err));
00208             exit (1);
00209     }
00210     if ((err = snd_pcm_sw_params_set_start_threshold (handle, sw_params, 0U)) < 0) {
00211             fprintf (stderr, "cannot set start mode (%s)\n",
00212                                  snd_strerror (err));
00213             exit (1);
00214     }
00215     if ((err = snd_pcm_sw_params (handle, sw_params)) < 0) {
00216             fprintf (stderr, "cannot set software parameters (%s)\n",
00217                                  snd_strerror (err));
00218             exit (1);
00219     }
00220 
00221     /* Zerolevel=0x80 seems to work best for both 8 and 16 bit audio */
00222     if (sign) zerolevel=0;
00223     else zerolevel=0x80;
00224 
00225     snd_pcm_hw_params_get_period_size(params, &chunk_size, 0);
00226 
00227     return 0;
00228 }
00229 
00230 void alsa_recover(int e) {
00231         /* Recover from various errors */
00232         if (e == -EAGAIN) {
00233             return;
00234         } else if (e == -EPIPE) {
00235                 err = snd_pcm_prepare(handle);
00236                 if (err < 0) {
00237                         ALSA9_ERROR("alsa_recover(): Unable to recover from underrun. ",err);
00238                         return;
00239                 }
00240         } else if (e == -ESTRPIPE) {
00241                 while ((err = snd_pcm_resume(handle)) == -EAGAIN) sleep(1);
00242                 if (err < 0) {
00243                         err = snd_pcm_prepare(handle);
00244                         if (err < 0) {
00245                                 ALSA9_ERROR("alsa_recover(): Unable to recover from suspend. ",err);
00246                                 return;
00247                         }
00248                 }
00249         } else ALSA9_ERROR("alsa_recover(): ",e);
00250 
00251 }
00252 
00253 
00254 
00268 int audio_play(int buffer, int off) {
00269     int count = (settings.buflen - off) / sample_size;
00270 
00271 #ifdef SOUND_DEBUG
00272     fprintf(stderr, "audio_play: ALSA9\n");
00273     fflush(stderr);
00274 #endif
00275 
00276     if (count > chunk_size) count=chunk_size;
00277 
00278 #ifdef SOUND_DEBUG_WRITES
00279     printf("audio play - writing starting at %d, %d bytes, off=%d\n",
00280           settings.buflen*buffer+off,count, off);
00281     fflush(stdout);
00282 #endif
00283 
00284     err = snd_pcm_writei(handle,buffers+settings.buflen*buffer+off, count);
00285 
00286     if (err < 0) {
00287         alsa_recover(err);
00288         return 0;
00289     } else {
00290         return err * sample_size;
00291     }
00292 }
00293 
00294 /* Plays sound 'soundnum'.  soundtype is 0 for normal sounds, 1 for
00295  * spell_sounds.  This might get extended in the future.  x,y are offset
00296  * (assumed from player) to play sound.  This information is used to
00297  * determine value and left vs right speaker balance.
00298  * This doesn't really play a sound, rather it just addes it to
00299  * the buffer to be played later on.
00300  */
00301 
00302 void play_sound(int soundnum, int soundtype, int x, int y)
00303 {
00304     Sound_Info *si;
00305     int buf,off;
00306     int i;
00307     unsigned left_ratio,right_ratio;
00308     double dist;
00309 
00310 #ifdef SOUND_DEBUG
00311     fprintf(stderr, "play_sound: ALSA9\n");
00312     fflush(stderr);
00313 #endif
00314 
00315     buf=current_buffer;
00316     if (buf>=settings.buffers) buf=1;
00317 
00318     if (buf == 0) buf++;
00319 
00320     /* check if the buffer isn't full */
00321 #ifdef SOUND_DEBUG
00322     fprintf(stderr,"Sounds in buffer %i: %i\n",buf,sounds_in_buffer[buf]);
00323 #endif
00324     if (sounds_in_buffer[buf]>settings.simultaneously) return;
00325 
00326     if (soundnum>=MAX_SOUNDS || soundnum<0) {
00327         fprintf(stderr,"Invalid sound number: %d\n", soundnum);
00328         return;
00329     }
00330 
00331     if (soundtype < SOUND_NORMAL || soundtype == 0) soundtype = SOUND_NORMAL;
00332 
00333     if (soundtype==SOUND_NORMAL) {
00334         si = &normal_sounds[soundnum];
00335     }
00336     else if (soundtype==SOUND_SPELL) {
00337         si = &spell_sounds[soundnum];
00338     }
00339     else {
00340         fprintf(stderr,"Unknown soundtype: %d\n", soundtype);
00341         return;
00342     }
00343 
00344     if (!si->filename) {
00345         fprintf(stderr,"Sound %d (type %d) is not defined\n", soundnum, soundtype);
00346         return;
00347     }
00348 
00349     /*
00350      *   Load the sound if it is not loaded yet.
00351      *
00352      */
00353     if (!si->data){
00354        FILE *f;
00355        struct stat sbuf;
00356 #ifdef SOUND_DEBUG
00357        fprintf(stderr,"Loading file: %s\n",si->filename);
00358 #endif
00359        if (stat(si->filename,&sbuf)){
00360            perror(si->filename);
00361            return;
00362        }
00363        si->size=sbuf.st_size;
00364        if (si->size <=0 ) return;
00365        if (si->size*sample_size > settings.buflen*(settings.buffers-1) ){
00366           fprintf(stderr,"Sound %s too long (%i > %i)\n",si->filename,si->size,
00367                     settings.buflen*(settings.buffers-1)/sample_size);
00368           return;
00369        }
00370        si->data=(unsigned char *)malloc(si->size);
00371        f=fopen(si->filename,"r");
00372        if (!f){
00373            perror(si->filename);
00374            return;
00375        }
00376        fread(si->data,1,si->size,f);
00377        fclose(f);
00378     }
00379 
00380     /* calculate volume multiplers */
00381     dist=sqrt(x*x+y*y);
00382 #ifdef SOUND_DEBUG
00383     fprintf(stderr,"Playing sound %i (%s), volume %i, x,y=%d,%d, dist=%f\n",soundnum,si->symbolic,si->volume,x,y, dist);
00384 #endif
00385     right_ratio=left_ratio=((1<<16)*si->volume)/(100*(1+SOUND_DECREASE*dist));
00386 
00387     if (stereo){
00388         double diff;
00389         if (dist)
00390             diff=(1.0-fabs((double)x/dist));
00391         else
00392             diff=1;
00393 #ifdef SOUND_DEBUG
00394         printf("diff: %f\n",diff);
00395         fflush(stdout);
00396 #endif
00397         if (x<0) right_ratio*=diff;
00398         else left_ratio*=diff;
00399     }
00400 
00401 #ifdef SOUND_DEBUG
00402     fprintf(stderr,"Ratio: %i, %i\n",left_ratio,right_ratio);
00403 #endif
00404 
00405     /* insert the sound to the buffers */
00406     sounds_in_buffer[buf]++;
00407     off=0;
00408     for(i=0;i<si->size;i++){
00409         int dat=si->data[i]-0x80;
00410         if (settings.bit8) {
00411           if (!stereo){
00412              buffers[buf*settings.buflen+off]+=(dat*left_ratio)>>16;
00413           }
00414           else{
00415             buffers[buf*settings.buflen+off]+=(dat*left_ratio)>>16;
00416             buffers[buf*settings.buflen+off+1]+=(dat*right_ratio)>>16;
00417           }
00418         }
00419         else{ /* 16 bit output */
00420           if (!stereo){
00421 #ifdef WORDS_BIGENDIAN
00422              buffers[buf*settings.buflen+off+1]+=((dat*left_ratio)>>8)&0xff;
00423              buffers[buf*settings.buflen+off]+=(dat*left_ratio)>>16;
00424           }
00425           else{
00426             buffers[buf*settings.buflen+off+1]+=((dat*left_ratio)>>8)&0xff;
00427             buffers[buf*settings.buflen+off]+=(dat*left_ratio)>>16;
00428             buffers[buf*settings.buflen+off+3]+=((dat*right_ratio)>>8)&0xff;
00429             buffers[buf*settings.buflen+off+2]+=(dat*right_ratio)>>16;
00430           }
00431 #else
00432              buffers[buf*settings.buflen+off]+=((dat*left_ratio)>>8)&0xff;
00433              buffers[buf*settings.buflen+off+1]+=(dat*left_ratio)>>16;
00434           }
00435           else{
00436             buffers[buf*settings.buflen+off]+=((dat*left_ratio)>>8)&0xff;
00437             buffers[buf*settings.buflen+off+1]+=(dat*left_ratio)>>16;
00438             buffers[buf*settings.buflen+off+2]+=((dat*right_ratio)>>8)&0xff;
00439             buffers[buf*settings.buflen+off+3]+=(dat*right_ratio)>>16;
00440           }
00441 #endif
00442         }
00443 
00444         off+=sample_size;
00445 
00446         if (off>=settings.buflen){
00447           off=0;
00448           buf++;
00449           if (buf>=settings.buffers) {
00450             buf=0;
00451           }
00452         }
00453     }
00454 #ifdef SOUND_DEBUG
00455     fprintf(stderr,"Added %d bytes, last buffer=%d, lastpos=%d\n",
00456             si->size, buf, off);
00457 #endif
00458     /* This write did not wrap the buffers */
00459     if (buf+1 > current_buffer) {
00460         if ((buf+1 > first_free_buffer) && (first_free_buffer >= current_buffer))
00461             first_free_buffer = buf+1;
00462     } else {    /* Buffers did wrap */
00463         if (((buf+1 > first_free_buffer) && (first_free_buffer < current_buffer)) ||
00464             (first_free_buffer >= current_buffer))
00465                 first_free_buffer = buf+1;
00466     }
00467     if (first_free_buffer >= settings.buffers) first_free_buffer=0;
00468 
00469 
00470 }
00471 
00479 void play_music(const char *name) {
00480 
00481 #ifdef SOUND_DEBUG
00482     fprintf(stderr, "play_music: no music support for this sound system.\n");
00483     fflush(stderr);
00484 #endif
00485 
00486     return;
00487 }
00488 
00489 int main(int argc, char *argv[])
00490 {
00491     int infd;
00492     char inbuf[1024];
00493     int inbuf_pos=0, sfd, frames, maxframes;
00494     fd_set inset;
00495     struct timeval timeout;
00496 
00497     printf ("%s\n",rcsid_sound_src_alsa9_c);
00498     fflush(stdout);
00499 
00500     if (read_settings()) write_settings();
00501     if (init_sounds()) return 1;
00502 
00503     infd=fileno(stdin);
00504     FD_ZERO(&inset);
00505     FD_SET(infd,&inset);
00506 
00507     /* need to know max amount of space */
00508     maxframes = snd_pcm_avail_update (handle);
00509 
00510     while(1){
00511         timeout.tv_sec=0;
00512         timeout.tv_usec=10000;
00513         FD_SET(infd,&inset);
00514 
00515         sfd = select(FD_SETSIZE,&inset,NULL,NULL,&timeout);
00516 
00517         /* ALSA9 doesn't provide us with an fd to use to make
00518          * sure we have space for writing.  So instead, we use
00519          * a timeout with select and if there is space, make send
00520          * more data.
00521          */
00522         frames = snd_pcm_avail_update (handle);
00523 /*      fprintf(stderr,"frames=%d\n", frames);*/
00524         while (((settings.buflen+frames)> maxframes) || (frames == -EPIPE)) {
00525 
00526             if (current_buffer != first_free_buffer) {
00527                 int wrote;
00528 
00529                 if (frames == -EPIPE)   snd_pcm_prepare(handle);
00530                 wrote = audio_play(current_buffer, sndbuf_pos);
00531 #ifdef SOUND_DEBUG_WRITES
00532                 printf("play_sound(): wrote %d\n",wrote);
00533                 fflush(stdout);
00534 #endif
00535                 if (wrote < settings.buflen-sndbuf_pos) {
00536                     sndbuf_pos+=wrote;
00537                 }
00538                 else{
00539                     /* clean the buffer */
00540                     memset(buffers+settings.buflen*current_buffer,zerolevel,settings.buflen);
00541                     sounds_in_buffer[current_buffer]=0;
00542                     sndbuf_pos=0;
00543                     current_buffer++;
00544                     if (current_buffer>=settings.buffers) current_buffer=0;
00545                 }
00546             } else
00547                 break;
00548             frames = snd_pcm_avail_update (handle);
00549         }
00550 
00551 
00552         if (sfd > 0) {
00553             if (FD_ISSET(infd,&inset)){
00554                 int err=read(infd,inbuf+inbuf_pos,1);
00555                 if (err<1 && errno!=EINTR){
00556                     if (err<0) perror("read");
00557                     break;
00558                 }
00559                 if (inbuf[inbuf_pos]=='\n'){
00560                     inbuf[inbuf_pos++]=0;
00561                     StdinCmd((char *)inbuf, inbuf_pos);
00562                     inbuf_pos=0;
00563                 }
00564                 else {
00565                     inbuf_pos++;
00566                     if (inbuf_pos>=1024){
00567                         fprintf(stderr,"Input buffer overflow!\n");
00568                         inbuf_pos=0;
00569                     }
00570                 }
00571             }
00572         }
00573     }
00574 
00575     return 0;
00576 }