/*
   File: obuffer.cc
   
   Description:
   Audio output buffer implementation
*/

#ifdef __GNUG__
#pragma implementation
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <String.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#ifndef LINUX
#include <stropts.h>
#endif
#include <fstream.h>
#ifdef IRIX
extern "C" {
#include <dmedia/audio.h>
}
#endif
#ifdef SOLARIS
#include <sys/audioio.h>
#endif
#ifdef LINUX
#include <sys/soundcard.h>
#endif

#include <memory.h>
#include <sys/select.h>
#include <sys/time.h>  // Time

#ifndef LINUX
extern "C" {
int ioctl(int, int, ...);
}
#else
#define IOCTL(a,b,c)		ioctl(a,b,&c)
#endif

#include "athread.hh"

#include <errno.h>

#include "error.hh"
#include "debug.hh"
#include "util.hh"

#include "sync.hh"
#include "mpeg2const.hh"
#include "mpeg2buff.hh"

#include "astream.hh"
#include "crc.hh"
#include "header.hh"
#include "obuffer.hh"


ShortObuffer::ShortObuffer(uint32 number_of_channels){
#ifdef DEBUG
  if (!number_of_channels || number_of_channels > MAXCHANNELS){
    String err("ShortObuffer: number of channels has to be in [1, ");
    err+=itoa(MAXCHANNELS);
    err+="]!";
    error(err.chars());
    athr_exit(0);
  }
#endif
  channels = number_of_channels;
  for (unsigned int i=0; i<number_of_channels; i++) bufferp[i]=buffer+i;
}


void ShortObuffer::append(uint32 channel, int16 value){
#ifdef DEBUG
  if (channel >= channels){
    error("illegal channelnumber in ShortObuffer::append()!");
    athr_exit(0);
  }
  if (bufferp[channel] - buffer >= OBUFFERSIZE){
    error("ShortObuffer: buffer overflow!");
    athr_exit(0);
  }
#endif
  *bufferp[channel] = value;
  bufferp[channel] += channels;
}


void ShortObuffer::write_buffer (int fd){
  int length = (int)((char *)bufferp[0] - (char *)buffer);
  if (write (fd, buffer, length) != length)
    warning("couldn't write all samples");
  for (unsigned int i=0; i<channels; i++) bufferp[i]=buffer+i;
}



#ifdef IRIX
  
IrixObuffer::IrixObuffer(uint32 number_of_channels, Header* header){
#ifdef DEBUG
  if (!number_of_channels || number_of_channels > MAXCHANNELS){
    cerr << "IrixObuffer: number of channels has to be in [1, " <<  MAXCHANNELS << "] !\n";
    exit(1);
  }
#endif
  channels = number_of_channels;
  for (int i = 0; i < number_of_channels; ++i) bufferp[i] = buffer + i;
  
  // open an audio port and configure it:
    ALconfig config;
  if (!(config = ALnewconfig())){
    error("ALnewconfig failed!");
    exit(1);
  }
  ALsetwidth(config, AL_SAMPLE_16);
  if (channels == 1)
    ALsetchannels(config, AL_MONO);
  else  ALsetchannels(config, AL_STEREO);
  if (!(port = ALopenport("MPEG audio player", "w", config))){
    error("can't allocate an audio port!");
    exit (1);
  }
  
  // set sample rate:
  long pvbuffer[2] = { AL_OUTPUT_RATE, 0 };
  pvbuffer[1] = header->frequency ();
  ALsetparams(AL_DEFAULT_DEVICE, pvbuffer, 2);
  ALfreeconfig(config);
}

IrixObuffer::~IrixObuffer(){
  while (ALgetfilled(port) > 0) sleep (1);
  ALcloseport(port);
}


// All inline to avoid linking problem!
void IrixObuffer::append(uint32 channel, int16 value){
#ifdef DEBUG
  if (channel >= channels){
    error("illegal channelnumber in IrixObuffer::append()!");
    athr_exit(0);
  }
  if (bufferp[channel] - buffer >= OBUFFERSIZE){
    error("IrixObuffer: buffer overflow!");
    athr_exit(0);
  }
#endif
  *bufferp[channel] = value;
  bufferp[channel] += channels;
}

void IrixObuffer::write_buffer(int /* dummy */){
  ALwritesamps(port, buffer, (long)(bufferp[0] - buffer));
  for (int i = 0; i < channels; ++i) bufferp[i] = buffer + i;
}

#endif // IRIX



#ifdef SOLARIS
int SparcObuffer::audio_fd = -1;

