/* Encode and write some audio from the ring buffer to disk */ static int write_lame(void *fh, size_t sample_count, jack_default_audio_sample_t *buffer[]) { FILE* file = (FILE*)fh; size_t i16_desired = sample_count * sizeof( short int ); int bytes_encoded=0, bytes_written=0; int c=0; // Convert to 16-bit integer samples for (c=0; c<channels; c++) { i16_buffer[c] = (short int*)realloc(i16_buffer[c], i16_desired ); if (!i16_buffer[c]) rotter_fatal( "realloc on i16_buffer failed" ); float32_to_short( buffer[c], i16_buffer[c], sample_count ); } // Encode it bytes_encoded = lame_encode_buffer( lame_opts, i16_buffer[0], i16_buffer[1], sample_count, mpeg_buffer, WRITE_BUFFER_SIZE ); if (bytes_encoded<0) { rotter_fatal( "Error: while encoding audio."); return -1; } else if (bytes_encoded>0) { // Write it to disk bytes_written = fwrite(mpeg_buffer, 1, bytes_encoded, file); if (bytes_written != bytes_encoded) { rotter_error( "Warning: failed to write encoded audio to disk: %s", strerror(errno) ); return -1; } } // Success return 0; }
static int rotter_open_file(rotter_ringbuffer_t *ringbuffer) { char filepath[MAX_FILEPATH_LEN]; int err = -1; struct tm tm; if (utc) { gmtime_r( &ringbuffer->file_start.tv_sec, &tm ); } else { localtime_r( &ringbuffer->file_start.tv_sec, &tm ); } if (!strcasecmp(file_layout, "hierarchy")) { err = time_to_filepath_hierarchy( &tm, encoder->file_suffix, filepath ); } else if (!strcasecmp(file_layout, "flat")) { err = time_to_filepath_flat( &tm, encoder->file_suffix, filepath ); } else if (!strcasecmp(file_layout, "combo")) { err = time_to_filepath_combo( &tm, encoder->file_suffix, filepath ); } else if (!strcasecmp(file_layout, "dailydir")) { err = time_to_filepath_dailydir( &tm, encoder->file_suffix, filepath ); } else if (!strcasecmp(file_layout, "accurate")) { err = time_to_filepath_accurate( &tm, ringbuffer->file_start.tv_usec, encoder->file_suffix, filepath ); } else { err = time_to_filepath_custom( &tm, file_layout, filepath ); } if (err) { rotter_fatal( "Failed to build file path for layout: %s", file_layout ); return -1; } // Make sure the parent directory exists if (rotter_mkdir_for_file(filepath)) { rotter_fatal( "Failed to create parent directories for filepath: %s (%s)", filepath, strerror(errno) ); return -1; } // Open the new file rotter_info( "Opening new archive file for ringbuffer %c: %s", ringbuffer->label, filepath ); ringbuffer->file_handle = encoder->open(filepath, &ringbuffer->file_start); if (ringbuffer->file_handle) { // Success return 0; } else { return 1; } }
/* Callback called by JACK when audio is available Use as little CPU time as possible, just copy accross the audio into the ring buffer */ static int callback_jack(jack_nframes_t nframes, void *arg) { jack_nframes_t frames_until_whole_second = 0; jack_nframes_t read_pos = 0; time_t this_period; struct timeval tv; // Get the current time if (gettimeofday(&tv, NULL)) { rotter_fatal("Failed to gettimeofday(): %s", strerror(errno)); return 1; } // FIXME: this won't work if rotter is started *just* before the archive period if (active_ringbuffer) { unsigned int duration; int result; // Calculate the number of frames until we have a whole number of seconds // FIXME: what if the callback buffer contains over 1 second of audio? frames_until_whole_second = ceil(jack_get_sample_rate( client ) * ((double)(1000000 - tv.tv_usec) / 1000000)); if (frames_until_whole_second < nframes) { result = write_to_ringbuffer(active_ringbuffer, read_pos, frames_until_whole_second); if (result) return result; // Calculate the duration of the audio that we wrote // and add it on to the current time duration = ((double)frames_until_whole_second / jack_get_sample_rate( client )) * 1000000; tv.tv_usec += (duration - 1000000); tv.tv_sec += 1; nframes -= frames_until_whole_second; read_pos += frames_until_whole_second; } } // Time to swap ring buffers, if we are now in a new archive period this_period = start_of_period(tv.tv_sec); if (active_ringbuffer == NULL || active_ringbuffer->period_start != this_period) { if (active_ringbuffer) { active_ringbuffer->close_file = 1; } if (active_ringbuffer == ringbuffers[0]) { active_ringbuffer = ringbuffers[1]; } else { active_ringbuffer = ringbuffers[0]; } active_ringbuffer->file_start = tv; active_ringbuffer->period_start = this_period; } // Finally, write any frames after the 1 second boundary return write_to_ringbuffer(active_ringbuffer, read_pos, nframes); }
static int write_to_ringbuffer(rotter_ringbuffer_t *rb, jack_nframes_t start, jack_nframes_t nframes) { size_t to_write = sizeof(jack_default_audio_sample_t) * nframes; unsigned int c; if (nframes <= 0) return 0; for (c=0; c < channels; c++) { size_t space = jack_ringbuffer_write_space(rb->buffer[c]); if (space < to_write) { // Glitch in audio is preferable to a fatal error or ring buffer corruption rb->overflow = 1; return 0; } } for (c=0; c < channels; c++) { jack_default_audio_sample_t *buf = jack_port_get_buffer(inport[c], nframes); size_t len = 0; len = jack_ringbuffer_write(rb->buffer[c], (char*)&buf[start], to_write); if (len < to_write) { rotter_fatal("Failed to write to ring buffer."); return 1; } } // Success return 0; }
// Crude way of automatically connecting up jack ports int autoconnect_jack_ports( jack_client_t* client ) { const char **all_ports; unsigned int ch=0; int i; // Get a list of all the jack ports all_ports = jack_get_ports(client, NULL, NULL, JackPortIsOutput); if (!all_ports) { rotter_fatal("autoconnect_jack_ports(): jack_get_ports() returned NULL."); return -1; } // Step through each port name for (i = 0; all_ports[i]; ++i) { // Connect the port if (connect_jack_port( all_ports[i], inport[ch] )) { return -1; } // Found enough ports ? if (++ch >= channels) break; } free( all_ports ); return 0; }
static size_t rotter_read_from_ringbuffer(rotter_ringbuffer_t *ringbuffer, size_t desired_frames) { size_t desired_bytes = desired_frames * sizeof(jack_default_audio_sample_t); size_t available_bytes = 0; int c, bytes_read = 0; // Is there enough in the ring buffers? for (c=0; c<channels; c++) { available_bytes = jack_ringbuffer_read_space( ringbuffer->buffer[c] ); if (available_bytes <= 0) { // Try again later return 0; } } if (available_bytes > desired_bytes) available_bytes = desired_bytes; // Get the audio out of the ring buffer for (c=0; c<channels; c++) { // Copy frames from ring buffer to temporary buffer bytes_read = jack_ringbuffer_read( ringbuffer->buffer[c], (char*)tmp_buffer[c], available_bytes); if (bytes_read != available_bytes) { rotter_fatal( "Failed to read from ringbuffer %c channel %d.", ringbuffer->label, c); return 0; } } return bytes_read / sizeof(jack_default_audio_sample_t); }
// Initialise Jack related stuff int init_jack( const char* client_name, jack_options_t jack_opt ) { jack_status_t status; // Register with Jack if ((client = jack_client_open(client_name, jack_opt, &status)) == 0) { rotter_fatal("Failed to start jack client: 0x%x", status); return -1; } rotter_info( "JACK client registered as '%s'.", jack_get_client_name( client ) ); // Create our input port(s) if (channels==1) { if (!(inport[0] = jack_port_register(client, "mono", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0))) { rotter_fatal("Cannot register mono input port."); return -1; } } else { if (!(inport[0] = jack_port_register(client, "left", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0))) { rotter_fatal("Cannot register left input port."); return -1; } if (!(inport[1] = jack_port_register(client, "right", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0))) { rotter_fatal( "Cannot register left input port."); return -1; } } // Register xrun callback jack_set_xrun_callback(client, xrun_callback_jack, client); // Register shutdown callback jack_on_shutdown(client, shutdown_callback_jack, NULL); // Register callback if (jack_set_process_callback(client, callback_jack, NULL)) { rotter_fatal( "Failed to set Jack process callback."); return -1; } // Success return 0; }
static int init_ringbuffers() { size_t ringbuffer_size = 0; int b,c; ringbuffer_size = jack_get_sample_rate( client ) * rb_duration * sizeof(jack_default_audio_sample_t); rotter_debug("Size of the ring buffers is %2.2f seconds (%d bytes).", rb_duration, (int)ringbuffer_size ); for(b=0; b<2; b++) { char label = ('A' + b); ringbuffers[b] = malloc(sizeof(rotter_ringbuffer_t)); if (!ringbuffers[b]) { rotter_fatal("Cannot allocate memory for ringbuffer %c structure.", label); return -1; } if (mlock(ringbuffers[b], sizeof(rotter_ringbuffer_t))) { rotter_error("Failed to lock data structure for ringbuffer %c into physical memory.", label); } ringbuffers[b]->label = label; ringbuffers[b]->period_start = 0; ringbuffers[b]->file_handle = NULL; ringbuffers[b]->overflow = 0; ringbuffers[b]->xrun_usecs = 0; ringbuffers[b]->close_file = 0; ringbuffers[b]->buffer[0] = NULL; ringbuffers[b]->buffer[1] = NULL; for(c=0; c<channels; c++) { ringbuffers[b]->buffer[c] = jack_ringbuffer_create( ringbuffer_size ); if (!ringbuffers[b]->buffer[c]) { rotter_fatal("Cannot create ringbuffer buffer %c%d.", label, c); return -1; } // Lock into physical memory to avoid delays during the realtime callback if (jack_ringbuffer_mlock(ringbuffers[b]->buffer[c])) { rotter_error("Failed to lock JACK ringbuffer %c%d into physical memory.", label, c); } } } return 0; }
static void deletefiles_in_dir( const char* dirpath, dev_t device, time_t timestamp ) { DIR *dirp = opendir(dirpath); struct dirent *dp; if (dirp==NULL) { rotter_fatal( "Failed to open directory: %s.", dirpath ); return; } // Check we are on the same device if (get_file_device(dirpath) != device) { rotter_debug( "Warning: %s isn't on same device as root dir.", dirpath ); closedir( dirp ); return; } // Check each item in the directory while( (dp = readdir( dirp )) != NULL ) { int newpath_len; char* newpath; if (strcmp( ".", dp->d_name )==0) continue; if (strcmp( "..", dp->d_name )==0) continue; newpath_len = strlen(dirpath) + strlen(dp->d_name) + 2; newpath = malloc( newpath_len ); snprintf( newpath, newpath_len, "%s/%s", dirpath, dp->d_name ); if (dp->d_type == DT_DIR) { // Process sub directory deletefiles_in_dir( newpath, device, timestamp ); delete_file( newpath, device, timestamp ); } else if (dp->d_type == DT_REG) { delete_file( newpath, device, timestamp ); } else { rotter_error( "Warning: not a file or a directory: %s" ); } free( newpath ); } closedir( dirp ); }
// Connect one Jack port to another int connect_jack_port( const char* out, jack_port_t *port ) { const char* in = jack_port_name( port ); int err; rotter_info("Connecting '%s' to '%s'", out, in); if ((err = jack_connect(client, out, in)) != 0) { rotter_fatal("connect_jack_port(): failed to jack_connect() ports: %d", err); return err; } // Success return 0; }
static int init_tmpbuffers(int sample_count) { size_t buffer_size = sample_count * sizeof(jack_default_audio_sample_t); int c; for(c=0; c<2; c++) { tmp_buffer[c] = (jack_default_audio_sample_t*)malloc(buffer_size); if (!tmp_buffer[c]) { rotter_fatal( "Failed to allocate memory for temporary buffer %d", c); return -1; } } return 0; }
int main(int argc, char *argv[]) { int autoconnect = 0; jack_options_t jack_opt = JackNullOption; char *client_name = DEFAULT_CLIENT_NAME; char *connect_left = NULL; char *connect_right = NULL; const char *format_name = NULL; int bitrate = DEFAULT_BITRATE; int sync_period = DEFAULT_SYNC_PERIOD; float sleep_time = 0; time_t next_sync = 0; int i,opt; // Make STDOUT unbuffered setbuf(stdout, NULL); // Parse Switches while ((opt = getopt(argc, argv, "al:r:n:N:O:p:jf:b:Q:d:c:R:L:s:uvqh")) != -1) { switch (opt) { case 'a': autoconnect = 1; break; case 'l': connect_left = optarg; break; case 'r': connect_right = optarg; break; case 'n': client_name = optarg; break; case 'N': archive_name = optarg; break; case 'O': originator = strdup(optarg); break; case 'p': archive_period_seconds = atol(optarg); break; case 'j': jack_opt |= JackNoStartServer; break; case 'f': format_name = rotter_str_tolower(optarg); break; case 'b': bitrate = atoi(optarg); break; case 'Q': vbr_quality = atof(optarg); break; case 'd': delete_hours = atoi(optarg); break; case 'c': channels = atoi(optarg); break; case 'R': rb_duration = atof(optarg); break; case 'L': file_layout = optarg; break; case 's': sync_period = atoi(optarg); break; case 'u': utc = 1; break; case 'v': verbose = 1; break; case 'q': quiet = 1; break; default: usage(); break; } } // Validate parameters if (quiet && verbose) { rotter_error("Can't be quiet and verbose at the same time."); usage(); } // Check the number of channels if (channels!=1 && channels!=2) { rotter_error("Number of channels should be either 1 or 2."); usage(); } // Check remaining arguments argc -= optind; argv += optind; if (argc!=1) { rotter_error("%s requires a root directory argument.", PACKAGE_NAME); usage(); } else { root_directory = argv[0]; if (root_directory[strlen(root_directory)-1] == '/') root_directory[strlen(root_directory)-1] = 0; if (rotter_directory_exists(root_directory)) { rotter_debug("Root directory: %s", root_directory); } else { rotter_fatal("Root directory does not exist: %s", root_directory); goto cleanup; } } // Search for the selected output format if (format_name) { for(i=0; format_list[i].name; i++) { if (strcmp( format_list[i].name, format_name ) == 0) { // Found desired format output_format = &format_list[i]; rotter_debug("User selected [%s] '%s'.", output_format->name, output_format->desc); break; } } if (output_format==NULL) { rotter_fatal("Failed to find format [%s], please check the supported format list.", format_name); goto cleanup; } } else { output_format = &format_list[0]; } // No originator defined? if (!originator) { originator = rotter_get_hostname(); } // Initialise JACK if (init_jack( client_name, jack_opt )) { rotter_debug("Failed to initialise Jack client."); goto cleanup; } // Create ring buffers if (init_ringbuffers()) { rotter_debug("Failed to initialise ring buffers."); goto cleanup; } // Create temporary buffer for reading samples into if (init_tmpbuffers(output_format->samples_per_frame)) { rotter_debug("Failed to initialise temporary buffers."); goto cleanup; } // Initialise encoder encoder = output_format->initfunc(output_format, channels, bitrate); if (encoder==NULL) { rotter_debug("Failed to initialise encoder."); goto cleanup; } // Activate JACK if (jack_activate(client)) { rotter_fatal("Cannot activate JACK client."); goto cleanup; } // Setup signal handlers signal(SIGTERM, rotter_termination_handler); signal(SIGINT, rotter_termination_handler); signal(SIGHUP, rotter_termination_handler); // Auto-connect our input ports ? if (autoconnect) autoconnect_jack_ports( client ); if (connect_left) connect_jack_port( connect_left, inport[0] ); if (connect_right && channels == 2) connect_jack_port( connect_right, inport[1] ); // Calculate period to wait when there is no audio to process sleep_time = (2.0f * output_format->samples_per_frame / jack_get_sample_rate( client )); rotter_debug("Sleep period is %dms.", (int)(sleep_time * 1000)); while( rotter_run_state == ROTTER_STATE_RUNNING ) { time_t now = time(NULL); int samples_processed = rotter_process_audio(); if (samples_processed <= 0) { usleep(sleep_time * 1000000); } // Is it time to sync the encoded audio to disk? if (next_sync < now) { rotter_sync_to_disk(); next_sync = now + sync_period; } deletefiles_cleanup_child(); } cleanup: // Clean up JACK deinit_jack(); // Free buffers and close files deinit_tmpbuffers(); deinit_ringbuffers(); // Shut down encoder if (encoder) encoder->deinit(); // Free the originator string if (originator) free(originator); // Did something go wrong? if (rotter_run_state == ROTTER_STATE_QUITING) { return EXIT_SUCCESS; } else { return EXIT_FAILURE; } }