Logo Search packages:      
Sourcecode: yiff version File versions  Download package

ysound.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#if defined(__linux__)
# include <linux/soundcard.h>
#elif defined(__FreeBSD__)
# include <machine/soundcard.h>
#endif

#ifdef YSHM_SUPPORT
# include "../include/shm.h"
#endif

#include "ytypes.h"
#include "midiiow.h"
#include "audiocd.h"
#include "ysound.h"
#include "options.h"


int YSoundInit(Recorder *recorder, Audio *audio);
int YSoundShellOut(Recorder *recorder);
void YSoundCaliberateCycle(Recorder *recorder);
int YSoundPlayBuffer(Recorder *recorder);
void YSoundSync(Recorder *recorder, int options);
void YSoundShutdown(Recorder *recorder);


#define MIN(a,b)        ((a) < (b) ? (a) : (b))
#define MAX(a,b)        ((a) > (b) ? (a) : (b))
#define CLIP(a,l,h)     (MIN(MAX((a),(l)),(h)))
#define ABSOLUTE(x)     (((x) < 0) ? ((x) * -1) : (x))


/*
 *    Initializes the recorder, the Audio values specified in
 *    audio will be coppied to the recorder's Audio values and
 *    initialized.
 *
 *    Any errors which occured from opening any devices will be printed
 *    to stderr.
 */