SparcObuffer::SparcObuffer (uint32 number_of_channels, Header *header,
			    bool use_speaker, bool use_headphone, bool use_line_out){
  channels=number_of_channels;
#ifdef DEBUG
  if (!number_of_channels || number_of_channels > MAXCHANNELS){
    cerr << "SparcObuffer: 0 < number of channels < " << MAXCHANNELS << "!\n";
    exit (1);
  }
#endif
  for (unsigned int i=0; i<number_of_channels; i++) bufferp[i]=buffer+i;

  if (audio_fd < 0){
    error("SparcObuffer::audio_fd has to be initialized by SparcObuffer::class_suitable()!");
    athr_exit(0);
  }

  // configure the device:
  audio_info info;
  AUDIO_INITINFO (&info);
  info.output_muted = False;
  info.play.sample_rate = header->frequency ();
  info.play.channels = channels;
  info.play.precision = 16;
  info.play.encoding = AUDIO_ENCODING_LINEAR;
//  info.play.gain=AUDIO_MAX_GAIN/3;
  if (use_speaker)
    info.play.port |= AUDIO_SPEAKER;
  if (use_headphone)
    info.play.port |= AUDIO_HEADPHONE;
  if (use_line_out)
    info.play.port |= AUDIO_LINE_OUT;
  info.play.buffer_size=OBUFFERSIZE;
  info.play.samples=0;
  info.play.eof=0;
  info.play.pause=0;
  info.play.error=0;
  info.play.waiting=0;
  info.play.balance = AUDIO_MID_BALANCE;
  if (ioctl(audio_fd, AUDIO_SETINFO, &info)){
    error("configuration of /dev/audio failed");
    athr_exit(0);
  }
}


static int droppedframes=0;
static timeval timeout;

SparcObuffer::~SparcObuffer (void){
  ioctl(audio_fd, AUDIO_DRAIN);
  close (audio_fd);
  msg("Number of interrupted frames: ");  message(itoa(droppedframes));
}


void SparcObuffer::append(uint32 channel, int16 value){
#ifdef DEBUG
  if (channel >= channels){
    cerr << "illegal channelnumber in SparcObuffer::append()!\n";
    exit (1);
  }
  if (bufferp[channel] - buffer >= OBUFFERSIZE){
    cerr << "buffer overflow!\n";
    exit (1);
  }
#endif
  *bufferp[channel] = value;
  bufferp[channel] += channels;
}


void SparcObuffer::write_buffer(int){
  int length = (int)((char*) bufferp[0] - (char*) buffer);

#ifdef SPARC5
  // This stuff seems useful on a SUN Sparc 5 
  static fd_set wfs;
  FD_ZERO(&wfs);
  FD_SET(audio_fd, &wfs);
  if (select(FD_SETSIZE, 0, &wfs, 0, &timeout)>=0){
    if (FD_ISSET(audio_fd, &wfs)){
#endif
      if (write(audio_fd, buffer, length) != length){
        message("Warning: couldn't write all samples to /dev/audio");
      }
#ifdef SPARC5
    }
    else {
      ioctl(audio_fd, I_FLUSH, FLUSHW);    // flush current stuff and replace by new
      msg("flush audio");
      droppedframes++;
      if (write(audio_fd, buffer, length) != length){
        message("Warning: couldn't write all samples to /dev/audio");
      }
    }
  }
#endif

  for (unsigned int i=0; i<channels; i++) bufferp[i]=buffer+i;
}


int SparcObuffer::open_audio_device (void){
  int fd=0;
  // just check
  if ((fd=open("/dev/audio", O_WRONLY | O_NDELAY)) < 0) // 
    if (errno == EBUSY){
      cerr << "Sorry, the audio device is busy!\n";
      exit (1);
    }
    else{
      perror ("can't open /dev/audio for writing");
      exit (1);
    }

  ioctl(fd, I_FLUSH, FLUSHRW);
  close(fd);
  /* now open (blocking) // | O_NDELAY)) */
  if ((fd=open("/dev/audio", O_WRONLY))< 0){ 
    if (errno == EBUSY){
      error("Sorry, the audio device is busy!");
      athr_exit(0);
    }
    else {
      error("can't open /dev/audio for writing");
      athr_exit(0);
    }
  }
  ioctl(fd, I_FLUSH, FLUSHW);

  // next time out
  timeout.tv_sec=0;             // length/5513;  // length/5513=length*8 / 44100 (bps) 
  timeout.tv_usec=752600;
  // (long int) (length*181);   // length*181= length*8/ 44100 * 1000000
}


void SparcObuffer::get_device_type (int fd, audio_device *devtype){
  if (ioctl (fd, AUDIO_GETDEV, devtype)){
    error("ioctl on /dev/audio");
    athr_exit(0);
  }
}


