/* Copyright (c) 1996 MultiMedia Access Corporation - Osprey Technologies Division. 
 * 1150 S.E. Maynard Rd., Suite 110, Cary, NC 27511 USA. All Rights Reserved.
 *
 * This product and related documentation is protected by copyright laws and
 * international treaties; however, the user of this software is granted a 
 * nonexclusive, royalty free license to modify, make derivitive works, use,
 * distribute, or otherwise incorporate this software in products which use
 * or incorporate the SLIV-Video product.
 *
 * MMAC reserves the right to change any products herein at any time and 
 * without notice. MMAC does not assume any liability arising from the 
 * application or use of this product, except as expressly agreed to in 
 * writing by MMAC. Use and purchase of this product does not convey a license
 * under any patent rights, copyrights, trademark rights, or any other 
 * intellectual property rights of MMAC, Sun Microsystems, or others. 
 */

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <math.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/file.h>
#include <sys/filio.h>
#include <sys/stat.h>
#include <sys/signal.h>
#include <sys/ioctl.h>
#include <stropts.h>

#include <sys/audioio.h>

#include <multimedia/libaudio.h>
#include <multimedia/audio_device.h>
#include <multimedia/audio_encode.h>

#include "oti_libaudio.h"
#include "oti_audio_device.h"

#define BUFFER_SIZE (1024*4)
#define MIN_SIZE    (50)

int   Audio_fd    = -1;
int   Audioctl_fd = -1;

char  aud_data[BUFFER_SIZE];
char  *aud_ptr;
int   byte_count = 0;
int   looping = 0;
int   poll_enabled = 0;
char  filename[80];
char DEFAULT_DEVICE[] = "/dev/o1k0";

void usage(char* pgm)
{
  fprintf(stderr,"usage: %s [options]\n",pgm);
  fprintf(stderr,"Legal Options:                                   Defaults\n");
  fprintf(stderr, " <-D device>   : device name                    /dev/o1k0\n");  
  fprintf(stderr, " <-f format>   : encoding format                0\n");
  fprintf(stderr, "                 (0=ULAW,  1=ALAW, 2=PCM8,\n");
  fprintf(stderr, "                  3=PCM16, 4=G722, 5=G728)\n");
  fprintf(stderr, " <-r rate>     : sample rate                    8000\n");
  fprintf(stderr, "                 (8000, 11025, 16000, 22050)\n");
  fprintf(stderr, " <-c channels> : channels (1,2)                 1\n");
  fprintf(stderr, " <-p port>     : input port (0=LINE,1=MIC)      0\n");
  fprintf(stderr, " <-g gain>     : input gain (0..1.0)            0.125\n");
  fprintf(stderr, " <-v gain>     : output gain (0..1.0)           1.0\n");  
  fprintf(stderr, " <-b size>     : buffer size                    1000\n");  
  fprintf(stderr, " <-s secs>     : seconds to run for, 0==forever 0\n");  
}

/*------------------------------------------------------------------------
 *
 *  Function:	record_service
 *
 *  Description:
 *	This routine is called async from the POLL signal handler.
 *      Care is taken not to enter the routine recursively. Typically
 *      the POLL signal was issued because we can now either read or
 *      write to the audio device successfully. We read some data from
 *      the audio device then write this audio data back to the device.
 *      Thus, we are looping audio input to audio output.
 *      If an error occurs (overflow/underflow), the error is cleared
 *      and we continue looping.
 *
 *  Parameters: none
 *	
 *  Returns: void
 *	
 *------------------------------------------------------------------------*/

