/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: audlinux_alsa.cpp,v 1.1.2.1 2004/07/09 02:01:42 hubbe Exp $
 * 
 * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
 * 
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 * 
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 * 
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 * 
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 * 
 * Contributor(s):
 * 
 * ***** END LICENSE BLOCK ***** */

#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/ioctl.h>

#define ALSA_PCM_NEW_HW_PARAMS_API
#define ALSA_PCM_NEW_SW_PARAMS_API

#include <alsa/asoundlib.h>

#include <stdio.h> 
#include <math.h>

#include "ihxpckts.h"
#include "hxtick.h"
#include "hxprefs.h"
#include "timeval.h"
#include "hxthread.h"
#include "audlinux_alsa.h"
#include "hxstrutl.h"

#include "dllacces.h"
#include "dllpath.h"

//------------------------------------------
// Ctors and Dtors.
//------------------------------------------
CAudioOutLinuxAlsa::CAudioOutLinuxAlsa() :
  CAudioOutUNIX(),
  m_ulTickCount(0),
  m_ulLastBytesPlayed(0),
  m_ulLastTimeStamp(0),
  m_ulPausePosition(0),
  m_bHasHardwarePause(FALSE),
  m_bHasHardwareResume(FALSE),
  pcm_handle(0),
  mixer_handle(0)
{
};

CAudioOutLinuxAlsa::~CAudioOutLinuxAlsa()
{
#ifdef _DEBUG
  printf("d\'tor\n");
#endif
  //The mixer is opened independently of the audio device. Make sure
  //it is closed.
  _CloseMixer();

  snd_pcm_hw_params_free(hwparams);
  //    snd_pcm_status_free(status);
};


// These Device Specific methods must be implemented
// by the platform specific sub-classes.
INT16 CAudioOutLinuxAlsa::_Imp_GetAudioFd(void)
{
  return (INT16) pcm_handle;
}


//Device specific methods to open/close the mixer and audio devices.
HX_RESULT CAudioOutLinuxAlsa::_OpenAudio()
{
  HX_RESULT retCode = RA_AOE_NOERR;
  int err=0;
    
  //Set the tick count to zero
  m_ulTickCount       = 0;
  m_ulLastTimeStamp   = 0;
  m_ulLastBytesPlayed = 0;
  m_ulPausePosition   = 0;

  //Check the environmental variable to let user overide default device.
  char *pszOverrideName = getenv( "AUDIO" );
  char szDevName[MAX_DEV_NAME];

  // Use defaults if no environment variable is set.
  if ( pszOverrideName && strlen(pszOverrideName)>0 )
    {
      SafeStrCpy( szDevName, pszOverrideName, MAX_DEV_NAME );
    }
  else
    {
      SafeStrCpy( szDevName, "default", MAX_DEV_NAME );
      //        SafeStrCpy( szDevName, "hw:0,0", MAX_DEV_NAME );
      //        SafeStrCpy( szDevName, "plughw:0,0", MAX_DEV_NAME );
    }

  // Open the audio device if it isn't already open
  if ( pcm_handle <= 0 )
    {
      if ( snd_pcm_open( &pcm_handle, szDevName, SND_PCM_STREAM_PLAYBACK /*stream*/, 0) < 0) {
#ifdef _DEBUG
        fprintf( stderr, "Failed to open audio device %s : %d  errno: %d\n",
                 szDevName, pcm_handle, errno );
#endif
        retCode = RA_AOE_BADOPEN;
      }
    }

  if((err=snd_pcm_nonblock( pcm_handle, 1)) < 0)
    {
#ifdef _DEBUG
      fprintf (stderr, "Cannot set nonblock (%s)\n",
               snd_strerror (err));
#endif
    }

  //     if((err = snd_pcm_status_malloc( &status)) <0)
  //       {
  //           fprintf (stderr, "cannot allocate status parameter structure (%s)\n",
  //                    snd_strerror (err));
  //       }

  if((err = snd_pcm_hw_params_malloc( &hwparams)) < 0)
    {
#ifdef _DEBUG
      fprintf (stderr, "HW parameters malloc failed %s\n",
               snd_strerror (err));
#endif
    }
 
  if ((err = snd_pcm_hw_params_any( pcm_handle, hwparams)) < 0) {
#ifdef _DEBUG
    fprintf (stderr, "HW parameters init failed %s\n",
             snd_strerror (err));
#endif
  }

  m_wLastError = retCode;
  return m_wLastError;
}


HX_RESULT CAudioOutLinuxAlsa::_CloseAudio()
{
  HX_RESULT retCode = RA_AOE_NOERR;
  if( pcm_handle > 0 )
    //        if (snd_pcm_state( pcm_handle) == SND_PCM_STATE_OPEN)
    {
      snd_pcm_close( pcm_handle);
      pcm_handle = 0;//NO_FILE_DESCRIPTOR;
#ifdef _DEBUG
      printf("pcm_handle is now %d\n", pcm_handle);
#endif
    }
  else
    {
      retCode = RA_AOE_DEVNOTOPEN;
    }

  m_wLastError = retCode;
  return m_wLastError;
}


