/* Kjetil Matheussen, 2005-2008. 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 #include #include #include #include #include #include #include #include #include #include #include #include "atomic/lflifo.h" #include "atomic/lffifo.h" extern char **environ; #define JC_MAX(a,b) (((a)>(b))?(a):(b)) #define JC_MIN(a,b) (((a)<(b))?(a):(b)) #define OPTARGS_CHECK_GET(wrong,right) lokke==argc-1?(fprintf(stderr,"Must supply argument for '%s'\n",argv[lokke]),exit(-2),wrong):right #define OPTARGS_BEGIN(das_usage) {int lokke;const char *usage=das_usage;for(lokke=1;lokkevalue[0]); for(i=0;i0){ cportnames=realloc(cportnames,(num_cportnames+add_ch)*sizeof(char*)); { int ch=0; while(outportnames[ch]!=NULL){ cportnames[num_cportnames]=outportnames[ch]; ch++; num_cportnames++; } } }else{ fprintf(stderr,"\nWarning, No port(s) with name \"%s\".\n",name); if(cportnames==NULL) if(silent==false) fprintf(stderr,"This could lead to using default ports instead.\n"); } } static char **portnames_get_connections(int ch){ if(ch>=num_cportnames) return NULL; else{ jack_port_t* port=jack_port_by_name(client,cportnames[ch]); char **ret; if(jack_port_flags(port) & JackPortIsInput){ ret=(char**)jack_port_get_all_connections(client,port); }else{ ret=my_calloc(2,sizeof(char*)); ret[0]=cportnames[ch]; } return ret; } } ///////////////////////////////////////////////////////////////////// //////////////////////// console meter ////////////////////////////// ///////////////////////////////////////////////////////////////////// // Note that only the name "vu" is used instead of "console meter". // I know (now) it's not a vu at all. :-) // Function iec_scale picked from meterbridge by Steve Harris. static int iec_scale(float db) { float def = 0.0f; /* Meter deflection %age */ if (db < -70.0f) { def = 0.0f; } else if (db < -60.0f) { def = (db + 70.0f) * 0.25f; } else if (db < -50.0f) { def = (db + 60.0f) * 0.5f + 5.0f; } else if (db < -40.0f) { def = (db + 50.0f) * 0.75f + 7.5; } else if (db < -30.0f) { def = (db + 40.0f) * 1.5f + 15.0f; } else if (db < -20.0f) { def = (db + 30.0f) * 2.0f + 30.0f; } else if (db < 0.0f) { def = (db + 20.0f) * 2.5f + 50.0f; } else { def = 100.0f; } return (int)(def * 2.0f); } static void init_vu(){ //int channels=4; int ch; for(ch=0;ch vu_peaks[ch]) { vu_peaks[ch] = pos; vu_peakvals[ch] = val; vu_times[ch] = 0; } else if (vu_times[ch]++ > 40) { vu_peaks[ch] = pos; vu_peakvals[ch] = val; } if(ch>9){ vol[0]='0'+ch/10; vol[1]='0'+ch-(10*(ch/10)); }else{ vol[0]='0'; vol[1]='0'+ch; } for(i=0;i0.0f) vol[4+i]='*'; else if(i<=pos && val>0.0f) vol[4+i]='-'; else vol[4+i]=' '; if(vu_peakvals[ch]>=1.0f){ vol[4+vu_len]='!'; printf("%c[31m",0x1b); //red color puts(vol); printf("%c[36m",0x1b); // back to cyan }else{ vol[4+vu_len]='|'; puts(vol); } } if(show_bufferusage){ float buflen=num_buffers*jack_buffer_size/jack_samplerate; int num_bufleft=lfsize(&free_buffers); float bufleft=num_bufleft*jack_buffer_size/jack_samplerate; printf("%c[32m",0x1b); // green color printf("Buffer: %.2fs / %.2fs. Disk high priority: [%c]. Overruns: %d\n",bufleft,buflen,disk_thread_has_high_priority?'x':' ',total_overruns); //100*(1+jack_ringbuffer_write_space(rb))/(num_buffers*sizeof(struct ringbuffer_block))); } printf("%c[0m",0x1b); // reset colors } ///////////////////////////////////////////////////////////////////// //////////////////////// Helper thread ////////////////////////////// ///////////////////////////////////////////////////////////////////// char message_string[5000]={0}; static int helper_thread_running=0; static int init_meterbridge_ports(); static void *helper_thread_func(void *arg){ helper_thread_running=1; if(use_vu) init_vu(); if(show_bufferusage) init_show_bufferusage(); while(is_running){ if(use_vu || show_bufferusage) print_console(); if(message_string[0]!=0){ //fprintf(stderr,"%c[0m",0x1b); // reset colors fprintf(stderr,message_string); message_string[0]=0; if(use_vu) init_vu(); if(show_bufferusage) init_show_bufferusage(); } if(init_meterbridge_ports()==1 && use_vu==false && show_bufferusage==false) break; usleep(1000000/20); } message_string[0]=0; helper_thread_running=0; return NULL; } static pthread_mutex_t print_error_mutex = PTHREAD_MUTEX_INITIALIZER; static void print_error(const char *fmt, ...){ pthread_mutex_lock(&print_error_mutex);{ while(1 && helper_thread_running==1 && message_string[0]!=0 ) usleep(20); { va_list argp; va_start(argp,fmt); vsprintf(message_string,fmt,argp); va_end(argp); } if(helper_thread_running==0){ fprintf(stderr,"%c[0m",0x1b); // reset colors fprintf(stderr,message_string); } }pthread_mutex_unlock(&print_error_mutex); } void setup_helper_thread (void){ if(0 || use_vu || show_bufferusage || use_meterbridge) pthread_create(&helper_thread, NULL, helper_thread_func, NULL); } static void stop_helper_thread(void){ if(0 || use_vu || show_bufferusage || use_meterbridge) pthread_join(helper_thread, NULL); } ///////////////////////////////////////////////////////////////////// //////////////////////// DISK /////////////////////////////////////// ///////////////////////////////////////////////////////////////////// // These four variables are used in case we break the 4GB barriere for standard wav files. static int num_files=1; static int64_t disksize=0; static bool is_using_wav=true; static int bytes_per_frame; static SNDFILE *soundfile=NULL; static unsigned long long overruns=0; #include "setformat.c" static int open_soundfile(void){ int subformat; SF_INFO sf_info={0}; if(write_to_stdout==true) return 1; sf_info.samplerate = jack_get_sample_rate (client); sf_info.channels = channels; setformat(&sf_info,soundfile_format); if(sf_info.format==SF_FORMAT_WAV) is_using_wav=true; else is_using_wav=false; bytes_per_frame=bitdepth/8; switch (bitdepth) { case 8: subformat = SF_FORMAT_PCM_U8; break; case 16: subformat = SF_FORMAT_PCM_16; break; case 24: subformat = SF_FORMAT_PCM_24; break; case 32: subformat = SF_FORMAT_PCM_32; break; default: if(!strcasecmp("flac",soundfile_format)){ subformat=SF_FORMAT_PCM_24; bytes_per_frame=3; // wav is still being used in case flac is not available. }else{ subformat = SF_FORMAT_FLOAT; bytes_per_frame=4; } break; } sf_info.format |= subformat; if(filename==NULL) filename=strdup(base_filename); if(write_to_stdout==true) soundfile=sf_open_fd(fileno(stdout),SFM_WRITE,&sf_info,false); else soundfile=sf_open(filename,SFM_WRITE,&sf_info); // debugging lines below. //static int ai=0; //ai++; if(soundfile==NULL){ // || ai==10){ fprintf (stderr, "\nCan not open sndfile \"%s\" for output (%s)\n", filename,sf_strerror(NULL)); return 0; } return 1; } static void close_soundfile(void){ if(soundfile!=NULL && write_to_stdout==false) sf_close (soundfile); if (overruns > 0) { print_error("\njack_capture failed with a total of %llu overruns.\n", overruns); print_error(" try a bigger buffer than -B %f\n",buffer_time); } if (disk_errors > 0) print_error("\nWarning: jack_capture failed with a total of %d disk errors.\n",disk_errors); } // To test filelimit handler, uncomment two next lines. //#undef UINT32_MAX //#define UINT32_MAX 100000+(1024*1024) static int handle_filelimit(size_t frames){ int new_bytes=frames*bytes_per_frame*channels; if(is_using_wav && (disksize + ((int64_t)new_bytes) >= UINT32_MAX-(1024*1024))){ // (1024*1024) should be enough for the header. sf_close(soundfile);{ char *filename_new; filename_new=my_calloc(1,strlen(base_filename)+500); sprintf(filename_new,"%s.%0*d.wav",base_filename,2,num_files); print_error("Warning. 4GB limit on wav file almost reached. Closing %s, and continue writing to %s.\n",filename,filename_new); num_files++; free(filename); filename=filename_new; disksize=0; } if(!open_soundfile()) return 0; } disksize+=new_bytes; return 1; } // stdout_write made by looking at http://mir.dnsalias.com/oss/jackstdout/start // made by Robin Gareus. static int stdout_write(float *buffer,size_t frames){ static char *tobuffer=NULL; static int bufferlen=0; int bytes_to_write=frames*channels*2; if(bufferlen>8)&0xff); } } { int fd=fileno(stdout); while(bytes_to_write > 0){ int written=write(fd,tobuffer,bytes_to_write); if(written==-1){ fprintf(stderr,"Error writing to stdout.\n"); break; } bytes_to_write -= written; } } return 1; } static int disk_write(void *data,size_t frames){ if(write_to_stdout==true) return stdout_write(data,frames); if(soundfile==NULL) return 0; if(!handle_filelimit(frames)) return 0; if(sf_writef_float(soundfile,data,frames) != frames){ print_error("Error. Can not write sndfile (%s)\n", sf_strerror(soundfile) ); disk_errors++; return 0; } return 1; } static int disk_write_overruns(int num_overruns){ int lokke; print_error( "\nWarning. jack_capture failed with %d overrun%s. Some parts of the recording will contain silence.\n" "Try a bigger buffer than -B %f\n%s", num_overruns,num_overruns==1 ? "" : "s", buffer_time, is_running != 0 ? "Continue recording...\n" : "" ); overruns+=num_overruns; for(lokke=0;lokke 0)){ buffer_t *buffer=(buffer_t*)fifoget(&used_buffers); if( buffer->overruns > 0 && ( ! disk_write_overruns(buffer->overruns)) && disk_error_stop) goto done; if( (! disk_write(buffer->data,jack_buffer_size) ) && disk_error_stop) goto done; lfpush(&free_buffers,(lifocell*)buffer); read_space=fifosize(&used_buffers); if( read_space >= (num_buffers/2)) disk_thread_should_boost_priority=true; disk_thread_control_priority(); } if(is_running==0) goto done; if(jack_buffer_size_is_changed_to>0) buffers_init(jack_buffer_size_is_changed_to); /* wait until process() signals more data */ pthread_cond_wait(&data_ready, &disk_thread_lock); // Can't check this here. It's just finished reading. //fprintf(stderr,"printit: %d %d %d\n",read_space,(jack_ringbuffer_write_space(rb)/sizeof(struct ringbuffer_block)),(num_buffers/2)); // Check if priority needs to be boosted. disk_thread_control_priority(); } } done: if(unreported_overruns>0) disk_write_overruns(unreported_overruns); close_soundfile(); { pthread_mutex_unlock(&disk_thread_lock); if(silent==false) fprintf(stderr,"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 PROCESS /////////////////////////////// ///////////////////////////////////////////////////////////////////// void put_jack_buffers_into_buffer(buffer_t *buffer){ sample_t *data=buffer->data; jack_default_audio_sample_t *in[channels]; int ch; int i,pos=0; for(ch=0;chvu_vals[ch]) vu_vals[ch]=val; } #if 0 if(use_vu) for(ch=0;chvu_vals[ch]) vu_vals[ch]=new; } for(i=0;ivu_vals[ch]) vu_vals[ch]=-new; } } #endif } void send_buffer_to_disk_thread(buffer_t *buffer){ buffer->overruns=unreported_overruns; fifoput(&used_buffers,(fifocell*)buffer); unreported_overruns=0; if (pthread_mutex_trylock (&disk_thread_lock) == 0) { pthread_cond_signal (&data_ready); pthread_mutex_unlock (&disk_thread_lock); } } int process (jack_nframes_t nframes, void *arg){ if(is_initialized==0) return 0; if(is_running==0) return 0; { int num_free_buffers=lfsize(&free_buffers); if(num_free_buffers < (num_buffers/2)) disk_thread_should_boost_priority=true; if(( jack_buffer_size_is_changed_to != 0) // The inserted silence will have the wrong size, but at least the user will get a report that something was not recorded. || num_free_buffers==0 ){ unreported_overruns++; total_overruns++; }else{ buffer_t *buffer=(buffer_t*)lfpop(&free_buffers); put_jack_buffers_into_buffer(buffer); send_buffer_to_disk_thread(buffer); } } return 0; } ///////////////////////////////////////////////////////////////////// /////////////////// METERBRIDGE ///////////////////////////////////// ///////////////////////////////////////////////////////////////////// static char* meterbridge_jackname; pid_t meterbridge_pid; pid_t jack_capture_pid; int meterbridge_started=0; static void start_meterbridge(int num_channels){ meterbridge_jackname=my_calloc(1,5000); sprintf(meterbridge_jackname,"%s_meterbridge",jack_get_client_name(client)); //meterbridge -t vu -n meterbri xmms-jack_12250_000:out_0 xmms-jack_12250_000:out_1 jack_capture_pid=getpid(); meterbridge_pid=fork(); if(meterbridge_pid==0){ char *argv[100+num_channels]; argv[0]=WHICH_METERBRIDGE; argv[1]="-t"; argv[2]=meterbridge_type; argv[3]="-n"; argv[4]=meterbridge_jackname; argv[5]="-r"; argv[6]=meterbridge_reference; { int ch; for(ch=0;ch=num_cportnames will ever be used... char name[500]; sprintf(name,"input%d",ch+1); ports[ch]=jack_port_register(client,name,JACK_DEFAULT_AUDIO_TYPE,JackPortIsInput,0); if(ports[ch]==0){ print_error("\nCan not register input port \"%s\"!\n", name); jack_client_close(client); exit(1); } } } } ///////////////////////////////////////////////////////////////////// /////////////////// INIT / WAIT / SHUTDOWN ////////////////////////// ///////////////////////////////////////////////////////////////////// sem_t stop_sem; static void finish(int sig){ sem_post(&stop_sem); } static void jack_shutdown(void *arg){ fprintf(stderr,"jack_capture: JACK shutdown.\n"); jack_has_been_shut_down=true; sem_post(&stop_sem); } static jack_client_t *new_jack_client(char *name){ jack_status_t status; jack_client_t *client=jack_client_open(name,JackNoStartServer,&status,NULL); if(client==NULL){ print_error("jack_client_open() failed, " "status = 0x%2.0x\n", status); exit(1); } return client; } static void start_jack(void){ static bool I_am_already_called=false; if(I_am_already_called) // start_jack is called more than once if the --port argument has been used. return; client=new_jack_client("jack_capture"); jack_buffer_size=jack_get_buffer_size(client); jack_samplerate=jack_get_sample_rate(client); I_am_already_called=true; } static pthread_t waiting_thread={0}; static void* waiting_func(void* arg){ if(recording_time>0.0){ fprintf(stderr,"Recording to \"%s\". The recording is going to last %lf seconds. Press to stop before that.\n", base_filename, recording_time); // Should the recording time be accurate? Or is this good enough? (probably good enough. :-) ) setup_helper_thread(); sleep(recording_time); usleep( (recording_time-floor(recording_time)) * 1000000); }else{ // Wait for or SIGINT fprintf(stderr,"Recording to \"%s\". Press or to stop.\n",base_filename); setup_helper_thread(); char gakk[64]; fgets(gakk,49,stdin); } sem_post(&stop_sem); return NULL; } static void start_waiting_thread(void){ pthread_create(&waiting_thread, NULL, waiting_func, NULL); } int main (int argc, char *argv[]){ fprintf(stderr,"WARNING! Do not use this release!\n"); mainpid=getpid(); // Arguments { OPTARGS_BEGIN("jack_capture [--bitdepth n] [--bufsize seconds] [--channels n] [--port port] [filename]\n" " [ -b n] [ -B seconds] [ -c n] [ -p port]\n" "\n" "\"filename\" is by default auotogenerated to something like \"jack_capture_.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 20 seconds.\n" "\"port\" is by default set to the physical outputs, which means that jack_capture\n" " will be constantly connected to the ports connecting to the\n" " physical outputs. The \"port\" argument can be specified more than once.\n" "\n" "\n" "Additional arguments:\n" "[--duration s] or [-d s] -> Recording is stopped after \"s\" seconds.\n" "[--leading-zeros n] or [-z n] -> \"n\" is the number of zeros to in the autogenerated filename.\n" " (-z 2 -> jack_capture_001.wav, and so on.) (default is 1)\n" "[--format format] or [-f format] -> See http://www.mega-nerd.com/libsndfile/api.html#open\n" " (Default is wav)\n" "[--print-formats] or [-pf] -> Prints all valid sound formats to screen and then exits.\n" "[--version] or [-v] -> Prints out version.\n" "[--silent] or [-s] -> Suppress some common messages printed to the terminal.\n" "[--write-to-stdout] or [-ws] -> Write 16 bit little endian to stdout.\n" "[--disable-meter] or [-dm] -> Disable console meter\n" "[--hide-buffer-usage] or [-hbu] -> Disable buffer usage.\n" "[--disable-console] or [-dc] -> Disable console updates. Same as \"-dm -hbu\".\n" "[--dB-meter] or [-dB] -> Use dB scale for the console meter\n" "[--dB-meter-reference or [-dBr] -> Specify reference level for dB meter. (default=0)\n" "[--meterbridge] or [-mb] -> Start up meterbridge to monitor recorded sound.\n" "[--meterbridge-type] or [-mt] -> Specify type. vu (default), ppm, dpm, jf or sco.\n" "[--meterbridge-reference]/[-mr] -> Specify reference level for meterbidge.\n" "[--filename] or [-fn] -> Specify filename.\n" " (It's usually easier to set last argument instead)\n" "\n" "Examples:\n" "\n" "To record a stereo file of what you hear:\n" " $jack_capture\n" "\n" "To record a stereo file of what you hear in the flac format:\n" " $jack_capture -f flac\n" "\n" "To record a stereo file of what you hear in the mp3 format:\n" " $jack_capture -ws|lame -V2 -r -x - output.mp3\n" "\n" "To record a stereo file of what you hear in the ogg format:\n" " $jack_capture -ws|oggenc -o output.ogg -r -\n" "\n" "To record a stereo file of what you hear (same as the default):\n" " $jack_capture --port system:playback_1 --port system:playback_2\n" "\n" "Same result as above, but using a different syntax:\n" " $jack_capture --channels 2 --port system:playback*\n" "\n" "To record the output from jamin:\n" " $jack_capture --port jamin:out* sound_from_jamin.wav\n" "\n" "To record all sound coming in to jamin:\n" " $jack_capture --port jamin:in* sound_to_jamin.wav\n" "\n" "To record all sound coming in and out of jamin:\n" " $jack_capture --port jamin* sound_to_and_from_jamin.wav\n" "\n" "To record a stereo file from the soundcard:\n" " $jack_capture -c 2 -p system:capture*\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 = OPTARG_GETINT(); OPTARG("--recording-time","-d") recording_time = OPTARG_GETFLOAT(); OPTARG("--port","-p") start_jack() ; portnames_add(OPTARG_GETSTRING()); OPTARG("--format","-f") soundfile_format=OPTARG_GETSTRING(); OPTARG("--version","-v") puts(VERSION);exit(0); OPTARG("--silent","-s") silent=true; OPTARG("--print-formats","-pf") print_all_formats();exit(0); OPTARG("--write-to-stdout","-ws") write_to_stdout=true;use_vu=false;show_bufferusage=false; OPTARG("--disable-meter","-dm") use_vu=false; OPTARG("--hide-buffer-usage","-hbu") show_bufferusage=false; OPTARG("--disable-console","-dc") use_vu=false;show_bufferusage=false; OPTARG("--linear-meter","-lm") vu_dB=false; OPTARG("--dB-meter-reference","-dBr") vu_dB=true;vu_bias=powf(10.0f,OPTARG_GETFLOAT()*-0.05f);//from meterbridge OPTARG("--meterbridge","-mb") use_meterbridge=true; OPTARG("--meterbridge-type","-mt") use_meterbridge=true;meterbridge_type=OPTARG_GETSTRING(); OPTARG("--meterbridge-reference","-mr") use_meterbridge=true;meterbridge_reference=OPTARG_GETSTRING(); OPTARG("--filename","-fn") base_filename=OPTARG_GETSTRING(); OPTARG_LAST() base_filename=OPTARG_GETSTRING(); }OPTARGS_END; } // Find filename { if(base_filename==NULL){ int try=0; base_filename=my_calloc(1,5000); for(;;){ sprintf(base_filename,"jack_capture_%0*d.%s",leading_zeros+1,++try,soundfile_format); if(access(base_filename,F_OK)) break; } } } // Init jack 1 { start_jack(); portnames_add_defaults(); } // Init buffers { buffers_init(jack_buffer_size); } // Open soundfile and start disk thread { if(!open_soundfile()){ jack_client_close(client); return 1; } setup_disk_thread (); } // Init jack 2 { jack_set_process_callback(client, process, NULL); jack_on_shutdown(client, jack_shutdown, NULL); jack_set_graph_order_callback(client,graphordercallback,NULL); jack_set_buffer_size_callback(client,buffersizecallback,NULL); start_connection_thread(); if (jack_activate(client)) { fprintf (stderr,"\nCan not activate client"); } create_ports(); connect_ports(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; wake_up_connection_thread(); // Usually (?) not necessarry, but just in case. } // Start meterbridge { if(use_meterbridge) start_meterbridge(channels); } // Init waiting. { sem_init(&stop_sem,0,0); signal(SIGINT,finish); start_waiting_thread(); } // Wait { sem_wait(&stop_sem); if(silent==false) print_error("Please wait while writing all data to disk. (shouldn't take long)\n"); } //stop recording and clean up. { is_running=0; stop_disk_thread(); stop_connection_thread(); if(jack_has_been_shut_down==false) jack_client_close(client); if(meterbridge_started) kill(meterbridge_pid,SIGINT); stop_helper_thread(); } return 0; }