void loop_service()
{
  int	read_size;
  int	rtn, wrtn;
  int   aud_amount;
  unsigned int error;

  static int in_loop_service = 0;

  /*
   * avoid recursive calls
   */
  if (in_loop_service != 0){
    return;
  }
  in_loop_service = 1;

  if (!looping){
    return;
  }
  
  do {
    /* Determine how much we can read */
    if (oti_ioctl(Audio_fd, FIONREAD, &read_size) < 0) {
      perror("loop_service error: oti_ioctl FIONREAD");
      break;
    }

    /*
     * Check for underflow: Sun's /dev/audio might stop
     * recording on overflow. This is detected here and
     * the error is cleared. The Osprey doesn't stop
     * recording on overflow.
     */
    oti_audio_get_record_error(Audio_fd, &error);
    if (error){
      error = 0;
      oti_audio_set_record_error(Audio_fd, &error);
    }

    /*
     * Check for underflow: Sun's /dev/audio may stop
     * playing on underflow. This is detected here and
     * the error is cleared. The Osprey doesn't stop
     * playing on underflow, silence is played instead.
     */
    oti_audio_get_play_error(Audio_fd, &error);
    if (error){
      error = 0;
      oti_audio_set_play_error(Audio_fd, &error);
    }
    
    /* Don't bother if there is nothing to read or if not much. */
    if ((read_size == 0) || ((read_size < MIN_SIZE)))
      break;

    read_size = (read_size < BUFFER_SIZE) ? read_size : BUFFER_SIZE;
    
    if (read_size == 0)
      break;

    rtn = oti_read(Audio_fd, aud_data, read_size);

    if (rtn <= 0) {
      break;
    }
    
    /* play the audio data */
    aud_amount = rtn;
    aud_ptr = aud_data;
    while (aud_amount > 0) {
      /* write as much data as possible to device */
      rtn = oti_write(Audio_fd, aud_ptr, aud_amount);
      if (rtn > 0) {
        aud_amount -= rtn;
        aud_ptr += rtn;
      } else {
        break;
      }
    }
    
    byte_count += aud_amount;
    
  } while (0);

  in_loop_service = 0;
}

/*------------------------------------------------------------------------
 *
 *  Function:	stop_loop
 *
 *  Description:
 *	Stops looping by pausing the audio device and then flushing it.
 *
 *  Parameters: none
 *	
 *  Returns: void
 *	
 *------------------------------------------------------------------------*/

void stop_loop()
{
  /* Pause the device */
  oti_audio_pause(Audio_fd);
  /* Flush the device */
  oti_audio_flush(Audio_fd);
}

/*------------------------------------------------------------------------
 *
 *  Function:	g_exit
 *
 *  Description:
 *	Hanldes signals which would cause process to exit (like Ctl-C).
 *      Stops looping and closes the various file descriptors. This
 *      routine can be called directly.
 *
 *  Parameters:
 *	int signo: signal number
 *	
 *  Returns: void
 *	
 *------------------------------------------------------------------------*/

void g_exit(int signo)
{
  int rc;

  poll_enabled = 0;
  
  stop_loop();
  looping = 0;
  
  if (Audio_fd != -1){
    oti_close(Audio_fd);
    Audio_fd = -1;
  }
  
  if (Audioctl_fd != -1){
    oti_close(Audioctl_fd);
    Audioctl_fd = -1;
  }

  exit(0);
}

/*------------------------------------------------------------------------
 *
 *  Function:	poll_handler
 *
 *  Description:
 *	Hanldes SIGPOLL signal. A SIGPOLL is generated by the audio device
 *	when input and output ready conditions change. This is used to play
 *      or record audio data in an async, efficient manner.
 *	
 *  Parameters:
 *	int signo: signal number
 *	
 *  Returns: void
 *	
 *------------------------------------------------------------------------*/

void poll_handler(int signo)
{
  /* must re-signal immediately in case another signal occurs */
  signal(signo, poll_handler);

  /*
   * you can only make oti_xxx calls in signal handler when you
   * know that you are not making such calls anywhere else.
   */
     
  if (poll_enabled){
    loop_service();
  }
}

/*------------------------------------------------------------------------
 *
 *  Function:	get_audioctl_name
 *
 *  Description:
 *	Given an audio device name, determine the control device name
 *	
 *  Parameters:
 *	char* adevice_ctl_name_ptr: ptr to place control device name into
 *      char* adevice_name_ptr:     ptr to audio device name
 *	
 *  Returns: void
 *	
 *  Side Effects:
 *	Modifies buffer pointed to by adevice_ctl_name_ptr. 
 *	
 *  Notes:
 *	adevice_ctl_name_ptr must point to a reasonable sized buffer
 *	
 *------------------------------------------------------------------------*/

