[GnomeMeeting-devel-list] ALSA plugin



Here are the latest versions of the ALSA plugin. Please test too.
Thanks for those of you who have tested it until now.
-- 
Damien Sandras <dsandras seconix com>
/*
 * sound_alsa.cxx
 *
 * Sound driver implementation.
 *
 * Portable Windows Library
 *
 * Copyright (c) 1993-1998 Equivalence Pty. Ltd.
 *
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is Portable Windows Library.
 *
 * The Initial Developer of the Original ALSA Code is 
 * Damien Sandras <dsandras seconix com>
 *
 * Portions are Copyright (C) 1993 Free Software Foundation, Inc.
 * All Rights Reserved.
 *
 * Contributor(s): /
 *
 * $Log: sound_alsa.cxx,v $
 */

#include "sound_alsa.h"

#if !P_USE_INLINES
#include <ptlib/contain.inl>
#endif

#include <ptlib.h>

DECLARE_PLUGIN("ALSA", PDeviceManager::SoundIn | PDeviceManager::SoundOut);
DECLARE_PLUGIN_SOUNDINPUT(PSoundChannelALSA);
DECLARE_PLUGIN_SOUNDOUTPUT(PSoundChannelALSA);

///////////////////////////////////////////////////////////////////////////////
    
PAudioDelay::PAudioDelay()
{
  firstTime = TRUE;
  error = 0;
}
    
void PAudioDelay::Restart()
{
  firstTime = TRUE;
}
  
BOOL PAudioDelay::Delay(int frameTime)
{
  if (firstTime) {
    firstTime = FALSE;
    previousTime = PTime();
    return TRUE;
  }

  error += frameTime;

  PTime now;
  PTimeInterval delay = now - previousTime;
  error -= (int)delay.GetMilliSeconds();
  previousTime = now;

  if (error > 0)
#ifdef P_LINUX
    usleep(error * 1000);
#else
    PThread::Current()->Sleep(error);
#endif

  return error <= -frameTime;

  //if (headRoom > MAX_HEADROOM)
  //  PThread::Current()->Sleep(headRoom - MIN_HEADROOM);
}

///////////////////////////////////////////////////////////////////////////////
// declare type for sound handle dictionary

PSound::PSound(unsigned channels,
               unsigned samplesPerSecond,
               unsigned bitsPerSample,
               PINDEX   bufferSize,
               const BYTE * buffer)
{
  encoding = 0;
  numChannels = channels;
  sampleRate = samplesPerSecond;
  sampleSize = bitsPerSample;
  SetSize(bufferSize);
  if (buffer != NULL)
    memcpy(GetPointer(), buffer, bufferSize);
}


PSound::PSound(const PFilePath & filename)
{
  encoding = 0;
  numChannels = 1;
  sampleRate = 8000;
  sampleSize = 16;
  Load(filename);
}


PSound & PSound::operator=(const PBYTEArray & data)
{
  PBYTEArray::operator=(data);
  return *this;
}


void PSound::SetFormat(unsigned channels,
                       unsigned samplesPerSecond,
                       unsigned bitsPerSample)
{
  encoding = 0;
  numChannels = channels;
  sampleRate = samplesPerSecond;
  sampleSize = bitsPerSample;
  formatInfo.SetSize(0);
}


BOOL PSound::Load(const PFilePath & /*filename*/)
{
  return FALSE;
}


BOOL PSound::Save(const PFilePath & /*filename*/)
{
  return FALSE;
}


BOOL PSound::Play()
{
  PSoundChannel channel(PSoundChannelALSA::GetDefaultDevice(PSoundChannelALSA::Player),
                        PSoundChannelALSA::Player);
  if (!channel.IsOpen())
    return FALSE;

  return channel.PlaySound(*this, TRUE);
}


BOOL PSound::PlayFile(const PFilePath & file, BOOL wait)
{
  PSoundChannel channel(PSoundChannelALSA::GetDefaultDevice(PSoundChannelALSA::Player),
                        PSoundChannelALSA::Player);
  if (!channel.IsOpen())
    return FALSE;

  return channel.PlayFile(file, wait);
}


///////////////////////////////////////////////////////////////////////////////

SoundHandleEntry::SoundHandleEntry()
{
  handle    = -1;
  direction = 0;
}