int YSoundInit(Recorder *recorder, Audio *audio)
{
      int status;
      Sound *sound_ptr;
      Audio *audio_ptr;
      MIDIAudio *midi_audio_ptr;
#ifdef YSHM_SUPPORT
      int shm_id;
#endif
#ifdef OSS_BUFFRAG
      unsigned int fragment, num_fragments, fragment_size;
      int buffer_size;
#endif      /* OSS_BUFFRAG */
/*    struct mixer_info mixer_info_buf; */
      struct synth_info synth_info_buf;


      if((recorder == NULL) || (audio == NULL))
          return(-1);

      /* Get pointers to substructures on recorder. */
      sound_ptr = &recorder->sound;
      audio_ptr = &recorder->audio;
      midi_audio_ptr = &recorder->audio.midi_audio;

      /* Reset recorder values. */
      memset(sound_ptr, 0x00, sizeof(Sound));
      memset(audio_ptr, 0x00, sizeof(Audio));
      memset(midi_audio_ptr, 0x00, sizeof(MIDIAudio));


      /* Get values from input Audio structure. */
      sound_ptr->buffer_length = 0;
      sound_ptr->buffer_content_length = 0;

      sound_ptr->device_buffer_length = 0;
      sound_ptr->device_buffer_content_length = 0;

      audio_ptr->cycle_set = audio->cycle_set;

      audio_ptr->cycle.ms = audio->cycle.ms;
      audio_ptr->cycle.us = audio->cycle.us;
      audio_ptr->compensated_cycle.ms = audio_ptr->cycle.ms;
      audio_ptr->compensated_cycle.us = audio_ptr->cycle.us;

      audio_ptr->write_ahead.ms = audio->write_ahead.ms;
      audio_ptr->write_ahead.us = audio->write_ahead.us;

      audio_ptr->cycle_ahead_left.ms = audio->write_ahead.ms;
      audio_ptr->cycle_ahead_left.us = audio->write_ahead.us;

      audio_ptr->cumulative_latency.ms = 0;
      audio_ptr->cumulative_latency.us = 0;
      
      audio_ptr->sample_size = audio->sample_size;
      audio_ptr->channels = audio->channels;  
      audio_ptr->sample_rate = audio->sample_rate;
      audio_ptr->bytes_per_second = audio->sample_rate *
          MAX((audio->sample_size / 8), 1)
      ;

#ifdef OSS_BUFFRAG
      audio_ptr->allow_fragments = audio->allow_fragments;
      audio_ptr->num_fragments = audio->num_fragments;  
      audio_ptr->fragment_size = audio->fragment_size;  
#endif  /* OSS_BUFFRAG */

      audio_ptr->flip_stereo = audio->flip_stereo;

      audio_ptr->direction = audio->direction;
      audio_ptr->audio_mode_name = NULL;
      audio_ptr->fd = -1;
      audio_ptr->mixer_fd = -1;

      audio_ptr->sound_name = NULL;

      audio_ptr->shelled_out = False;


      /* ******************************************************** */
      /* Initialize MIDI IO resources. */
#ifdef ALSA_RUN_CONFORM
      midi_audio_ptr->midi_device_number = MAX(
          option.midi_device_number, 0
      );
#endif      /* ALSA_RUN_CONFORM */
      if(MIDIIOWInit(midi_audio_ptr))
      {
          YSoundShutdown(recorder);
          return(-1);
      }

      /* ******************************************************** */
      /* Initialize audio CD context. */
      audio_ptr->audiocd_context = AudioCDInit();

      /* ******************************************************** */
      /* Open audio device. */
      audio_ptr->fd = open(
          fname.device,
          ((audio_ptr->direction == AUDIO_DIRECTION_RECORD) ?
            O_RDONLY : O_WRONLY
          )
      );
      if(audio_ptr->fd < 0)
      {
          fprintf(
            stderr,
            "%s: Cannot open for %s.\n",
            fname.device,
            ((audio_ptr->direction == AUDIO_DIRECTION_RECORD) ?
                "recording" : "playing"
            )
          );

          YSoundShutdown(recorder);
          return(-1);
      }

      /* Open mixer. */
      if(fname.mixer[0] != '\0')
      {
          audio_ptr->mixer_fd = open(
            fname.mixer,
              O_RDWR
          );
          if(audio_ptr->mixer_fd < 0)
          {
            /* Warn about failure to open mixer, do not fail
             * entire procedure.
             */
            fprintf(stderr,
                "%s: Cannot open.\n",
                fname.mixer
            );
          }
      }
      else
      {
          /* No mixer specified. */
          audio_ptr->mixer_fd = -1;
      }


#ifdef OSS_BUFFRAG
      /*
       * OSS ioctl() call SNDCTL_DSP_SETFRAGMENT:
       *
       * Accepts an int parameter which has format 0x00nn00ss where
       * the nn is max number of buffer fragments (between 0x02 and 0xff)
       * and the ss gives indirectly the size of a buffer fragment
       * (fragment_size = (1 << ss)). Valid sizes are between
       * (ss=0x07 -> 128 bytes and ss=0x11 (17 dec) -> 128k).
       *
       *      Linux documentation suggestion: 0x00020009
       *      XBlast Sound Server: 0x0004000a
       */
      if(audio_ptr->allow_fragments)
      {
          num_fragments = audio_ptr->num_fragments;
          fragment_size = audio_ptr->fragment_size;

          /* Pack fragment argument. */
          fragment = (num_fragments << 16) | fragment_size;

          /* Set new fragment parameters. */
          status = ioctl(
            audio_ptr->fd,
            SNDCTL_DSP_SETFRAGMENT,
            &fragment
          );
          if(status == -1)
          {
            fprintf(stderr,
     "%s: Warning: Cannot set buffer fragment configuration to 0x%.8x\n",
                fname.device, fragment
            );
          }
      }
#endif  /* OSS_BUFFRAG */ 


      /* Set sample format (aka sample size or number of bits). */
      if(ioctl(
          audio_ptr->fd,
          SNDCTL_DSP_SAMPLESIZE,
          &audio_ptr->sample_size
      ) == -1)
      {
          fprintf(stderr,
            "%s: Warning: Cannot set sample size to %i bits.\n",
            fname.device, audio_ptr->sample_size
          );
      }

      /* Set channels (1 or 2, mono or stereo respectivly). */
      if(ioctl(
          audio_ptr->fd,
          SNDCTL_DSP_CHANNELS,
          &audio_ptr->channels
      ) == -1)
      {
          fprintf(stderr,
            "%s: Warning: Cannot set channels to %i.\n",
            fname.device, audio_ptr->channels
          );
      }

      /* Set sample rate. */
      if(ioctl(
          audio_ptr->fd,
          SNDCTL_DSP_SPEED,   /* aka SOUND_PCM_WRITE_RATE. */
          &audio_ptr->sample_rate
      ) == -1)
      {
          fprintf(stderr,
            "%s: Warning: Cannot set sample rate to %i Hz.\n",
            fname.device, audio_ptr->sample_rate
          );
      }


/* Query audio format. */
/*
ioctl(audio_ptr->fd, AFMT_QUERY, &status);
printf("Format: %i\n", status);
 */

#ifdef OSS_BUFFRAG
      /* OSS requires exact audio buffer segment size (because of the
       * fragmented buffers).
       *
       * All write()'s to the device must have a buffer of exactly
       * this size.  SNDCTL_DSP_GETBLKSIZE must be called after
       * SNDCTL_DSP_SETFRAGMENT.
       */
      status = ioctl(
          audio_ptr->fd,
          SNDCTL_DSP_GETBLKSIZE,
          &buffer_size
      );
      if(status == -1)
      {
          fprintf(stderr,
            "%s: Warning: Cannot get buffer size.\n",
            fname.device
          );
      }

      sound_ptr->buffer_length = buffer_size;
      sound_ptr->device_buffer_length = buffer_size;


      /*   Calculate theoretical and compensated cycle.
       *
       *   Warning: This value is rarly correct and should be
       *   set manually by being defined in a YMode.
       */
      if(audio_ptr->cycle_set == CYCLE_SET_HARDWARE)
      {
          audio_ptr->cycle.ms =
            (int)sound_ptr->buffer_length *
            1000 / (int)audio_ptr->sample_rate /
              MAX(((int)audio_ptr->sample_size / 8), 1);
          audio_ptr->cycle.us =
            (double)sound_ptr->buffer_length /
            (double)audio_ptr->sample_rate * 1000000 /
            MAX(((int)audio_ptr->sample_size / 8), 1);
          audio_ptr->cycle.us = audio_ptr->cycle.us -
              (audio_ptr->cycle.ms * 1000);

          /* For now, compensated is the same as theoretical. */
          audio_ptr->compensated_cycle.ms = audio_ptr->cycle.ms;
          audio_ptr->compensated_cycle.us = audio_ptr->cycle.us;
      }

      /* Not sure what this is... divides fragment segments? */
/*
      status = 4;
      ioctl(audio_ptr->fd, SOUND_PCM_SUBDIVIDE, &status);
 */

#endif      /* OSS_BUFFRAG */



      /* ********************************************************** */
      /* Allocate "midway" Sound buffer. */
#ifdef YSHM_SUPPORT
      sound_ptr->buffer = (SoundBuffer *)SHMNew(
          sound_ptr->buffer_length * sizeof(SoundBuffer),
          &shm_id
      );
      sound_ptr->buffer_shm_id = shm_id;
#else
      sound_ptr->buffer = (SoundBuffer *)calloc(
          sound_ptr->buffer_length,
          sizeof(SoundBuffer)
      );
#endif      /* YSHM_SUPPORT */
      if(sound_ptr->buffer == NULL)
      {
          YSoundShutdown(recorder);
          return(-1);
      }

      /* Allocate Sound buffer shared with the actual DSP device. */
      sound_ptr->device_buffer = (SoundBuffer *)calloc(  
          sound_ptr->device_buffer_length,
          sizeof(SoundBuffer)
      );
      if(sound_ptr->device_buffer == NULL)
      {
          YSoundShutdown(recorder);
          return(-1);
      }


      /* Get sound name. */
/*
      status = ioctl(
          audio_ptr->fd, SOUND_MIXER_INFO, &mixer_info_buf
      );
      if(status != -1)
      {
          free(audio_ptr->sound_name);
          audio_ptr->sound_name = strdup(mixer_info_buf.name);
      }
 */
      synth_info_buf.device = 0;
      status = ioctl(
          audio_ptr->fd, SNDCTL_SYNTH_INFO, &synth_info_buf
      );
      if(status != -1)
      {
          free(audio_ptr->sound_name);
          audio_ptr->sound_name = strdup(synth_info_buf.name);
      }


      /* Caliberate cycle (only if cycle is to be set by program). */
      if(audio_ptr->cycle_set == CYCLE_SET_PROGRAM)
          YSoundCaliberateCycle(recorder);

/*
fprintf(stderr, "Cycle: %i.%i (comp %i.%i)  Buffer: %i bytes\n",
 audio_ptr->cycle.ms,
 audio_ptr->cycle.us,
 audio_ptr->compensated_cycle.ms,
 audio_ptr->compensated_cycle.us,
 sound_ptr->device_buffer_length
);

fprintf(stderr, "Channels: %i  Bits: %i  Sample Rate: %i\n",
 audio_ptr->channels,
 audio_ptr->sample_size,
 audio_ptr->sample_rate
);   
*/


      return(0);
}

