Logo Search packages:      
Sourcecode: jack-tools version File versions  Download package

jack.play.c

/***** jack.play.c - (c) rohan drape, 2003-2004 *****/

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#define MAX_CHANNELS 32
#define FAILURE      exit( EXIT_FAILURE )
#define TRUE         1

#include "file.c"
#include "jack.c"
#include "memory.c"
#include "signal.c"
#include "sound-file.c"

typedef struct 
{
  int buffer_bytes ;
  int buffer_samples ;
  int buffer_frames ;
  int minimal_frames ;
  float *d_buffer ;
  float *j_buffer ;
  SNDFILE *sound_file ;
  int channels ;
  jack_port_t *output_port[MAX_CHANNELS];
  jack_ringbuffer_t *ring_buffer ;
  pthread_t disk_thread ;
  int pipe[2];
} 
jackplay_t ;

/* Read the sound file from disk and write to the ring buffer until
   the end of file, at which point return. */

void *
jackplay_disk_thread_procedure ( void *PTR )
{
  jackplay_t *d = ( jackplay_t *) PTR ;
  while ( ! end_of_process_signaled () ) {

    /* Wait until there is space for at least d->minimal_frames
       available at the ring buffer.  If the buffer is full up, wait
       for notification that the JACK process callback has read some
       data.  This waits until a byte arrives on the communication
       pipe, the JACK process callback writes a byte to this pipe each
       time it removes data from the ring buffer. */

    int nbytes = d->minimal_frames * sizeof(float) * d->channels ;
    nbytes = jack_ringbuffer_wait_for_write ( d->ring_buffer , nbytes , d->pipe[0] ) ;

    /* Drop excessive space to not overflow the local buffer. In this
       program this cannot occur since the ring buffer is known to
       have d->buffer_bytes places. */

    if ( nbytes > d->buffer_bytes ) {
      fprintf ( stderr , "jack.play: impossible condition , write space.\n" ) ;
      nbytes = d->buffer_bytes ;
    }

    /* Do read operation.  The sample count *must* be an integral
       number of frames. */

    int nframes = ( nbytes / sizeof(float) ) / d->channels ;
    int nsamples = nframes * d->channels ;
    loff_t err = xsf_read_float ( d->sound_file ,
                          d->d_buffer ,
                          ( sf_count_t ) nsamples ) ;
    
    /* Handle end of file. */

    if ( err == 0 ) {
      break ;
    }

    /* Write data to ring buffer. This is the copying transfer mode of
       the ring buffer, not the most efficient but simplest. */

    jack_ringbuffer_write ( d->ring_buffer ,
                      ( char *) d->d_buffer , 
                      ( size_t ) err * sizeof(float) ) ;
  }

  return NULL ;
}

/* Write data from the ring buffer to the JACK output ports.  If the
   disk thread is late, ie. the ring buffer is empty print a warning
   and zero the output ports.  */

int
jackplay_process ( jack_nframes_t nframes , void *PTR )
{
  jackplay_t *d = ( jackplay_t *) PTR ;
  int nsamples = nframes * d->channels ;
  int nbytes = nsamples * sizeof(float) ;

  /* Get port data buffers. */

  int i ,j ;
  sample_t *out[MAX_CHANNELS];
  for ( i=0; i<d->channels ; i++) {
    out[i] = ( sample_t *) jack_port_get_buffer ( d->output_port[i], nframes ) ;
  }

  /* Check period size is workable. If the buffer is large , ie 4096
     frames , this should never be of practical concern. */

  if ( nbytes >= d->buffer_bytes ) {
    fprintf ( stderr , "jack.play: period size exceeds limit\n") ;
    FAILURE ;
    return 1 ;
  }

  /* Get data from ring buffer.  We could check the read size first ,
     but we would still need to check the actual result size , and
     therefore have two error cases.  Since we are dropping a frame in
     any case it does not matter much.  This is the copying transfer
     mode of the ring buffer , not the most efficient but simplest. */

  int err = jack_ringbuffer_read ( d->ring_buffer ,
                           ( char *) d->j_buffer ,
                           ( size_t ) nsamples * sizeof(float) ) ;
  err /= d->channels * sizeof(float) ;
  

  /* If data is unavailable inform the user that a 'frame' has been
     dropped and zero the output buffers , else uninterleave the data
     to the output buffers.  The print statement is not correct , a
     real client should set a flag and have another thread take
     appropriate action. */

  for ( i = 0; i < err ; i++) {
    for ( j = 0; j < d->channels ; j++) {
      out[j][i] = ( sample_t ) d->j_buffer[( i*d->channels )+j];
    }
  }
  if ( err < nframes ) {
    fprintf ( stderr , "jack.play: disk thread late (%d < %d)\n" , err , nframes ) ;
    for ( i = err ; i < nframes ; i++) {
      for ( j = 0; j < d->channels ; j++) {
      out[j][i] = ( sample_t ) d->j_buffer[( i*d->channels )+j];
      }
    }
  }
  
  /* Indicate to the disk thread that the ring buffer has been read
     from.  This is done by writing a single byte to a communication
     pipe.  Once the disk thread gets so far ahead that the ring
     buffer is full it reads this communication channel to wait for
     space to become available.  So long as PIPE_BUF is not a
     pathologically small value this write operation is atomic and
     will not block.  The number of bytes that can accumulate in the
     pipe is a factor of the relative sizes of the ring buffer and the
     process callback, but should in no case be very large. */

  char b = 1;
  xwrite ( d->pipe[1] , &b , 1 ) ;

  return 0;
}