static PStringArray playback_devices;
static PStringArray capture_devices;

///////////////////////////////////////////////////////////////////////////////

PSoundChannelALSA::PSoundChannelALSA()
{
  PSoundChannelALSA::Construct();
}


PSoundChannelALSA::PSoundChannelALSA (const PString &device,
				      Directions dir,
				      unsigned numChannels,
				      unsigned sampleRate,
				      unsigned bitsPerSample)
{
  Construct();
  Open (device, dir, numChannels, sampleRate, bitsPerSample);
}


void PSoundChannelALSA::Construct()
{
  frame_bytes = 0;
  period_size = 0;
  periods = 0;
  os_handle = NULL;
}


PSoundChannelALSA::~PSoundChannelALSA()
{
  Close();
}


PStringArray PSoundChannelALSA::GetDeviceNames (Directions dir)
{
  int card = -1, dev = -1;
  
  snd_ctl_t *handle = NULL;
  snd_ctl_card_info_t *info = NULL;
  snd_pcm_info_t *pcminfo = NULL;
  snd_pcm_stream_t stream;

  char *name = NULL;
  char card_id [32];

  if (dir == Recorder) {

    stream = SND_PCM_STREAM_CAPTURE;
    capture_devices = PStringArray ();
  }
  else {

    stream = SND_PCM_STREAM_PLAYBACK;
    playback_devices = PStringArray ();
  }

  snd_ctl_card_info_alloca (&info);
  snd_pcm_info_alloca (&pcminfo);

  /* No sound card found */
  if (snd_card_next (&card) < 0 || card < 0) {

    return PStringArray ();
  }


  while (card >= 0) {

    snprintf (card_id, 32, "hw:%d", card);
    
    snd_ctl_open (&handle, card_id, 0);
    snd_ctl_card_info (handle, info);

    while (1) {

      snd_ctl_pcm_next_device (handle, &dev);

      if (dev < 0)
        break;

      snd_pcm_info_set_device (pcminfo, dev);
      snd_pcm_info_set_subdevice (pcminfo, 0);
      snd_pcm_info_set_stream (pcminfo, stream);

      if (snd_ctl_pcm_info (handle, pcminfo) >= 0) {
    
	snd_card_get_name (card, &name);
	if (dir == Recorder) {

	  if (capture_devices.GetStringsIndex (name) == P_MAX_INDEX)
	    capture_devices.AppendString (name);
	}
	else {

	  if (playback_devices.GetStringsIndex (name) == P_MAX_INDEX)
	    playback_devices.AppendString (name);
	}
	    
	free (name);
      }
    }


    snd_ctl_close(handle);
    snd_card_next (&card);
  }


  snd_pcm_info_free (pcminfo);

  if (dir == Recorder)
    return capture_devices;
  else 
    return playback_devices;
}


PString PSoundChannelALSA::GetDefaultDevice(Directions dir)
{
  PStringArray devicenames;
  devicenames = PSoundChannelALSA::GetDeviceNames (dir);

  return devicenames[0];
}


BOOL PSoundChannelALSA::Open (const PString & _device,
                              Directions _dir,
			      unsigned _numChannels,
			      unsigned _sampleRate,
			      unsigned _bitsPerSample)
{
  PString real_device_name;
  PINDEX i = 0;
  snd_pcm_stream_t stream;

  Close();

  os_handle = NULL;

  if (_dir == Recorder)
    stream = SND_PCM_STREAM_CAPTURE;
  else
    stream = SND_PCM_STREAM_PLAYBACK;

  /* Open in NONBLOCK mode */
  if ((i = (_dir == Recorder) ? capture_devices.GetStringsIndex (_device) : playback_devices.GetStringsIndex (_device)) != P_MAX_INDEX)
    real_device_name = "plughw:" + PString (i);
  else {
    
    PTRACE (1, "ALSA\tDevice unavailable");
    return FALSE;
  }

  if (snd_pcm_open (&os_handle, real_device_name, stream, 0) < 0) {

    PTRACE (1, "ALSA\tOpen Failed");
    return FALSE;
  }
  else 
    snd_pcm_nonblock (os_handle, 0);
   
  /* save internal parameters */
  direction = _dir;
  device = real_device_name;
  mNumChannels = _numChannels;
  mSampleRate = _sampleRate;
  mBitsPerSample = _bitsPerSample;
  isInitialised = FALSE;

  PTRACE (1, "ALSA\tDevice " << real_device_name << " Opened");

  return TRUE;
}