/*
 *    Shells out the recorder, by closing all device descriptors.
 *
 *    This simulates the recorder being active but in reality
 *    any operations to the actual recorder device(s) compoents
 *    will not be performed. This fools the clients into thinking
 *    that the sound objects are still being played.
 *
 *    To reinitialize the recorder again, you need to call
 *    YSoundShutdown() and then YSoundInit().
 */
int YSoundShellOut(Recorder *recorder)
{
      Audio *audio_ptr;


      if(recorder == NULL)
          return(-1);

      audio_ptr = &recorder->audio;


      /* Close DSP device. */
      if(audio_ptr->fd > -1)
      {
          close(audio_ptr->fd);
          audio_ptr->fd = -1;
      }

      audio_ptr->shelled_out = True;

      /* Leave mixer device up. */

/* Not sure what to do about the MIDI device. */


      return(0);
}

/*
 *    Attempts to caliberate the right audio.cycle and
 *    audio.compensated_cycle for the sound device.
 *
 *    Consider this (program calculating cycle).
 */
void YSoundCaliberateCycle(Recorder *recorder)
{
#define TOTAL_SYNC_PASSES     4

      int i, fd;
      time_t prev_delta_u, delta_u;

      Sound *sound_ptr;
      Audio *audio_ptr;
      YTime start_time, end_time;

      SoundBuffer *buf;
      YDataLength buf_len;


      if(recorder == NULL)
          return;

      /* Get pointers to substructures. */
      sound_ptr = &recorder->sound; 
      audio_ptr = &recorder->audio; 

      /* Not allow program to set cycle? */
      if(audio_ptr->cycle_set != CYCLE_SET_PROGRAM)
          return;

      if(sound_ptr->device_buffer == NULL)
          return;
      if(recorder->audio.fd < 0)
          return;

      fd = recorder->audio.fd;
      buf = sound_ptr->device_buffer;
      buf_len = sound_ptr->device_buffer_length;


      /* Reset actual buffer for sound device. */
      memset(buf, (SoundBuffer)0x00, buf_len);


      /* ****************************************************** */
      /* Begin syncing. */

      /*   Sync in case audio is still playing.  Otherwise
       *   the average cycle calculated is garbage.
       */
      ioctl(recorder->audio.fd, SNDCTL_DSP_SYNC, 0);

      /*   Write and sync a couple of times, get average cycle
       *   value.
       */
      GetCurrentTime(&start_time);
      write(fd, buf, buf_len);
      ioctl(recorder->audio.fd, SNDCTL_DSP_SYNC, 0);
      GetCurrentTime(&end_time);

      delta_u = MAX(((end_time.ms * 1000) + end_time.us) -
                  ((start_time.ms * 1000) + start_time.us), 0
              );
      prev_delta_u = delta_u;

      for(i = 0; i < TOTAL_SYNC_PASSES; i++)
      {
          GetCurrentTime(&start_time);
          write(fd, buf, buf_len);
          ioctl(recorder->audio.fd, SNDCTL_DSP_SYNC, 0);
          GetCurrentTime(&end_time);

          delta_u = MAX(((end_time.ms * 1000) + end_time.us) -
                    ((start_time.ms * 1000) + start_time.us), 0
                  );

          /* Average it. */
          delta_u = (prev_delta_u + delta_u) / 2;
          prev_delta_u = delta_u;
      }

      audio_ptr->cycle.ms = delta_u / 1000;
      audio_ptr->cycle.us = delta_u % 1000;

      audio_ptr->compensated_cycle.ms = audio_ptr->cycle.ms;
      audio_ptr->compensated_cycle.us = audio_ptr->cycle.us;
}


