Exemple #1
0
static PyObject*
py_ao_is_big_endian(PyObject *self, PyObject *args)
{
  if (!PyArg_ParseTuple(args, ""))
    return NULL;
  return PyInt_FromLong(ao_is_big_endian());
}
Exemple #2
0
/* Create a new ao_device structure and populate it with data */
static ao_device* _create_device(int driver_id, driver_list *driver,
                 ao_sample_format *format, FILE *file)
{
    ao_device *device;

    device = calloc(1,sizeof(ao_device));

    if (device != NULL) {
        device->type = driver->functions->driver_info()->type;
        device->driver_id = driver_id;
        device->funcs = driver->functions;
        device->file = file;
        device->machine_byte_format =
          ao_is_big_endian() ? AO_FMT_BIG : AO_FMT_LITTLE;
        device->client_byte_format =
          _real_byte_format(format->byte_format);
        device->swap_buffer = NULL;
        device->swap_buffer_size = 0;
        device->internal = NULL;
                device->output_channels = format->channels;
                device->inter_permute = NULL;
                device->output_matrix = NULL;
    }

    return device;
}
Exemple #3
0
/* helper function to convert a byte_format of AO_FMT_NATIVE to the
   actual byte format of the machine, otherwise just return
   byte_format */
static int _real_byte_format(int byte_format)
{
    if (byte_format == AO_FMT_NATIVE) {
        if (ao_is_big_endian())
            return AO_FMT_BIG;
        else
            return AO_FMT_LITTLE;
    } else
        return byte_format;
}
/* play num_bytes of audio data */
int ao_plugin_play(ao_device *device, const char *output_samples, 
		uint_32 num_bytes)
{
  ao_alsa_internal *internal = (ao_alsa_internal *) device->internal;
  int endianp = ao_is_big_endian();

  /* the bit padding should be at a higher layer where we're doing
     other permutation/swizzling, but for now only ALSA has need of
     this, and moving it up will require a plugin API change */

  if(internal->padbuffer){
    /* pad and forward ~ a page at a time; must not hang on fractional frames */
    int ibytewidth = internal->sample_size / device->output_channels;
    int obytewidth = internal->padoutw;
    int istride = internal->sample_size;
    int ostride = obytewidth*device->output_channels;

    while(num_bytes >= internal->sample_size){
      int oframes = 4096/(obytewidth*device->output_channels);
      int iframes = num_bytes/internal->sample_size;
      int frames = oframes<iframes ? oframes : iframes;
      int obytes = frames * obytewidth * device->output_channels;
      int ibytes = frames * ibytewidth * device->output_channels;
      int i,j;

      /* copy */
      for(j=0;j<ibytewidth;j++){
        const char *s = output_samples + j;
        char *d = internal->padbuffer + (endianp ? j : obytewidth-ibytewidth+j);
        for(i=0;i<frames*device->output_channels;i++){
          *d = *s;
          s+=ibytewidth;
          d+=obytewidth;
        }
      }

      /* pad */
      for(;j<obytewidth;j++){
        char *d = internal->padbuffer + (endianp ? j : j-ibytewidth);
        for(i=0;i<frames*device->output_channels;i++){
          *d = 0;
          d+=obytewidth;
        }
      }

      if(!ao_plugin_playi(device,internal->padbuffer,obytes,obytewidth*device->output_channels))
        return 0;

      num_bytes-=frames*internal->sample_size;
      output_samples+=frames*internal->sample_size;
    }
    return 1;
  }else
    return ao_plugin_playi(device,output_samples,num_bytes,internal->sample_size);
}
Exemple #5
0
static int
audio_output(shairplay_session_t *session, const void *buffer, int buflen)
{
	short *shortbuf;
	char tmpbuf[4096];
	int tmpbuflen, i;

	tmpbuflen = (buflen > sizeof(tmpbuf)) ? sizeof(tmpbuf) : buflen;
	memcpy(tmpbuf, buffer, tmpbuflen);
	if (ao_is_big_endian()) {
		for (i=0; i<tmpbuflen/2; i++) {
			char tmpch = tmpbuf[i*2];
			tmpbuf[i*2] = tmpbuf[i*2+1];
			tmpbuf[i*2+1] = tmpch;
		}
	}
	shortbuf = (short *)tmpbuf;
	for (i=0; i<tmpbuflen/2; i++) {
		shortbuf[i] = shortbuf[i] * session->volume;
	}
	ao_play(session->device, tmpbuf, tmpbuflen);
	return tmpbuflen;
}
Exemple #6
0
/* Open a device.  If this is a live device, file == NULL. */
static ao_device* _open_device(int driver_id, ao_sample_format *format,
                   ao_option *options, FILE *file)
{
    ao_functions *funcs;
    driver_list *driver;
    ao_device *device=NULL;
    int result;
        ao_sample_format sformat=*format;
        sformat.matrix=NULL;

    /* Get driver id */
    if ( (driver = _get_driver(driver_id)) == NULL ) {
        errno = AO_ENODRIVER;
        goto error;
    }

    funcs = driver->functions;

    /* Check the driver type */
    if (file == NULL &&
        funcs->driver_info()->type != AO_TYPE_LIVE) {

        errno = AO_ENOTLIVE;
        goto error;
    } else if (file != NULL &&
           funcs->driver_info()->type != AO_TYPE_FILE) {

        errno = AO_ENOTFILE;
        goto error;
    }

    /* Make a new device structure */
    if ( (device = _create_device(driver_id, driver,
                      format, file)) == NULL ) {
        errno = AO_EFAIL;
        goto error;
    }

    /* Initialize the device memory; get static channel mapping */
    if (!funcs->device_init(device)) {
        errno = AO_EFAIL;
        goto error;
    }

    /* Load options */
        errno = ao_device_load_options(device,ao_global_options);
        if(errno) goto error;
        errno = ao_device_load_options(device,options);
        if(errno) goto error;

        /* also sanitize the format input channel matrix */
        if(format->matrix){
          sformat.matrix = _sanitize_matrix(format->channels, format->matrix, device);
          if(!sformat.matrix)
            awarn("Input channel matrix invalid; ignoring.\n");

          /* special-case handling of 'M'. */
          if(sformat.channels==1 && sformat.matrix && !strcmp(sformat.matrix,"M")){
            free(sformat.matrix);
            sformat.matrix=NULL;
          }
        }

        /* If device init was able to declare a static channel mapping
           mechanism, reconcile it to the input now.  Odd drivers that
           need to communicate with a backend device to determine
           channel mapping strategy can still bypass this mechanism
           entirely.  Also, drivers like ALSA may need to adjust
           strategy depending on what device is successfully opened,
           etc, but this still saves work later. */

        if(device->output_matrix && sformat.matrix){
          switch(device->output_matrix_order){
          case AO_OUTPUT_MATRIX_FIXED:
            /* Backend channel ordering is immutable and unused
               channels must still be sent.  Look for the highest
               channel number we are able to map from the input
               matrix. */
            {
              unsigned int mask = _matrix_to_channelmask(sformat.channels,sformat.matrix,
                                                         device->output_matrix,
                                                         &device->input_map);
              int max = _channelmask_maxbit(mask);
              if(max<0){
                aerror("Unable to map any channels from input matrix to output");
                errno = AO_EBADFORMAT;
                goto error;
              }
              device->output_channels = max+1;
              device->output_mask = mask;
              device->inter_matrix = _strdup(device->output_matrix);
            }
            break;

          case AO_OUTPUT_MATRIX_COLLAPSIBLE:
            /* the ordering of channels submitted to the backend is
               fixed, but only the channels in use should be present
               in the audio stream */
            {
              unsigned int mask = _matrix_to_channelmask(sformat.channels,sformat.matrix,
                                                         device->output_matrix,
                                                         &device->input_map);
              int channels = _channelmask_bits(mask);
              if(channels<0){
                aerror("Unable to map any channels from input matrix to output");
                errno = AO_EBADFORMAT;
                goto error;
              }
              device->output_channels = channels;
              device->output_mask = mask;
              device->inter_matrix = _channelmask_to_matrix(mask,device->output_matrix);
            }
            break;

          case AO_OUTPUT_MATRIX_PERMUTABLE:
            /* The ordering of channels is freeform.  Only the
               channels in use should be sent, and they may be sent in
               any order.  If possible, leave the input ordering
               unchanged */
            {
              unsigned int mask = _matrix_to_channelmask(sformat.channels,sformat.matrix,
                                                         device->output_matrix,
                                                         &device->input_map);
              int channels = _channelmask_bits(mask);
              if(channels<0){
                aerror("Unable to map any channels from input matrix to output");
                errno = AO_EBADFORMAT;
                goto error;
              }
              device->output_channels = channels;
              device->output_mask = mask;
              device->inter_matrix = _matrix_intersect(sformat.matrix,device->output_matrix);
            }
            break;

          default:
            aerror("Driver backend failed to set output ordering.\n");
            errno = AO_EFAIL;
            goto error;
          }

        }else{
          device->output_channels = sformat.channels;
        }

        /* other housekeeping */
        device->input_channels = sformat.channels;
        device->bytewidth = (sformat.bits+7)>>3;
        device->rate = sformat.rate;

    /* Open the device */
    result = funcs->open(device, &sformat);
    if (!result) {
          errno = AO_EOPENDEVICE;
          goto error;
    }

        /* set up permutation based on finalized inter_matrix mapping */
        /* The only way device->output_channels could be zero here is
           if the driver has opted to ouput no channels (eg, the null
           driver). */
        if(sformat.matrix){
          if(!device->inter_matrix){
            awarn("Driver %s does not support automatic channel mapping;\n"
                 "\tRouting only L/R channels to output.\n\n",
                 info_table[device->driver_id]->short_name);
            device->inter_matrix=_strdup("L,R");
          }
          {

            /* walk thorugh the inter matrix, match channels */
            char *op=device->inter_matrix;
            int count=0;
            device->inter_permute = calloc(device->output_channels,sizeof(int));

            adebug("\n");

            while(count<device->output_channels){
              int m=0,mm;
              char *h=op;

              if(*op){
                /* find mnemonic offset of inter channel */
                while(*h && *h!=',')h++;
                while(mnemonics[m]){
                  if(!strncmp(mnemonics[m],op,h-op))
                    break;
                  m++;
                }
                mm=m;

                /* find match in input if any */
                device->inter_permute[count] = _find_channel(m,sformat.matrix);
                if(device->inter_permute[count] == -1 && sformat.channels == 1){
                  device->inter_permute[count] = _find_channel(1,sformat.matrix);
                  mm=1;
                }
              }else
                device->inter_permute[count] = -1;

              /* display resulting mapping for now */
              if(device->inter_permute[count]>=0){
                adebug("input %d (%s)\t -> backend %d (%s)\n",
                       device->inter_permute[count], mnemonics[mm],
                       count,mnemonics[m]);
              }else{
                adebug("             \t    backend %d (%s)\n",
                       count,mnemonics[m]);
              }
              count++;
              op=h;
              if(*h)op++;
            }
            {
              char **inch = _tokenize_matrix(sformat.matrix);
              int i,j;
              int unflag=0;
              for(j=0;j<sformat.channels;j++){
                for(i=0;i<device->output_channels;i++)
                  if(device->inter_permute[i]==j)break;
                if(i==device->output_channels){
                  adebug("input %d (%s)\t -> none\n",
                         j,inch[j]);
                  unflag=1;
                }
              }
              _free_map(inch);
              if(unflag)
                awarn("Some input channels are unmapped and will not be used.\n");
            }
            averbose("\n");

          }
        }

        /* if there's no actual permutation to do, release the permutation vector */
        if(device->inter_permute && device->output_channels == device->input_channels){
          int i;
          for(i=0;i<device->output_channels;i++)
            if(device->inter_permute[i]!=i)break;
          if(i==device->output_channels){
            free(device->inter_permute);
            device->inter_permute=NULL;
          }
        }

    /* Resolve actual driver byte format */
    device->driver_byte_format =
        _real_byte_format(device->driver_byte_format);

    /* Only create swap buffer if needed */
        if (device->bytewidth>1 &&
            device->client_byte_format != device->driver_byte_format){
          adebug("swap buffer required:\n");
          adebug("  machine endianness: %d\n",ao_is_big_endian());
          adebug("  device->client_byte_format:%d\n",device->client_byte_format);
          adebug("  device->driver_byte_format:%d\n",device->driver_byte_format);
        }

    if ( (device->bytewidth>1 &&
              device->client_byte_format != device->driver_byte_format) ||
             device->inter_permute){

          result = _realloc_swap_buffer(device, DEF_SWAP_BUF_SIZE);

          if (!result) {

            if(sformat.matrix)free(sformat.matrix);
            device->funcs->close(device);
            device->funcs->device_clear(device);
            free(device);
            errno = AO_EFAIL;
            return NULL; /* Couldn't alloc swap buffer */
          }
    }

    /* If we made it this far, everything is OK. */
        if(sformat.matrix)free(sformat.matrix);
    return device;

 error:
        {
          int errtemp = errno;
          if(sformat.matrix)
            free(sformat.matrix);
          ao_close(device);
          errno=errtemp;
        }
        return NULL;
}
Exemple #7
0
int ao_plugin_open(ao_device *device, ao_sample_format *format)
{
  ao_macosx_internal *internal = (ao_macosx_internal *) device->internal;
  OSStatus result = noErr;
  AudioComponent comp;
  AudioComponentDescription desc;
  AudioStreamBasicDescription requestedDesc;
  AURenderCallbackStruct      input;
  UInt32 i_param_size, requestedEndian;

  /* Locate the default output audio unit */
  desc.componentType = kAudioUnitType_Output;
  desc.componentSubType = kAudioUnitSubType_HALOutput;
  desc.componentManufacturer = kAudioUnitManufacturer_Apple;
  desc.componentFlags = 0;
  desc.componentFlagsMask = 0;

  comp = AudioComponentFindNext (NULL, &desc);
  if (comp == NULL) {
    aerror("Failed to start CoreAudio: AudioComponentFindNext returned NULL");
    return 0;
  }

  /* Open & initialize the default output audio unit */
  result = AudioComponentInstanceNew (comp, &internal->outputAudioUnit);
  if (result) {
    aerror("AudioComponentInstanceNew() error => %d\n",(int)result);
    return 0;
  }
  /* Set the desired output device if not default */
  if (internal->outputDevice != kAudioObjectUnknown) {
    result = AudioUnitSetProperty (internal->outputAudioUnit,
                                   kAudioOutputUnitProperty_CurrentDevice,
                                   kAudioUnitScope_Global,
                                   0,
                                   &internal->outputDevice,
                                   sizeof(internal->outputDevice));
    if (result) {
      aerror("AudioComponentSetDevice() error => %d\n",(int)result);
      AudioComponentInstanceDispose(internal->outputAudioUnit);
      return 0;
    }
  }
  internal->output_p=1;

  /* Request desired format of the audio unit.  Let HAL do all
     conversion since it will probably be doing some internal
     conversion anyway. */

  device->driver_byte_format = format->byte_format;
  requestedDesc.mFormatID = kAudioFormatLinearPCM;
  requestedDesc.mFormatFlags = kAudioFormatFlagIsPacked;
  switch(format->byte_format){
  case AO_FMT_BIG:
    requestedDesc.mFormatFlags |= kAudioFormatFlagIsBigEndian;
    break;
  case AO_FMT_NATIVE:
    if(ao_is_big_endian())
      requestedDesc.mFormatFlags |= kAudioFormatFlagIsBigEndian;
    break;
  }
  requestedEndian = requestedDesc.mFormatFlags & kAudioFormatFlagIsBigEndian;
  if (format->bits > 8)
    requestedDesc.mFormatFlags |= kAudioFormatFlagIsSignedInteger;

  requestedDesc.mChannelsPerFrame = device->output_channels;
  requestedDesc.mSampleRate = format->rate;
  requestedDesc.mBitsPerChannel = format->bits;
  requestedDesc.mFramesPerPacket = 1;
  requestedDesc.mBytesPerFrame = requestedDesc.mBitsPerChannel * requestedDesc.mChannelsPerFrame / 8;
  requestedDesc.mBytesPerPacket = requestedDesc.mBytesPerFrame * requestedDesc.mFramesPerPacket;

  result = AudioUnitSetProperty (internal->outputAudioUnit,
				 kAudioUnitProperty_StreamFormat,
				 kAudioUnitScope_Input,
				 0,
				 &requestedDesc,
				 sizeof(requestedDesc));

  if (result) {
    aerror("AudioUnitSetProperty error => %d\n",(int)result);
    return 0;
  }

  /* what format did we actually get? */
  i_param_size = sizeof(requestedDesc);
  result = AudioUnitGetProperty(internal->outputAudioUnit,
			     kAudioUnitProperty_StreamFormat,
			     kAudioUnitScope_Input,
			     0,
			     &requestedDesc,
			     &i_param_size );
  if (result) {
    aerror("Failed to query modified device hardware settings => %d\n",(int)result);
    return 0;
  }

  /* If any major settings differ, abort */
  if(fabs(requestedDesc.mSampleRate-format->rate) > format->rate*.05){
    aerror("Unable to set output sample rate\n");
    return 0;
  }
  if(requestedDesc.mChannelsPerFrame != device->output_channels){
    aerror("Could not configure %d channel output\n",device->output_channels);
    return 0;
  }
  if(requestedDesc.mBitsPerChannel != format->bits){
    aerror("Could not configure %d bit output\n",format->bits);
    return 0;
  }
  if(requestedDesc.mBitsPerChannel != format->bits){
    aerror("Could not configure %d bit output\n",format->bits);
    return 0;
  }
  if(requestedDesc.mFormatFlags & kAudioFormatFlagIsFloat){
    aerror("Could not configure integer sample output\n");
    return 0;
  }
  if((requestedDesc.mFormatFlags & kAudioFormatFlagsNativeEndian) !=
     requestedEndian){
    aerror("Could not configure output endianness\n");
    return 0;
  }

  if (format->bits > 8){
    if(!(requestedDesc.mFormatFlags & kAudioFormatFlagIsSignedInteger)){
      aerror("Could not configure signed output\n");
      return 0;
    }
  }else{
    if((requestedDesc.mFormatFlags & kAudioFormatFlagIsSignedInteger)){
      aerror("Could not configure unsigned output\n");
      return 0;
    }
  }
  if(requestedDesc.mSampleRate != format->rate){
    awarn("Could not set sample rate to exactly %d; using %g instead.\n",
	  format->rate,(double)requestedDesc.mSampleRate);
  }

  /* set the channel mapping.  MacOSX AUHAL is capable of mapping any
     channel format currently representable in the libao matrix. */

  if(device->output_mask){
    AudioChannelLayout layout;
    memset(&layout,0,sizeof(layout));

    layout.mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelBitmap;
    layout.mChannelBitmap = device->output_mask;

    result = AudioUnitSetProperty(internal->outputAudioUnit,
				  kAudioUnitProperty_AudioChannelLayout,
				  kAudioUnitScope_Input, 0, &layout, sizeof(layout));
    if (result) {
      aerror("Failed to set audio channel layout => %d\n", (int)result);
    }
  }

  /* Set the audio callback */
  input.inputProc = (AURenderCallback) audioCallback;
  input.inputProcRefCon = internal;

  result = AudioUnitSetProperty( internal->outputAudioUnit,
				 kAudioUnitProperty_SetRenderCallback,
				 kAudioUnitScope_Input,
				 0, &input, sizeof( input ));


  if (result) {
    aerror("Callback set error => %d\n",(int)result);
    return 0;
  }

  result = AudioUnitInitialize (internal->outputAudioUnit);
  if (result) {
    aerror("AudioUnitInitialize() error => %d\n",(int)result);
    return 0;
  }

  /* Since we don't know how big to make the buffer until we open the device
     we allocate the buffer here instead of ao_plugin_device_init() */

  internal->bufferByteCount =  (internal->buffer_time * format->rate / 1000) *
    (device->output_channels * format->bits / 8);

  internal->firstValidByteOffset = 0;
  internal->validByteCount = 0;
  internal->buffer = malloc(internal->bufferByteCount);
  memset(internal->buffer, 0, internal->bufferByteCount);
  if (!internal->buffer) {
    aerror("Unable to allocate queue buffer.\n");
    return 0;
  }

  /* limited to stereo for now */
  //if(!device->output_matrix)
  //device->output_matrix=strdup("L,R");

  return 1;
}
Exemple #8
0
void play (char *source_string)
{
  transport_t *transport;
  format_t *format;
  data_source_t *source;
  decoder_t *decoder;

  decoder_callbacks_t decoder_callbacks;
  void *decoder_callbacks_arg;

  /* Preserve between calls so we only open the audio device when we 
     have to */
  static audio_format_t old_audio_fmt = { 0, 0, 0, 0, 0 };
  audio_format_t new_audio_fmt;
  audio_reopen_arg_t *reopen_arg;

  /* Flags and counters galore */
  int eof = 0, eos = 0, ret;
  int nthc = 0, ntimesc = 0;
  int next_status = 0;
  static int status_interval = 0;

  /* Reset all of the signal flags */
  sig_request.cancel   = 0;
  sig_request.skipfile = 0;
  sig_request.exit     = 0;
  sig_request.pause    = 0;

  /* Set preferred audio format (used by decoder) */
  new_audio_fmt.big_endian = ao_is_big_endian();
  new_audio_fmt.signed_sample = 1;
  new_audio_fmt.word_size = 2;

  /* Select appropriate callbacks */
  if (audio_buffer != NULL) {
    decoder_callbacks.printf_error = &decoder_buffered_error_callback;
    decoder_callbacks.printf_metadata = &decoder_buffered_metadata_callback;
    decoder_callbacks_arg = audio_buffer;
  } else {
    decoder_callbacks.printf_error = &decoder_error_callback;
    decoder_callbacks.printf_metadata = &decoder_metadata_callback;
    decoder_callbacks_arg = NULL;
  }

  /* Locate and use transport for this data source */  
  if ( (transport = select_transport(source_string)) == NULL ) {
    status_error(_("No module could be found to read from %s.\n"), source_string);
    return;
  }
  
  if ( (source = transport->open(source_string, &options)) == NULL ) {
    status_error(_("Cannot open %s.\n"), source_string);
    return;
  }

  /* Detect the file format and initialize a decoder */
  if ( (format = select_format(source)) == NULL ) {
    status_error(_("The file format of %s is not supported.\n"), source_string);
    return;
  }
  
  if ( (decoder = format->init(source, &options, &new_audio_fmt, 
			       &decoder_callbacks,
			       decoder_callbacks_arg)) == NULL ) {

    /* We may have failed because of user command */
    if (!sig_request.cancel)
      status_error(_("Error opening %s using the %s module."
		     "  The file may be corrupted.\n"), source_string,
		   format->name);
    return;
  }

  /* Decide which statistics are valid */
  select_stats(stat_format, &options, source, decoder, audio_buffer);

  /* Start the audio playback thread before we begin sending data */    
  if (audio_buffer != NULL) {
    
    /* First reset mutexes and other synchronization variables */
    buffer_reset (audio_buffer);
    buffer_thread_start (audio_buffer);
  }

  /* Show which file we are playing */
  decoder_callbacks.printf_metadata(decoder_callbacks_arg, 1,
				    _("Playing: %s"), source_string);

  /* Skip over audio */
  if (options.seekpos > 0.0) {
    if (!format->seek(decoder, options.seekpos, DECODER_SEEK_START)) {
      status_error(_("Could not skip %f seconds of audio."), options.seekpos);
      if (audio_buffer != NULL)
	buffer_thread_kill(audio_buffer);
      return;
    }
  }

  /* Main loop:  Iterates over all of the logical bitstreams in the file */
  while (!eof && !sig_request.exit) {
    
    /* Loop through data within a logical bitstream */
    eos = 0;    
    while (!eos && !sig_request.exit) {
      
      /* Check signals */
      if (sig_request.skipfile) {
	eof = eos = 1;
	break;
      }

      if (sig_request.pause) {
	if (audio_buffer)
	  buffer_thread_pause (audio_buffer);

	kill (getpid(), SIGSTOP); /* We block here until we unpause */
	
	/* Done pausing */
	if (audio_buffer)
	  buffer_thread_unpause (audio_buffer);

	sig_request.pause = 0;
      }


      /* Read another block of audio data */
      ret = format->read(decoder, convbuffer, convsize, &eos, &new_audio_fmt);

      /* Bail if we need to */
      if (ret == 0) {
	eof = eos = 1;
	break;
      } else if (ret < 0) {
	status_error(_("Error: Decoding failure.\n"));
	break;
      }

      
      /* Check to see if the audio format has changed */
      if (!audio_format_equal(&new_audio_fmt, &old_audio_fmt)) {
	old_audio_fmt = new_audio_fmt;
	
	/* Update our status printing interval */
	status_interval = new_audio_fmt.word_size * new_audio_fmt.channels * 
	  new_audio_fmt.rate / options.status_freq;
	next_status = 0;

	reopen_arg = new_audio_reopen_arg(options.devices, &new_audio_fmt);

	if (audio_buffer)	  
	  buffer_insert_action_at_end(audio_buffer, &audio_reopen_action,
				      reopen_arg);
	else
	  audio_reopen_action(NULL, reopen_arg);
      }
      

      /* Update statistics display if needed */
      if (next_status <= 0) {
	display_statistics(stat_format, audio_buffer, source, decoder); 
	next_status = status_interval;
      } else
	next_status -= ret;

      if (options.endpos > 0.0 && options.endpos <= current_time(decoder)) {
	eof = eos = 1;
	break;
      }


      /* Write audio data block to output, skipping or repeating chunks
	 as needed */
      do {
	
	if (nthc-- == 0) {
	  if (audio_buffer)
	    buffer_submit_data(audio_buffer, convbuffer, ret);
	  else
	    audio_play_callback(convbuffer, ret, eos, &audio_play_arg);
	  
	  nthc = options.nth - 1;
	}
	
      } while (!sig_request.exit && !sig_request.skipfile &&
	       ++ntimesc < options.ntimes);

      ntimesc = 0;
            
    } /* End of data loop */
    
  } /* End of logical bitstream loop */
  
  /* Done playing this logical bitstream.  Clean up house. */

  if (audio_buffer) {
    
    if (!sig_request.exit && !sig_request.skipfile) {
      buffer_mark_eos(audio_buffer);
      buffer_wait_for_empty(audio_buffer);
    }

    buffer_thread_kill(audio_buffer);
  }

  /* Print final stats */
  display_statistics_quick(stat_format, audio_buffer, source, decoder); 
   
  
  format->cleanup(decoder);
  transport->close(source);
  status_reset_output_lock();  /* In case we were killed mid-output */

  status_message(1, _("Done."));
  
  if (sig_request.exit)
    exit (0);
}