[GnomeMeeting-devel-list] ALSA module



Hello to all,


Here is the result of my late night hacking. This is an ALSA module for
native ALSA support and it works great, at least here.

The current features are :
- Supports real devices names
- Works as it should now that bugs in the plugins have been fixed (what
a night ;) )

The current things that do not work:
- In some cases, the audio will drop and the codec will be closed. I
know why, Ill fix it.
- There is still a weird behavior when the open of the device fails.
That will disappear once people will be using either OSS or ALSA but not
both at the same time. Julien is working on that.
- Too many devices are detected. For example, the microphone of the
webcam can not be used as a player (only recorder) but it is in the list
of possible players. That will be fixed.

Despite those problems, I urge you to test them with your config and to
try all weird cases possible (device busy and so on). I want at least 20
mails of people having tried on monday, or I stop coding, and Julien too
:D

Thanks,

-- 
Damien Sandras <dsandras seconix com>
#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 buffer_size;
};
/*
 * sound.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 Code is Equivalence Pty. Ltd.
 *
 * Portions are Copyright (C) 1993 Free Software Foundation, Inc.
 * All Rights Reserved.
 *
 * Contributor(s): Loopback feature: Philip Edelbrock <phil netroedge com>.
 *
 * $Log: sound_alsa.cxx,v $
 */

#include "sound_alsa.h"

#include <ptlib.h>

DECLARE_PLUGIN("ALSA", PDeviceManager::SNDIN | PDeviceManager::SNDOUT);
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 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;
  buffer_size = 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;

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

  snd_pcm_stream_t stream;

  if (dir == Recorder)
    stream = SND_PCM_STREAM_CAPTURE;
  else
    stream = SND_PCM_STREAM_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 devices;
  }


  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);
	devices.AppendString (name);
	free (name);
	/*      }
    }


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

  
  return 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 = devices.GetStringsIndex (_device)) != P_MAX_INDEX)
    real_device_name = "plughw:" + PString (i);
  else
    real_device_name = PString ("plughw:0");

  if (snd_pcm_open (&os_handle, real_device_name, stream, 0) < 0)
    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 (buffer_size) 
    err = snd_pcm_hw_params_set_buffer_size_near (os_handle, hw_params, 
					    buffer_size / frame_bytes);
  PTRACE (1, "LALLALA " << err);

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

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

  snd_pcm_hw_params_free (hw_params);


  if ((err = snd_pcm_prepare (os_handle)) < 0) {
	
    PTRACE (1, "ALSA\tCannot prepare audio interface for use" << 
	    snd_strerror (err));
    no_error = FALSE;
  }

  snd_pcm_prepare (os_handle);

  isInitialised = TRUE;

  return no_error;
}


BOOL PSoundChannelALSA::Close()
{
  /* if the channel isn't open, do nothing */
  if (!os_handle)
    return FALSE;
  PTRACE (1, "Trying to close");
  Abort ();

  snd_pcm_close (os_handle);
  os_handle = NULL;

  return TRUE;
}


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

  if (!isInitialised && !Setup())
    return FALSE;

  /* the number of frames to write is the buffer length 
     divided by the size of one frame */
  while ((r = snd_pcm_writei (os_handle, buf, len / frame_bytes)) < 0) {

    PTRACE (1, "Could not write");

    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, "Written " << r);

  return TRUE;
}


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

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

  PTRACE (1, "Read with len " << len << " frame_bytes " 
	  << frame_bytes);

  lastReadCount = 0;

  if (!isInitialised && !Setup())
    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);
    PTRACE (1, "Read " << r << " at " << pos);

    if (r > 0) {

      pos += r * frame_bytes;
      len -= r * frame_bytes;
      lastReadCount += r * frame_bytes;
      PTRACE (1, "Still len to read " << len << " at " << pos);
    }
    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);
    }


  } while (r >= 1 && len > 0);


  PTRACE (1, "Read Done " << r);
 
  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;
}


// Get  the number of channels (mono/stereo) in the sound.
unsigned PSoundChannelALSA::GetChannels()   const
{
  return mNumChannels;
}

// Get the sample rate in samples per second.
unsigned PSoundChannelALSA::GetSampleRate() const
{
  return mSampleRate;
}

// Get the sample size in bits per sample.
unsigned PSoundChannelALSA::GetSampleSize() const
{
  return mBitsPerSample;
}


BOOL PSoundChannelALSA::SetBuffers (PINDEX size, PINDEX count)
{
  PTRACE (1, "Asked size " << size << " and count " << count);

  buffer_size = size * count; 

  return TRUE;
}


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


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


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


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 TRUE;
}


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


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


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


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


BOOL PSoundChannelALSA::Abort()
{
  PTRACE (1, "ABORT");
  if (os_handle)
    snd_pcm_drop (os_handle);

  return TRUE;
}



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


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

BOOL PSoundChannelALSA::IsOpen () const
{
  PTRACE (1, "IsOpen ");
  return (os_handle != NULL);
}
include ../make/unix.mak

ifeq (1, $(HAS_AVC1394))
EXTERNALOBJS += lib/video/avc.so
endif
ifeq (1, $(HAS_V4L))
EXTERNALOBJS += lib/video/v4l.so
endif
ifeq (1, $(HAS_OSS))
EXTERNALOBJS += lib/sound/oss.so
endif

EXTERNALOBJS += lib/sound/alsa.so

TARGET = plugins
include ../make/common.mak


plugins: lib/video lib/sound $(EXTERNALOBJS)

lib/video:
	mkdir -p lib/video

lib/sound:
	mkdir -p lib/sound

lib/video/v4l.so: src/vidinput_v4l.cxx
	$(CPLUS) $(CFLAGS) $(STDCCFLAGS) \
		-I./include -shared $< -o $@

lib/video/avc.so: src/vidinput_avc.cxx
	$(CPLUS) $(CFLAGS) $(STDCCFLAGS) \
		-lraw1394 -ldv -lrom1394 \
		-I./include -shared $< -o $@

lib/sound/oss.so: src/sound_oss.cxx
	$(CPLUS) $(CFLAGS) $(STDCCFLAGS) \
		-DPTRACING=1 -I./include -shared $< -o $@

lib/sound/alsa.so: src/sound_alsa.cxx
	$(CPLUS) $(CFLAGS) $(STDCCFLAGS) \
		-lasound \
		-DPTRACING=1 -I./include -shared $< -o $@


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