/*
 *    Copys the midway buffer to the sound buffer and attempts
 *    to have the sound device play it.  Afterwards the midway
 *    buffer's contents are reset.
 */
int YSoundPlayBuffer(Recorder *recorder)
{
      int bytes_written;
      Sound *sound_ptr;
      Audio *audio_ptr;


      if(recorder == NULL)
          return(-1);

      /* Get pointers to substructures. */
      sound_ptr = &recorder->sound;
      audio_ptr = &recorder->audio;


      /* Check if DSP device is opened. If it is not, then still
       * return 0 because it may need to indicate it is shelled
       * out.
       */
      if(audio_ptr->fd < 0)
          return(0);

      /* Make sure buffers are allocated. */
      if((sound_ptr->buffer == NULL) ||
         (sound_ptr->device_buffer == NULL)
      )
          return(-1);

      /* **************************************************** */
      /* Copy buffer to device_buffer. */

      memcpy(
          sound_ptr->device_buffer,       /* target. */
          sound_ptr->buffer,              /* source. */
          MIN(sound_ptr->buffer_length,
              sound_ptr->device_buffer_length
          )
      );

      /* Write to audio device. */
      bytes_written = write(
          audio_ptr->fd,
          sound_ptr->device_buffer,
          sound_ptr->device_buffer_length
      );


      /* Clear midway buffer. */
      memset(
          sound_ptr->buffer,
          (SoundBuffer)0x00,
          sound_ptr->buffer_length
      );


      return(0);
}