HX_RESULT CAudioOutLinuxAlsa::_OpenMixer()
{
  HX_RESULT retCode = RA_AOE_NOERR;
  int result;
    
  if(!m_bMixerPresent)
    {
      //Let user override default device with environ variable.
      char *pszOverrideName = getenv( "MIXER" );
      char szDevCtlName[MAX_DEV_NAME];

      // could be "hw:0,0", or "plughw:0,0" or similiar
      if (pszOverrideName && strlen(pszOverrideName) > 0 )
        {
          SafeStrCpy( szDevCtlName , pszOverrideName, MAX_DEV_NAME );
        }
      else
        {
          SafeStrCpy( szDevCtlName , "default", MAX_DEV_NAME ); 
          // default for volume
        }

      if (( result = snd_mixer_open( &mixer_handle, 0)) < 0)
        {
#ifdef _DEBUG
          printf( "Open audio device failed %d", result);
#endif
          retCode = RA_AOE_BADOPEN;
        }
  
      if (( result = snd_mixer_attach( mixer_handle, szDevCtlName )) < 0) {
#ifdef _DEBUG
        printf("Mixer attach %s failed: %s", szDevCtlName, snd_strerror( result));
#endif
        snd_mixer_close( mixer_handle);
        retCode = RA_AOE_NOTENABLED;
      }
  
      if (( result = snd_mixer_selem_register( mixer_handle, NULL, NULL)) < 0) {
#ifdef _DEBUG
        printf("Register mixer error: %s", snd_strerror( result));
#endif
        snd_mixer_close( mixer_handle);
        retCode = RA_AOE_NOTENABLED;
      }

      if((result = snd_mixer_load( mixer_handle)) < 0 )
        {
          retCode = RA_AOE_NOTENABLED;
        }

      if (mixer_handle > 0)
        {
          m_bMixerPresent = 1;
          _Imp_GetVolume();
        }
      else
        {
          mixer_handle = 0;// NO_FILE_DESCRIPTOR;
          m_bMixerPresent = 0;
        }
    }

  m_wLastError = retCode;
  return m_wLastError;
}

HX_RESULT CAudioOutLinuxAlsa::_CloseMixer()
{
  if( mixer_handle > 0 )
    {
      //Let user override default device with environ variable.
      char *pszOverrideName = getenv( "MIXER" );
      char szDevCtlName[MAX_DEV_NAME];

      // could be "hw:0,0", or "plughw:0,0" or similiar
      if (pszOverrideName && strlen(pszOverrideName) > 0 )
        {
          SafeStrCpy( szDevCtlName , pszOverrideName, MAX_DEV_NAME );
        }
      else
        {
          SafeStrCpy( szDevCtlName , "default", MAX_DEV_NAME ); 
          // default for volume
        }

      if( snd_mixer_detach( mixer_handle, szDevCtlName) < 0)
        {
#ifdef _DEBUG
          printf("Detach mixer failed\n");
#endif
        }
  
      if(snd_mixer_close ( mixer_handle) < 0)
        {
#ifdef _DEBUG
          printf("Close mixer failed\n");
#endif
          mixer_handle = 0;//NO_FILE_DESCRIPTOR;
        }
    }

  return m_wLastError;
}


