#if 0 Very Stupid sound Recorder (C) 1999 Serguei Patchkovskii This program needs System V IPC and signal facilities (shared memory and semaphores), POSIX.1b real-time priorities support, OSS sound drivers, and 32-bit or wider integer type. It should be portable otherwise. It is still not fool-proof; however, it is fairly robust, and will keep running under *light* system load. It will die on a system with IDE drives under heavy load; there is nothing which can be done about it (short of using a real mass storage device). If you use an IDE hard drive, it may help to enable interrupts while processing data transfers with: '/sbin/hdparm -u /dev/hda' - or whatever is the device name for your hard drive. Please make sure you read the hdparm manual page before you do this: enabling async interrupts may result in data corruption with some drives and IDE controllers. #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Data structures */ struct shared_control { volatile pid_t recorder_pid, writer_pid[2], ui_pid ; volatile int stop_recording ; volatile int buf_first_unused ; /* Can be adjusted ONLY by the recording thread */ volatile int buf_first_unprocessed ; /* Can be adjusted ONLY by a writing thread */ volatile int buf_wrap_offset ; /* Can be adjusted ONLY by the recording thread */ volatile unsigned long dsp_byte_count ; volatile unsigned long write_byte_count ; volatile int file_count ; volatile int file_generation ; /* Incremented by *2*, and only by the EVEN writer */ } ; enum { sem_data_ready = 0, sem_odd_writer_done, sem_even_writer_done, sem_acquisition_ready, sem_odd_writer_ready, sem_even_writer_ready, sem_buffer_lock, num_semaphores } semaphores ; /* * Global data areas. These are initialized prior to fork()'ing the threads and * are read-only withing the threads. All modifyable shared data should be * colledted in (struct shared_control *) s_ctl */ static int thread_count = 3 ; /* Don't change ! */ static int thread_linger = 2 ; /* seconds */ static int interface_interval = 2 ; /* seconds */ static int buf_size = 4*1024*1024 ; static int sem_id = -1 ; static int writer_id = -1 ; /* -1 is reader */ static char thread_identity[80] = "" ; static char * file_template = "vsr%05.5d.wav" ; static char file_name[1024] = "" ; static int file_handle = -1 ; static unsigned long file_max_data_length = 2352*1000 ; static char * audio_device = "/dev/audio" ; static int audio_handle = -1 ; static int audio_format = AFMT_S16_LE ; static int audio_stereo = 1 ; static int audio_rate = 44100 ; static int audio_io_size = 256 ; static time_t audio_record_timeout = 0 ; /* seconds */ static int verbose = 0 ; static struct shared_control * s_ctl = NULL ; static char * s_data = NULL ; char * ident = "$Id: vsr.c,v 1.12 1999/12/16 03:01:16 ps Exp $" ; /* * Audio service routines */ void init_dsp(void) { int format, stereo, rate, iosize, t_iosize ; if( ( audio_handle = open( audio_device, O_RDONLY, 0 ) ) == -1 ){ fprintf(stderr, "Can't open '%s' for reading: %s\n", audio_device, strerror(errno)) ; exit(EXIT_FAILURE) ; } format = audio_format ; if( ioctl( audio_handle, SNDCTL_DSP_SETFMT, &format ) != 0 ){ fprintf(stderr, "SNDCTL_DSP_SETFMT failed on '%s': %s\n", audio_device, strerror(errno)) ; exit(EXIT_FAILURE) ; } if( format != audio_format ){ fprintf(stderr, "Can't set the required audio format on '%s': requested %d, got %d\n", audio_device, audio_format, format) ; exit(EXIT_FAILURE) ; } stereo = audio_stereo ; if( ioctl( audio_handle, SNDCTL_DSP_STEREO, &stereo ) != 0 ){ fprintf(stderr, "SNDCTL_DSP_STEREO failed on '%s': %s\n", audio_device, strerror(errno)) ; exit(EXIT_FAILURE) ; } if( stereo != audio_stereo ){ fprintf(stderr, "Can't set the number of channels on '%s': requested %d, got %d\n", audio_device, audio_stereo + 1, stereo + 1 ) ; exit(EXIT_FAILURE) ; } rate = audio_rate ; if( ioctl( audio_handle, SNDCTL_DSP_SPEED, &rate ) != 0 ){ fprintf(stderr, "SNDCTL_DSP_SPEED failed on '%s': %s\n", audio_device, strerror(errno)) ; exit(EXIT_FAILURE) ; } if( rate != audio_rate ){ fprintf(stderr, "Can't set sampling rate on '%s': requested %d, got %d\n", audio_device, audio_rate, rate ) ; exit(EXIT_FAILURE) ; } for( iosize = 20 ; iosize > 0 ; iosize-- ){ /* Start with a megabyte frag, and work down */ t_iosize = 0x7fff0000 | iosize ; if( ioctl( audio_handle, SNDCTL_DSP_SETFRAGMENT, &t_iosize ) == 0 ) break ; } if( iosize <= 0 ){ fprintf(stderr, "Fragment size on '%s' dropped to zero?!\n", audio_device ) ; } if( ioctl( audio_handle, SNDCTL_DSP_GETBLKSIZE, &audio_io_size ) != 0 ){ fprintf(stderr, "Can't obtain fragment size for '%s': %s\n", audio_device, strerror(errno)) ; exit(EXIT_FAILURE) ; } if( verbose ){ fprintf(stderr,"Fragment size on '%s' is %d bytes\n", audio_device, audio_io_size ) ; } } /* * System service routines */ void stop_signalled( int sig ) { if( s_ctl != NULL ) s_ctl->stop_recording = 1 ; fprintf(stderr,"%s: Stop request received, and is being processed. \n", thread_identity ) ; } void init_signals( int catch_stop ) { int stop_signals [] = { SIGHUP, SIGINT, SIGQUIT, SIGTERM } ; int ignore_signals[] = { SIGPIPE, SIGALRM, SIGUSR1, SIGUSR2, SIGTSTP, SIGTTIN, SIGTTOU } ; int i, sig ; struct sigaction act ; sigemptyset( &act.sa_mask ) ; act.sa_handler = catch_stop ? stop_signalled : SIG_IGN ; act.sa_flags = 0 ; for( i = 0 ; i < sizeof(stop_signals)/sizeof(int) ; i++ ){ sig = stop_signals[i] ; if( sigaction( sig, &act, NULL ) != 0 ){ fprintf(stderr, "%s: Failed to set signal handler for termination signal %d: %s\n", thread_identity, sig, strerror(errno) ) ; } } sigemptyset( &act.sa_mask ) ; act.sa_handler = SIG_IGN ; act.sa_flags = 0 ; for( i = 0 ; i < sizeof(ignore_signals)/sizeof(int) ; i++ ){ sig = ignore_signals[i] ; if( sigaction( sig, &act, NULL ) != 0 ){ fprintf(stderr, "%s: Failed to set signal handler for ignored signal %d: %s\n", thread_identity, sig, strerror(errno) ) ; } } } void realtime( int priority_decrement ) { struct sched_param prio = { 0 } ; if( priority_decrement >= 0 ){ /* * Lock into memory and switch to the real-time scheduler */ prio.sched_priority = sched_get_priority_max(SCHED_FIFO) - priority_decrement ; if( mlockall(MCL_CURRENT|MCL_FUTURE) != 0 ) fprintf(stderr,"%s: Can't lock memory pages: %s\n", thread_identity, strerror(errno)) ; if( sched_setscheduler( 0, SCHED_FIFO, &prio ) != 0 ) fprintf(stderr,"%s: Can't switch to a real-time scheduler: %s\n", thread_identity, strerror(errno)) ; } seteuid(getuid()); seteuid(getuid()); /* We do it twice so that the saved uid is cleared */ init_signals( priority_decrement < 0 ) ; } void adjust_semaphore( int sem, int increment ) { struct sembuf sop = { 0 } ; sop.sem_num = sem ; sop.sem_op = increment ; if( semop( sem_id, &sop, 1 ) != 0 ){ fprintf(stderr, "%s: adjust_semaphore(%d,%d) failed: %s\n", thread_identity, sem, increment, strerror(errno)) ; exit(EXIT_FAILURE) ; } } void set_semaphore( int sem, int value ) { if( semctl(sem_id,sem,SETVAL,value) == -1 ){ fprintf(stderr, "%s: set_semaphore(%d,%d) failed: %s\n", thread_identity, sem, value, strerror(errno)) ; exit(EXIT_FAILURE) ; } } void raise_semaphore( int sem ) { adjust_semaphore( sem, 1 ) ; } void wait_semaphore( int sem ) { adjust_semaphore( sem, -1 ) ; } void block_until_initialization_completes(void) { struct sembuf sops[3] = { { 0 } } ; sops[0].sem_num = sem_acquisition_ready ; sops[1].sem_num = sem_odd_writer_ready ; sops[2].sem_num = sem_even_writer_ready ; sops[0].sem_op = -1 ; sops[1].sem_op = -1 ; sops[2].sem_op = -1 ; if( semop( sem_id, sops, 3 ) != 0 ){ fprintf(stderr,"%s: Readiness wait failed: %s\n", thread_identity, strerror(errno)) ; exit(EXIT_FAILURE) ; } } void barrier( int my_semaphore ) { if( verbose > 2 ) fprintf(stderr,"%s: approaching barrier on %d\n", thread_identity, my_semaphore ) ; adjust_semaphore(my_semaphore,thread_count) ; block_until_initialization_completes() ; if( verbose > 2 ) fprintf(stderr,"%s: passed barrier on %d\n", thread_identity, my_semaphore ) ; } /* * File handling routines */ int store_tag( char *p, char *tag ) { *p++ = *tag++ ; *p++ = *tag++ ; *p++ = *tag++ ; *p++ = *tag++ ; return 4 ; } int store_u16le( char *p, unsigned value ) { *p++ = (char)( value & 0xff ) ; *p++ = (char)( (value >> 8) & 0xff ) ; return 2 ; } int store_u32le( char *p, unsigned value ) { p += store_u16le( p, value & 0xffff ) ; store_u16le( p, (value >> 16) & 0xffff ) ; return 4 ; } void update_wave_header( int byte_count ) { char wave_header[128] ; char *p = wave_header ; int header_size, io_size ; int riff_chunk_size, fmt_chunk_size ; int audio_byte_rate, audio_alignment ; int audio_bytes_per_sample, audio_bits_per_sample ; if( lseek( file_handle, 0, SEEK_SET ) != 0 ){ fprintf(stderr,"%s: Can't seek '%s' to the origin: %s\n", thread_identity, file_name, strerror(errno) ) ; exit(EXIT_FAILURE) ; } /* * Prepare the header. RIFF format is much richer than this, but * we only want to write out enough data to allow other applications * to handle the file. We are using several magic constants, which * should properly be computed on the fly. C'est la vie. */ fmt_chunk_size = 2 + 2 + 4 + 4 + 2 + 2 ; riff_chunk_size = 4 + 8 + fmt_chunk_size + 8 + ((byte_count+1)&~1) ; switch( audio_format ){ default: fprintf(stderr,"Catastrophe: don't know what the WAVE header for the sound format %d is\n", audio_format ) ; exit(EXIT_FAILURE) ; case AFMT_S16_LE: audio_bits_per_sample = 16 ; break ; case AFMT_U8: audio_bits_per_sample = 8 ; break ; } audio_bytes_per_sample = ( 1 + audio_stereo ) * ( ( audio_bits_per_sample + 7 ) / 8 ) ; audio_byte_rate = audio_bytes_per_sample * audio_rate ; audio_alignment = audio_bytes_per_sample ; /* * Form the header. Yes, this code DOES look ugly. However, it is portable ... */ p += store_tag ( p, "RIFF" ) ; p += store_u32le( p, riff_chunk_size ) ; p += store_tag ( p, "WAVE" ) ; p += store_tag ( p, "fmt " ) ; p += store_u32le( p, fmt_chunk_size ) ; p += store_u16le( p, 1 ) ; /* Format category = PCM */ p += store_u16le( p, audio_stereo + 1 ) ; /* Channels count */ p += store_u32le( p, audio_rate ) ; /* Sampling rate */ p += store_u32le( p, audio_byte_rate ) ; /* Average I/O rate */ p += store_u16le( p, audio_alignment ) ; /* Block alignment */ p += store_u16le( p, audio_bits_per_sample ) ; /* Requird for PCM */ p += store_tag ( p, "data" ) ; p += store_u32le( p, byte_count ) ; header_size = p - wave_header ; if( header_size > sizeof(wave_header) ){ fprintf(stderr,"Catastrophe: run out of space in WAVE file header\n") ; abort() ; } /* * Write the header, and we are all set. */ if( ( io_size = write( file_handle, wave_header, header_size ) ) != header_size ){ fprintf(stderr,"Write of the WAVE header for '%s' failed: Requested %d, wrote %d bytes\n" "Reason = %s\n", file_name, header_size, io_size, strerror(errno) ) ; exit(EXIT_FAILURE) ; } } void create_wave_file( int number ) { if( snprintf(file_name, sizeof(file_name), file_template, number ) == -1 ){ fprintf(stderr,"%s: Out of buffer space processing instance %d of template '%s'\n", thread_identity, number, file_template ) ; exit(EXIT_FAILURE) ; } /* * For some reason (which is totally beyond my humble understanding) OSS/Free drivers * on my Inspiron 3500 *always* log some recording overruns under heavy system load - * unless O_SYNC is set in the open() flags below. Why? Beats me, but it seems to * work. */ file_handle = open(file_name, O_CREAT|O_TRUNC|O_WRONLY|O_SYNC, S_IREAD|S_IWRITE) ; if( file_handle == -1 ){ fprintf(stderr,"%s: Unable to create '%s': %s\n", thread_identity, file_name, strerror(errno)) ; exit(EXIT_FAILURE) ; } update_wave_header( file_max_data_length ) ; } /* * Thread processes */ void recorder_thread(void) { int max_io_size, io_size ; strcpy(thread_identity,"Recorder thread") ; realtime(0) ; /* We are running with user privileges after this point */ init_dsp() ; barrier(sem_acquisition_ready) ; io_size = 0 ; /* For the first buffer position adjustment */ while(1){ /* * Figure out our write location; to avoid getting in all kinds of logic * traps on a multiprocessor, we'll do this inside a critical section. * The logic here is slightly complicated due to the fact that we would * like to avoid splitting DSP read requests at the buffer wrap boundary. */ wait_semaphore(sem_buffer_lock) ; s_ctl->buf_first_unused += io_size ; /* * Note that the maximum I/O size is set to 1 bytes less than it could be * otherwise; this is necessary in order to avoid crashing into the end * of the buffer - we cannot detect this condition, as it is undistinguishable * from a perfectly empty buffer as far as we are concerned. */ if( s_ctl->buf_first_unprocessed > s_ctl->buf_first_unused ){ /* * Tail of the buffer is occupied by unwritted data. */ max_io_size = s_ctl->buf_first_unprocessed - s_ctl->buf_first_unused - 1 ; } else { /* * Tail of the buffer is unoccupied; we may have an opportunity to wrap * around. */ max_io_size = buf_size - s_ctl->buf_first_unused - 1 ; s_ctl->buf_wrap_offset = buf_size ; if( max_io_size < audio_io_size ){ s_ctl->buf_wrap_offset = s_ctl->buf_first_unused ; s_ctl->buf_first_unused = 0 ; max_io_size = s_ctl->buf_first_unprocessed - 1 ; } } /* * If still not enough space, this is a recording overrun. */ if(verbose>2) fprintf(stderr,"Recording: Got buffer at %d, size = %d\n", s_ctl->buf_first_unused, max_io_size ) ; if( max_io_size < audio_io_size ){ fprintf(stderr,"Catastrophe: recording thread run out of buffer space\n") ; exit(EXIT_FAILURE) ; } if( s_ctl->stop_recording ) break ; raise_semaphore(sem_buffer_lock) ; if( max_io_size < audio_io_size ){ fprintf(stderr,"Logic error: lost in the buffer space\n") ; abort() ; } /* * This is not the most obvious place to wake up the writer thread; the more * logical location would be right after the read(). The latter alternative * will however result in excessive contention for the buffer lock, which * we would like to avoid. We use set_semaphore() rather than raise_semaphore() * here because the writer thread is smart enough to coalesce the data. */ set_semaphore(sem_data_ready,1) ; /* * Blocking read; this is likely to take a while. */ io_size = read( audio_handle, s_data + s_ctl->buf_first_unused, audio_io_size ) ; if( io_size < 0 ){ fprintf(stderr,"Catastrophe: DSP read failed: %s\n", strerror(errno)) ; exit(EXIT_FAILURE) ; } /* * It's Ok to adjust the buffer position without entering critical * section: we are only updating a single integer variable; this * should happen atomically. */ s_ctl->dsp_byte_count += io_size ; } /* * When we bail out, buffer lock is still being held by us */ raise_semaphore(sem_buffer_lock) ; if( close(audio_handle) != 0 ){ fprintf(stderr,"Error closing '%s': %s\n", audio_device, strerror(errno)) ; exit(EXIT_FAILURE) ; } /* * A writer thread may still be blocked on data ready semaphore, we'll * have to wake it up even if there were no fresh data. */ set_semaphore(sem_data_ready,1) ; /* * Wait until writer threads finish with their business, and die. */ barrier(sem_acquisition_ready) ; /* * We should not leave immediately, or other threads may fail the * barrier() check. */ sleep(thread_linger) ; exit(EXIT_SUCCESS) ; } void writer_thread( int id ) /* id is 0 for the odd writer, 1 for the even writer */ { int max_io_size, io_size, can_do_more ; unsigned long data_length ; snprintf(thread_identity,sizeof(thread_identity)-1,"Writer thread %d",id) ; writer_id = id ; realtime(1) ; /* Running with user privileges after this point */ /* * File creation may be an expensive operation; we don't want to start * acquiring audio data before it is complete. */ create_wave_file( s_ctl->file_generation + 1 + id ) ; barrier(sem_odd_writer_ready+writer_id) ; if( writer_id == 1 ) wait_semaphore( sem_odd_writer_done ) ; /* We are not yet needed */ /* * Within the main loop, each writer thread dumps audio samples to its * file until file_max_data_length data bytes has been transfered. After * that, it wakes the companion thread, and proceeds to finalize its own * file (closing file may be expensive!). Then, it creates a new file, * and goes to sleep until it must take over the recording duties. */ while(1){ /* * We start recording with can_do_more set to handle very small files * gracefully. Otherwise, if the acquisition block size is larger than * the maximum files size, the buffer will keep growing indefinitly. */ can_do_more = 1 ; data_length = 0 ; while(1){ if( !s_ctl->stop_recording && !can_do_more ) wait_semaphore(sem_data_ready) ; /* * Figure out how much data we have to write; we'll have to get a critical * section to avoid subtle logical errors. In order to avoid buffer overruns, * this should be finished ASAP. */ wait_semaphore(sem_buffer_lock) ; if( s_ctl->buf_first_unprocessed == s_ctl->buf_wrap_offset ) s_ctl->buf_first_unprocessed = 0 ; /* Wrap around */ if( s_ctl->buf_first_unused >= s_ctl->buf_first_unprocessed ){ max_io_size = s_ctl->buf_first_unused - s_ctl->buf_first_unprocessed ; } else { max_io_size = s_ctl->buf_wrap_offset - s_ctl->buf_first_unprocessed ; } raise_semaphore(sem_buffer_lock) ; if(verbose>2) fprintf(stderr,"Dumping: Got buffer at %d, size = %d\n", s_ctl->buf_first_unprocessed, max_io_size ) ; if( max_io_size == 0 && s_ctl->stop_recording ) break ; /* * Make sure we do not exceed the maximum per-file data length, and fire off * the I/O request. */ if( data_length >= file_max_data_length ) break ; if( data_length + max_io_size > file_max_data_length ) max_io_size = (int)( file_max_data_length - data_length ) ; io_size = write( file_handle, s_data + s_ctl->buf_first_unprocessed, max_io_size ) ; if( io_size < 0 ){ fprintf(stderr,"%s: Unable to write %d bytes to '%s': %s\n", thread_identity, max_io_size, file_name, strerror(errno)) ; exit(EXIT_FAILURE) ; } can_do_more = ( io_size < max_io_size ) ; s_ctl->buf_first_unprocessed += io_size ; s_ctl->write_byte_count += io_size ; data_length += io_size ; } /* end of the record loop */ /* * Before we can switch to the next file, we'll have to wake up our partner, * so that she can take over the output stream. Once that is done, it is * safe to spend a few seconds cleaning up the files. */ raise_semaphore( sem_odd_writer_done + writer_id ) ; if(verbose>1) fprintf(stderr,"%s: Done with the file '%s'\n",thread_identity, file_name ) ; if( data_length == 0 ){ unlink(file_name) ; close(file_handle) ; break ; /* We can only come here because recording was stopped */ } update_wave_header( data_length ) ; close( file_handle ) ; s_ctl->file_count++ ; /* * Create a new (empty) file, and go to sleep. If we are in the odd thread, * we'll have to bump up the generation counter as well. */ if( writer_id == 0 ) s_ctl->file_generation += 2 ; create_wave_file( s_ctl->file_generation + 1 + writer_id ) ; wait_semaphore( sem_odd_writer_done + ((writer_id+1)&1) ) ; } /* end of the file loop */ barrier(sem_odd_writer_ready+writer_id) ; /* * Do not leave immediately: other threads will have to consult the semaphore, * and it is removed by exit() ; */ sleep(thread_linger) ; exit(EXIT_SUCCESS) ; } void user_interface_thread(void) { int th_pids[3], th_status[3], th_wait[3] ; char *th_names[3] = { "Recorder", "Odd writer", "Even writer" } ; int i, done, success, code ; char *type ; time_t start_time, current_time, running_time, remaining_time ; strcpy(thread_identity,"User interface") ; realtime(-1) ; /* does not do anything useful; simply drops any extra privileges we might have had */ fprintf(stderr,"Press ^C to stop recording\n") ; th_pids[0] = s_ctl->recorder_pid ; th_pids[1] = s_ctl->writer_pid[0] ; th_pids[2] = s_ctl->writer_pid[1] ; start_time = time(NULL) ; if( start_time == (time_t)-1 ){ fprintf(stderr,"%s: Unable to obtain system time: %s\n", thread_identity, strerror(errno) ) ; exit(EXIT_FAILURE) ; } while(1){ sleep( interface_interval ) ; /* * Check timer for fixed-interval recording */ current_time = time(NULL) ; if( current_time == (time_t)-1 ){ fprintf(stderr,"%s: Unable to obtain system time: %s\n", thread_identity, strerror(errno) ) ; exit(EXIT_FAILURE) ; } running_time = current_time - start_time ; if( audio_record_timeout != 0 && running_time > audio_record_timeout && ! s_ctl->stop_recording ){ fprintf(stderr,"Recording session timed out, initiating shutdown. \n") ; s_ctl->stop_recording = 1 ; } remaining_time = -1 ; if( audio_record_timeout != 0 ){ remaining_time = audio_record_timeout - running_time ; } /* * Update status display */ fprintf( stderr, "%5ld sec (%5ld left), %3d files, %10lu bytes, %10lu written%c", (long)running_time, (long)remaining_time, s_ctl->file_count + 1, s_ctl->dsp_byte_count, s_ctl->write_byte_count, verbose<=1 ? '\r' : '\n' ) ; fflush(stderr) ; /* * Make sure the real-time threads are still around. If they are not, * clean up and bail out. */ done = 0 ; for( i = 0 ; i < 3 ; i++ ){ th_wait[i] = waitpid( th_pids[i], &th_status[i], WNOHANG ) ; if( th_wait[i] == -1 ){ fprintf( stderr, "%s: waitpid(%d) failed: %s\n", thread_identity, th_pids[i], strerror(errno)) ; exit(EXIT_FAILURE) ; } if( th_wait[i] != 0 ) done = 1 ; } if( done ) break ; } if(verbose<=1) fprintf(stderr,"\n") ; /* * If any of the real-time threads died, the remaining threads may deadlock. * Give them a bit of time to clean up things, and then just kill the buggers. */ sleep(interface_interval) ; for( i = 0 ; i < 3 ; i++ ){ kill( th_pids[i], SIGKILL ) ; } /* * Collect any remaining completion codes. The errors check below is not * particularly bright, and should be moved to a subroutine. */ for( i = 0 ; i < 3 ; i++ ){ if( th_wait[i] != 0 ) continue ; th_wait[i] = waitpid( th_pids[i], &th_status[i], 0 ) ; if( th_wait[i] == -1 ){ fprintf( stderr, "%s: waitpid(%d) failed: %s\n", thread_identity, th_pids[i], strerror(errno)) ; exit(EXIT_FAILURE) ; } } success = 0 ; for( i = 0 ; i < 3 ; i++ ){ if( WIFEXITED(th_status[i]) ){ code = WEXITSTATUS(th_status[i]) ; if( code == 0 ){ success++ ; continue ; } type = "terminated with error code" ; } else if( WIFSIGNALED(th_status[i]) ){ code = WTERMSIG(th_status[i]) ; type = "killed by signal" ; } else if( WIFSTOPPED(th_status[i]) ){ code = WSTOPSIG(th_status[i]) ; type = "suspended by signal" ; } else { type = "stopped with unparceable error code" ; code = th_status[i] ; } fprintf(stderr, "%s: %s thread %s %d\n", thread_identity, th_names[i], type, code ) ; } if( verbose > 1 ) fprintf(stderr, "Orderly shutdown\n" ) ; exit( success == 3 ? EXIT_SUCCESS : EXIT_FAILURE ) ; } /* * Initialization routines */ unsigned long suffix_worth( char suffix ) { unsigned long worth ; unsigned long sec = audio_rate * (1+audio_stereo) * ( 1 + (audio_format == AFMT_S16_LE) ) ; switch(suffix){ case 'b': worth = 1 ; break ; case 'K': worth = 1024 ; break ; case 'F': worth = 2352 ; break ; case 'M': worth = 1024*1024 ; break ; case 's': worth = sec ; break ; case 'm': worth = 60*sec ; break ; case 'h': worth = 3600*sec ; break ; default: fprintf(stderr,"Suffix letter '%c' is not recognized; only b, K, F, M, s, m, and h are accepted\n", suffix ) ; exit(EXIT_FAILURE) ; } return worth ; } void print_help_banner(void) { fprintf( stderr, "vsr: Very Simple sound Recorder. (C) 1999 Serguei Patchkovskii\n" "Vsr uses an OSS sound driver to records a sequence of little-endian Microsoft\n" "RIFF files. Vsr takes special care to avoid buffer overruns at high sampling\n" "rates and on active systems. Nonevertheless, on systems with certain brain-\n" "dead hardware (such as IDE disks) it may occationally experience overruns under\n" "heavy system load.\n" "\n" "Vsr comes with ABSOLUTELY NO WARRANTY; This is free software, and you are\n" "welcome to redistribute it under conditions outlined in the GNU General Public\n" "License. \n" "\n" "Usage: vsr [options] file\n" "Options are:\n" " -a audio_device Selects OSS audio input device (Default: '%s')\n" " -c channels Number of channels, either 1 (mono) or 2 (stereo) (%d)\n" " -r sampling_rate Audio sampling rate (%dHz)\n" " -w sample_width Can be either 8 or 16 bit (Default: %d)\n" "\n" " -b buffer_size Circular buffer used for intermediate storage of sound\n" " samples. Should not exceed half of the available system\n" " memory. (%dK)\n" " -f fragment_size Maximum size of an individual sound file (%ldF)\n" " -t timeout Stop recording after obtaing this much data (%lds)\n" " -v Be verbose (-vv be even more verbose)\n" "\n" "-b, -f, and -t values can have one the following suffixes: b (bytes), K (1024 \n" "bytes), F (2352 bytes), M (1024 K), s (seconds), m (minutes), and h (hours). They\n" "can also be given as a product of two values, e.g. 20x64F. These options must\n" "appear AFTER -c, -r, and -w, or they may be processed incorrectly.\n" "\n" "If the file argument contains percent sign, for example 'snd%%05.5d.wav', it \n" "is interpreted as a C format (see 'man 3 sprintf'), which is passed to sprintf\n" "with a single integer parameter (file number). The output of sprintf() is then \n" "used to create the next sound file. If the file argument lacks percent sign,\n" "names of the output files are generated by appending a five-digit file number\n" "to the literal file argument, plus the '.wav' suffix.\n", audio_device, audio_stereo + 1, audio_rate, audio_format == AFMT_S16_LE? 16: 8, buf_size/1024, file_max_data_length/2352, audio_record_timeout ) ; } unsigned long parse_amount( char *str ) { unsigned long n1 = 1, n2 = 1, s = 1 ; char *p ; /* * Make sure the string is in a valid format, and extract all the * components in the progress. */ n1 = atol(str) ; p = str ; do { if( ! isdigit(*p) ) break ; p++ ; while( isdigit(*p) ) p++ ; if( *p == 'x' ){ n2 = atol(++p) ; if( ! isdigit(*p) ) break ; p++ ; while( isdigit(*p) ) p++ ; } if( *p != 0 ){ s = suffix_worth( *p++ ) ; } if( *p != 0 ) break ; /* * At this point, the string must be in the correct format, and with all * components figured out. All we have to do is to multiply them out */ return n1 * n2 * s ; } while(0) ; fprintf(stderr,"Can't parse '%s' as a valid data length specifier\n", str ) ; exit(EXIT_FAILURE) ; } unsigned long parse_time( char *str ) { unsigned long bytes_worth = parse_amount(str) ; unsigned long second_bytes = suffix_worth('s') ; return ( bytes_worth + second_bytes - 1 ) / second_bytes ; } int parse_width( char *str ) { int width = atoi(str) ; int format ; switch(width){ case 16: format = AFMT_S16_LE ; break ; case 8: format = AFMT_U8 ; break ; default: fprintf(stderr,"'%s' is not a valid choice for the sample width; use either 8 or 16\n",str) ; exit(EXIT_FAILURE) ; } return format ; } void parse_arguments( int argc, char *argv[] ) { int opt ; char *p ; if( argc <= 1 ) { print_help_banner() ; exit(EXIT_FAILURE) ; } while( ( opt = getopt( argc, argv, "ha:b:c:f:r:t:vw:" ) ) != -1 ){ switch( opt ){ case 'h': print_help_banner() ; break ; case 'a': audio_device = strdup( optarg ) ; break ; case 'b': buf_size = parse_amount( optarg ) ; break ; case 'c': audio_stereo = atoi( optarg ) - 1 ; break ; case 'f': file_max_data_length = parse_amount( optarg ) ; break ; case 'r': audio_rate = atoi( optarg ) ; break ; case 't': audio_record_timeout = parse_time( optarg ) ; break ; case 'v': verbose++ ; break ; case 'w': audio_format = parse_width( optarg ) ; break ; default: fprintf(stderr,"Catastrophe: logic error in parse_arguments()\n") ; abort() ; } } if( argc - optind != 1 ){ fprintf(stderr,"Can't parse command line: expected 1 position argument, but got %d\n", argc - optind) ; exit(EXIT_FAILURE) ; } if( strchr(argv[optind],'%') != NULL ){ file_template = strdup(argv[optind]) ; } else { p = malloc( strlen(argv[optind]) + strlen("%05.5d.wav") + 1 ) ; if( p == NULL ){ fprintf(stderr,"Out of memory procesing command line\n" ) ; exit(EXIT_FAILURE) ; } strcpy(p,argv[optind]) ; strcat(p,"%05.5d.wav") ; file_template = p ; } } void init_shared_memory(void) { int ctl_id = -1, buf_id = -1 ; do { ctl_id = shmget( IPC_PRIVATE, sizeof(struct shared_control), IPC_CREAT|S_IREAD|S_IWRITE ) ; if( ctl_id == -1 ){ perror( "Creation of the shared control block failed" ) ; break ; } s_ctl = shmat( ctl_id, 0, 0 ) ; if( s_ctl == NULL ){ perror( "Attach of the shared control block failed" ) ; break ; } if( shmctl( ctl_id, SHM_LOCK, NULL ) != 0 ){ perror( "Can't lock the control segment in memory" ) ; } shmctl( ctl_id, IPC_RMID, NULL ) ; /* Don't care if this fails - I am just trying to be nice */ ctl_id = -1 ; buf_id = shmget( IPC_PRIVATE, buf_size, IPC_CREAT|S_IREAD|S_IWRITE ) ; if( buf_id == -1 ){ perror( "Creation of the shared buffer failed" ) ; break ; } s_data = shmat( buf_id, 0, 0 ) ; if( s_data == NULL ){ perror( "Attach of the shared buffer failed" ) ; break ; } if( shmctl( buf_id, SHM_LOCK, NULL ) != 0 ){ perror( "Can't lock the buffer segment in memory" ) ; } shmctl( buf_id, IPC_RMID, NULL ) ; /* Don't care if this fails - I am just trying to be nice */ buf_id = -1 ; /* * At this point, both buffers are mapped in and marked for deletion. Once the * last thread terminates (for whatever reason), the buffers will be automatically * removed. */ memset( s_ctl, 0, sizeof(struct shared_control) ) ; s_ctl->buf_wrap_offset = buf_size ; return ; } while(0) ; if( ctl_id != -1 ) shmctl( ctl_id, IPC_RMID, NULL ) ; if( buf_id != -1 ) shmctl( buf_id, IPC_RMID, NULL ) ; exit(EXIT_FAILURE) ; } void destroy_semaphores(void) /* May be called multiple times from different threads; this is Ok */ { if( sem_id != -1 ) semctl( sem_id, 0, IPC_RMID, 0 ) ; } void init_semaphores(void) { short values[num_semaphores] = { 0 } ; struct semid_ds owner ; if( atexit(destroy_semaphores) != 0 ){ fprintf( stderr, "Failed to register semaphore destructor. You may have to run ipcs -rs manually\n" ) ; } do { sem_id = semget( IPC_PRIVATE, num_semaphores, IPC_CREAT|S_IREAD|S_IWRITE) ; if( sem_id == -1 ){ perror("Can't create semaphore set") ; break ; } values[sem_buffer_lock] = 1 ; if( semctl( sem_id, 0, SETALL, values ) == -1 ){ perror("Can't clear semaphores") ; break ; } owner.sem_perm.uid = getuid() ; owner.sem_perm.gid = getgid() ; owner.sem_perm.mode = S_IREAD|S_IWRITE ; if( semctl( sem_id, 0, IPC_SET, &owner ) == -1 ){ perror("Can't change semaphore ownership to our real uid") ; break ; } return ; } while(0) ; exit(EXIT_FAILURE) ; } void start_threads(void) { int i ; pid_t pid ; for( i = 0 ; i < 2 ; i++ ){ pid = fork() ; if( pid == 0 ){ writer_thread( i ) ; /* Should never return */ exit(EXIT_FAILURE) ; } if( pid == -1 ){ perror("Creation of a writer thread failed") ; exit(EXIT_FAILURE) ; } s_ctl->writer_pid[i] = pid ; } pid = fork() ; if( pid == 0 ){ recorder_thread() ; /* Should never return */ exit(EXIT_FAILURE) ; } if( pid == -1 ){ perror("Creation of the recorder thread failed") ; exit(EXIT_FAILURE) ; } s_ctl->recorder_pid = pid ; s_ctl->ui_pid = getpid() ; user_interface_thread() ; exit(EXIT_FAILURE) ; } int main( int argc, char *argv[] ) { parse_arguments( argc, argv ) ; if( geteuid() != 0 ){ fprintf(stderr, "\n" "vsr was started without super-user privileges.\n" "\n" "Although it will run, it will not be able to lock sound acquision buffers\n" "in memory, or to use real-time execution priorities. This will likely result\n" "in degraded sound quality, and may cause sound acquisition to fail altogether\n" "If possible, please ask your system administrator to execute the following\n" "two commands as super-user (root):\n" "\n" "chown root.sys %s\n" "chmod u+s %s\n" "\n" "vsr has been designed to run as a set-uid program. Special care has been taken\n" "in order to prevent normal users from gaining additional privileges through a\n" "set-uid vsr binary.\n" "\n" "Unless you stop vsr by pressing ^C now, sound acquision will start in twenty\n" "seconds\n" "\n", argv[0], argv[0] ) ; sleep(20) ; } init_shared_memory() ; init_semaphores() ; start_threads() ; /* Not expected to return */ return 0 ; }