/* 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 MAX_BUFFER_SIZE (1024*4)

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

char  aud_data[MAX_BUFFER_SIZE];
char  *aud_ptr = aud_data;
int   aud_amount = 0;
unsigned int buffer_size = 1000;
int   blocking_io = 0;

int   playing = 0;
int   poll_enabled = 0;
char  filename[80];

char DEFAULT_DEVICE[] = "/dev/o1k0";

void usage(char* pgm)
{
  fprintf(stderr,"usage: %s  [options] -i filename\n",pgm);
  fprintf(stderr,"Legal Options:                                  Defaults\n");
  fprintf(stderr, " <-D device>   : device name                   /dev/o1k0)\n");  
  fprintf(stderr, " <-v gain>     : output gain (0...1.0)         1.0\n");
  fprintf(stderr, " <-b size>     : buffer size                   1000\n");   
  fprintf(stderr, " <-B>          : use blocking io for writes      \n");   
}

/*------------------------------------------------------------------------
 *
 *  Function:	play_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 there is space avail in
 *      the audio device. We read some data from the audio file and
 *      write this audio data to the device. When done playing the
 *      file, an EOF marker is written to the audio device.
 *      If an error occurs (underflow), the error is cleared and we
 *      continue playing.
 *
 *  Parameters: none
 *	
 *  Returns: void
 *	
 *------------------------------------------------------------------------*/

void play_service()
{
  int	start;
  int	end;
  int	outcnt;
  int	rtn;
  unsigned int error;
  
  static int in_play_service = 0;

  /*
   * avoid reentrant code here
   */
  if (in_play_service != 0){
    return;
  }
  in_play_service = 1;

  if (input_fd >= 0){
    if (aud_amount == 0){
      /* fill buffer with data */
      aud_amount  = read(input_fd, aud_data, buffer_size);
      aud_ptr = aud_data;
    }

    /*
     * 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);
    }
    
    if (aud_amount == 0){
      /* done with file: do eof to mark end */
#if 0
      /* this can be done by zero length write: */
      oti_write(Audio_fd, aud_data, 0);
#else
      /* or this can be done by api call */
      oti_audio_play_eof(Audio_fd);
#endif
      close(input_fd);
      input_fd = -1;
    }
    
    while (aud_amount > 0) {
      /* write as much data as possible */
      rtn = oti_write(Audio_fd, aud_ptr, aud_amount);
      
      if (rtn > 0) {
        aud_amount -= rtn;
        aud_ptr += rtn;
      } else {
        break;
      }
    }
  }
  
  in_play_service = 0;
}

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

void stop_play()
{
  /* Pause the device */
  oti_audio_pause_play(Audio_fd);
  oti_audio_flush_play(Audio_fd);
}

