static int rotter_process_audio() { int total_samples = 0; int result; int b; for(b=0; b<2; b++) { rotter_ringbuffer_t *ringbuffer = ringbuffers[b]; int samples = 0; // Has there been a ringbuffer overflow? if (ringbuffer->overflow) { rotter_error( "Ringbuffer %c overflowed while writing audio.", ringbuffer->label); ringbuffer->overflow = 0; } // Has there been a jackd xrun? if (ringbuffer->xrun_usecs) { rotter_error( "jackd experienced a %d microsecond buffer xrun.", ringbuffer->xrun_usecs); ringbuffer->xrun_usecs = 0; } // Read some audio from the buffer samples = rotter_read_from_ringbuffer( ringbuffer, output_format->samples_per_frame ); if (samples > 0) { total_samples += samples; // Open a new file? if (ringbuffer->file_handle == NULL) { result = rotter_open_file(ringbuffer); if (result) { rotter_error("Failed to open file."); break; } } // Write some audio to disk result = encoder->write(ringbuffer->file_handle, samples, tmp_buffer); if (result) { rotter_error("An error occured while trying to write audio to disk."); break; } } // Close the old file if (samples <= 0 && ringbuffer->close_file) { rotter_close_file(ringbuffer); // Delete files older delete_hours if (delete_hours>0) deletefiles( root_directory, delete_hours ); } } // for(b=0..2) return total_samples; }
// Shut down jack related stuff int deinit_jack() { if (client) { rotter_debug("Stopping Jack client."); if (jack_deactivate(client)) { rotter_error("Failed to de-activate Jack"); } if (jack_client_close(client)) { rotter_error("Failed to close Jack client"); } } return 0; }
static int deinit_ringbuffers() { int b,c; for(b=0; b<2; b++) { if (ringbuffers[b]) { for(c=0;c<2;c++) { if (ringbuffers[b]->buffer[c]) { jack_ringbuffer_free(ringbuffers[b]->buffer[c]); } } if (munlock(ringbuffers[b], sizeof(rotter_ringbuffer_t))) { rotter_error("Failed to unlock ringbuffer %c from physical memory.", ringbuffers[b]->label); } if (ringbuffers[b]->file_handle) { rotter_close_file(ringbuffers[b]); ringbuffers[b]->file_handle = NULL; } free(ringbuffers[b]); } } return 0; }
/* 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; }
// Callback called by JACK when jackd is shutting down static void shutdown_callback_jack(void *arg) { rotter_error("Rotter quitting because jackd is shutting down." ); // Signal the main thead to stop rotter_run_state = ROTTER_STATE_QUITING; }
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 dev_t get_file_device( const char* filepath ) { struct stat sb; if (stat( filepath, &sb )) { rotter_error( "Warning: failed to stat file: %s", filepath ); return -1; } return sb.st_dev; }
// Delete files older than 'hours' int deletefiles( const char* dirpath, int hours ) { int old_niceness, new_niceness = 15; time_t now = time(NULL); dev_t device = get_file_device( dirpath ); if (hours<=0) return 0; if (delete_child_pid) { rotter_error( "Not deleting files: the last deletion process has not finished." ); return delete_child_pid; } rotter_info( "Deleting files older than %d hours in %s.", hours, dirpath ); // Fork a new process delete_child_pid = fork(); if (delete_child_pid>0) { // Parent is here rotter_debug( "Forked new proccess to delete files (pid=%d).", delete_child_pid ); return delete_child_pid; } else if (delete_child_pid<0) { rotter_error( "Warning: fork failed: %s", strerror(errno) ); return 0; } // Make this process nicer // (deleting files is pretty unimportant) old_niceness = nice( new_niceness ); rotter_debug( "Changed child proccess niceless from %d to %d.", old_niceness, new_niceness ); // Recursively process directories deletefiles_in_dir( dirpath, device, now-(hours*3600) ); // End of child process exit(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 ); }
static void delete_file( const char* filepath, dev_t device, time_t timestamp ) { struct stat sb; if (stat( filepath, &sb )) { rotter_error( "Warning: failed to stat file: %s", filepath ); return; } if (sb.st_dev != device) { rotter_debug( "Warning: %s isn't on same device as root dir.", filepath ); return; } if (sb.st_mtime < timestamp) { rotter_debug( "Deleting file: %s", filepath ); if (unlink(filepath) && rmdir(filepath)) { rotter_error( "Warning: failed to delete file: %s", filepath ); return; } } }
void deletefiles_cleanup_child() { // Has a child process finished? if (delete_child_pid) { int status = 0; pid_t pid = waitpid( delete_child_pid, &status, WNOHANG ); if (pid) { delete_child_pid = 0; if (status) { rotter_error( "File deletion child-process exited with status %d", status ); } else { rotter_debug( "File deletion child-process has finished." ); } } } }
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; } }
encoder_funcs_t* init_lame( output_format_t* format, int channels, int bitrate ) { encoder_funcs_t* funcs = NULL; lame_opts = lame_init(); if (lame_opts==NULL) { rotter_error("lame error: failed to initialise."); return NULL; } if ( 0 > lame_set_num_channels( lame_opts, channels ) ) { rotter_error("lame error: failed to set number of channels."); return NULL; } if ( 0 > lame_set_in_samplerate( lame_opts, jack_get_sample_rate( client ) )) { rotter_error("lame error: failed to set input samplerate."); return NULL; } if ( 0 > lame_set_out_samplerate( lame_opts, jack_get_sample_rate( client ) )) { rotter_error("lame error: failed to set output samplerate."); return NULL; } if ( 0 > lame_set_brate( lame_opts, bitrate) ) { rotter_error("lame error: failed to set bitrate."); return NULL; } if ( 0 > lame_init_params( lame_opts ) ) { rotter_error("lame error: failed to initialize parameters."); return NULL; } rotter_info( "Encoding using liblame version %s.", get_lame_version() ); rotter_debug( " Input: %d Hz, %d channels", lame_get_in_samplerate(lame_opts), lame_get_num_channels(lame_opts)); rotter_debug( " Output: %s Layer 3, %d kbps, %s", lame_get_version_name(lame_opts), lame_get_brate(lame_opts), lame_get_mode_name(lame_opts)); // Allocate memory for encoded audio mpeg_buffer = malloc( 1.25*SAMPLES_PER_FRAME + 7200 ); if ( mpeg_buffer==NULL ) { rotter_error( "Failed to allocate memery for encoded audio." ); return NULL; } // Allocate memory for callback functions funcs = calloc( 1, sizeof(encoder_funcs_t) ); if ( funcs==NULL ) { rotter_error( "Failed to allocate memery for encoder callback functions structure." ); return NULL; } funcs->file_suffix = "mp3"; funcs->open = open_mpegaudio_file; funcs->close = close_mpegaudio_file; funcs->write = write_lame; funcs->deinit = deinit_lame; return funcs; }