Esempio n. 1
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;
}
Esempio n. 2
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;
  }
}
Esempio n. 3
0
/* 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);
}
Esempio n. 4
0
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;
}
Esempio n. 5
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;
}
Esempio n. 6
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);
}
Esempio n. 7
0
// 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;
}
Esempio n. 8
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;
}
Esempio n. 9
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 );
}
Esempio n. 10
0
// 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;
}
Esempio n. 11
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;
}
Esempio n. 12
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;
  }
}