/* Kjetil Matheussen, 2005/2006. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include #define JC_MAX(a,b) (((a)>(b))?(a):(b)) #define JC_MIN(a,b) (((a)<(b))?(a):(b)) #define OPTARGS_BEGIN(das_usage) {int lokke;const char *usage=das_usage;for(lokke=1;lokkesize/4,num_buffers); /* When JACK is running realtime, jack_activate() will have * called mlockall() to lock our pages into memory. But, we * still need to touch any newly allocated pages before * process() starts using them. Otherwise, a page fault could * create a delay that would force JACK to shut us down. (from jackrec, -Kjetil)*/ memset(rb->buf,0,rb->size); } { jack_buffer_size=jackbuffersize; jack_buffer_size_is_changed_to=0; } } static sample_t *buffer_get(void){ if(num_free_buffers==0){ return empty_buffer; }else{ sample_t *ret=buffers[next_free_buffer]; num_free_buffers--; next_free_buffer++; if(next_free_buffer==num_buffers) next_free_buffer=0; return ret; } } static void buffer_put(sample_t *buffer){ if(buffer!=empty_buffer) num_free_buffers++; } int buffersizecallback(size_t newsize,void *arg){ jack_buffer_size_is_changed_to=newsize; if(pthread_mutex_trylock(&disk_thread_lock)==0){ pthread_cond_signal(&data_ready); pthread_mutex_unlock(&disk_thread_lock); } return 0; } ///////////////////////////////////////////////////////////////////// //////////////////////// PORTNAMES ////////////////////////////////// ///////////////////////////////////////////////////////////////////// static char **cportnames=NULL; static void portnames_add(char *name){ channels++; cportnames=realloc(cportnames,channels*sizeof(char*)); cportnames[channels]=name; } ///////////////////////////////////////////////////////////////////// //////////////////////// DISK /////////////////////////////////////// ///////////////////////////////////////////////////////////////////// static void *disk_thread_func (void *arg){ SNDFILE *soundfile; // Init soundfile { SF_INFO sf_info={0}; int short_mask; sf_info.samplerate = jack_get_sample_rate (client); sf_info.channels = channels; switch (bitdepth) { case 8: short_mask = SF_FORMAT_PCM_U8; break; case 16: short_mask = SF_FORMAT_PCM_16; break; case 24: short_mask = SF_FORMAT_PCM_24; break; case 32: short_mask = SF_FORMAT_PCM_32; break; default: short_mask = SF_FORMAT_FLOAT; break; } sf_info.format = SF_FORMAT_WAV|short_mask; soundfile=sf_open(filename,SFM_WRITE,&sf_info); if(soundfile==NULL){ char errstr[256]; sf_error_str (0, errstr, sizeof (errstr) - 1); fprintf (stderr, "cannot open sndfile \"%s\" for output (%s)\n", filename, errstr); jack_client_close (client); exit (1); } } // Main disk loop { unsigned long long lastchecked_overruns=0; unsigned long long lastchecked_overruns_serious=0; for(;;){ if(overruns > lastchecked_overruns) { fprintf (stderr, "Warning. jack_capture failed with %ld overruns. Some parts of the recording will contain silence.\n", (long)(overruns-lastchecked_overruns)); fprintf (stderr, " try a bigger buffer than -B %f\n",buffer_time); fprintf(stderr,"Continue recording...\n"); lastchecked_overruns=overruns; } if(overruns_serious > lastchecked_overruns_serious) { fprintf (stderr, "Warning. jack_capture failed seriously regarding buffers. Your soundfile will so far be %llu frames shorter than the recording.\n", overruns_serious*jack_buffer_size); fprintf (stderr, " try a bigger buffer than -B %f\n",buffer_time); fprintf(stderr,"Continue recording...\n"); lastchecked_overruns_serious=overruns_serious; } while(is_initialized && (jack_ringbuffer_read_space (rb) >= sizeof(sample_t*))) { sample_t *buffer=NULL; jack_ringbuffer_read(rb,(void*)&buffer,sizeof(sample_t*)); if(sf_writef_float(soundfile,buffer,buffer_size/channels) != buffer_size/channels) { char errstr[256]; sf_error_str (0, errstr, sizeof (errstr) - 1); fprintf (stderr, "Error. Cannot write sndfile (%s)\n", errstr); goto done; } buffer_put(buffer); } if(jack_buffer_size_is_changed_to>0) buffers_init(jack_buffer_size_is_changed_to); if(is_running==0){ goto done; } /* wait until process() signals more data */ pthread_cond_wait (&data_ready, &disk_thread_lock); } } done: // Close soundfile { sf_close (soundfile); if (overruns+overruns_serious > 0) { fprintf (stderr, "jack_capture failed with a total of %llu overruns.\n", overruns+overruns_serious); fprintf (stderr, " try a bigger buffer than -B %f\n",buffer_time); } } { pthread_mutex_unlock (&disk_thread_lock); printf ("disk thread finished\n"); } return 0; } void setup_disk_thread (void){ pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); pthread_mutex_lock(&disk_thread_lock); pthread_create(&disk_thread, NULL, disk_thread_func, NULL); } void stop_disk_thread(void){ /* Wake up disk thread. (no trylock) (isrunning==0)*/ pthread_cond_signal(&data_ready); pthread_join(disk_thread, NULL); } ///////////////////////////////////////////////////////////////////// //////////////////////// JACK /////////////////////////////////////// ///////////////////////////////////////////////////////////////////// int process (jack_nframes_t nframes, void *arg){ int chn; jack_default_audio_sample_t *in[channels]; if(is_initialized==0 || jack_buffer_size_is_changed_to!=0) return 0; for(chn=0;chn disk buffer { sample_t *buffer=buffer_get(); int i,chn,pos=0; if(buffer==empty_buffer){ overruns++; }else{ for(i=0;i.wav\"\n" "\"Bitdepth\" is by default FLOAT. It can be set to either 8, 16, 24 or 32.\n" "\"Channels\" is by default 2.\n" "\"Bufsize\" is by default 60 seconds.\n" "\"leading-zeros\" is by default 0, and means how many zeros to add before the number in the autogenerated filename. (not\n" " how many zeros to add to the start of the soundfile.)\n" "\n" "Examples:\n" "\n" "To record a stereo file of what you hear:\n" " $jack_capture\n" "\n" "In case you use alsa, this one does the same as the one above:\n" " $jack_capture --port alsa_pcm:playback_1 --port alsa_pcm:playback_2\n" "\n" "This one does exactly the same as the one above:\n" " $jack_capture --channels 2 --port alsa_pcm:playback*\n" "\n" "Recording the output from jamin:\n" " $jack_capture --port jamin:out* sound_from_jamin.wav\n" "\n" "Recording all sound coming in to jamin:\n" " $jack_capture --port jamin:in* sound_to_jamin.wav\n" "\n" "Recording all sound coming in and out of jamin:\n" " $jack_capture --port jamin* sound_to_and_from_jamin.wav\n" "\n" ) { OPTARG("--bitdepth","-b") bitdepth = OPTARG_GETINT(); OPTARG("--bufsize","-B") buffer_time = OPTARG_GETFLOAT(); OPTARG("--channels","-c") channels = OPTARG_GETINT(); OPTARG("--leading-zeros","-z") leading_zeros = JC_MIN(8,OPTARG_GETINT()); OPTARG_LAST() start_jack() ; portnames_add(OPTARG_GETSTRING()); }OPTARGS_ELSE(){ start_jack() ; portnames_add(OPTARG_GETSTRING()); }OPTARGS_END; } // Filename { if(filename==NULL){ int try=0; filename=calloc(1,5000); for(;;){ const char *temp[20]={"jack_capture_%d.wav","jack_capture_%01d.wav","jack_capture_%02d.wav","jack_capture_%03d.wav", "jack_capture_%04d.wav","jack_capture_%05d.wav","jack_capture_%06d.wav","jack_capture_%07d.wav", "jack_capture_%08d.wav","jack_capture_%09d.wav"}; sprintf(filename,temp[leading_zeros+1],++try); if(access(filename,F_OK)) break; } } } // Init jack 1 { start_jack(); } // { buffers_init(jack_buffer_size); } // { setup_disk_thread (); } // Init jack 2 { jack_set_process_callback(client, process, NULL); jack_on_shutdown(client, jack_shutdown, NULL); jack_set_buffer_size_callback(client,buffersizecallback,NULL); if (jack_activate(client)) { fprintf (stderr, "cannot activate client"); } create_ports(); connect_ports(); } // Everything initialized. // (The threads are waiting for this variable, not the other way around, so now it just needs to be set.) { is_initialized=1; } // Wait for keyboard { printf("Recording to \"%s\". Press to stop it.\n",filename); { char gakk[64]; fgets(gakk,49,stdin); } printf("Please wait while writing all data to disk. (shouldn't take long)\n"); } // { do_exit(1); } return 0; }