/** @file simple_client.c * * @brief This simple client demonstrates the basic features of JACK * as they would be used by many applications. */ #include #include #include #include #include #include #include #include #include #include "memops.h" #include "alsa/asoundlib.h" #include // Here are the lists of the jack ports... JSList *capture_ports = NULL; JSList *capture_srcs = NULL; JSList *playback_ports = NULL; JSList *playback_srcs = NULL; jack_client_t *client; snd_pcm_t *alsa_handle; int jack_sample_rate; int jack_buffer_size; int quit = 0; double resample_mean = 1.0; double static_resample_factor = 1.0; double resample_lower_limit = 0.25; double resample_upper_limit = 4.0; double *offset_array; double *window_array; int offset_differential_index = 0; double offset_integral = 0; // ------------------------------------------------------ commandline parameters int sample_rate = 0; /* stream rate */ int num_channels = 2; /* count of channels */ int period_size = 1024; int num_periods = 2; int target_delay = 0; /* the delay which the program should try to approach. */ int max_diff = 0; /* the diff value, when a hard readpointer skip should occur */ int catch_factor = 100000; int catch_factor2 = 10000; double pclamp = 15.0; double controlquant = 10000.0; int smooth_size = 256; int good_window=0; int verbose = 0; int instrument = 0; int samplerate_quality = 2; // Debug stuff: volatile float output_resampling_factor = 1.0; volatile int output_new_delay = 0; volatile float output_offset = 0.0; volatile float output_integral = 0.0; volatile float output_diff = 0.0; snd_pcm_uframes_t real_buffer_size; snd_pcm_uframes_t real_period_size; // buffers char *tmpbuf; char *outbuf; float *resampbuf; // format selection, and corresponding functions from memops in a nice set of structs. typedef struct alsa_format { snd_pcm_format_t format_id; size_t sample_size; void (*jack_to_soundcard) (char *dst, jack_default_audio_sample_t *src, unsigned long nsamples, unsigned long dst_skip, dither_state_t *state); void (*soundcard_to_jack) (jack_default_audio_sample_t *dst, char *src, unsigned long nsamples, unsigned long src_skip); const char *name; } alsa_format_t; alsa_format_t formats[] = { { SND_PCM_FORMAT_FLOAT_LE, 4, sample_move_dS_floatLE, sample_move_floatLE_sSs, "float" }, { SND_PCM_FORMAT_S32, 4, sample_move_d32u24_sS, sample_move_dS_s32u24, "32bit" }, { SND_PCM_FORMAT_S24_3LE, 3, sample_move_d24_sS, sample_move_dS_s24, "24bit - real" }, { SND_PCM_FORMAT_S24, 4, sample_move_d24_sS, sample_move_dS_s24, "24bit" }, { SND_PCM_FORMAT_S16, 2, sample_move_d16_sS, sample_move_dS_s16, "16bit" } }; #define NUMFORMATS (sizeof(formats)/sizeof(formats[0])) int format=0; // Alsa stuff... i dont want to touch this bullshit in the next years.... please... static int xrun_recovery(snd_pcm_t *handle, int err) { // printf( "xrun !!!.... %d\n", err ); if (err == -EPIPE) { /* under-run */ err = snd_pcm_prepare(handle); if (err < 0) printf("Can't recovery from underrun, prepare failed: %s\n", snd_strerror(err)); return 0; } else if (err == -EAGAIN) { while ((err = snd_pcm_resume(handle)) == -EAGAIN) usleep(100); /* wait until the suspend flag is released */ if (err < 0) { err = snd_pcm_prepare(handle); if (err < 0) printf("Can't recovery from suspend, prepare failed: %s\n", snd_strerror(err)); } return 0; } return err; } static int set_hwformat( snd_pcm_t *handle, snd_pcm_hw_params_t *params ) { int i; int err; for( i=0; i (target_delay+max_diff) ) { snd_pcm_rewind( alsa_handle, delay - target_delay ); output_new_delay = (int) delay; delay = target_delay; // Set the resample_rate... we need to adjust the offset integral, to do this. // first look at the PI controller, this code is just a special case, which should never execute once // everything is swung in. offset_integral = - (resample_mean - static_resample_factor) * catch_factor * catch_factor2; // Also clear the array. we are beginning a new control cycle. for( i=0; i 0) { snd_pcm_uframes_t to_write = ((target_delay-delay) > 512) ? 512 : (target_delay-delay); snd_pcm_writei( alsa_handle, tmpbuf, to_write ); delay += to_write; } delay = target_delay; // Set the resample_rate... we need to adjust the offset integral, to do this. offset_integral = - (resample_mean - static_resample_factor) * catch_factor * catch_factor2; // Also clear the array. we are beginning a new control cycle. for( i=0; i resample_upper_limit ) current_resample_factor = resample_upper_limit; // Now Calculate how many samples we need. rlen = ceil( ((double)nframes) * current_resample_factor )+2; assert( rlen > 2 ); // Calculate resample_mean so we can init ourselves to saner values. resample_mean = 0.9999 * resample_mean + 0.0001 * current_resample_factor; /* * now this should do it... */ outbuf = alloca( rlen * formats[format].sample_size * num_channels ); resampbuf = alloca( rlen * sizeof( float ) ); /* * render jack ports to the outbuf... */ int chn = 0; JSList *node = playback_ports; JSList *src_node = playback_srcs; SRC_DATA src; while ( node != NULL) { jack_port_t *port = (jack_port_t *) node->data; float *buf = jack_port_get_buffer (port, nframes); SRC_STATE *src_state = src_node->data; src.data_in = buf; src.input_frames = nframes; src.data_out = resampbuf; src.output_frames = rlen; src.end_of_input = 0; src.src_ratio = current_resample_factor; src_process( src_state, &src ); formats[format].jack_to_soundcard( outbuf + format[formats].sample_size * chn, resampbuf, src.output_frames_gen, num_channels*format[formats].sample_size, NULL); src_node = jack_slist_next (src_node); node = jack_slist_next (node); chn++; } // now write the output... again: err = snd_pcm_writei(alsa_handle, outbuf, src.output_frames_gen); //err = snd_pcm_writei(alsa_handle, outbuf, src.output_frames_gen); if( err < 0 ) { printf( "err = %d\n", err ); if (xrun_recovery(alsa_handle, err) < 0) { printf("Write error: %s\n", snd_strerror(err)); exit(EXIT_FAILURE); } goto again; } return 0; } /** * the latency callback. * sets up the latencies on the ports. */ void latency_cb (jack_latency_callback_mode_t mode, void *arg) { jack_latency_range_t range; JSList *node; range.min = range.max = target_delay; if (mode == JackCaptureLatency) { for (node = capture_ports; node; node = jack_slist_next (node)) { jack_port_t *port = node->data; jack_port_set_latency_range (port, mode, &range); } } else { for (node = playback_ports; node; node = jack_slist_next (node)) { jack_port_t *port = node->data; jack_port_set_latency_range (port, mode, &range); } } } /** * Allocate the necessary jack ports... */ void alloc_ports( int n_capture, int n_playback ) { int port_flags = JackPortIsOutput | JackPortIsPhysical | JackPortIsTerminal; int chn; jack_port_t *port; char buf[32]; capture_ports = NULL; for (chn = 0; chn < n_capture; chn++) { snprintf (buf, sizeof(buf) - 1, "capture_%u", chn+1); port = jack_port_register (client, buf, JACK_DEFAULT_AUDIO_TYPE, port_flags, 0); if (!port) { printf( "jacknet_client: cannot register port for %s", buf); break; } capture_srcs = jack_slist_append( capture_srcs, src_new( 4-samplerate_quality, 1, NULL ) ); capture_ports = jack_slist_append (capture_ports, port); } port_flags = JackPortIsInput; playback_ports = NULL; for (chn = 0; chn < n_playback; chn++) { snprintf (buf, sizeof(buf) - 1, "playback_%u", chn+1); port = jack_port_register (client, buf, JACK_DEFAULT_AUDIO_TYPE, port_flags, 0); if (!port) { printf( "jacknet_client: cannot register port for %s", buf); break; } playback_srcs = jack_slist_append( playback_srcs, src_new( 4-samplerate_quality, 1, NULL ) ); playback_ports = jack_slist_append (playback_ports, port); } } /** * This is the shutdown callback for this JACK application. * It is called by JACK if the server ever shuts down or * decides to disconnect the client. */ void jack_shutdown (void *arg) { exit (1); } /** * be user friendly. * be user friendly. * be user friendly. */ void printUsage() { fprintf(stderr, "usage: alsa_out [options]\n" "\n" " -j - client name\n" " -d \n" " -c \n" " -p \n" " -n \n" " -r \n" " -q \n" " -t \n" " -i turns on instrumentation\n" " -v turns on printouts\n" "\n"); } /** * the main function.... */ void sigterm_handler( int signal ) { quit = 1; } int main (int argc, char *argv[]) { char jack_name[30] = "alsa_out"; char alsa_device[30] = "hw:0"; extern char *optarg; extern int optind, optopt; int errflg=0; int c; while ((c = getopt(argc, argv, "ivj:r:c:p:n:d:q:m:t:f:F:C:Q:s:")) != -1) { switch(c) { case 'j': strcpy(jack_name,optarg); break; case 'r': sample_rate = atoi(optarg); break; case 'c': num_channels = atoi(optarg); break; case 'p': period_size = atoi(optarg); break; case 'n': num_periods = atoi(optarg); break; case 'd': strcpy(alsa_device,optarg); break; case 't': target_delay = atoi(optarg); break; case 'q': samplerate_quality = atoi(optarg); break; case 'm': max_diff = atoi(optarg); break; case 'f': catch_factor = atoi(optarg); break; case 'F': catch_factor2 = atoi(optarg); break; case 'C': pclamp = (double) atoi(optarg); break; case 'Q': controlquant = (double) atoi(optarg); break; case 'v': verbose = 1; break; case 'i': instrument = 1; break; case 's': smooth_size = atoi(optarg); break; case ':': fprintf(stderr, "Option -%c requires an operand\n", optopt); errflg++; break; case '?': fprintf(stderr, "Unrecognized option: -%c\n", optopt); errflg++; } } if (errflg) { printUsage(); exit(2); } if( (samplerate_quality < 0) || (samplerate_quality > 4) ) { fprintf (stderr, "invalid samplerate quality\n"); return 1; } if ((client = jack_client_open (jack_name, 0, NULL)) == 0) { fprintf (stderr, "jack server not running?\n"); return 1; } /* tell the JACK server to call `process()' whenever there is work to be done. */ jack_set_process_callback (client, process, 0); /* tell the JACK server to call `jack_shutdown()' if it ever shuts down, either entirely, or if it just decides to stop calling us. */ jack_on_shutdown (client, jack_shutdown, 0); if (jack_set_latency_callback) jack_set_latency_callback (client, latency_cb, 0); // get jack sample_rate jack_sample_rate = jack_get_sample_rate( client ); if( !sample_rate ) sample_rate = jack_sample_rate; static_resample_factor = (double) sample_rate / (double) jack_sample_rate; resample_lower_limit = static_resample_factor * 0.25; resample_upper_limit = static_resample_factor * 4.0; resample_mean = static_resample_factor; offset_array = malloc( sizeof(double) * smooth_size ); if( offset_array == NULL ) { fprintf( stderr, "no memory for offset_array !!!\n" ); exit(20); } window_array = malloc( sizeof(double) * smooth_size ); if( window_array == NULL ) { fprintf( stderr, "no memory for window_array !!!\n" ); exit(20); } int i; for( i=0; i target_delay ) { fprintf( stderr, "target_delay (%d) cant be smaller than max_diff(%d)\n", target_delay, max_diff ); exit(20); } if( (target_delay+max_diff) > (num_periods*period_size) ) { fprintf( stderr, "target_delay+max_diff (%d) cant be bigger than buffersize(%d)\n", target_delay+max_diff, num_periods*period_size ); exit(20); } // now open the alsa fd... alsa_handle = open_audiofd( alsa_device, 0, sample_rate, num_channels, period_size, num_periods); if( alsa_handle == 0 ) exit(20); printf( "selected sample format: %s\n", formats[format].name ); // alloc input ports, which are blasted out to alsa... alloc_ports( 0, num_channels ); outbuf = malloc( num_periods * period_size * formats[format].sample_size * num_channels ); resampbuf = malloc( num_periods * period_size * sizeof( float ) ); tmpbuf = malloc( 512 * formats[format].sample_size * num_channels ); if ((outbuf == NULL) || (resampbuf == NULL) || (tmpbuf == NULL)) { fprintf( stderr, "no memory for buffers.\n" ); exit(20); } /* tell the JACK server that we are ready to roll */ if (jack_activate (client)) { fprintf (stderr, "cannot activate client"); return 1; } signal( SIGTERM, sigterm_handler ); signal( SIGINT, sigterm_handler ); if( verbose ) { while(!quit) { usleep(500000); if( output_new_delay ) { printf( "delay = %d\n", output_new_delay ); output_new_delay = 0; } printf( "res: %f, \tdiff = %f, \toffset = %f \n", output_resampling_factor, output_diff, output_offset ); } } else if( instrument ) { printf( "# n\tresamp\tdiff\toffseti\tintegral\n"); int n=0; while(!quit) { usleep(1000); printf( "%d\t%f\t%f\t%f\t%f\n", n++, output_resampling_factor, output_diff, output_offset, output_integral ); } } else { while(!quit) { usleep(500000); if( output_new_delay ) { printf( "delay = %d\n", output_new_delay ); output_new_delay = 0; } } } jack_deactivate( client ); jack_client_close (client); exit (0); }