/*------------------------------------------------------------------------
 *
 *  Function:	g_exit
 *
 *  Description:
 *	Hanldes signals which would cause process to exit (like Ctl-C).
 *      Stops playing and the closes 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_play();
  playing = 0;
  
  if (Audio_fd != -1){
    oti_close(Audio_fd);
    Audio_fd = -1;
  }
  
  if (Audioctl_fd != -1){
    oti_close(Audioctl_fd);
    Audioctl_fd = -1;
  }
  
  if (input_fd != -1){
    close(input_fd);
    input_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)
    play_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];
  
  double   ogain = 1.0;
  double   mgain = 0.0;  /* playing audio: want monitor gain at 0 */
  
  char     info[120];
  unsigned int value;
  
  Audio_hdr hdr;
  Audio_info tmpinfo;
  
  strcpy(info, "");
  
  for (i=1; i<argc; i++){
    switch (argv[i][1])
    {
      case 'D':
      {
        aud_device = argv[++i];
        break;
      }
      case 'v':
      {
        ogain = atof(argv[++i]);
        break;
      }
      case 'b':
      {
        buffer_size = atoi(argv[++i]);
        if (buffer_size > MAX_BUFFER_SIZE){
          fprintf(stderr,"Desired buffer size %d too large. Using %d\n",
                  buffer_size, MAX_BUFFER_SIZE);  
          buffer_size = MAX_BUFFER_SIZE;
        }
        break;
      }
      case 'B':
      {
        blocking_io = 1;
        break;
      }
      case 'i':
      {
        strcpy(filename, argv[++i]);
        if (oti_audio_isaudiofile(filename)){
          input_fd = open(filename, O_RDONLY, 0666);
          if (input_fd < 0)
            fprintf(stderr,"could not open %s\n",filename);
        } else {
          fprintf(stderr,"file %s is not an audio file\n",filename);
        }
        break;
      }
      case '?':
      default:
      {
        usage(argv[0]);
        exit(0);
      }
    }
  }

  if (input_fd < 0){
    usage(argv[0]);
    exit(0);
  }

  /*
   * setup signal handlers
   */
  signal(SIGINT,  g_exit);
  if (!blocking_io){
      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 using non-blocking io
   */  
  if (blocking_io){
      if ((Audio_fd = oti_open(aud_device, O_WRONLY))<0){
          fprintf(stderr,"could not open osprey audio device\n");
          g_exit(0);
      }
  } else {
      if ((Audio_fd = oti_open(aud_device, O_WRONLY | O_NDELAY))<0){
          fprintf(stderr,"could not open osprey audio device\n");
          g_exit(0);
      }
      if (oti_ioctl(Audio_fd, I_SETSIG, (void*)(S_OUTPUT | S_MSG)) < 0){
          perror("I_SETSIG ioctl");
          g_exit(0);
      }
  }

  /*
   * Open audio control device and setup POLL signal if using non-blocking io
   */
  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 (!blocking_io){
      if (oti_ioctl(Audioctl_fd, I_SETSIG, (void*)(S_MSG)) < 0){
          perror("I_SETSIG ioctl");
          g_exit(0);
      }
  }

  /*
   * Set the desired gains
   */
  oti_audio_set_play_gain(Audio_fd, &ogain);
  oti_audio_set_monitor_gain(Audio_fd, &mgain);

  /*
   * Set the desired buffer size
   */
  AUDIO_INITINFO(&tmpinfo);  
  tmpinfo.record.buffer_size = buffer_size;
  tmpinfo.play.buffer_size = buffer_size;
  tmpinfo.play.pause = 0;
  tmpinfo.play.samples = 0;
  tmpinfo.play.error = 0;
  oti_audio_setinfo(Audio_fd, &tmpinfo);
  
  /*
   * Get the audio file format
   */
  if ((rc = oti_audio_read_filehdr(input_fd, &hdr, info, strlen(info))) != AUDIO_SUCCESS){
    oti_close(Audio_fd);
    fprintf(stderr,"unable to read audio header from %s\n",filename);
    g_exit(0);
  }
  
  printf("playing file %s %s\n",filename, info);
  
  printf("   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);

  /* using audio_write_filehdr always seems to nuke endian select */
  hdr.endian = AUDIO_ENDIAN_BIG;
  oti_audio_set_play_config(Audio_fd, &hdr);

  value = 0;
  oti_audio_set_play_eof(Audio_fd, &value);
  
  if (!blocking_io)
      poll_enabled = 1;

  playing  = 1;

  if (!blocking_io) {
      (void) kill(getpid(), SIGPOLL);  /* SIGPOLL kicks off play_service() */
  
      /*
       * Playing happens in the background via async POLL signals.
       * Wait here for EOF to be processed
       */
      while (playing){
          poll(NULL,0,5);
          poll_enabled = 0;
          oti_audio_get_play_eof(Audio_fd, &value);
          poll_enabled = 1;    
          if (value > 0)
              playing = 0;
      }
  } else {
      /* blocking i/o */
      unsigned int error;
      int	rtn;

      do {
          /* fill buffer with data */
          aud_amount  = read(input_fd, aud_data, MAX_BUFFER_SIZE);
          aud_ptr = aud_data;

          /*
           * 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);
          }

          if (aud_amount == 0){
              /* done with file: do eof to mark end */
#if 0
              /* this can be done by zero length write: */
              oti_write(Audio_fd, aud_data, 0);
#else
              /* or this can be done by api call */
              oti_audio_play_eof(Audio_fd);
#endif
              close(input_fd);
              input_fd = -1;
          } else {
              
              /* write data */
              rtn = oti_write(Audio_fd, aud_ptr, aud_amount);
          }

      } while (aud_amount);
  }

  /*
   * Wait for drain
   */
  poll(NULL,0,20);
  
  g_exit(0);
}
  
  