//Devic specific method to set the audio device characteristics. Sample rate,
//bits-per-sample, etc.
//Method *must* set member vars. m_unSampleRate and m_unNumChannels.
HX_RESULT CAudioOutLinuxAlsa::_SetDeviceConfig( const HXAudioFormat* pFormat )
{
  HX_RESULT retCode = RA_AOE_NOERR;
  int err=0;

  if (snd_pcm_state( pcm_handle) != SND_PCM_STATE_OPEN)
    return RA_AOE_DEVNOTOPEN;

  if (snd_pcm_hw_params_any( pcm_handle, hwparams) < 0) {
#ifdef _DEBUG
    fprintf(stderr, "Configure device failed\n");
#endif
    return  retCode = RA_AOE_NOTENABLED;
  }

  // SND_PCM_ACCESS_RW_INTERLEAVED or       
  // SND_PCM_ACCESS_RW_NONINTERLEAVED.      
  // There are also access types for MMAPed 
  if (snd_pcm_hw_params_set_access( pcm_handle, hwparams,
                                    SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
#ifdef _DEBUG
    fprintf(stderr, "Set access failed\n");
#endif
    return  retCode = RA_AOE_NOTENABLED;
  }

  //  Now set the format. Either 8-bit or 16-bit audio is supported.
  // alsa supports up to 64 bit
  int      nSampleWidth  = pFormat->uBitsPerSample;
  ULONG32  nSampleRate   = pFormat->ulSamplesPerSec;
  int      numChannels   = pFormat->uChannels;
  int      nFormat1      = 0;
  int      nFormat2      = 0;

  snd_pcm_format_t m_format;
  snd_pcm_uframes_t bufferSz;
  unsigned long buffer_size, period_size;

  if( nSampleWidth == 16)
    {
      m_format = SND_PCM_FORMAT_S16;
    }
  else
    {
      m_format =  SND_PCM_FORMAT_U8;
    }

  m_frameBytes = snd_pcm_format_physical_width( m_format);

  if ((err = snd_pcm_hw_params_set_format( pcm_handle, hwparams,
                                           m_format) < 0)) {
#ifdef _DEBUG
    fprintf (stderr, "Set sample format failed %s %d\n",
             snd_strerror (err), m_format );
#endif        
    return (  m_wLastError = RA_AOE_NOTENABLED );
  }

  //If we went to 8-bit then
  if( m_format ==  SND_PCM_FORMAT_U8 )
    {
      nSampleWidth = 8;
    }

  m_uSampFrameSize = samplesize =nSampleWidth/8;

  if ( nSampleWidth != pFormat->uBitsPerSample )
    {
      ((HXAudioFormat*)pFormat)->uBitsPerSample = nSampleWidth;
    }

  // Set sample rate. If the exact rate is not supported
  // by the hardware, use nearest possible rate.         
  unsigned int rrate = (unsigned int) nSampleRate;
  if(( err = snd_pcm_hw_params_set_rate_near( pcm_handle, hwparams, &rrate, 0) ) < 0 )
    {
#ifdef _DEBUG
      fprintf(stderr,
              "The rate %d Hz is not supported by your hardware.\n==> Using %d Hz instead.\n",
              nSampleRate, rrate);
#endif
      return ( m_wLastError = RA_AOE_NOTENABLED );
    }

  m_unSampleRate = rrate;

  if ( rrate != pFormat->ulSamplesPerSec )
    {
      ((HXAudioFormat*)pFormat)->ulSamplesPerSec = rrate;
    }


  if (snd_pcm_hw_params_set_channels( pcm_handle, hwparams, numChannels) < 0) {
#ifdef _DEBUG
    fprintf(stderr, "Error setting channels.\n");
#endif
    return ( m_wLastError = RA_AOE_NOTENABLED );
  }

  m_unNumChannels = channels = numChannels;

  if ( numChannels != pFormat->uChannels )
    {
      ((HXAudioFormat*)pFormat)->uChannels = numChannels;
    }


  int periods = 2;       // Number of periods 
  snd_pcm_uframes_t periodsize = 2048; // FIXME
  int direct=0;
  snd_pcm_uframes_t period = 0;

  // The period size == fragment size.
  // Note that this in frames (frame = nr_channels * sample_width)
  // So a value of 1024 means 4096 bytes (1024 x 2 x 16-bits)
        
  //     if((err = snd_pcm_hw_params_get_period_size_max( hwparams, &period,& direct) ) < 0)
  //       {
  // #ifdef _DEBUG
  //           printf("Unable to get period size %ld : %s\n", period,  snd_strerror(err));
  // #endif
  //       }
  //     direct=0;

  if((err = snd_pcm_hw_params_set_period_size_near( pcm_handle, hwparams, &periodsize, &direct) ) < 0)
    {
#ifdef _DEBUG
      printf("Set period size failed %ld : %s\n", periodsize,  snd_strerror(err));
#endif
    }


  //////////////////////////////////// set periods
  //      printf("set periods\n");
  //      if ((err = snd_pcm_hw_params_set_periods_near( pcm_handle, hwparams, &periods, &direct ))  < 0 )
  //        {
  //            printf("Error setting periods  %s\n", snd_strerror(err));
  //        }
 
  /*     if(( err =snd_pcm_hw_params_get_period_size( hwparams, &period_size, NULL)) < 0)
         {
         printf("unable to get peroidsize:\n") ;
         }
  */  
  //     snd_pcm_hw_params_get_period_size( hwparams, &val);



  //     if((err = snd_pcm_hw_params_set_buffer_size( pcm_handle, hwparams, bufferSz )) < 0)
  //      if((err = snd_pcm_hw_params_set_buffer_size_near( pcm_handle, hwparams, bufferSz )) < 0)
  //        {
  //            printf("Unable to set buffer size %li : %s\n", bufferSz, snd_strerror(err));
  //        }
  //     printf("get buffer size\n");

  ////////////////////////// Get buffer size in frames 
  //  m_wBlockSize = m_ulBytesPerGran;
  if((err = snd_pcm_hw_params_get_buffer_size_max( hwparams, &bufferSz)) < 0) 
    {
#ifdef _DEBUG
      printf("Get buffer size failed:\n") ;
#endif
      if(  m_ulDeviceBufferSize <= 0)
        blocksize = m_wBlockSize = m_ulDeviceBufferSize = 8192 * 2;
    }
  else
    {
      blocksize = bufferSz;
      m_wBlockSize = blocksize;
      m_ulDeviceBufferSize  = blocksize *  samplesize * channels;
    }
  //    printf("blocksize %d, samplesize %d, channels %d\n", blocksize, samplesize, channels );
  //    printf("m_ulDeviceBufferSize %ld\n",  m_ulDeviceBufferSize);

  
  if (snd_pcm_hw_params( pcm_handle, hwparams) < 0) //write device
    {
#ifdef _DEBUG
      fprintf(stderr, "Error setting HW params.\n");
#endif
      return ( m_wLastError = RA_AOE_NOTENABLED );
    }


  snd_pcm_sw_params_alloca(&swparams);

  if((err = snd_pcm_sw_params_current( pcm_handle, swparams)) <0)
    {
#ifdef _DEBUG
      printf("Current SW params failed: %s\n", snd_strerror(err));
#endif
    }

  //  if ((err = snd_pcm_sw_params_set_avail_min ( pcm_handle, swparams, 2048)) < 0) {
  //       fprintf (stderr, "cannot set minimum available count (%s)\n",
  //                snd_strerror (err));
  //   }

  if((err = snd_pcm_sw_params_set_start_threshold( pcm_handle, swparams, bufferSz )) < 0)
    {
#ifdef _DEBUG
      printf("Set start threshold mode failed: %s\n", snd_strerror(err));
#endif
    }

  if((err = snd_pcm_sw_params_set_xfer_align( pcm_handle, swparams, 1)) < 0)
    {
#ifdef _DEBUG
      printf("Set transfer align failed: %s\n", snd_strerror(err));
#endif
    } 

  if((err = snd_pcm_sw_params_set_tstamp_mode( pcm_handle, swparams,SND_PCM_TSTAMP_MMAP )) < 0 )
    {
#ifdef _DEBUG
      printf("Set sw params time stamp mode failed: %s\n", snd_strerror(err));
#endif
    }

  if((err = snd_pcm_sw_params( pcm_handle, swparams)) < 0)
    {
#ifdef _DEBUG
      printf("Set sw params failed: %s\n", snd_strerror(err));
#endif
    }

  if ((err=snd_pcm_prepare ( pcm_handle)) < 0)
    {
#ifdef _DEBUG
      fprintf (stderr, "Prepare audio interface failed %s\n",
               snd_strerror (err));
#endif
      return  retCode = RA_AOE_NOTENABLED;
    }       


#ifdef _DEBUG
  snd_output_t *output = NULL;
  snd_output_stdio_attach(&output, stdout, 0);
  printf("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\n");
  snd_pcm_dump( pcm_handle, output);
  printf("\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\n");
  snd_pcm_hw_params_dump( hwparams, output);
  printf("\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\n");
  snd_pcm_sw_params_dump( swparams, output); 
  printf("\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\n");
  //     snd_pcm_status_dump( status, output);
  //     printf("\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\n");

  fprintf( stdout, "Device Configured:\n");
  fprintf( stdout, "         Sample Rate: %d\n",  m_unSampleRate);
  fprintf( stdout, "        Sample Width: %d\n",  nSampleWidth);
  fprintf( stdout , "        Num channels: %d\n",  m_unNumChannels);
  fprintf( stdout, "          Block size: %d\n",  m_wBlockSize);
  fprintf( stdout, "  Device buffer size: %lu\n", m_ulDeviceBufferSize);

#endif

  //     snd_pcm_sw_params_free( swparams);

  return RA_AOE_NOERR;
}

void CAudioOutLinuxAlsa::_SyncUpTimeStamps(ULONG32 writeCount)
{
  snd_pcm_hwsync( pcm_handle);
  int bytes2  = 0;
  int err = 0;

  snd_pcm_sframes_t framedelay;

  if((err = snd_pcm_delay( pcm_handle, &framedelay)) < 0)
    {
#ifdef _DEBUG
      fprintf (stderr, "cannot get delay %s\n", snd_strerror (err));
#endif
    }

  bytes2 = snd_pcm_frames_to_bytes( pcm_handle, framedelay);
             
  if( bytes2 > 0)
    {
      m_ulLastBytesPlayed = (UINT64)( m_ulTotalWritten + writeCount - bytes2);
      m_ulLastTimeStamp   = GetTickCount();
    }
}


//Device specific method to write bytes out to the audiodevice and return a
//count of bytes written.
HX_RESULT CAudioOutLinuxAlsa::_WriteBytes( UCHAR* buffer, ULONG32 ulBuffLength, LONG32& lCount )
{
  LONG32 writeCount;
  HX_RESULT retCode = RA_AOE_NOERR;
  int err=0;
  if( m_ulTickCount == 0 )
    m_ulTickCount = GetTickCount();

  snd_pcm_sframes_t num_frames, size;

  lCount = 0;

  num_frames = snd_pcm_bytes_to_frames( pcm_handle, ulBuffLength); //alsa in frames

  // Returns the number of frames actually written. 
  size = snd_pcm_writei( pcm_handle, buffer, num_frames );

  if(size < 0)
    {
      switch (size)
        {
        case -EBADFD:
          {
#ifdef _DEBUG
            printf("EBADFD: Device not in the right state \n");
#endif
            retCode = RA_AOE_DEVBUSY;
          }
          break;
        case -EPIPE: //lets handle underruns
          {
            if(( size = snd_pcm_prepare( pcm_handle) ) < 0 )
              {
#ifdef _DEBUG
                printf("EPIPE: Recovery from underrun is difficult: %s\n", snd_strerror( size));
#endif
                retCode = RA_AOE_DEVBUSY;
              }
          }
          break;
        case -ESTRPIPE:
          {
            while (( size = snd_pcm_resume( pcm_handle)) == -EAGAIN)
              sleep(1);
            if ( size < 0)
              {
                size = snd_pcm_prepare( pcm_handle);
                if ( size < 0)
                  {
#ifdef _DEBUG
                    printf("ESTRPIPE: Recover from suspend is difficult: %s\n", snd_strerror( size));
#endif
                    retCode = RA_AOE_DEVBUSY;
                  }
              }
          }
          break;
        };
    }

//  printf("frames written = %d\n", size);
  writeCount = snd_pcm_frames_to_bytes( pcm_handle, size );

  if( writeCount < 0 )
    {
      if( errno == EAGAIN )
        retCode = RA_AOE_NOERR;
      if( errno == EINTR )
        retCode = RA_AOE_DEVBUSY;
    }
  else
    {
      _SyncUpTimeStamps( writeCount);

      // XXXRGG: Figure out why writeCount != ulBuffLength
      // lCount = writeCount;
      lCount = ulBuffLength;
    }

  return retCode;
}


UINT64 CAudioOutLinuxAlsa::_GetBytesActualyPlayed(void) const
{
  // Get current playback position in device DMA. 
  int     bytes2 = 0;
  UINT64  ulTheAnswer = 0;

  if( m_ulTotalWritten > 0 )
    {
      HX_ASSERT( m_unSampleRate!=0 && m_uSampFrameSize!=0 );
      ULONG32 ulTick = GetTickCount();
      //We need to update the timestamps every so often.
      //This make sure that if the XServer was blocked, and
      //we ran dry, that we re-sync up.
      if( (ulTick - m_ulLastTimeStamp) > 200 )
        {
          ((CAudioOutLinuxAlsa*)this)->_SyncUpTimeStamps();
          ulTick = GetTickCount();
        }
      ulTheAnswer = (UINT64)(m_ulLastBytesPlayed+
                             ((float)(ulTick-m_ulLastTimeStamp)*
                              (float)m_unNumChannels/1000.0*
                              m_unSampleRate*m_uSampFrameSize) +0.5 );
    }

  //    printf("BytesActualyPlayed %ld\n", ulTheAnswer);
  return  ulTheAnswer;
}


//this must return the number of bytes that can be written without blocking.
HX_RESULT CAudioOutLinuxAlsa::_GetRoomOnDevice(ULONG32& ulBytes) const
{
  HX_RESULT  retCode = RA_AOE_NOERR;

  // FIXME

  ulBytes = m_ulDeviceBufferSize - ( m_ulTotalWritten - _GetBytesActualyPlayed() );
  //    printf("RoomOnDevice %ld\n", ulBytes);
  m_wLastError = retCode;
  return m_wLastError;
}


//Device specific method to get/set the devices current volume.
UINT16 CAudioOutLinuxAlsa::_GetVolume() const
{

  UINT16 nRetVolume   = 0;
  long pmin = 0, pmax = 0;
  long percentage = 0;
  
  long frontLeftVolume = 0;
  long frontRightVolume = 0;
  
  /*  long frontCenterVolume = 0;

  long rearLeftVolume = 0;
  long rearRightVolume = 0;
  
  long wooferVolume = 0;

  snd_mixer_selem_id_t *sid;
  snd_mixer_selem_id_alloca(&sid);
  */
  snd_mixer_elem_t *mixer_elements2;

  mixer_elements2 = snd_mixer_first_elem( mixer_handle);
  // grab first elem of mixer
  if (snd_mixer_selem_has_playback_volume( mixer_elements2)) {
            
    if( snd_mixer_selem_get_playback_volume( mixer_elements2,
                                             SND_MIXER_SCHN_FRONT_LEFT, &frontLeftVolume) < 0)
      frontLeftVolume = 0;
           
    if( snd_mixer_selem_get_playback_volume( mixer_elements2,
                                             SND_MIXER_SCHN_FRONT_RIGHT, &frontRightVolume) < 0)
      frontRightVolume = 0;
  }

  /*    snd_mixer_selem_get_playback_volume_range( mixer_elements2, &pmin, &pmax);
        int range = pmax - pmin;
        int val = frontLeftVolume;
        frontRightVolume =  frontLeftVolume;
        val -= pmin;

        //    printf("min %d, max %d, range %d, val %d\n", pmin, pmax, range, val );
        percentage = (long)rint((double)val / (double)range * 100);
  */      

  //    printf("fleft %ld, fright %ld, percent %d\n",
  //           frontLeftVolume, frontRightVolume, percentage);


  // Save for future use, demonstrates multichannel mixer access.
  

  //   for ( mixer_elements2 = snd_mixer_first_elem( mixer_handle);
  //         mixer_elements2;
  //         mixer_elements2 = snd_mixer_elem_next( mixer_elements2) )  {
    
  //     snd_mixer_selem_get_id( mixer_elements2, sid);
    
  // //     if ( !snd_mixer_selem_is_active( mixer_elements2))
  // //       continue;
    
  //     printf("mixer control '%s',%i\n", 
  //     snd_mixer_selem_id_get_name( sid), 
  //     snd_mixer_selem_id_get_index( sid));
  
  //        snd_mixer_selem_get_playback_volume_range( mixer_elements2, &pmin, &pmax);
  //         // if its got mono mixer
  //        if ( snd_mixer_selem_has_common_volume( mixer_elements2)) //mono
  //      {
  //            // check for playback vol mixer         
  //          if (snd_mixer_selem_has_playback_volume( mixer_elements2)) {
  //                //score!        
  // //           if( snd_mixer_find_selem( mixer_handle,mixer_elements2) == SND_MIXER_SCHN_MONO)
  // //             {
  //              snd_mixer_selem_get_playback_volume( mixer_elements2,
  //                                                   SND_MIXER_SCHN_MONO, &frontLeftVolume);
  //                //for finding percentage
                
  //              int range = pmax - pmin;
  //              int val = frontLeftVolume;
  //              frontRightVolume =  frontLeftVolume;
  //              val -= pmin;
  //              percentage = (long)rint((double)val / (double)range * 100);
  //              printf("common min %d, max %d, range %d\n", pmin, pmax, range);
   
  //          }
  //      }
  //        else //stereo
  //       {
  // //          printf("get seperate r and l volumes\n");       
  //           if (snd_mixer_selem_has_playback_volume( mixer_elements2)) {
            
  //               if( snd_mixer_selem_get_playback_volume( mixer_elements2,
  //                                                        SND_MIXER_SCHN_FRONT_LEFT, &frontLeftVolume) < 0)
  //                   frontLeftVolume = 0;
           
  //               if( snd_mixer_selem_get_playback_volume( mixer_elements2,
  //                                                        SND_MIXER_SCHN_FRONT_RIGHT, &frontRightVolume) < 0)
  //                   frontRightVolume = 0;
              
  // //               if( snd_mixer_selem_get_playback_volume( mixer_elements2,
  // //                                                        SND_MIXER_SCHN_FRONT_CENTER, &frontCenterVolume) < 0)
  // //                   frontCenterVolume = 0;
           
  // //               if( snd_mixer_selem_get_playback_volume( mixer_elements2,
  // //                                                        SND_MIXER_SCHN_REAR_RIGHT, &rearRightVolume) < 0)
  // //                   rearRightVolume = 0;
              
  // //               if( snd_mixer_selem_get_playback_volume( mixer_elements2,
  // //                                                        SND_MIXER_SCHN_REAR_LEFT, &rearLeftVolume) < 0)
  // //                   rearLeftVolume = 0;
           
  // //               if( snd_mixer_selem_get_playback_volume( mixer_elements2,
  // //                                                        SND_MIXER_SCHN_WOOFER, &wooferVolume) < 0)
  // //                   wooferVolume = 0;
                
  //               long range = pmax - pmin;
  //               long val = frontLeftVolume;
  //               frontRightVolume =  frontLeftVolume;
  //               val -= pmin;

  //               percentage = (long)rint(val / range * 100);
        
  //                 //            printf("min %d, max %d, range %d\n", pmin, pmax, range);

  //               printf("fleft %ld, fright %ld, center %ld, rleft %ld, rright %ld, woofer %ld, percent %d\n",
  //                      frontLeftVolume, frontRightVolume, frontCenterVolume, rearLeftVolume,
  //                      rearRightVolume, percentage);
  //           }
  // //         snd_mixer_selem_get_playback_volume_range( mixer_elements2, &pmin, &pmax);
  //       }
  //     }
  

  //    nLeftVolume  = (nVolume & 0x000000ff); 
  //    nRightVolume = (nVolume & 0x0000ff00) >> 8;

  nRetVolume  = (UINT16)frontLeftVolume;

  //    printf("nRetVolume is %ld\n",nRetVolume);

  return nRetVolume;
}


HX_RESULT CAudioOutLinuxAlsa::_SetVolume(UINT16 unVolume)
{
  HX_RESULT retCode = RA_AOE_NOERR;
  ///////////////////////// FIXME: 
  long pmin = 0, pmax = 0;
  long percentage = 0;
  
  long frontLeftVolume = 0;
  long frontRightVolume = 0;
  
  /*  long frontCenterVolume = 0;

  long rearLeftVolume = 0;
  long rearRightVolume = 0;
  
  long wooferVolume = 0;

  snd_mixer_selem_id_t *sid;
  snd_mixer_selem_id_alloca(&sid);
  */
  snd_mixer_elem_t *mixer_elements;

  frontLeftVolume = frontRightVolume = unVolume;
  //    nNewVolume = (unVolume & 0xff) | ((unVolume &0xff) << 8);

  mixer_elements = snd_mixer_first_elem( mixer_handle); //just grab first elem for now

  if (snd_mixer_selem_has_playback_volume( mixer_elements)) {

    // do this as the volume range in alsa might be more or less than 100
    // act like its a percentage, the vol range for rmedigi96 is 16383, and my pci128 is 16
    snd_mixer_selem_get_playback_volume_range( mixer_elements, &pmin, &pmax);

    int range = pmax - pmin;
    int val = frontLeftVolume;

    //percent to volume
    frontLeftVolume = rint((double)range * ((double)val*.01)) + pmin; 
  
    frontRightVolume = frontLeftVolume;
  
    if( snd_mixer_selem_set_playback_volume( mixer_elements,
                                             SND_MIXER_SCHN_FRONT_LEFT,
                                             frontLeftVolume) < 0)
      frontLeftVolume = 0;

    if( snd_mixer_selem_set_playback_volume( mixer_elements,
                                             SND_MIXER_SCHN_FRONT_RIGHT,
                                             frontRightVolume) < 0)
      frontRightVolume = 0;
  }              
#ifdef _DEBUG
  printf("setting volume left %ld, right %ld %ld\%\n", frontLeftVolume, frontRightVolume, unVolume );
#endif




  // Save for future use, demonstrates multichannel mixer access.

 
  //     for ( mixer_elements = snd_mixer_first_elem( mixer_handle); //ramble through elems
  //           mixer_elements; 
  //           mixer_elements = snd_mixer_elem_next( mixer_elements) )
  //       {
    
  //           snd_mixer_selem_get_id( mixer_elements, sid);
    
  //           printf("mixer control '%s',%i\n", 
  //                  snd_mixer_selem_id_get_name( sid), 
  //                  snd_mixer_selem_id_get_index( sid));

  //           if ( snd_mixer_selem_has_common_volume( mixer_elements)) //is mono
  //             {
  // //          // check for playback vol mixer         
  //                 if (snd_mixer_selem_has_playback_volume( mixer_elements)) {
  //                     snd_mixer_selem_get_playback_volume_range( mixer_elements, &pmin, &pmax);

  //                     int range = pmax - pmin;
  //                     int val = frontLeftVolume;

  //                     val -= pmin;
  //                     frontLeftVolume  =  rint((double)range * ((double)val*.01)) + pmin; //percent to volume
  //                       //frontLeftVolume  = rint((double)val/(double)range * 100);
  //                       // frontLeftVolume = (long) ( unVolume & 0x000000ff);
  //                     frontRightVolume = frontLeftVolume;
  //                       //  frontRightVolume = (long) ( unVolume & 0x0000ff00);
  //                       //score!       
  //                     if( snd_mixer_selem_set_playback_volume( mixer_elements,
  //                                                              SND_MIXER_SCHN_MONO,
  //                                                              frontLeftVolume) < 0)
  //                         frontLeftVolume = 0;
  //                     val -= pmin;
  //                     percentage = (long)rint((double)val/(double)range * 100);
  //                 }
  //             }
  //           else //is stereo
  //             {
  // //        if (snd_mixer_selem_has_playback_volume( mixer_elements)) {
  //                 snd_mixer_selem_get_playback_volume_range( mixer_elements, &pmin, &pmax);
  //                 printf("vol min %d, max %d\n", pmin, pmax);
  //                 int range = pmax - pmin;
  //                 int tmp;
  //                 int val = frontLeftVolume;
  //                 frontLeftVolume  =  rint((double)range * ((double)val*.01)) + pmin; //percent to volume
  //                   //rint((double)val/(double)range * 100);
  
  // //  frontLeftVolume = (long) ( unVolume & 0x000000ff);
  //                 frontRightVolume = frontLeftVolume;
  // //  frontRightVolume = (long) ( unVolume & 0x0000ff00);

  
  //                 if( snd_mixer_selem_set_playback_volume( mixer_elements,
  //                                                          SND_MIXER_SCHN_FRONT_LEFT,
  //                                                          frontLeftVolume) < 0)
  //                     frontLeftVolume = 0;

  //                 if( snd_mixer_selem_set_playback_volume( mixer_elements,
  //                                                          SND_MIXER_SCHN_FRONT_RIGHT,
  //                                                          frontRightVolume) < 0)
  //                     frontRightVolume = 0;
              
  // //              if( snd_mixer_selem_set_playback_volume( mixer_elements,
  // //                SND_MIXER_SCHN_FRONT_CENTER, &frontCenterVolume) < 0)
  // //               frontCenterVolume = 0;
           
  // //               if( snd_mixer_selem_set_playback_volume( mixer_elements,
  // //                  SND_MIXER_SCHN_REAR_RIGHT, &rearRightVolume) < 0)
  // //                  rearRightVolume = 0;
              
  // //              if( snd_mixer_selem_set_playback_volume( mixer_elements,
  // //                SND_MIXER_SCHN_REAR_LEFT, &rearLeftVolume) < 0)
  // //               rearLeftVolume = 0;
           
  // //               if( snd_mixer_selem_set_playback_volume( mixer_elements,
  // //                    SND_MIXER_SCHN_WOOFER, &wooferVolume) < 0)
  // //                   wooferVolume = 0;
  // //        }
  //                 int range2 = pmax - pmin;
  //                 int val2 = frontLeftVolume;
  //                 val2 -= pmin;
  //                 percentage = (long)rint((double)val2/(double)range2 * 100);
        
  //             }
  //       }

  //     printf("fleft %d, fright %d, center %d, rleft %d, rright %d, woofer %d, percent %d\n",
  //            frontLeftVolume, frontRightVolume, frontCenterVolume, rearLeftVolume, 
  //            rearRightVolume, percentage);

  m_wLastError = retCode;
  return m_wLastError;
}


//Device specific method to drain a device. This should play the remaining
//bytes in the devices buffer and then return.
HX_RESULT CAudioOutLinuxAlsa::_Drain()
{
  HX_RESULT retCode = RA_AOE_NOERR;
  if ( pcm_handle < 0 )
    {
      retCode = RA_AOE_DEVNOTOPEN;
    }
  // Stop PCM device after pending frames have been played
  if( snd_pcm_drain( pcm_handle) < 0)
    retCode = RA_AOE_GENERAL;
    

  m_wLastError = retCode;
  return m_wLastError;
}


//Device specific method to reset device and return it to a state that it
//can accept new sample rates, num channels, etc.
HX_RESULT CAudioOutLinuxAlsa::_Reset()
{
  HX_RESULT retCode = RA_AOE_NOERR;
  m_ulPausePosition = 0;

  if( pcm_handle > 0 )
    //    if (snd_pcm_state( pcm_handle) == SND_PCM_STATE_OPEN)
    {
      if( snd_pcm_reset( pcm_handle) < 0)
        {
          retCode = RA_AOE_GENERAL;
        }
    }
  else
    {
      retCode = RA_AOE_DEVNOTOPEN;
    }

  m_wLastError = retCode;
  return m_wLastError;
}

HX_RESULT CAudioOutLinuxAlsa::_CheckFormat( const HXAudioFormat* pFormat )
{
  int   nBitsPerSample = pFormat->uBitsPerSample;
  int   ulTmp          = pFormat->ulSamplesPerSec;
  int   nNumChannels   = pFormat->uChannels;
  float fTmp           = 0.0;
  int err=0;
  snd_pcm_format_t m_format;
  HX_RESULT retCode = RA_AOE_NOERR;

  if( nBitsPerSample = 16)
    {
      m_format = SND_PCM_FORMAT_S16;
    }
  else
    {
      m_format =  SND_PCM_FORMAT_U8;
    }

  //Is the device already open?
  if( pcm_handle > 0 ||  RA_AOE_NOERR != _OpenAudio() )
    {
      retCode = RA_AOE_DEVBUSY;
      return retCode;
    }

  //See if the sample rate is supported.
  if( snd_pcm_hw_params_test_rate ( pcm_handle, hwparams, ulTmp  ,0) < 0)
    {
#ifdef _DEBUG
      fprintf (stderr, "cannot set sample rate (%s)\n",
               snd_strerror (err));
#endif
      //Not quite the real error, but it is what we need to return.
      retCode = RA_AOE_BADFORMAT;
      goto donechecking;
    }

  //Check num channels.
  if(snd_pcm_hw_params_test_channels ( pcm_handle, hwparams, nNumChannels) < 0)
    {
      retCode = RA_AOE_BADFORMAT;
      goto donechecking;
    }

  if ( snd_pcm_hw_params_test_format( pcm_handle, hwparams, m_format  /*SND_PCM_FORMAT_S16*/ ) < 0)
    {
      retCode = RA_AOE_BADFORMAT;
      goto donechecking;
    }

  //Close the audio device.
 donechecking:
  _CloseAudio();
  m_wLastError = retCode;
  return retCode;
}


HX_RESULT CAudioOutLinuxAlsa::_CheckSampleRate( ULONG32 ulSampleRate )
{
  ULONG32 ulTmp = ulSampleRate;

  m_wLastError = RA_AOE_NOERR;
  //Is the device already open?
  if( pcm_handle > 0 || RA_AOE_NOERR != _OpenAudio() )
    {
      m_wLastError = RA_AOE_DEVBUSY;
    }
  else
    {
      //See if the sample rate is supported.
      if( snd_pcm_hw_params_test_rate ( pcm_handle, hwparams, (uint)ulTmp, 0) != 1)
        {
          //Not quite the real error, but it is what we need to return.
          m_wLastError = RA_AOE_DEVBUSY;
        }
      else if( ulSampleRate != ulTmp )
        {
          //It is NOT supported
          m_wLastError = RA_AOE_BADFORMAT;
        }

      _CloseAudio();
    }

  return m_wLastError;
}


HX_RESULT CAudioOutLinuxAlsa::_Pause()
{
  m_wLastError = HXR_OK;
  m_ulPausePosition = m_ulTotalWritten;
  m_ulTickCount = 0;
  m_ulLastTimeStamp   = 0;
  int err=0;
  if( m_bHasHardwarePause)
    {
      if((err = snd_pcm_pause( pcm_handle, 1)) < 0)
        {
#ifdef _DEBUG
          fprintf(stderr, "Error : cannot pause (%s)\n", snd_strerror (err));
#endif
          m_wLastError = RA_AOE_NOTSUPPORTED;
        }
    }
  else
    {
      m_wLastError = RA_AOE_NOTSUPPORTED;
    }

  return m_wLastError;
}

HX_RESULT CAudioOutLinuxAlsa::_Resume()
{
  m_wLastError = HXR_OK;

  if( m_ulTotalWritten > 0 )
    {
      m_ulTickCount = GetTickCount();
      m_ulLastTimeStamp = m_ulTickCount;
    }

  if( m_bHasHardwarePause &&  m_bHasHardwareResume)
    {
      int err=0;
      if((err = snd_pcm_resume( pcm_handle)) < 0)
        {
#ifdef _DEBUG
          fprintf (stderr, "Error: cannot resume (%s)\n",
                   snd_strerror (err));
#endif
          m_wLastError = RA_AOE_NOTSUPPORTED;
        }
    }
  else
    {
      m_wLastError = RA_AOE_NOTSUPPORTED;
    }
    
  return m_wLastError;
}

BOOL CAudioOutLinuxAlsa::_HardwarePauseSupported() const
{
    
  if( snd_pcm_hw_params_can_resume( hwparams) == 1)
    {
      m_bHasHardwareResume = true;
    }
  else
    {
      m_bHasHardwareResume = false;
    }

  if( snd_pcm_hw_params_can_pause( hwparams) == 1)
    {
      m_bHasHardwarePause = true;
      return TRUE;
    }
  else
    {
      m_bHasHardwarePause = false;
    }
  return FALSE;
}