bool SparcObuffer::class_suitable (void){
  audio_fd=open_audio_device();      // check for the CS4231 or dbri audio device
  audio_device devtype;
  get_device_type(audio_fd, &devtype);
  // Sparc 5: CS4321, Sparc 10: dbri
  return (!strcmp (devtype.name, "SUNW,CS4231") || !strcmp (devtype.name, "SUNW,dbri")) ? True : False;
}

#endif // SOLARIS



#ifdef LINUX
int LinuxObuffer::audio_fd = -1;

LinuxObuffer::LinuxObuffer (uint32 number_of_channels,Header *header) {
  int abuf_size;
  int sample_rate;
  int sample_bits=16;
  int chan = channels=number_of_channels;
  
#ifdef DEBUG
  if (!number_of_channels || number_of_channels > MAXCHANNELS){
    cerr << "LinuxObuffer: 0 < number of channels < " << MAXCHANNELS << "!\n";
    exit (1);
  }
#endif
  for (unsigned int i=0; i<number_of_channels; i++) bufferp[i]=buffer+i;

  if (audio_fd < 0){
    error("LinuxObuffer::audio_fd has to be initialized by LinuxObuffer::class_suitable()!");
    athr_exit(0);
  }

  IOCTL(audio_fd, SNDCTL_DSP_GETBLKSIZE, abuf_size);
  if (abuf_size < 4096 || abuf_size > 65536) {
    if (abuf_size == -1) {
      error ("LinuxObuffer::abufsize failure");
    } else {
      error("Invalid audio buffers size \n");
    }
    athr_exit (0);
  }


  // configure the device:
  sample_rate = header->frequency ();
  if (IOCTL(audio_fd, SNDCTL_DSP_SPEED, sample_rate) < 0) {
    error("unable to set audio speed");
    athr_exit (0);
  }
  if ( IOCTL(audio_fd, SNDCTL_DSP_CHANNELS, chan) < 0 ) {
    printf("LinuxObuffer::amount of channels (%d) not supported\n",chan);
    athr_exit(0);
  }
  chan--;
  if ( IOCTL(audio_fd, SNDCTL_DSP_STEREO, chan) < 0 ) {
    printf("LinuxObuffer::Error when setting stereo\n");
    athr_exit(0);
  }

  IOCTL(audio_fd, SNDCTL_DSP_SAMPLESIZE, sample_bits);

}


static int droppedframes=0;
static timeval timeout;

LinuxObuffer::~LinuxObuffer (void){
  ioctl(audio_fd, SNDCTL_DSP_SYNC,NULL);
  close (audio_fd);
  msg("Number of interrupted frames: ");  message(itoa(droppedframes));
}


void LinuxObuffer::append(uint32 channel, int16 value){
#ifdef DEBUG
  if (channel >= channels){
    cerr << "illegal channelnumber in SparcObuffer::append()!\n";
    exit (1);
  }
  if (bufferp[channel] - buffer >= OBUFFERSIZE){
    cerr << "buffer overflow!\n";
    exit (1);
  }
#endif
  *bufferp[channel] = value;
  bufferp[channel] += channels;
}


void LinuxObuffer::write_buffer(int){
  int length = (int)((char*) bufferp[0] - (char*) buffer);

      if (write(audio_fd, buffer, length) != length){
        message("Warning: couldn't write all samples to /dev/audio");
      }

  for (unsigned int i=0; i<channels; i++) bufferp[i]=buffer+i;
}


int LinuxObuffer::open_audio_device (void){
  int fd=0;
  // just check
  if ((fd=open("/dev/dsp", O_WRONLY | O_NDELAY)) < 0) // 
    if (errno == EBUSY){
      cerr << "Sorry, the audio device is busy!\n";
      exit (1);
    }
    else{
      perror ("can't open /dev/audio for writing");
      exit (1);
    }

  ioctl(fd, SNDCTL_DSP_SYNC,NULL);
  close(fd);
  /* now open (blocking) // | O_NDELAY)) */
  if ((fd=open("/dev/dsp", O_WRONLY))< 0){ 
    if (errno == EBUSY){
      error("Sorry, the audio device is busy!");
      athr_exit(0);
    }
    else {
      error("can't open /dev/audio for writing");
      athr_exit(0);
    }
  }
  ioctl(fd, SNDCTL_DSP_SYNC,NULL);

  // next time out
  timeout.tv_sec=0;             // length/5513;  // length/5513=length*8 / 44100 (bps) 
  timeout.tv_usec=752600;
  // (long int) (length*181);   // length*181= length*8/ 44100 * 1000000
  return fd;
}

bool LinuxObuffer::class_suitable (void){
  audio_fd=open_audio_device();      // check for audio device
  if (audio_fd == -1)
    return False;
  return True;
}

#endif // LINUX