/*
 *    Syncronizes recorder with process, then resets cycle ahead
 *    left value in recorder's Audio.
 */
void YSoundSync(Recorder *recorder, int options)
{
      Audio *audio_ptr;


      if(recorder == NULL)
          return;

      audio_ptr = &recorder->audio;


      /* Tell DSP device to sync if it's opened. */
      if(audio_ptr->fd > -1)
      {
          /* Sync and end any current playback or recording for
           * the DSP device.
           */
          ioctl(audio_ptr->fd, SNDCTL_DSP_SYNC, 0);
      }

      /* Reset cycle ahead time, so writes to the DSP
       * device are performed more frequently for a while.
       */
      audio_ptr->cycle_ahead_left.ms = audio_ptr->write_ahead.ms;
      audio_ptr->cycle_ahead_left.us = audio_ptr->write_ahead.us;

/*
fprintf(stderr,
 "Sync: Cyclc ahead reset to %ld ms\n",
 (long)((audio_ptr->cycle_ahead_left.ms * 1000) +
 audio_ptr->cycle_ahead_left.us)
);
 */
}

/*
 *    Shuts down the recorder, deallocating any of its
 *    allocated resources.
 */
void YSoundShutdown(Recorder *recorder)
{
      Sound *sound_ptr;
      Audio *audio_ptr;
      MIDIAudio *midi_audio_ptr;


      if(recorder == NULL)
          return;


      /* Get pointers to substructures. */
      sound_ptr = &recorder->sound;
      audio_ptr = &recorder->audio;
      midi_audio_ptr = &recorder->audio.midi_audio;


      /* Shutdown MIDI IO resources. */
      MIDIIOWShutdown(midi_audio_ptr);

      /* Shutdown audio CD context. */
      AudioCDShutdown(audio_ptr->audiocd_context);
      audio_ptr->audiocd_context = NULL;


      /* Close recorder's audio device as needed. */
      if(audio_ptr->fd > -1)
      {
          /* Wait for sound to finish playing. */
          ioctl(audio_ptr->fd, SNDCTL_DSP_SYNC, 0);

          close(audio_ptr->fd);
          audio_ptr->fd = -1;
      }

      /* Close recorder's mixer as needed. */
      if(audio_ptr->mixer_fd > -1)
      {
          close(audio_ptr->mixer_fd);
          audio_ptr->mixer_fd = -1;
      }


      /* Free allocated substructures. */
#ifdef YSHM_SUPPORT
      SHMUnref(sound_ptr->buffer);
#else
      free(sound_ptr->buffer);
#endif
      sound_ptr->buffer = NULL;
      sound_ptr->buffer_length = 0;
      sound_ptr->buffer_content_length = 0;

      free(sound_ptr->device_buffer);
      sound_ptr->device_buffer = NULL;
      sound_ptr->device_buffer_length = 0;
      sound_ptr->device_buffer_content_length = 0;

      memset(&audio_ptr->cycle, 0, sizeof(YDeltaTime));
      memset(&audio_ptr->compensated_cycle, 0, sizeof(YDeltaTime));

      memset(&audio_ptr->write_ahead, 0, sizeof(YDeltaTime));
      memset(&audio_ptr->cycle_ahead_left, 0, sizeof(YDeltaTime));
      memset(&audio_ptr->cumulative_latency, 0, sizeof(YDeltaTime));

      audio_ptr->sample_size = 0;
      audio_ptr->channels = 0;
      audio_ptr->sample_rate = 0;
      audio_ptr->bytes_per_second = 0;

#ifdef OSS_BUFFRAG
      audio_ptr->allow_fragments = False;
      audio_ptr->num_fragments = 0;
      audio_ptr->fragment_size = 0;
#endif /* OSS_BUFFRAG */

      audio_ptr->flip_stereo = False;
      free(audio_ptr->audio_mode_name);
      audio_ptr->audio_mode_name = NULL;
      audio_ptr->fd = -1;
      audio_ptr->mixer_fd = -1;
      free(audio_ptr->sound_name);
      audio_ptr->sound_name = NULL;
      audio_ptr->shelled_out = False;
}

Generated by  Doxygen 1.6.0   Back to index