BOOL PSoundChannelALSA::Setup()
{
  snd_pcm_hw_params_t *hw_params = NULL;
  int err = 0;
  enum _snd_pcm_format val = SND_PCM_FORMAT_UNKNOWN;
  BOOL no_error = TRUE;


  if (os_handle == NULL) {

    PTRACE(6, "ALSA\tSkipping setup of " << device << " as not open");
    return FALSE;
  }

  if (isInitialised) {

    PTRACE(6, "ALSA\tSkipping setup of " << device << " as instance already initialised");
    return TRUE;
  }


#if PBYTE_ORDER == PLITTLE_ENDIAN
  val = (mBitsPerSample == 16) ? SND_PCM_FORMAT_S16_LE : SND_PCM_FORMAT_U8;
#else
  val = (mbitsPerSample == 16) ? SND_PCM_FORMAT_S16_BE : SND_PCM_FORMAT_U8;
#endif

  frame_bytes = (mNumChannels * (snd_pcm_format_width (val) / 8));

  snd_pcm_hw_params_alloca (&hw_params);


  if ((err = snd_pcm_hw_params_any (os_handle, hw_params)) < 0) {

    PTRACE (1, "ALSA\tCannot initialize hardware parameter structure " <<
	    snd_strerror (err));
    no_error = FALSE;
  }


  if ((err = snd_pcm_hw_params_set_access (os_handle, hw_params, 
				    SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {

    PTRACE (1, "ALSA\tCannot set access type " <<
	    snd_strerror (err));
    no_error = FALSE;
  }


  if ((err = snd_pcm_hw_params_set_format (os_handle, hw_params, val)) < 0) {

    PTRACE (1, "ALSA\tCannot set sample format " <<
             snd_strerror (err));
    no_error = FALSE;
  }


  if ((err = snd_pcm_hw_params_set_rate (os_handle, hw_params, 
					 mSampleRate, 0)) < 0) {

    PTRACE (1, "ALSA\tCannot set sample rate " <<
	    snd_strerror (err));
    no_error = FALSE;
  }


  if ((err = snd_pcm_hw_params_set_channels (os_handle, hw_params, 
					     mNumChannels)) < 0) {

    PTRACE (1, "ALSA\tCannot set channel count " <<
             snd_strerror (err));
    no_error = FALSE;
  }


  // Ignore errors here 
  if (periods && period_size) {

    if ((err = snd_pcm_hw_params_set_period_size_near (os_handle, 
						       hw_params, 
						       period_size/frame_bytes, 
						       0)) < 0)
      PTRACE (1, "ALSA\tCannot set period size " <<
	      snd_strerror (err));
    
    if ((err = snd_pcm_hw_params_set_periods (os_handle, 
					      hw_params, 
					      periods, 0)) < 0)
      PTRACE (1, "ALSA\tCannot set number of periods " <<
	      snd_strerror (err));

    if ((err = snd_pcm_hw_params_set_buffer_size_near (os_handle, 
						       hw_params, 
						       periods*period_size/frame_bytes)) < 0)
      PTRACE (1, "ALSA\tCannot set buffer size " <<
	      snd_strerror (err));
  }


  if ((err = snd_pcm_hw_params (os_handle, hw_params)) < 0) {

    PTRACE (1, "ALSA\tCannot set parameters " <<
	    snd_strerror (err));
    no_error = FALSE;
  }


  isInitialised = TRUE;

  return no_error;
}


BOOL PSoundChannelALSA::Close()
{
  PWaitAndSignal m(device_mutex);

  /* if the channel isn't open, do nothing */
  if (!os_handle)
    return FALSE;

  if (isInitialised)
    Abort ();

  snd_pcm_close (os_handle);
  os_handle = NULL;

  return TRUE;
}


BOOL PSoundChannelALSA::Write (const void *buf, PINDEX len)
{
  long r = 0;
  char *buf2 = (char *) buf;
  int pos = 0, max_try = 0;

  PWaitAndSignal m(device_mutex);

  if (!isInitialised && !Setup() || !len || !os_handle)
    return FALSE;


  do {

    /* the number of frames to read is the buffer length 
       divided by the size of one frame */
    r = snd_pcm_writei (os_handle, (char *) &buf2 [pos], len / frame_bytes);

    if (r > 0) {

      pos += r * frame_bytes;
      len -= r * frame_bytes;
    }
    else {

      if (r == -EPIPE) {    /* under-run */

	snd_pcm_prepare (os_handle);
      
      } else if (r == -ESTRPIPE) {

	while ((r = snd_pcm_resume (os_handle)) == -EAGAIN)
	  sleep(1);       /* wait until the suspend flag is released */
      
	if (r < 0) 
	  snd_pcm_prepare (os_handle);
      }

      PTRACE (1, "ALSA\tCould not write");
      max_try++;
    }
  } while (len > 0 && max_try < 5);


  if (len != 0) {

    memset ((char *) &buf2 [pos], 0, len);

    PTRACE (1, "ALSA\tWrite Error, filling with zeros");
  }

  return TRUE;
}


BOOL PSoundChannelALSA::Read (void * buf, PINDEX len)
{
  long r = 0;

  char *buf2 = (char *) buf;
  int pos = 0, max_try = 0;

  lastReadCount = 0;

  PWaitAndSignal m(device_mutex);

  if (!isInitialised && !Setup() || !len || !os_handle)
    return FALSE;

  do {

    /* the number of frames to read is the buffer length 
       divided by the size of one frame */
    r = snd_pcm_readi (os_handle, (char *) &buf2 [pos], len / frame_bytes);

    if (r > 0) {

      pos += r * frame_bytes;
      len -= r * frame_bytes;
      lastReadCount += r * frame_bytes;
    }
    else {

      if (r == -EPIPE) {    /* under-run */

	snd_pcm_prepare (os_handle);
      
      } else if (r == -ESTRPIPE) {

	while ((r = snd_pcm_resume (os_handle)) == -EAGAIN)
	  sleep(1);       /* wait until the suspend flag is released */

	if (r < 0) 
	  snd_pcm_prepare (os_handle);
      }

      PTRACE (1, "ALSA\tCould not read");
      max_try++;
    }
  } while (len > 0 && max_try < 5);

 
  if (len != 0) {

    memset ((char *) &buf2 [pos], 0, len);
    lastReadCount += len;

    PTRACE (1, "ALSA\tRead Error, filling with zeros");
  }

  return TRUE;
}


BOOL PSoundChannelALSA::SetFormat (unsigned numChannels,
				   unsigned sampleRate,
				   unsigned bitsPerSample)
{
  if (!os_handle)
    return SetErrorValues(NotOpen, EBADF);

  /* check parameters */
  PAssert((bitsPerSample == 8) || (bitsPerSample == 16), PInvalidParameter);
  PAssert(numChannels >= 1 && numChannels <= 2, PInvalidParameter);

  mNumChannels   = numChannels;
  mSampleRate    = sampleRate;
  mBitsPerSample = bitsPerSample;
 
  /* mark this channel as uninitialised */
  isInitialised = FALSE;

  return TRUE;
}


unsigned PSoundChannelALSA::GetChannels()   const
{
  return mNumChannels;
}


unsigned PSoundChannelALSA::GetSampleRate() const
{
  return mSampleRate;
}


unsigned PSoundChannelALSA::GetSampleSize() const
{
  return mBitsPerSample;
}


BOOL PSoundChannelALSA::SetBuffers (PINDEX size, PINDEX count)
{
  periods = count;
  period_size = size;

  return TRUE;
}


BOOL PSoundChannelALSA::GetBuffers(PINDEX & size, PINDEX & count)
{
  return FALSE;
}


BOOL PSoundChannelALSA::PlaySound(const PSound & sound, BOOL wait)
{
  return FALSE;
}


BOOL PSoundChannelALSA::PlayFile(const PFilePath & filename, BOOL wait)
{
  return FALSE;
}


BOOL PSoundChannelALSA::HasPlayCompleted()
{
  return TRUE;
}


BOOL PSoundChannelALSA::WaitForPlayCompletion()
{
  return TRUE;
}


BOOL PSoundChannelALSA::RecordSound(PSound & sound)
{
  return FALSE;
}


BOOL PSoundChannelALSA::RecordFile(const PFilePath & filename)
{
  return FALSE;
}


BOOL PSoundChannelALSA::StartRecording()
{
  return FALSE;
}


BOOL PSoundChannelALSA::IsRecordBufferFull()
{
  return TRUE;
}


BOOL PSoundChannelALSA::AreAllRecordBuffersFull()
{
  return TRUE;
}


BOOL PSoundChannelALSA::WaitForRecordBufferFull()
{
  return TRUE;
}


BOOL PSoundChannelALSA::WaitForAllRecordBuffersFull()
{
  return FALSE;
}


BOOL PSoundChannelALSA::Abort()
{
  int r = 0;

  if (!os_handle)
    return FALSE;

  if ((r = snd_pcm_drop (os_handle)) < 0) {

    PTRACE (1, "ALSA\tCannot abort" <<
	    snd_strerror (r));
    return FALSE;
  }
  else
    return TRUE;
}



BOOL PSoundChannelALSA::SetVolume(unsigned newVal)
{
  return FALSE;
}


BOOL  PSoundChannelALSA::GetVolume(unsigned &devVol)
{
  return FALSE;
}
  

BOOL PSoundChannelALSA::IsOpen () const
{
  return (os_handle != NULL);
}
#include <ptlib.h>
#include <ptlib/plugins.h>

#include <alsa/asoundlib.h>

class PAudioDelay : public PObject
{
  PCLASSINFO(PAudioDelay, PObject);

  public:
    PAudioDelay();
    BOOL Delay(int time);
    void Restart();
    int  GetError();

  protected:
    PTime  previousTime;
    BOOL   firstTime;
    int    error;
};

#define MIN_HEADROOM    30
#define MAX_HEADROOM    60

class SoundHandleEntry : public PObject {

  PCLASSINFO(SoundHandleEntry, PObject)

  public:
    SoundHandleEntry();

    int handle;
    int direction;

    unsigned numChannels;
    unsigned sampleRate;
    unsigned bitsPerSample;
    unsigned fragmentValue;
    BOOL isInitialised;
};

#define LOOPBACK_BUFFER_SIZE 5000
#define BYTESINBUF ((startptr<endptr)?(endptr-startptr):(LOOPBACK_BUFFER_SIZE+endptr-startptr))

class PSoundChannelALSA: public PSoundChannel
{
 public:
  PSoundChannelALSA();
  void Construct();
  PSoundChannelALSA(const PString &device,
		   PSoundChannel::Directions dir,
		   unsigned numChannels,
		   unsigned sampleRate,
		   unsigned bitsPerSample);
  ~PSoundChannelALSA();
  static PStringArray GetDeviceNames(PSoundChannel::Directions);
  static PString GetDefaultDevice(PSoundChannel::Directions);
  BOOL Open(const PString & _device,
       Directions _dir,
       unsigned _numChannels,
       unsigned _sampleRate,
       unsigned _bitsPerSample);
  BOOL Setup();
  BOOL Close();
  BOOL Write(const void * buf, PINDEX len);
  BOOL Read(void * buf, PINDEX len);
  BOOL SetFormat(unsigned numChannels,
	    unsigned sampleRate,
	    unsigned bitsPerSample);
  unsigned GetChannels() const;
  unsigned GetSampleRate() const;
  unsigned GetSampleSize() const;
  BOOL SetBuffers(PINDEX size, PINDEX count);
  BOOL GetBuffers(PINDEX & size, PINDEX & count);
  BOOL PlaySound(const PSound & sound, BOOL wait);
  BOOL PlayFile(const PFilePath & filename, BOOL wait);
  BOOL HasPlayCompleted();
  BOOL WaitForPlayCompletion();
  BOOL RecordSound(PSound & sound);
  BOOL RecordFile(const PFilePath & filename);
  BOOL StartRecording();
  BOOL IsRecordBufferFull();
  BOOL AreAllRecordBuffersFull();
  BOOL WaitForRecordBufferFull();
  BOOL WaitForAllRecordBuffersFull();
  BOOL Abort();
  BOOL SetVolume(unsigned newVal);
  BOOL GetVolume(unsigned &devVol);
  virtual BOOL IsOpen() const;

 private:

  snd_pcm_t *os_handle; /* Handle, different fromt the PChannel handle */
  int frame_bytes; /* Number of bytes in a frame */
  int period_size;
  int periods;
  PMutex device_mutex;
};


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]