/*------------------------------------------------------------------------
 *
 *  File:	o1k_audio.cc
 *  Revision:	1.21
 *  Last Mod:	13:56:48, 03/14/97
 *
 *  Description:
 *     Implements the O1kAudio object.
 *     
 *----------------------------------------------------------------------*/
#pragma ident	"@(#)o1k_audio.cc	1.21\t97/03/14  "

#include <stdio.h>
#include <errno.h>
#include <thread.h>
#include <synch.h>
#include <string.h>
#include <math.h>
#include <fcntl.h>
#include <signal.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 "o1k_conf.h"
#include "o1k_misc.h"
#include "packet.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"

extern char dev_name[];

/*
 * useful strings
 */

char* AudioTypeStr[] = {"G722", "G728", "ULAW", "ALAW", "PCM8", "PCM16", "NONE" };
char* AudioChannelsStr[] = { "MONO", "STEREO"}; 
char* AudioSampleRateStr[] = { "8 KHz", "11.25 KHz", "16 KHz", "22.50 KHz", "32 KHz"};
char* AudioPortStr[] = { "LINE_IN", "MIC_IN" };

/*------------------------------------------------------------------------
 *
 *  Function:	O1kAudio::O1kAudio()
 *
 *  Description:
 *     Init O1kAudio to default state.
 *     
 *  Notes:
 *     Failure of create is not checked.
 *     
 *----------------------------------------------------------------------*/

