#include #include #include #include #include #include #include #include #include #include #include #include /* For inet_ntoa */ #include #include #include #include "../include/string.h" #include "../include/strexp.h" #include "../include/disk.h" #include "ymode.h" #include "yhost.h" #include "ynet.h" #include "ymixer.h" #include "rcfile.h" #include "yiff.h" #include "options.h" #include "config.h" int YAntiShift(int in); void YiffSignalHandler(int sig); void YiffResetMixer(void); int YiffInit(int argc, char **argv); void YiffShutdown(void); int YiffCheckNewConnection(int socket); void YiffCloseConnection(YConnectionNumber con_num); void YiffManageConnections(void); int YiffReadNextDSP( PlayStack *ps_ptr, int bytes_per_cycle ); int YiffCreatePlaystack( const char *path, YConnectionNumber owner, YID yid, YDataPosition pos, YVolumeStruct *volume, int sample_rate, int repeats ); void YiffDestroyPlaystack(int n); void YiffManageSound(void); void YiffUpdateTimers(void); void YiffResetTimers(void); #define MIN(a,b) ((a) < (b) ? (a) : (b)) #define MAX(a,b) ((a) > (b) ? (a) : (b)) #define CLIP(a,l,h) (MIN(MAX((a),(l)),(h))) #define ABSOLUTE(x) (((x) < 0) ? ((x) * -1) : (x)) #define STRDUP(s) (((s) != NULL) ? strdup(s) : NULL) #define MINSC(a,b) ((a < b) ? (char)127 : (char)(a)) #define MAXSC(a,b) ((a > b) ? (char)-128 : (char)(a)) PlayStack **playstack; int total_playstacks; SoundPath **soundpath; int total_soundpaths; YConnection **yconnection; int total_yconnections; YMode **ymode; int total_ymodes; YHost **yhost; int total_yhosts; yiff_option_struct option; yiff_stats_struct ystats; yiff_fname_struct fname; int runlevel; YTime cur_time; yiff_next_struct next; Recorder *recorder; int listen_socket; /* * Local timings: */ #define MIN_NEXT_SOUND_DELTA 100 /* In microseconds */ static time_t next_sound_delta_us; /* In microseconds */ /* * Segfault counter. */ static int segfault_count; /* * Anti shift macro: */ int YAntiShift(int in) { int s, i; for(s = 0, i = in; i; i >>= 1) s++; return(s); } /* * Procedure to perform when a SIGHUP signal is recieved. */ void YiffHangupHandle(void) { int i; /* Reload YIFF sound server configuration */ RCLoadFromFile(fname.rcfile); /* Delete all playstacks and notify owner connections about it */ for(i = 0; i < total_playstacks; i++) YiffDestroyPlaystack(i); /* Notify all connections about YSHM sound closing */ YNetDoNotifyAllYSHMSoundClose(); /* Close sound device */ YSoundShutdown(recorder); /* Do not free recorder (other functions may be reffering to it * during threaded execution of this function. */ /* Initialize recorder */ if(YSoundInit(recorder, &option.audio)) { fprintf(stderr, "Error: Unable to initialize recorder.\n\ To try again type: `kill -s HUP %i'\n\ To terminate type: `kill -s INT %i'\n", ystats.pid, ystats.pid ); return; } /* Load mixer settings from file */ if(MixerRCLoadFromFile(fname.mixerrcfile, recorder)) { /* Could not load settings from file, so reset mixer * values to defaults. */ YiffResetMixer(); } /* Update timers (to help sync audio device) */ YiffUpdateTimers(); /* Need to sync audio device right after initialization */ YSoundSync(recorder, 0); /* Reset global next sound delta (use min) */ next_sound_delta_us = MIN_NEXT_SOUND_DELTA; /* Schedual next refresh */ next.refresh.ms = cur_time.ms + option.refresh_int.ms; next.refresh.us = 0; return; } /* * Signal handler. */ void YiffSignalHandler(int sig) { switch(sig) { case SIGHUP: signal(SIGHUP, YiffSignalHandler); YiffHangupHandle(); break; case SIGINT: runlevel = 1; break; case SIGKILL: runlevel = 1; break; case SIGPIPE: signal(SIGPIPE, YiffSignalHandler); break; case SIGSEGV: /* Increment segfault counter */ segfault_count++; if(segfault_count >= 2) exit(-1); runlevel = 1; break; case SIGTERM: runlevel = 1; break; default: break; } return; } /* * Procedure to reset mixer values. */ void YiffResetMixer(void) { Coefficient value[YMixerValues]; if(recorder == NULL) return; value[0] = 0.5; value[1] = 0.5; value[2] = 0.5; value[3] = 0.5; YMixerSet(recorder, YMixerCodeBass, value); value[0] = 0.75; value[1] = 0.75; value[2] = 0.75; value[3] = 0.75; YMixerSet(recorder, YMixerCodeCD, value); YMixerSet(recorder, YMixerCodeGainIn, value); YMixerSet(recorder, YMixerCodeGainOut, value); value[0] = 0.75; value[1] = 0.75; value[2] = 0.75; value[3] = 0.75; YMixerSet(recorder, YMixerCodeLine, value); YMixerSet(recorder, YMixerCodeLine1, value); YMixerSet(recorder, YMixerCodeLine2, value); YMixerSet(recorder, YMixerCodeLine3, value); value[0] = 0.15; value[1] = 0.15; value[2] = 0.15; value[3] = 0.15; YMixerSet(recorder, YMixerCodeMic, value); value[0] = 0.75; value[1] = 0.75; value[2] = 0.75; value[3] = 0.75; YMixerSet(recorder, YMixerCodeMix, value); YMixerSet(recorder, YMixerCodePCM, value); YMixerSet(recorder, YMixerCodePCM2, value); value[0] = 0.15; value[1] = 0.15; value[2] = 0.15; value[3] = 0.15; YMixerSet(recorder, YMixerCodeRec, value); value[0] = 0.75; value[1] = 0.75; value[2] = 0.75; value[3] = 0.75; YMixerSet(recorder, YMixerCodeSpeaker, value); YMixerSet(recorder, YMixerCodeSynth, value); value[0] = 0.5; value[1] = 0.5; value[2] = 0.5; value[3] = 0.5; YMixerSet(recorder, YMixerCodeTreble, value); value[0] = 0.75; value[1] = 0.75; value[2] = 0.75; value[3] = 0.75; YMixerSet(recorder, YMixerCodeVolume, value); } /* * Initializes the YIFF Sound Server. * * Return values: * * 0 Success. * -1 General error. * * -4 Help or version print, do not run. */ int YiffInit(int argc, char **argv) { int i, n, status; char *strptr, *arg_ptr; struct sockaddr_in addr; char cwd[PATH_MAX]; struct stat stat_buf; int override_device = 0; char device[PATH_MAX + NAME_MAX]; int override_mixer = 0; char mixer[PATH_MAX + NAME_MAX]; int override_port = 0; int port = -1; YIPUnion ip; /* First thing to do on initialization is to reset the * global segfault counter. */ segfault_count = 0; /* Handle basic command line arguments first, reset status * to 1. If any basic command line arguments were handled that * would require foreground running, set status to 0. */ for(i = 1, status = 1; i < argc; i++) { arg_ptr = argv[i]; if(arg_ptr == NULL) continue; if(!strcasecmp(arg_ptr, "--foreground") || !strcasecmp(arg_ptr, "--fg") || !strcasecmp(arg_ptr, "-foreground") || !strcasecmp(arg_ptr, "-fg") || strcasepfx(arg_ptr,"--h") || strcasepfx(arg_ptr,"-h") || strcasepfx(arg_ptr,"-?") || strcasepfx(arg_ptr,"/?") || strcasepfx(arg_ptr, "--ver") || strcasepfx(arg_ptr, "-ver") ) { status = 0; break; } } /* If no basic command line arguments were handled and was * not specified to run in foreground then status will be 1. */ if(status) { /* Fork process into background */ switch(fork()) { case -1: perror("fork"); return(-1); break; case 0: /* Child */ break; default: /* Parent */ exit(0); break; } } /* Get current working dir */ getcwd(cwd, PATH_MAX); cwd[PATH_MAX - 1] = '\0'; /* Reset global variables */ /* Options */ option.debug = False; option.port = DEF_PORT; option.audio.cycle_set = CYCLE_SET_USER; option.audio.cycle.ms = 30; option.audio.cycle.us = 0; option.audio.compensated_cycle.ms = option.audio.cycle.ms; option.audio.compensated_cycle.us = option.audio.cycle.us; option.audio.write_ahead.ms = 45; option.audio.write_ahead.us = 0; option.audio.cycle_ahead_left.ms = 0; option.audio.cycle_ahead_left.us = 0; option.audio.cumulative_latency.ms = 0; option.audio.cumulative_latency.us = 0; option.audio.sample_size = DEF_SAMPLE_SIZE; option.audio.channels = DEF_CHANNELS; option.audio.sample_rate = DEF_SAMPLE_RATE; option.audio.bytes_per_second = 0; /* Recalculated later * in YSoundInit(). */ #ifdef OSS_BUFFRAG option.audio.allow_fragments = True; option.audio.num_fragments = 2; option.audio.fragment_size = YAntiShift(512 - 1); #endif /* OSS_BUFFRAG */ option.audio.flip_stereo = False; option.audio.direction = AUDIO_DIRECTION_PLAY; option.refresh_int.ms = 30000; /* 30 seconds */ option.refresh_int.us = 0; option.midi_play_cmd = strdup(DEF_MIDI_CMD); #ifdef ALSA_RUN_CONFORM option.midi_device_number = -1; /* Set to -1 to mark as unset */ #endif /* ALSA_RUN_CONFORM */ /* File names */ strncpy(fname.rcfile, DEF_RCFILE_NAME, PATH_MAX + NAME_MAX); fname.rcfile[PATH_MAX + NAME_MAX - 1] = '\0'; strptr = getenv("HOME"); if(strptr == NULL) strncpy( fname.mixerrcfile, PrefixPaths("/", DEF_RCMIXERFILE_NAME), PATH_MAX + NAME_MAX ); else strncpy( fname.mixerrcfile, PrefixPaths(strptr, DEF_RCMIXERFILE_NAME), PATH_MAX + NAME_MAX ); strncpy(fname.device, DEF_DEVICE, PATH_MAX + NAME_MAX); fname.device[PATH_MAX + NAME_MAX - 1] = '\0'; strncpy(fname.mixer, DEF_MIXER, PATH_MAX + NAME_MAX); fname.mixer[PATH_MAX + NAME_MAX - 1] = '\0'; /* Statistics */ ystats.pid = getpid(); ystats.start_time = time(NULL); ystats.cycle_load = 0.0; /* Delete any existing sound paths (shouldn't be any) */ SoundPathDeleteAll(); /* Allocate current directory as first sound path */ i = SoundPathAllocate(); if(SoundPathIsAllocated(i)) { SoundPath *snd_path_ptr = soundpath[i]; free(snd_path_ptr->path); snd_path_ptr->path = strdup(cwd); } /* Delete any existing yhosts (shouldn't be any) */ YHostDeleteAll(); /* Add localhost (127.0.0.1) to list of allowed hosts to * connect to us. */ ip.charaddr[0] = 127; ip.charaddr[1] = 0; ip.charaddr[2] = 0; ip.charaddr[3] = 1; YHostAllocate(&ip); /* ****************************************************** */ /* Parse arguments */ for(i = 1; i < argc; i++) { arg_ptr = argv[i]; if(arg_ptr == NULL) continue; /* Help */ if(strcasepfx(arg_ptr, "--h") || strcasepfx(arg_ptr, "-h") || strcasepfx(arg_ptr, "-?") || strcasepfx(arg_ptr, "/?") ) { printf(PROG_HELP_MESG); return(-4); } /* Version */ else if(strcasepfx(arg_ptr, "--ver") || strcasepfx(arg_ptr, "-ver") ) { printf("%s Version %s\n", PROG_NAME, PROG_VERSION); printf("%s\n", PROG_COPYRIGHT); return(-4); } /* Device */ else if(strcasepfx(arg_ptr, "--d") || strcasepfx(arg_ptr, "-d") ) { i++; if(i < argc) { arg_ptr = argv[i]; strncpy(device, arg_ptr, PATH_MAX + NAME_MAX); device[PATH_MAX + NAME_MAX - 1] = '\0'; override_device = 1; } else { fprintf(stderr, "%s: Requires argument.\n", argv[i - 1] ); } } #ifdef ALSA_RUN_CONFORM /* MIDI device port number */ else if(strcasepfx(arg_ptr, "--midi_port")) { i++; if(i < argc) { arg_ptr = argv[i]; option.midi_device_number = atoi(arg_ptr); } else { fprintf(stderr, "%s: Requires argument.\n", argv[i - 1] ); } } #endif /* ALSA_RUN_CONFORM */ /* Mixer settings file */ else if(strcasepfx(arg_ptr, "--mixer_rc") || strcasepfx(arg_ptr, "-mixer_rc") ) { i++; if(i < argc) { arg_ptr = argv[i]; strncpy(fname.mixerrcfile, arg_ptr, PATH_MAX + NAME_MAX); fname.mixerrcfile[PATH_MAX + NAME_MAX - 1] = '\0'; } else { fprintf(stderr, "%s: Requires argument.\n", argv[i - 1] ); } } /* Mixer */ else if(strcasepfx(arg_ptr, "--m") || strcasepfx(arg_ptr, "-m") ) { i++; if(i < argc) { arg_ptr = argv[i]; strncpy(mixer, arg_ptr, PATH_MAX + NAME_MAX); mixer[PATH_MAX + NAME_MAX - 1] = '\0'; override_mixer = 1; } else { fprintf(stderr, "%s: Requires argument.\n", argv[i - 1] ); } } /* Port */ else if(strcasepfx(arg_ptr, "--po") || strcasepfx(arg_ptr, "-po") ) { i++; if(i < argc) { arg_ptr = argv[i]; port = atoi(arg_ptr); override_port = 1; } else { fprintf(stderr, "%s: Requires argument.\n", argv[i - 1] ); } } /* Sound path */ else if(strcasepfx(arg_ptr, "--pa") || strcasepfx(arg_ptr, "-pa") ) { i++; if(i < argc) { arg_ptr = argv[i]; n = SoundPathAllocate(); if(SoundPathIsAllocated(n)) { free(soundpath[n]->path); soundpath[n]->path = STRDUP(arg_ptr); if(stat(soundpath[n]->path, &stat_buf)) { fprintf(stderr, "%s: Warning: No such directory.\n", soundpath[n]->path ); } else { if(!S_ISDIR(stat_buf.st_mode)) fprintf(stderr, "%s: Warning: Not a directory.\n", soundpath[n]->path ); } } } else { fprintf(stderr, "%s: Requires argument.\n", argv[i - 1] ); } } /* All else, it's the rcfile */ else if(((*arg_ptr) != '\0') && ((*arg_ptr) != '-') && ((*arg_ptr) != '+') ) { strncpy(fname.rcfile, arg_ptr, PATH_MAX + NAME_MAX); fname.rcfile[PATH_MAX + NAME_MAX - 1] = '\0'; } } /* ****************************************************** */ /* Load configuration */ status = RCLoadFromFile(fname.rcfile); if(status) return(-1); /* Need to set current mode and options values to match * default Audio mode. */ n = 0; /* Default audio mode is the first one */ if(YModeIsAllocated(n)) { /* Set new Audio parameters to option's Audio structure * from Audio mode's values. */ Audio *audio_ptr = &option.audio; YMode *ymode_ptr = ymode[n]; /* Audio modes are always considered user set */ audio_ptr->cycle_set = CYCLE_SET_USER; audio_ptr->cycle.ms = ymode_ptr->cycle.ms; audio_ptr->cycle.us = ymode_ptr->cycle.us; /* This will be recalculated by YSoundInit() called * farther below. */ audio_ptr->compensated_cycle.ms = audio_ptr->cycle.ms; audio_ptr->compensated_cycle.us = audio_ptr->cycle.us; audio_ptr->write_ahead.ms = ymode_ptr->write_ahead.ms; audio_ptr->write_ahead.us = ymode_ptr->write_ahead.us; /* This will be reset by YSoundInit() */ audio_ptr->cycle_ahead_left.ms = 0; audio_ptr->cycle_ahead_left.us = 0; /* This will be reset by YSoundInit() */ audio_ptr->cumulative_latency.ms = 0; audio_ptr->cumulative_latency.us = 0; audio_ptr->sample_size = ymode_ptr->sample_size; audio_ptr->channels = ymode_ptr->channels; audio_ptr->sample_rate = ymode_ptr->sample_rate; /* This will be recalculated by YSoundInit() */ audio_ptr->bytes_per_second = 0; #ifdef OSS_BUFFRAG audio_ptr->allow_fragments = ymode_ptr->allow_fragments; audio_ptr->num_fragments = ymode_ptr->num_fragments; audio_ptr->fragment_size = ymode_ptr->fragment_size; #endif /* OSS_BUFFRAG */ audio_ptr->flip_stereo = ymode_ptr->flip_stereo; audio_ptr->direction = ymode_ptr->direction; } else { fprintf( stderr, "Warning: First Y Audio Mode not defined.\n" ); } /* Override device? */ if(override_device) { strncpy(fname.device, device, PATH_MAX + NAME_MAX); fname.device[PATH_MAX + NAME_MAX - 1] = '\0'; } /* Override mixer? */ if(override_mixer) { strncpy(fname.mixer, mixer, PATH_MAX + NAME_MAX); fname.mixer[PATH_MAX + NAME_MAX - 1] = '\0'; } /* Override port? */ if(override_port) { option.port = port; } /* ****************************************************** */ /* Set signals to watch */ signal(SIGHUP, YiffSignalHandler); signal(SIGINT, YiffSignalHandler); signal(SIGKILL, YiffSignalHandler); signal(SIGPIPE, YiffSignalHandler); signal(SIGSEGV, YiffSignalHandler); signal(SIGTERM, YiffSignalHandler); /* ****************************************************** */ /* Reset timers */ YiffResetTimers(); /* ****************************************************** */ /* Allocate recorder structure and initialize sound */ recorder = (Recorder *)calloc(1, sizeof(Recorder)); if(recorder == NULL) { fprintf(stderr, "Memory allocation error."); return(-1); } /* Initialize sound with the Audio parameters specified in * the option's Audio structure. */ if(YSoundInit(recorder, &option.audio)) return(-1); /* Load mixer settings from file */ if(MixerRCLoadFromFile(fname.mixerrcfile, recorder)) { /* Could not load settings from file, so reset mixer * values to defaults. */ YiffResetMixer(); } /* ****************************************************** */ /* Set up listening socket */ listen_socket = socket(AF_INET, SOCK_STREAM, 0); if(listen_socket < 0) { fprintf( stderr, "Cannot create socket for listening to port %i.\n", option.port ); fprintf( stderr, "Make sure machine is network capable and can create AF_INET sockets.\n" ); return(-1); } addr.sin_family = AF_INET; addr.sin_port = htons(option.port); addr.sin_addr.s_addr = INADDR_ANY; memset(&addr.sin_zero, 0, 8); /* Bind the socket */ status = bind( listen_socket, (struct sockaddr *)&addr, sizeof(struct sockaddr) ); if(status) { fprintf( stderr, "Cannot bind listening socket to port %i.\n", option.port ); fprintf( stderr, "Make sure no other servers are currently using the port.\n" ); return(-1); } status = listen(listen_socket, LISTEN_SOCKET_BACKLOG); if(status) { fprintf( stderr, "Cannot listen to socket on port %i.\n", option.port ); fprintf( stderr, "Make sure no other servers are currently using the port.\n" ); return(-1); } /* Set listening port socket to nonblocking */ fcntl(listen_socket, F_SETFL, O_NONBLOCK); /* ********************************************************** */ /* Update timers (to help sync audio device) */ YiffUpdateTimers(); /* Need to sync audio device right after initialization */ YSoundSync(recorder, 0); /* Reset global next sound delta (use min) */ next_sound_delta_us = MIN_NEXT_SOUND_DELTA; /* schedual next refresh */ next.refresh.ms = cur_time.ms + option.refresh_int.ms; next.refresh.us = 0; return(0); } void YiffShutdown(void) { int i; YConnection *con; /* Save mixer settings */ MixerRCSaveToFile(fname.mixerrcfile, recorder); /* Notify all connections about YSHM sound closing */ YNetDoNotifyAllYSHMSoundClose(); /* Notify all connections about shutdown by sending a * YShutdown event to all connections. */ for(i = 0; i < total_yconnections; i++) { con = yconnection[i]; if((con != NULL) ? (con->socket < 0) : True) continue; YNetSendShutdown(i, 0); } /* Free all sound paths */ SoundPathDeleteAll(); /* Free all play stacks */ PlayStackDeleteAll(); /* Deallocate recorder structure and shutdown sound */ YSoundShutdown(recorder); free(recorder); recorder = NULL; /* Close the listening socket */ if(listen_socket > -1) { shutdown(listen_socket, SHUT_RDWR); close(listen_socket); listen_socket = -1; } /* Disconnect all Y clients and free all Y connection * structures. This will NOT send a YDisconnect event to any of * the connections however the above YShutdown event should have * been recieved by the Y client(s) and they should have handled * it properly. */ ConnectionDeleteAll(); /* Free all yhosts */ YHostDeleteAll(); /* Free all ymodes */ YModeDeleteAll(); /* Begin deallocating options */ /* Free midi play command */ free(option.midi_play_cmd); option.midi_play_cmd = NULL; } /* * Check for and handle any incoming connections. */ int YiffCheckNewConnection(int socket) { int i; YConnection *con_ptr; struct timeval timeout; fd_set readfds; int new_socket; int sin_size; struct sockaddr_in foreign_addr; timeout.tv_sec = 0; timeout.tv_usec = 0; FD_ZERO(&readfds); FD_SET(socket, &readfds); select(socket + 1, &readfds, NULL, NULL, &timeout); if(!FD_ISSET(socket, &readfds)) return(0); /* Handle new connection */ sin_size = sizeof(struct sockaddr_in); new_socket = accept( socket, (struct sockaddr *)&foreign_addr, &sin_size ); if(new_socket == -1) return(-1); /* Set new socket non-blocking */ fcntl(new_socket, F_SETFL, O_NONBLOCK); /* Allocate a new connection structure */ i = ConnectionAllocate(); con_ptr = ConnectionGetPtr(i); if(con_ptr == NULL) { YShutdownSocket(new_socket, SHUT_RDWR, 30l); YCloseSocketWait(new_socket, 30l); new_socket = -1; return(-1); } /* Set up connection */ con_ptr->socket = new_socket; con_ptr->ip.whole = foreign_addr.sin_addr.s_addr; /* printf("Server: Got connection from %i.%i.%i.%i.\n", con_ptr->ip.charaddr[0], con_ptr->ip.charaddr[1], con_ptr->ip.charaddr[2], con_ptr->ip.charaddr[3] ); */ /* Is incoming connection's host allowed? */ if(!YHostInList(&con_ptr->ip)) { #if 0 fprintf( "Y Server: Connection %i.%i.%i.%i denied, not in allowed YHosts list.\n", con_ptr->ip.charaddr[0], con_ptr->ip.charaddr[1], con_ptr->ip.charaddr[2], con_ptr->ip.charaddr[3] ); #endif /* Send disconnect reason */ YNetSendDisconnect(i, 0); /* Close and delete connection */ YShutdownSocket(con_ptr->socket, SHUT_RDWR, 30l); YCloseSocketWait(con_ptr->socket, 30l); con_ptr->socket = -1; ConnectionDelete(i); } return(0); } /* * Closes connection con_num and deallocates its owned * resources. */ void YiffCloseConnection(YConnectionNumber con_num) { int i; YConnection *con; PlayStack *ps; con = ConnectionGetPtr(con_num); if(con == NULL) return; /* Delete all playstacks associated with this connection */ for(i = 0; i < total_playstacks; i++) { ps = playstack[i]; if(ps == NULL) continue; if(ps->owner == con_num) YiffDestroyPlaystack(i); } /* Close connection as needed */ if(con->socket > -1) { YNetSendDisconnect(con_num, 0); YShutdownSocket(con->socket, SHUT_RDWR, 30l); YCloseSocketWait(con->socket, 30l); con->socket = -1; } /* Delete this connection structure and all its allocated * resources. */ ConnectionDelete(con_num); } /* * Check for incoming connections, handle them and * manage each existing connection. */ void YiffManageConnections(void) { int i; YConnection *con; /* Check for and handle any new incoming connections */ YiffCheckNewConnection(listen_socket); /* Handle each connection for incoming data */ for(i = 0; i < total_yconnections; i++) { con = yconnection[i]; if((con != NULL) ? (con->socket < 0) : True) continue; YNetRecv(i); } } /* * Procedure to read next segment of data for the given play * stack. The sound object format must be a SndObjTypeDSP. * * Global recorder is assumed not NULL when this function is called. * * Return values are as follows: * * 0 Success * -1 General error or play stack has ended and needs to be * deleted */ int YiffReadNextDSP( PlayStack *ps_ptr, int bytes_per_cycle /* Actual bytes per cycle */ ) { int status = 0; int bytes_read; int actual_bytes_per_cycle; AFWDataStruct *afw_ptr; Coefficient sample_rate_dev_coeff; if((ps_ptr == NULL) || (bytes_per_cycle < 1) ) return(-1); /* Get pointer to audio file wrapper data structure */ afw_ptr = &ps_ptr->afw_data; /* Skip if play stack volume is 0.0, just load empty * (garbage) data. */ if((ps_ptr->volume_left <= 0.0) && (ps_ptr->volume_right <= 0.0) ) { /* Repeats exceeded? */ if((ps_ptr->total_repeats >= 0) && (ps_ptr->repeats >= ps_ptr->total_repeats) ) return(-1); /* Audio file data length allocation differs from * current Sound buffer length? */ if(afw_ptr->length != bytes_per_cycle) { /* Reallocate audio file wrapper structure data * buffer to match current Sound buffer length. * So it looks like we loaded something (but just garbage). */ afw_ptr->length = bytes_per_cycle; afw_ptr->buffer = (SoundBuffer *)realloc( afw_ptr->buffer, afw_ptr->length * sizeof(SoundBuffer) ); if(afw_ptr->buffer == NULL) { afw_ptr->length = 0; return(-1); } } /* Increment position as if we're still reading */ ps_ptr->position += bytes_per_cycle; if(ps_ptr->position >= ps_ptr->data_length) { ps_ptr->position = ps_ptr->position - ps_ptr->data_length; ps_ptr->repeats++; } /* Return success now, no additional mixing or cpu * resource waste. */ return(0); } /* Record actual length of Sound buffer in bytes */ actual_bytes_per_cycle = bytes_per_cycle; /* Calculate the sample rate deviance adjusted bytes per cycle */ sample_rate_dev_coeff = (Coefficient)ps_ptr->applied_sample_rate / (Coefficient)MAX(recorder->audio.sample_rate, 1); if(sample_rate_dev_coeff < 1.0) sample_rate_dev_coeff = 1.0; bytes_per_cycle = (int)((Coefficient)bytes_per_cycle * sample_rate_dev_coeff ); /* Round up bytes_per_cycle to an even number if it is odd */ if(bytes_per_cycle & 1) bytes_per_cycle++; /* Bytes per cycle cannot be less than actual bytes per cycle */ if(bytes_per_cycle < actual_bytes_per_cycle) bytes_per_cycle = actual_bytes_per_cycle; /* Note: bytes_per_cycle is the sample rate deviance adjusted * bytes per cycle which should be equal or greater than * actual_bytes_per_cycle. bytes_per_cycle should also * indicate the allocated dsp buffer on the playstack. * * actual_bytes_per_cycle is the actual bytes per cycle * of the Sound buffer. */ /* Increase playstack DSP buffer allocation size as needed to fit * sample rate deviance applied bytes per cycle. */ if((ps_ptr->dsp_buf == NULL) || (ps_ptr->dsp_buf_len != bytes_per_cycle) ) { ps_ptr->dsp_buf_len = bytes_per_cycle; ps_ptr->dsp_buf = (SoundBuffer *)realloc( ps_ptr->dsp_buf, ps_ptr->dsp_buf_len * sizeof(SoundBuffer) ); if(ps_ptr->dsp_buf == NULL) { ps_ptr->dsp_buf_len = 0; ps_ptr->dsp_buf_data_len = 0; return(-1); } } /* Is this play stack repeating just once? */ if(ps_ptr->total_repeats == 1) { /* Play stack plays once without any repeats */ /* Gone through entire segment of data (check inclusive)? */ if(ps_ptr->position >= ps_ptr->data_length) return(-1); /* Read next buffer segment */ if( AFWLoadSegment( afw_ptr, ps_ptr->position, bytes_per_cycle, &recorder->audio ) ) return(-1); /* Get number of bytes read by checking the audio file * wrapper structure buffer length. */ bytes_read = MAX(afw_ptr->length, 0); if(bytes_read == 0) { /* No bytes read implies finished play. Set playstack * position to match length of audio data so it will * later be checked and handled as `finished'. */ ps_ptr->position = ps_ptr->data_length; } /* Need to match Sound buffer length with number of * bytes read (which can be less if this is the last * segment read on the audio file). */ bytes_per_cycle = bytes_read; /* Increment play stack position */ ps_ptr->position += bytes_read; /* Copy read buffer segment to playstack DSP buffer */ if((afw_ptr->buffer != NULL) && (bytes_read > 0) ) { /* DSP indicated data length is sample rate deviance * added, need to reduce later. */ ps_ptr->dsp_buf_data_len = MIN(bytes_read, ps_ptr->dsp_buf_len); memcpy( ps_ptr->dsp_buf, afw_ptr->buffer, ps_ptr->dsp_buf_data_len ); } } else { /* Play stack repeats more than once or infinatly */ int total_bytes_read; /* Repeats finite and exceeded? */ if((ps_ptr->total_repeats >= 0) && (ps_ptr->repeats >= ps_ptr->total_repeats) ) return(-1); /* Read next buffer segment, need to read in a loop * so incase current data ends, we loop and keep loading * untill the buffer is completely loaded. */ for(total_bytes_read = 0; total_bytes_read < bytes_per_cycle; ) { /* Gone through entire segment of data (check * inclusive)? */ if(ps_ptr->position >= ps_ptr->data_length) { ps_ptr->position = 0; /* Increment repeats */ ps_ptr->repeats++; /* Repeats exceeded? */ if((ps_ptr->total_repeats >= 0) && (ps_ptr->repeats >= ps_ptr->total_repeats) ) { /* Return success this time, next call * to this function will return -1 when * repeats are exceeded. */ status = 0; break; } } /* Read next segment */ if( AFWLoadSegment( afw_ptr, ps_ptr->position, bytes_per_cycle - total_bytes_read, &recorder->audio ) ) { status = -1; break; } /* Get number of bytes read */ bytes_read = MAX(afw_ptr->length, 0); /* Copy segment of read buffer to tmp buffer */ if((afw_ptr->buffer != NULL) && (bytes_read > 0) ) memcpy( &ps_ptr->dsp_buf[total_bytes_read], afw_ptr->buffer, MIN(bytes_read, bytes_per_cycle - total_bytes_read) ); /* Increment positions */ total_bytes_read += bytes_read; ps_ptr->position += bytes_read; } /* Adjust playstack DSP buffer to indicate the read data * length, remember that the loop may have break'ed * prematurly and that total_bytes_read may be less * than bytes_per_cycle. Note that DSP buffer data length * is sample rate deviance added, will reduce it later. */ ps_ptr->dsp_buf_data_len = MIN(total_bytes_read, ps_ptr->dsp_buf_len); } /* Shrink buffer if due to applied sample rate */ if((bytes_per_cycle > actual_bytes_per_cycle) && (ps_ptr->dsp_buf != NULL) ) { if(recorder->audio.sample_size == 16) YiffShortenBuffer16( (u_int16_t *)ps_ptr->dsp_buf, ps_ptr->dsp_buf_len, /* Buffer len (in u_int8_t) */ actual_bytes_per_cycle /* Shorten len (in u_int8_t) */ ); else YiffShortenBuffer8( (u_int8_t *)ps_ptr->dsp_buf, ps_ptr->dsp_buf_len, /* Buffer len */ actual_bytes_per_cycle /* Shorten len */ ); /* Need to reduce playstack's dsp buffer data length since * it contains sample rate deviance. */ /* printf("Sample rate deviance %ld (%ld %ld) %f\n", ps_ptr->dsp_buf_data_len, ps_ptr->dsp_buf_len, actual_bytes_per_cycle, sample_rate_dev_coeff ); */ ps_ptr->dsp_buf_data_len = (YDataLength)( (Coefficient)ps_ptr->dsp_buf_data_len / sample_rate_dev_coeff ); if(ps_ptr->dsp_buf_data_len > ps_ptr->dsp_buf_len) ps_ptr->dsp_buf_data_len = ps_ptr->dsp_buf_len; } return(status); } /* * Attempts to allocate a new playstack, if successful, * then sets it up to the given values and loads it according * to the file specified in path. * * owner is the connection number that is to own this * playstack. */ int YiffCreatePlaystack( const char *path, YConnectionNumber owner, YID yid, YDataPosition pos, YVolumeStruct *volume, int sample_rate, int repeats ) { int i; PlayStack *ps_ptr, **ptr; int bytes_per_cycle; if(!ConnectionIsConnected(owner)) return(-1); if((recorder == NULL) || (path == NULL) || (yid == YIDNULL) || (volume == NULL) ) return(-1); /* Allocate new playstack */ i = PlayStackAllocate(); ps_ptr = PlayStackGetPtr(i); if(ps_ptr == NULL) { YNetSendSoundObjectPlay( owner, YIDNULL, 0, 0, 0, 0, volume, 0 ); return(-1); } ps_ptr->yid = yid; ps_ptr->owner = owner; ps_ptr->position = pos; ps_ptr->repeats = 0; ps_ptr->total_repeats = ((repeats <= 0) ? -1 : repeats); ps_ptr->data_length = 0; ps_ptr->volume_left = (Coefficient)volume->left / (Coefficient)((u_int16_t)-1); ps_ptr->volume_right = (Coefficient)volume->right / (Coefficient)((u_int16_t)-1); ps_ptr->volume_back_left = (Coefficient)volume->back_left / (Coefficient)((u_int16_t)-1); ps_ptr->volume_back_right = (Coefficient)volume->back_right / (Coefficient)((u_int16_t)-1); ps_ptr->applied_sample_rate = sample_rate; /* Applied sample rate deviance cannot be slower than current * Audio. */ if(ps_ptr->applied_sample_rate < recorder->audio.sample_rate) ps_ptr->applied_sample_rate = recorder->audio.sample_rate; /* Get bytes per cycle */ bytes_per_cycle = recorder->sound.buffer_length; if(bytes_per_cycle <= 0) { fprintf(stderr, "YiffCreatePlaystack(): Warning: bytes_per_cycle = %i.\n", bytes_per_cycle ); } /* Open audio file */ if(AFWOpen(path, &ps_ptr->afw_data)) { /* Unable to open audio file, delete playstack and send * play sound object error. */ PlayStackDelete(i); YNetSendSoundObjectPlay( owner, YIDNULL, 0, 0, 0, 0, volume, 0 ); return(-1); } ps_ptr->data_length = ps_ptr->afw_data.entire_length; ps_ptr->block_align = ps_ptr->afw_data.block_align; ps_ptr->block_length = ps_ptr->afw_data.block_length; /* Check sound object's type */ if(ps_ptr->afw_data.sndobj_format == SndObjTypeDSP) { /* DSP Sound Object */ /* Read next play stack position */ YiffReadNextDSP(ps_ptr, bytes_per_cycle); } else if(ps_ptr->afw_data.sndobj_format == SndObjTypeMIDI) { /* MIDI Sound Object */ /* Check if another playstack is playing this sound */ for(i = 0, ptr = playstack; i < total_playstacks; i++, ptr++ ) { if(*ptr == NULL) continue; if((*ptr)->afw_data.sndobj_format != SndObjTypeMIDI) continue; /* Skip this playstack */ if(*ptr == ps_ptr) continue; /* Let's let the AFW handle this and we respond properly to the AFW */ } /* Begin playing MIDI */ AFWLoadSegment( &ps_ptr->afw_data, 0, /* Position n/a */ 0, /* Length n/a */ &recorder->audio ); } /* Notify connection of successful create */ YNetSendSoundObjectPlay( ps_ptr->owner, ps_ptr->yid, ps_ptr->position, ps_ptr->data_length, ps_ptr->repeats, ps_ptr->total_repeats, volume, ps_ptr->applied_sample_rate ); return(0); } /* * Destroys the playstack n, notifies the owner connection * about it, and closes the reffered sound object on file. */ void YiffDestroyPlaystack(int n) { PlayStack *ps_ptr; /* Check if playstack n is allocated */ if(PlayStackIsAllocated(n)) ps_ptr = playstack[n]; else return; /* Check if playstack owner connection is connected */ if(ConnectionIsConnected(ps_ptr->owner)) { /* Notify owner of this sound object being destroyed */ YNetSendSoundObjectKill(ps_ptr->owner, ps_ptr->yid); } /* Delete this playstack, closing the audio file * and deallocating its resources. */ PlayStackDelete(n); return; } /* * Manage the recorder and the sound related resources. */ void YiffManageSound(void) { if(recorder == NULL) return; if(1) { /* Record or play? */ if(recorder->audio.direction == AUDIO_DIRECTION_RECORD) { /* Record */ } else /* AUDIO_DIRECTION_PLAY */ { /* Play */ int i, status, bytes_per_cycle; AFWDataStruct *afw_ptr; PlayStack *ps_ptr; SoundBuffer *tar_buf, *src_buf, *tmp_ptr; /* Play and reset the buffer, so we play first and * (pre)mix the buffer for the next cycle. This is * more efficient because mixing and then playing takes * up too much time and causes delay on this cycle. */ YSoundPlayBuffer(recorder); bytes_per_cycle = recorder->sound.buffer_length; tar_buf = recorder->sound.buffer; /* Manage each playstack */ for(i = 0; i < total_playstacks; i++) { ps_ptr = playstack[i]; if(ps_ptr == NULL) continue; afw_ptr = &ps_ptr->afw_data; /* Check sound object type */ if(afw_ptr->sndobj_format == SndObjTypeDSP) { /* DSP Sound Object */ /* Mix loaded DSP data on play stack to the * Sound buffer (target buffer). */ src_buf = ps_ptr->dsp_buf; if(src_buf != NULL) { YDataLength len = MIN( bytes_per_cycle, ps_ptr->dsp_buf_data_len ); YiffMixBuffers( &recorder->audio, tar_buf, src_buf, len, ps_ptr->volume_left, ps_ptr->volume_right ); } /* Read next play stack position */ status = YiffReadNextDSP(ps_ptr, bytes_per_cycle); if(status == -1) { YiffDestroyPlaystack(i); continue; } } else if(afw_ptr->sndobj_format == SndObjTypeMIDI) { /* MIDI Sound Object */ /* Call AFWLoadSegment() for MIDI Sound Objects, * this will check if it is still being played. * Returns: * 0 on success * -1 on error * -2 stopped because another MIDI play started * -3 stopped normally. */ status = AFWLoadSegment( afw_ptr, 0, /* Position n/a */ 0, /* Length n/a */ &recorder->audio ); if(status == -1) { /* General error, destroy it */ YiffDestroyPlaystack(i); continue; } else if(status == -2) { /* Another play started, thus this one must stop */ YiffDestroyPlaystack(i); continue; } else if(status == -3) { /* Playback stopped normally, repeat as needed */ ps_ptr->repeats++; /* Repeats exceeded? */ if((ps_ptr->total_repeats >= 0) && (ps_ptr->repeats >= ps_ptr->total_repeats) ) { YiffDestroyPlaystack(i); continue; } /* Begin playing midi object again */ if(AFWLoadSegment( afw_ptr, 0, /* Position n/a */ 0, /* Length n/a */ &recorder->audio )) { /* Definate error in playing MIDI object */ YiffDestroyPlaystack(i); continue; } } else { /* Still playing MIDI object, do nothing */ } } else { /* Unsupported sound object type */ } } /* Shift target buffer from being signed 8 to unsigned 8 */ for(i = 0, tmp_ptr = tar_buf; i < bytes_per_cycle; i++ ) *tmp_ptr++ = (int)(*tmp_ptr) + (int)128; } } } /* * Procedure to update timers. */ void YiffUpdateTimers(void) { YTime new_millitime; time_t audio_cycle_us, cycle_ahead_us; GetCurrentTime(&new_millitime); if(new_millitime.ms < cur_time.ms) { /* Timers need to be reset. Note that cur_time will * be updated in YiffResetTimers(); */ YiffResetTimers(); } else { if(recorder != NULL) { /* Get audio cycle in microseconds */ audio_cycle_us = (recorder->audio.compensated_cycle.ms * 1000) + recorder->audio.compensated_cycle.us; cycle_ahead_us = (recorder->audio.cycle_ahead_left.ms * 1000) + recorder->audio.cycle_ahead_left.us; /* * Calculate next_sound_delta_us, this is the * delta time from now untill the next sound * sound be played. * * audio_device_cycle - (current_time - * time_audio_was_last_played) - cycle_ahead * - 100 us */ next_sound_delta_us = audio_cycle_us - (((new_millitime.ms * 1000) + new_millitime.us) - ((cur_time.ms * 1000) + cur_time.us)) - cycle_ahead_us - 100; /* Sanitize */ if(next_sound_delta_us > audio_cycle_us) next_sound_delta_us = audio_cycle_us; if(next_sound_delta_us < MIN_NEXT_SOUND_DELTA) next_sound_delta_us = MIN_NEXT_SOUND_DELTA; /* Stats: calculate cycle_load */ if(audio_cycle_us != 0) ystats.cycle_load = (double)(audio_cycle_us - next_sound_delta_us) / (double)audio_cycle_us; /* if(cycle_ahead_us > 0) fprintf(stderr, "cycle_ahead_us: %ld us\n", cycle_ahead_us ); */ /* Decrease cycle_ahead_left as needed */ cycle_ahead_us = MAX(cycle_ahead_us - ((recorder->audio.compensated_cycle.ms * 1000) + recorder->audio.compensated_cycle.us), 0 ); recorder->audio.cycle_ahead_left.ms = cycle_ahead_us / 1000; recorder->audio.cycle_ahead_left.us = cycle_ahead_us % 1000; /* fprintf(stderr, "===> %ld (%ld %ld) us\n", next_sound_delta_us, recorder->audio.compensated_cycle.ms, (((new_millitime.ms * 1000) + new_millitime.us) - ((cur_time.ms * 1000) + cur_time.us)) ); */ } else { /* No audio device connected, set a reasonable * value for next_sound_delta_us since it is * used for the main loop usleep() */ next_sound_delta_us = 8000; /* Status: calculate cycle_load */ ystats.cycle_load = 0.00; } /* Update cur_time as usual */ cur_time.ms = new_millitime.ms; cur_time.us = new_millitime.us; } return; } /* * Resets all timers to 0. */ void YiffResetTimers(void) { /* Directly get current time (for syncing audio device) */ GetCurrentTime(&cur_time); /* Reset next schedual timers */ next.refresh.ms = 0; next.refresh.us = 0; /* Sync audio device */ YSoundSync(recorder, 0); /* How long to sleep before handling sound again (use min) */ next_sound_delta_us = MIN_NEXT_SOUND_DELTA; return; } int main(int argc, char *argv[]) { int status; runlevel = 1; /* Initialize everything */ status = YiffInit(argc, argv); switch(status) { case -4: YiffShutdown(); return(0); break; case 0: break; default: YiffShutdown(); return(1); break; } /* Main Y server loop: * * |--+----------+-------------------------------| * A B C D * * At point A the curent time is fetched (GetCurrentTime()) * and then the Sound buffer (already mixed and ready to play) * is played (YiffManageSound()). During the call to * YiffManageSound(); first the Sound buffer is played, then * it is immediatly mixed again and thus ready for the next * play. * * At point B, the YiffManageSound() call has finished and * the call to YiffManageConnections() takes place. This will * handle any client programs connected to us that have sent * data. * * At point C, all the management and maintainance (as needed) * calls are performed. Also at point C the function * YiffUpdateTimers() is called. This function updates all * timing and calculates how long it took to process from * point A to point C, subtracts that amount from the * `cycle' interval and sets the value into variable * next_sound_delta_us. usleep() is then called to sleep * for next_sound_delta_us microseconds. The call to * YiffUpdateTimers() needs to be as fast as possible to get * the most accurate next_sound_delta_us. Process sleeps * untill point D. * * At point D, the execution loops back to point A. */ runlevel = 2; while(runlevel > 1) { /* Get new time just after usleep() */ GetCurrentTime(&cur_time); /* Manage sound */ YiffManageSound(); /* Manage connections */ YiffManageConnections(); /* Time to refresh resources? */ if(next.refresh.ms < cur_time.ms) { /* Refresh resources */ if(recorder != NULL) { /* Sync audio device */ YSoundSync(recorder, 0); } next.refresh.ms = cur_time.ms + option.refresh_int.ms; } /* Update timings */ YiffUpdateTimers(); /* Sleep for a calculated amount of time, this time is * calculated by the above call to YiffUpdateTimers(). */ usleep(next_sound_delta_us); } /* Shutdown everything */ YiffShutdown(); runlevel = 0; return(0); }