void
jackplay_usage ( void )
{
  fprintf ( stderr , "Usage: jack.play [ options ] sound-file\n") ;
  fprintf ( stderr , "    -b N : Set the ring buffer size in frames ( default=4096).\n") ;
  fprintf ( stderr , "    -m N : Set the minimal disk read size in frames ( default=32).\n") ;
  FAILURE ;
}

int
main ( int argc , char *argv[] )
{
  install_signal_handlers () ;
  jackplay_t d ;
  d.buffer_frames = 4096 ;
  d.minimal_frames = 32 ;
  int c ;
  while ( ( c = getopt ( argc , argv , "b:hm:" ) ) != -1 ) {
    switch ( c ) {
    case 'b':
      d.buffer_frames = atoi ( optarg ) ;
      break ;
    case 'h':
      jackplay_usage () ;
      break ;
    case 'm':
      d.minimal_frames = atoi ( optarg ) ;
      break ;
    default:
      fprintf ( stderr , "jack.play: illegal option , %c\n" , c ) ;
      jackplay_usage () ;
      break ;
    }
  }
  if ( optind != argc - 1 ) {
    jackplay_usage () ;
  }

  /* Open sound file. */

  SF_INFO sfinfo ; 
  d.sound_file = xsf_open ( argv[optind] , SFM_READ , &sfinfo ) ;

  /* Enforce channel limit. */

  d.channels = sfinfo.channels ;
  if ( d.channels < 1 || d.channels > MAX_CHANNELS ) {
    fprintf ( stderr , "jack.play: illegal number of channels in file: %d\n" , d.channels ) ;
    FAILURE ;
  }

  /* Allocate buffers. */
  
  d.buffer_samples = d.buffer_frames * d.channels ;
  d.buffer_bytes = d.buffer_samples * sizeof(float) ;
  d.d_buffer = xmalloc ( d.buffer_bytes ) ;
  d.j_buffer = xmalloc ( d.buffer_bytes ) ;
  d.ring_buffer = jack_ringbuffer_create ( d.buffer_bytes ) ;

  /* Create communication pipe. */

  xpipe ( d.pipe ) ;

  /* Start disk thread. */

  pthread_create (&( d.disk_thread ),
              NULL , 
              jackplay_disk_thread_procedure , 
              &d ) ;

  /* Try to become a client of the JACK server.  Using a constant for
     the name means that only one instance can run under any given
     JACK server, here we append the process ID to side step this
     issue.  */

  jack_client_t *client = jack_client_uniq ( "jack.play" ) ;

  /* Set error, process and shutdown handlers. */

  jack_set_error_function ( jack_minimal_error_handler ) ;
  jack_on_shutdown ( client , jack_minimal_shutdown_handler , 0 ) ;
  jack_set_process_callback ( client , jackplay_process , &d ) ;

  /* Inform the user we do not do sample rate conversion , but still
     play the sound file. */

  if ( jack_get_sample_rate ( client ) != sfinfo.samplerate ) {
    fprintf ( stderr , "jack.play: sample rate of file not that of server\n" ) ;
  }

  /* Create output ports and activate client. */

  jack_make_standard_ports ( client , d.output_port , d.channels , TRUE ) ;
  xjack_activate ( client ) ;

  /* Wait for disk thread to end , which it does when it reaches the
     end of the file or is interrupted. */

  pthread_join ( d.disk_thread , NULL ) ;

  /* Close sound file, free ring buffer, close JACK connection, close
     pipe, free data buffers, indicate success. */

  jack_client_close ( client ) ;
  sf_close ( d.sound_file ) ;
  jack_ringbuffer_free ( d.ring_buffer ) ;
  close ( d.pipe[0] ) ;
  close ( d.pipe[1] ) ;
  free ( d.d_buffer ) ;
  free ( d.j_buffer ) ;
  return EXIT_SUCCESS ;
}

Generated by  Doxygen 1.6.0   Back to index