O1kAudio::O1kAudio()
{
  int x;
  
  /* Init audio info */
  type          = ULAW;
  channels      = MONO;
  sample_rate   = R8000;
  port          = LINE_IN;
  vol_output    = 255;

  vol_input     = 30;
  vol_monitor   = 0;
  aes_on        = TRUE;

  enable_output = TRUE;
  enable_input  = TRUE;

  bitrate_t   = 0;
  bitrate_r   = 0;  
  
  x = oti_audio_init(NULL, NULL);
  if (x < 0){
    fprintf(stderr,"oti_audio_init failed %d\n",x);
  }

  Audio_fd = -1;
  Audioctl_fd = -1;
  
  thread_t = new O1kThread("Audio Transmit");
  if (!thread_t)
    fprintf(stderr,"O1kAudio() could not allocate thread\n");

  thread_r = new O1kThread("Audio Receive");
  if (!thread_r)
    fprintf(stderr,"O1kAudio() could not allocate thread\n");
  
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kAudio::~O1kAudio()
 *
 *  Description:
 *     Delete allocated O1kAudio data
 *     
 *----------------------------------------------------------------------*/

O1kAudio::~O1kAudio()
{
  if (thread_t)
    delete thread_t;
  if (thread_r)
    delete thread_r;
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kAudio::settings()
 *
 *  Description:
 *	
 *     Debug print out of O1kAudio settings.
 *	
 *----------------------------------------------------------------------*/

void O1kAudio::settings()
{
  printf("Audio Codec Settings\n");
  printf("--------------------\n");
  if (type == AUD_NONE)
    printf("  non-audio conference\n");
  else {
    printf("  audio codec %s\n",AudioTypeStr[type]);
    if (type != G728){
      printf("  channels    %s\n",AudioChannelsStr[channels]);
      printf("  sample rate %s\n",AudioSampleRateStr[sample_rate]);
    }
  }
  
  if (type != AUD_NONE){
    printf("General Audio Settings\n");
    printf("----------------------\n");
    printf("  audio port    %s\n",AudioPortStr[port]);
    printf("  vol output    %d\n",vol_output);
    printf("  vol input     %d\n",vol_input);
    printf("  vol monitor   %d\n",vol_monitor);
    printf("  aes on        %d\n",aes_on);
    printf("  output enable %d\n",enable_output);
    printf("  input enable  %d\n",enable_input);
  }  
}


/*------------------------------------------------------------------------
 *
 *  Function:	o1k_audio_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 o1k_audio_poll_handler(int signo)
{
  /* must re-signal immediately in case another signal occurs */
  signal(signo, o1k_audio_poll_handler);
  if (verbose) printf("o1k_audio_poll_handler 0x%x %d\n",signo, signo);
  
}

/*------------------------------------------------------------------------
 *
 *  Function:	o1k_audio_dynamic_update
 *
 *  Description:
 *    Checks for changes in audio parameters while conf is in
 *    progress and makes audio calls to change these settings.
 *     
 *     
 *  Parameters:
 *     int init        : init flag
 *     O1kAudio* audio : main O1kAudio object
 *     
 *----------------------------------------------------------------------*/

void o1k_audio_dynamic_update(int init, O1kAudio* audio)
{

  static AudioPort     port;
  static unsigned int  vol_output;
  static unsigned int  vol_input;
  static unsigned int  vol_monitor;
  static unsigned char aes_on;

  if (init){
    port          = audio->getPort();
    vol_output    = audio->getVolOutput();
    vol_input     = audio->getVolInput();
    vol_monitor   = audio->getVolMonitor();
    aes_on        = audio->getAES();
    return;
  }

  if (port != audio->getPort()){
    unsigned iport;
    port = audio->getPort();
    iport  = (port == LINE_IN) ? AUDIO_LINE_IN : AUDIO_MICROPHONE;
    oti_audio_set_record_port(audio->Audio_fd, &iport);
  }
  
  if (vol_output != audio->getVolOutput()){
    double ogain;
    vol_output = audio->getVolOutput();
    ogain = vol_output/255.0;
    oti_audio_set_play_gain(audio->Audio_fd, &ogain);
  }
  
  if (vol_input != audio->getVolInput()){
    double igain;
    vol_input = audio->getVolInput();
    igain = vol_input/255.0;
    oti_audio_set_record_gain(audio->Audio_fd, &igain);
  }
  
  if (vol_monitor != audio->getVolMonitor()){
    double mgain;
    vol_monitor = audio->getVolMonitor();
    mgain = vol_monitor/255.0;
    oti_audio_set_monitor_gain(audio->Audio_fd, &mgain);
  }
  
  if (aes_on != audio->getAES()){
    aes_on = audio->getAES();
  }
}

/*------------------------------------------------------------------------
 *
 *  Function:	tAudThrd
 *
 *  Description:
 *     This is the transmit audio worker thread. It is responsible
 *     for reading audio data from the audio device and transmitting
 *     it on the network. The main loop of this thread:
 *
 *     - loop top
 *     - checks for dynamic audio setting updates
 *     - checks to see how much we can read from audio device
 *     - checks/resets any errors on audio device
 *     - if no audio data loop back to loop top
 *     - if data avail on audio device then
 *       - read audio data
 *       - transmit data if input enable on
 *       - record bitrate info
 *     - loop back to loop top
 *     
 *  Parameters:
 *     void* vp  : main O1kAudio object
 *     
 *     
 *----------------------------------------------------------------------*/

void* tAudThrd(void* vp)
{
  O1kAudio* audio = (O1kAudio*)vp;
  O1kThread* thread = audio->thread_t;
  
  int type = NET_TYPE_AUDIO; /* thread index type */  
  int rc;
  int rtn;
  unsigned char data[MAX_FRAME_SIZE];
  unsigned int size;
  unsigned int error;

  double tval;
  struct timeval t0, t1;
  int s_bytes = 0;

  /* see readme */
  if (register_trap)
    asm("ta 6");
  
  gettimeofday(&t0, NULL);
  
  if (verbose) printf("***** Audio Transmit tAudThrd Entering\n");

  /* init dynamic audio settings */
  o1k_audio_dynamic_update(1, audio);
  
  /* loop while enabled */
  thread->setStopped(FALSE);

  while (thread->isEnabled()){

    /* get data from device */
    do {

      /* check dynamic audio settings */
      o1k_audio_dynamic_update(0, audio);
      
      /* Determine how much we can read */
      if (oti_ioctl(audio->Audio_fd, FIONREAD, &size) < 0) {
        perror("Audio Transmit Thread Ioctl Error");
        fprintf(stderr,"***** Audio Transmit tAudThrd Early Exiting *****\n");
        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->Audio_fd, &error);
      if (error){
        error = 0;
        oti_audio_set_record_error(audio->Audio_fd, &error);
      }
      
      if (size == 0){
        poll(0,0,5); /* sleep for a little */
      } else {
        rtn = oti_read(audio->Audio_fd, data, size);
        if (rtn <= 0) {
          break;
        }
      }
    } while (size == 0 && thread->isEnabled() && conf->network->aud_thread_t->isEnabled());

    if (size == 0)
      break;
    
    /* size was not zero and we are still enabled*/

    /* check actual amount read */
    if (rtn <= 0) {
      fprintf(stderr,"Audio Transmit Thread Read Error %d\n",rtn);
      fprintf(stderr,"***** Audio Transmit tAudThrd Early Exiting *****\n");
      break;
    }

    /* transmit to network */
    if (audio->getEnableInput()){
      rc = o1k_conf_network_transmit(data, size, 1000, type);
      if (rc != 0){
        fprintf(stderr,"Audio Transmit Thread Error: transmit timeout %d\n",rc);
      }
    }
    s_bytes  += size;
    
    gettimeofday(&t1, NULL);
    if (t1.tv_sec - t0.tv_sec >= 1){
      tval = (t1.tv_sec - t0.tv_sec) + (t1.tv_usec - t0.tv_usec) * 1e-6;
      if (verbose) printf("transmitted audio %d bytes in %g seconds, %g bits/second\n", s_bytes, tval, (s_bytes*8.0) / tval);
      audio->bitrate_t   = (int)((s_bytes*8.0)/tval);
      s_bytes  = 0;
      gettimeofday(&t0, NULL);      
    }
  }
  
  /* leaving terminates the thread */

  if (verbose) printf("***** Audio Transmit tAudThrd Exiting *****\n");
  
  thread->setStopped(TRUE);
  return 0;
}

/*------------------------------------------------------------------------
 *
 *  Function:	rAudThrd
 *
 *  Description:
 *     This is the receive audio worker thread. It is responsible
 *     for getting audio data from the network and the writing this
 *     data to the audio device. The main loop of this thread:
 *
 *     - loop top
 *     - wait for data to arrive from network
 *     - break if thread no longer enabled
 *     - checks/resets any errors on audio device
 *     - if output enable, write audio data to audio device
 *     - record bitrate info
 *     - loop back to loop top
 *     
 *  Parameters:
 *     void* vp  : main O1kAudio object
 *     
 *----------------------------------------------------------------------*/

void* rAudThrd(void* vp)
{
  O1kAudio*  audio  = (O1kAudio*)vp;
  O1kThread* thread = audio->thread_r;
  
  int type = NET_TYPE_AUDIO; /* thread index type */  
  int rc;
  int rtn;
  unsigned char data[MAX_FRAME_SIZE];
  unsigned int size;
  unsigned char* aud_ptr;
  unsigned int error;

  double tval;
  struct timeval t0, t1;
  int s_bytes = 0;

  /* see readme */
  if (register_trap)
    asm("ta 6");
  
  gettimeofday(&t0, NULL);
  
  if (verbose) printf("***** Audio Receive rAudThrd Entering\n");
  
  /* loop while enabled */  
  thread->setStopped(FALSE);
  
  while (thread->isEnabled()){

    /* get data from network, timeout if thread disabled */
    do {
      /* sleep for a little */
      poll(0,0,5);
      rc = o1k_conf_network_receive(data, &size, 0, type);
    } while (rc != 0 && thread->isEnabled() && conf->network->aud_thread_r->isEnabled());

    /* break if threads not enabled */
    if (rc != 0)
      break;

    /* play 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->Audio_fd, &error);
    if (error){
      error = 0;
      oti_audio_set_play_error(audio->Audio_fd, &error);
    }

    s_bytes += size;

    /* write to audio device only if output enable on */
    if (audio->getEnableOutput()){
      aud_ptr = data;
      while (size > 0) {
        /* write as much data as possible to device */
        rtn = oti_write(audio->Audio_fd, aud_ptr, size);
        if (rtn > 0) {
          size -= rtn;
          aud_ptr += rtn;
        } else {
          break;
        }
      }
    }
    
    gettimeofday(&t1, NULL);
    if (t1.tv_sec - t0.tv_sec >= 1){
      tval = (t1.tv_sec - t0.tv_sec) + (t1.tv_usec - t0.tv_usec) * 1e-6;
      if (verbose) printf("received audio %d bytes in %g seconds, %g bits/second\n", s_bytes, tval, (s_bytes*8.0) / tval);
      audio->bitrate_r   = (int)((s_bytes*8.0)/tval);
      s_bytes  = 0;
      gettimeofday(&t0, NULL);      
    }
  }
  
  /* leaving terminates the thread */
  
  if (verbose) printf("***** Audio Receive rAudThrd Exiting *****\n");

  thread->setStopped(TRUE);
  return 0;
}


/*****************************************************************************
 *              A U D I O   S T A R T / S T O P   R O U T I N E S
 *****************************************************************************/

/*-------------------------------------------------------------------------

 *
 *  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';

}

/*-------------------------------------------------------------------------
 *
 * Function: o1k_audio_set_audio_hdr
 *
 * Description: Sets Audio_hdr hdr to desired conference settings
 *
 *-----------------------------------------------------------------------*/
void o1k_audio_set_audio_hdr(O1kAudio* audio, Audio_hdr*  hdr)
{
  /*
   * Determine the desired format
   */
  switch (audio->getFormat())
  {
    case ULAW:  hdr->encoding = AUDIO_ENCODING_ULAW;   break;
    case ALAW:  hdr->encoding = AUDIO_ENCODING_ALAW;   break;
    case PCM8:  hdr->encoding = AUDIO_ENCODING_LINEAR; break;
    case PCM16: hdr->encoding = AUDIO_ENCODING_LINEAR; break;
    case G722:  hdr->encoding = AUDIO_ENCODING_G722;   break;
    case G728:  hdr->encoding = AUDIO_ENCODING_G728;   break;
    default:    hdr->encoding = AUDIO_ENCODING_ULAW;
  }

  switch (audio->getSampleRate())
  {
    case R8000:  hdr->sample_rate =  8000; break;
    case R11025: hdr->sample_rate = 11025; break;
    case R16000: hdr->sample_rate = 16000; break;
    case R22050: hdr->sample_rate = 22050; break;
    default:     hdr->sample_rate =  8000; 
  }

  switch (audio->getChannels())
  {
    case MONO:   hdr->channels = 1; break;
    case STEREO: 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 (audio->getFormat() == PCM8)
        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;
}

extern Boolean bAudioInitWait;

/*-------------------------------------------------------------------------
 *
 * Function: o1k_conf_audio_start
 *
 * Description: Start audio device and threads
 *
 *-----------------------------------------------------------------------*/
int O1kAudio::start()
{
  char     audctl_device[80];

  unsigned int value;
  
  Audio_hdr    hdr;
  Audio_info   tmpinfo;

  unsigned iport  = (port == LINE_IN) ? AUDIO_LINE_IN : AUDIO_MICROPHONE;

  double   igain = vol_input/255.0;  
  double   ogain = vol_output/255.0;
  double   mgain = vol_monitor/255.0;

  
  unsigned int buffer_size = 1000;

  /*
   * The current XIL video drivers require that it be the first
   * thing to initialize the Osprey-1x00 device. Thus, we'll wait
   * here until we are signalled via the bAudioInitWait flag that
   * the video object has done XIL initialization.
   */
  while (bAudioInitWait){
    poll(0,0,5);
  }
  
  if (verbose) printf(">>>>>>> O1kAudio::start() <<<<<<<\n");
  
  /*
   * setup signal handlers
   */
  signal(SIGPOLL, o1k_audio_poll_handler);
  
  /*
   * Open audio device and setup POLL signal
   */
  if ((Audio_fd = oti_open(dev_name, O_RDWR | O_NDELAY))<0) {
    fprintf(stderr,"could not open osprey audio device\n");
    return 0;
  }
#ifdef USE_AUDIO_SIGNAL
  if (oti_ioctl(Audio_fd, I_SETSIG, (void*)(S_INPUT | S_OUTPUT | S_MSG)) < 0) {
    perror("I_SETSIG ioctl");
    return 0;
  }
#endif
  
  /*
   * Open audio control device and setup POLL signal
   */
  get_audioctl_name(audctl_device, dev_name);
  if ((Audioctl_fd = oti_open(audctl_device, O_RDWR))<0) {
    fprintf(stderr,"could not open audio ctl device\n");
    return 0;
  }
  
  if (verbose) printf("oti_open control O_RDWR %.8x\n",Audioctl_fd);
  
#ifdef USE_AUDIO_SIGNAL
  if (oti_ioctl(Audioctl_fd, I_SETSIG, (void*)(S_MSG)) < 0) {
    perror("I_SETSIG ioctl");
    return 0;
  } 
#endif
  /*
   * Set the desired gains and port
   */
  oti_audio_set_record_port(Audio_fd, &iport);
  oti_audio_set_record_gain(Audio_fd, &igain);
  oti_audio_set_monitor_gain(Audio_fd, &mgain);
  oti_audio_set_play_gain(Audio_fd, &ogain);
  
  /*
   * Get the desired format
   */
  o1k_audio_set_audio_hdr(this, &hdr);

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

  /*
   * 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);
  
  /* now ready to receive/send data: start threads */
  thread_r->start(rAudThrd, (void*)this);
  thread_t->start(tAudThrd, (void*)this);  

  if (verbose) printf(">>>>>>> O1kAudio::start() done <<<<<<<\n");
  
  return 0;
}

/*-------------------------------------------------------------------------
 *
 * Function: o1k_conf_audio_stop
 *
 * Description: Stop audio device and threads
 *
 *-----------------------------------------------------------------------*/
int O1kAudio::stop()
{
  if (verbose) printf("O1kAudio::stop()\n");
  
  /* stop threads */
  thread_r->stop();
  thread_t->stop();

  /* stop device ... */
  
  /* Pause the device */
  oti_audio_pause(Audio_fd);
  /* Flush the device */
  oti_audio_flush(Audio_fd);
  
  if (Audio_fd != -1){
    oti_close(Audio_fd);
    Audio_fd = -1;
  }
  
  if (Audioctl_fd != -1){
    oti_close(Audioctl_fd);
    Audioctl_fd = -1;
  }

  return 0;
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kAudio::getStatus
 *
 *  Description:
 *     Returns a string represent the interger value of the
 *     specified request status type.
 *	
 *  Parameters:
 *     AudioStatus ns  : desired status info requested
 *	
 *  Returns:
 *     char* : pointer to status info
 *	
 *  Notes:
 *     The caller should copy the returned status info for it 
 *     is only valid until the next call to this routine.
 *	
 *----------------------------------------------------------------------*/

char* O1kAudio::getStatus(AudioStatus as)
{
  static char status[10];

  switch (as)
  {
    case AS_BITRATE_T:
    {
      sprintf(status,"%d",bitrate_t);
      break;
    }
    case AS_BITRATE_R:
    {
      sprintf(status,"%d",bitrate_r);
      break;
    }
    default:
    {
      fprintf(stderr,"Unknown audio status query %d\n",as);
      strcpy(status,"");
    }
  }
  return status;
}