void get_audioctl_name(char* adevice_ctl_name_ptr, char* adevice_name_ptr)
{
  int i;
  char  c = '\0';
  char* s;

  /*
   *    Given         Produce      Example:
   *   xxxxaudio -> xxxxxaudioctl  /dev/audio -> /dev/audioctl
   *   xxxxo1kN  -> xxxxo1kctlN    /dev/o1k0  -> /dev/o1kctl0
   */

  strcpy(adevice_ctl_name_ptr, adevice_name_ptr);
  i = strlen(adevice_name_ptr);
  
  if (strstr(adevice_name_ptr,"o1k") != NULL){
    s = &adevice_ctl_name_ptr[i-1];
    c = *s;      
  } else {
    s = &adevice_ctl_name_ptr[i];
  }

  *s++ = 'c';
  *s++ = 't';
  *s++ = 'l';
  *s++ = c;
  *s++ = '\0';

}

void main(int argc, char* argv[])
{
  int   i;
  int   rc;
  char* aud_device = DEFAULT_DEVICE;
  char  audctl_device[80];

  int format   = 0;
  int rate     = 8000;
  int channels = 1;
  unsigned int port = AUDIO_LINE_IN;
  
  double  igain = 0.125;
  double  ogain = 1.0;  
  double  mgain = 0.0;  /* looping audio: want monitor gain at 0 */
  
  unsigned int value;
  unsigned int buffer_size = 1000;
  
  Audio_hdr  hdr;
  Audio_info tmpinfo;
      
  double seconds = 0.0;
  double t;
  struct timeval t0, t1;

  for (i=1; i<argc; i++){
    switch (argv[i][1])
    {
      case 'D':
      {
        aud_device = argv[++i];
        break;
      }
      case 'f':
      {
        format = atoi(argv[++i]);
        break;
      }
      case 'r':
      {
        rate = atoi(argv[++i]);
        break;
      }
      case 'c':
      {
        channels = atoi(argv[++i]);
        break;
      }
      case 'p':
      {
        port = atoi(argv[++i]);
        break;
      }
      case 'g':
      {
        igain = atof(argv[++i]);
        break;
      }
      case 'v':
      {
        ogain = atof(argv[++i]);
        break;
      }
      case 'b':
      {
        buffer_size = atoi(argv[++i]);
        break;
      }
      case 's':
      {
        seconds = (double)atoi(argv[++i]); 
        break;
      }
      case '?':
      default:
      {
        usage(argv[0]);
        exit(0);
      }
    }
  }

  /*
   * setup signal handlers
   */
  signal(SIGINT,  g_exit);
  signal(SIGPOLL, poll_handler);

  printf("initializing audio device...\n");

  /*
   * This call must be made before any other
   */
  if ((rc = oti_audio_init(NULL, NULL)) < 0){
    fprintf(stderr,"oti_audio_init failed %d\n",rc);
    g_exit(0);
  }
  
  /*
   * Open audio device and setup POLL signal
   */
  if ((Audio_fd = oti_open(aud_device, O_RDWR | O_NDELAY))<0){
    fprintf(stderr,"could not open osprey audio device\n");
    exit(0);
  }
  if (oti_ioctl(Audio_fd, I_SETSIG, (void*)(S_INPUT | S_OUTPUT | S_MSG)) < 0){
    perror("I_SETSIG ioctl");
    g_exit(0);
  }

  /*
   * Open audio control device and setup POLL signal
   */
  get_audioctl_name(audctl_device, aud_device);
  if ((Audioctl_fd = oti_open(audctl_device, O_RDWR))<0){
    fprintf(stderr,"could not open audio ctl device\n");
    g_exit(0);
  }    
  if (oti_ioctl(Audioctl_fd, I_SETSIG, (void*)(S_MSG)) < 0){
    perror("I_SETSIG ioctl");
    g_exit(0);
  }

  /*
   * Set the desired input port and gains
   */
  oti_audio_set_record_port(Audio_fd, &port);
  oti_audio_set_record_gain(Audio_fd, &igain);
  oti_audio_set_monitor_gain(Audio_fd, &mgain);  
  oti_audio_set_play_gain(Audio_fd, &ogain);

  /*
   * Determine the desired format
   */
  switch (format)
  {
    case 0: hdr.encoding = AUDIO_ENCODING_ULAW;   break;
    case 1: hdr.encoding = AUDIO_ENCODING_ALAW;   break;
    case 2: hdr.encoding = AUDIO_ENCODING_LINEAR; break;
    case 3: hdr.encoding = AUDIO_ENCODING_LINEAR; break;
    case 4: hdr.encoding = AUDIO_ENCODING_G722;   break;
    case 5: hdr.encoding = AUDIO_ENCODING_G728;   break;
    default: hdr.encoding = AUDIO_ENCODING_ULAW;
  }

  switch (rate)
  {
    case 8000:  hdr.sample_rate =  8000; break;
    case 11025: hdr.sample_rate = 11025; break;
    case 16000: hdr.sample_rate = 16000; break;
    case 22050: hdr.sample_rate = 22050; break;
    default:    hdr.sample_rate =  8000; 
  }

  switch (channels)
  {
    case 1:  hdr.channels = 1; break;
    case 2:  hdr.channels = 2; break;
    default: hdr.channels = 1; break;
  }

  switch(hdr.encoding)
  {
    case AUDIO_ENCODING_ULAW:
    case AUDIO_ENCODING_ALAW:    
    {
      hdr.samples_per_unit = 1;
      hdr.bytes_per_unit = 1;
      break;
    }
    case AUDIO_ENCODING_LINEAR:
    {
      hdr.samples_per_unit = 1;
      if (format == 2)
        hdr.bytes_per_unit = 1; /* 8 bit PCM */
      else
        hdr.bytes_per_unit = 2; /* 16 bit PCM */
      break;
    }
    case AUDIO_ENCODING_G722:
    {
      /* G722 fixed rate and channels */
      hdr.sample_rate = 8000;
      hdr.channels    = 1;
      hdr.samples_per_unit = 1;
      hdr.bytes_per_unit = 1;
      break;
    }
    case AUDIO_ENCODING_G728:
    {
      /* G728 fixed rate and channels */
      hdr.sample_rate = 1600;
      hdr.channels    = 1;
      hdr.samples_per_unit = 8;
      hdr.bytes_per_unit = 10;
      break;
    }
  }
  
  hdr.data_size = AUDIO_UNKNOWN_SIZE;
  hdr.endian = AUDIO_ENDIAN_BIG;

  /*
   * Pause the input and flush the read queue
   */
  (void) oti_audio_pause_record(Audio_fd);
  (void) oti_audio_flush_record(Audio_fd);

  /*
   * Set the desired buffer size
   */
  AUDIO_INITINFO(&tmpinfo);
  tmpinfo.record.buffer_size = buffer_size;
  tmpinfo.play.buffer_size = buffer_size;
  oti_audio_setinfo(Audio_fd, &tmpinfo);

  /*
   * Set the format
   */
  oti_audio_set_play_config(Audio_fd, &hdr);
  value = 0;
  oti_audio_set_play_eof(Audio_fd, &value);
  oti_audio_set_record_config(Audio_fd, &hdr);
  
  printf("Looping: encoding=%d sample_rate=%d samples_per_unit=%d\n",
         hdr.encoding, hdr.sample_rate, hdr.samples_per_unit);
  printf("           bytes_per_unit=%d channels=%d endian=%d\n",
         hdr.bytes_per_unit, hdr.channels, hdr.endian);
       
  looping    = 1;
  
  /*
   * Pause the input and flush the read queue
   */
  (void) oti_audio_pause_record(Audio_fd);
  
  AUDIO_INITINFO(&tmpinfo);
  tmpinfo.record.pause = 0;
  tmpinfo.record.samples = 0;
  tmpinfo.record.error = 0;
  tmpinfo.play.pause = 0;
  tmpinfo.play.samples = 0;
  tmpinfo.play.error = 0;
  oti_audio_setinfo(Audio_fd, &tmpinfo);

  (void) oti_audio_flush_record(Audio_fd);
  
  /*
   * Looping happens in the background via async POLL signals
   */
  poll_enabled = 1;

  gettimeofday(&t0, NULL);
  t = 0;

  while (seconds >= t){
    poll(NULL,0,5);
    if (seconds){
        gettimeofday(&t1, NULL);
        t = (t1.tv_sec - t0.tv_sec) + (t1.tv_usec - t0.tv_usec) * 1e-6;
    }
  }

  g_exit(0);

}
  
  
