1421 lines
		
	
	
		
			35 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			1421 lines
		
	
	
		
			35 KiB
		
	
	
	
		
			C++
		
	
	
	
| /* fhandler_dev_dsp: code to emulate OSS sound model /dev/dsp
 | |
| 
 | |
|    Copyright 2001, 2002, 2003, 2004, 2008, 2011, 2012, 2013 Red Hat, Inc
 | |
| 
 | |
|    Written by Andy Younger (andy@snoogie.demon.co.uk)
 | |
|    Extended by Gerd Spalink (Gerd.Spalink@t-online.de)
 | |
|      to support recording from the audio input
 | |
| 
 | |
| This file is part of Cygwin.
 | |
| 
 | |
| This software is a copyrighted work licensed under the terms of the
 | |
| Cygwin license.  Please consult the file "CYGWIN_LICENSE" for
 | |
| details. */
 | |
| 
 | |
| #include "winsup.h"
 | |
| #include <sys/soundcard.h>
 | |
| #include "cygerrno.h"
 | |
| #include "security.h"
 | |
| #include "path.h"
 | |
| #include "fhandler.h"
 | |
| #include "dtable.h"
 | |
| #include "cygheap.h"
 | |
| #include "sigproc.h"
 | |
| #include "cygwait.h"
 | |
| 
 | |
| /*------------------------------------------------------------------------
 | |
|   Simple encapsulation of the win32 audio device.
 | |
| 
 | |
|   Implementation Notes
 | |
|   1. Audio structures are malloced just before the first read or
 | |
|      write to /dev/dsp. The actual buffer size is determined at that time,
 | |
|      such that one buffer holds about 125ms of audio data.
 | |
|      At the time of this writing, 12 buffers are allocated,
 | |
|      so that up to 1.5 seconds can be buffered within Win32.
 | |
|      The buffer size can be queried with the ioctl SNDCTL_DSP_GETBLKSIZE,
 | |
|      but for this implementation only returns meaningful results if
 | |
|      sampling rate, number of channels and number of bits per sample
 | |
|      are not changed afterwards.
 | |
|      The audio structures are freed when the device is reset or closed,
 | |
|      and they are not passed to exec'ed processes.
 | |
|      The dev_ member is cleared after a fork. This forces the child
 | |
|      to reopen the audio device._
 | |
| 
 | |
|   2. Every open call creates a new instance of the handler. After a
 | |
|      successful open, every subsequent open from the same process
 | |
|      to the device fails with EBUSY.
 | |
|      The structures are shared between duped handles, but not with
 | |
|      children. They only inherit the settings from the parent.
 | |
|  */
 | |
| 
 | |
| class fhandler_dev_dsp::Audio
 | |
| { // This class contains functionality common to Audio_in and Audio_out
 | |
|  public:
 | |
|    Audio (fhandler_dev_dsp *my_fh);
 | |
|    ~Audio ();
 | |
| 
 | |
|   class queue;
 | |
| 
 | |
|   bool isvalid ();
 | |
|   void setconvert (int format);
 | |
|   void convert_none (unsigned char *buffer, int size_bytes) { }
 | |
|   void convert_U8_S8 (unsigned char *buffer, int size_bytes);
 | |
|   void convert_S16LE_U16LE (unsigned char *buffer, int size_bytes);
 | |
|   void convert_S16LE_U16BE (unsigned char *buffer, int size_bytes);
 | |
|   void convert_S16LE_S16BE (unsigned char *buffer, int size_bytes);
 | |
|   void fillFormat (WAVEFORMATEX * format,
 | |
| 		   int rate, int bits, int channels);
 | |
|   unsigned blockSize (int rate, int bits, int channels);
 | |
|   void (fhandler_dev_dsp::Audio::*convert_)
 | |
|     (unsigned char *buffer, int size_bytes);
 | |
| 
 | |
|   enum { MAX_BLOCKS = 12 };
 | |
|   int bufferIndex_;  // offset into pHdr_->lpData
 | |
|   WAVEHDR *pHdr_;    // data to be filled by write
 | |
|   WAVEHDR wavehdr_[MAX_BLOCKS];
 | |
|   char *bigwavebuffer_; // audio samples only
 | |
|   // Member variables below must be locked
 | |
|   queue *Qisr2app_; // blocks passed from wave callback
 | |
| 
 | |
|   fhandler_dev_dsp *fh;
 | |
| };
 | |
| 
 | |
| class fhandler_dev_dsp::Audio::queue
 | |
| { // non-blocking fixed size queues for buffer management
 | |
|  public:
 | |
|    queue (int depth = 4);
 | |
|   ~queue ();
 | |
| 
 | |
|   bool send (WAVEHDR *);  // queue an item, returns true if successful
 | |
|   bool recv (WAVEHDR **); // retrieve an item, returns true if successful
 | |
|   void reset ();
 | |
|   int query (); // return number of items queued
 | |
|   inline void lock () { EnterCriticalSection (&lock_); }
 | |
|   inline void unlock () { LeaveCriticalSection (&lock_); }
 | |
|   inline void dellock () { debug_printf ("Deleting Critical Section"); DeleteCriticalSection (&lock_); }
 | |
|   bool isvalid () { return storage_; }
 | |
|  private:
 | |
|   CRITICAL_SECTION lock_;
 | |
|   int head_;
 | |
|   int tail_;
 | |
|   int depth_;
 | |
|   WAVEHDR **storage_;
 | |
| };
 | |
| 
 | |
| static void CALLBACK waveOut_callback (HWAVEOUT hWave, UINT msg,
 | |
| 				       DWORD_PTR instance, DWORD_PTR param1,
 | |
| 				       DWORD_PTR param2);
 | |
| 
 | |
| class fhandler_dev_dsp::Audio_out: public Audio
 | |
| {
 | |
|  public:
 | |
|   Audio_out (fhandler_dev_dsp *my_fh) : Audio (my_fh) {}
 | |
| 
 | |
|   void fork_fixup (HANDLE parent);
 | |
|   bool query (int rate, int bits, int channels);
 | |
|   bool start ();
 | |
|   void stop (bool immediately = false);
 | |
|   int write (const char *pSampleData, int nBytes);
 | |
|   void buf_info (audio_buf_info *p, int rate, int bits, int channels);
 | |
|   void callback_sampledone (WAVEHDR *pHdr);
 | |
|   bool parsewav (const char *&pData, int &nBytes,
 | |
| 		 int rate, int bits, int channels);
 | |
| 
 | |
|  private:
 | |
|   void init (unsigned blockSize);
 | |
|   void waitforallsent ();
 | |
|   bool waitforspace ();
 | |
|   bool sendcurrent ();
 | |
| 
 | |
|   enum { MAX_BLOCKS = 12 };
 | |
|   HWAVEOUT dev_;     // The wave device
 | |
|   /* Private copies of audiofreq_, audiobits_, audiochannels_,
 | |
|      possibly set from wave file */
 | |
|   int freq_;
 | |
|   int bits_;
 | |
|   int channels_;
 | |
| };
 | |
| 
 | |
| static void CALLBACK waveIn_callback (HWAVEIN hWave, UINT msg,
 | |
| 				      DWORD_PTR instance, DWORD_PTR param1,
 | |
| 				      DWORD_PTR param2);
 | |
| 
 | |
| class fhandler_dev_dsp::Audio_in: public Audio
 | |
| {
 | |
| public:
 | |
|   Audio_in (fhandler_dev_dsp *my_fh) : Audio (my_fh) {}
 | |
| 
 | |
|   void fork_fixup (HANDLE parent);
 | |
|   bool query (int rate, int bits, int channels);
 | |
|   bool start (int rate, int bits, int channels);
 | |
|   void stop ();
 | |
|   bool read (char *pSampleData, int &nBytes);
 | |
|   void buf_info (audio_buf_info *p, int rate, int bits, int channels);
 | |
|   void callback_blockfull (WAVEHDR *pHdr);
 | |
| 
 | |
| private:
 | |
|   bool init (unsigned blockSize);
 | |
|   bool queueblock (WAVEHDR *pHdr);
 | |
|   bool waitfordata (); // blocks until we have a good pHdr_ unless O_NONBLOCK
 | |
| 
 | |
|   HWAVEIN dev_;
 | |
| };
 | |
| 
 | |
| /* --------------------------------------------------------------------
 | |
|    Implementation */
 | |
| 
 | |
| // Simple fixed length FIFO queue implementation for audio buffer management
 | |
| fhandler_dev_dsp::Audio::queue::queue (int depth)
 | |
| {
 | |
|   // allow space for one extra object in the queue
 | |
|   // so we can distinguish full and empty status
 | |
|   depth_ = depth;
 | |
|   storage_ = new WAVEHDR *[depth_ + 1];
 | |
| }
 | |
| 
 | |
| fhandler_dev_dsp::Audio::queue::~queue ()
 | |
| {
 | |
|   delete[] storage_;
 | |
| }
 | |
| 
 | |
| void
 | |
| fhandler_dev_dsp::Audio::queue::reset ()
 | |
|  {
 | |
|    /* When starting, after reset and after fork */
 | |
|    head_ = tail_ = 0;
 | |
|    debug_printf ("InitializeCriticalSection");
 | |
|    memset (&lock_, 0, sizeof (lock_));
 | |
|    InitializeCriticalSection (&lock_);
 | |
|  }
 | |
| 
 | |
| bool
 | |
| fhandler_dev_dsp::Audio::queue::send (WAVEHDR *x)
 | |
| {
 | |
|   bool res = false;
 | |
|   lock ();
 | |
|   if (query () == depth_)
 | |
|     system_printf ("Queue overflow");
 | |
|   else
 | |
|     {
 | |
|       storage_[tail_] = x;
 | |
|       if (++tail_ > depth_)
 | |
| 	tail_ = 0;
 | |
|       res = true;
 | |
|     }
 | |
|   unlock ();
 | |
|   return res;
 | |
| }
 | |
| 
 | |
| bool
 | |
| fhandler_dev_dsp::Audio::queue::recv (WAVEHDR **x)
 | |
| {
 | |
|   bool res = false;
 | |
|   lock ();
 | |
|   if (query () != 0)
 | |
|     {
 | |
|       *x = storage_[head_];
 | |
|       if (++head_ > depth_)
 | |
| 	head_ = 0;
 | |
|       res = true;
 | |
|     }
 | |
|   unlock ();
 | |
|   return res;
 | |
| }
 | |
| 
 | |
| int
 | |
| fhandler_dev_dsp::Audio::queue::query ()
 | |
| {
 | |
|   int n = tail_ - head_;
 | |
|   if (n < 0)
 | |
|     n += depth_ + 1;
 | |
|   return n;
 | |
| }
 | |
| 
 | |
| // Audio class implements functionality need for both read and write
 | |
| fhandler_dev_dsp::Audio::Audio (fhandler_dev_dsp *my_fh)
 | |
| {
 | |
|   bigwavebuffer_ = NULL;
 | |
|   Qisr2app_ = new queue (MAX_BLOCKS);
 | |
|   convert_ = &fhandler_dev_dsp::Audio::convert_none;
 | |
|   fh = my_fh;
 | |
| }
 | |
| 
 | |
| fhandler_dev_dsp::Audio::~Audio ()
 | |
| {
 | |
|   debug_printf("");
 | |
|   delete Qisr2app_;
 | |
|   delete[] bigwavebuffer_;
 | |
| }
 | |
| 
 | |
| inline bool
 | |
| fhandler_dev_dsp::Audio::isvalid ()
 | |
| {
 | |
|   return bigwavebuffer_ && Qisr2app_ && Qisr2app_->isvalid ();
 | |
| }
 | |
| 
 | |
| void
 | |
| fhandler_dev_dsp::Audio::setconvert (int format)
 | |
| {
 | |
|   switch (format)
 | |
|     {
 | |
|     case AFMT_S8:
 | |
|       convert_ = &fhandler_dev_dsp::Audio::convert_U8_S8;
 | |
|       debug_printf ("U8_S8");
 | |
|       break;
 | |
|     case AFMT_U16_LE:
 | |
|       convert_ = &fhandler_dev_dsp::Audio::convert_S16LE_U16LE;
 | |
|       debug_printf ("S16LE_U16LE");
 | |
|       break;
 | |
|     case AFMT_U16_BE:
 | |
|       convert_ = &fhandler_dev_dsp::Audio::convert_S16LE_U16BE;
 | |
|       debug_printf ("S16LE_U16BE");
 | |
|       break;
 | |
|     case AFMT_S16_BE:
 | |
|       convert_ = &fhandler_dev_dsp::Audio::convert_S16LE_S16BE;
 | |
|       debug_printf ("S16LE_S16BE");
 | |
|       break;
 | |
|     default:
 | |
|       convert_ = &fhandler_dev_dsp::Audio::convert_none;
 | |
|       debug_printf ("none");
 | |
|     }
 | |
| }
 | |
| 
 | |
| void
 | |
| fhandler_dev_dsp::Audio::convert_U8_S8 (unsigned char *buffer,
 | |
| 					int size_bytes)
 | |
| {
 | |
|   while (size_bytes-- > 0)
 | |
|     {
 | |
|       *buffer ^= (unsigned char)0x80;
 | |
|       buffer++;
 | |
|     }
 | |
| }
 | |
| 
 | |
| void
 | |
| fhandler_dev_dsp::Audio::convert_S16LE_U16BE (unsigned char *buffer,
 | |
| 					      int size_bytes)
 | |
| {
 | |
|   int size_samples = size_bytes / 2;
 | |
|   unsigned char hi, lo;
 | |
|   while (size_samples-- > 0)
 | |
|     {
 | |
|       hi = buffer[0];
 | |
|       lo = buffer[1];
 | |
|       *buffer++ = lo;
 | |
|       *buffer++ = hi ^ (unsigned char)0x80;
 | |
|     }
 | |
| }
 | |
| 
 | |
| void
 | |
| fhandler_dev_dsp::Audio::convert_S16LE_U16LE (unsigned char *buffer,
 | |
| 					      int size_bytes)
 | |
| {
 | |
|   int size_samples = size_bytes / 2;
 | |
|   while (size_samples-- > 0)
 | |
|     {
 | |
|       buffer++;
 | |
|       *buffer ^= (unsigned char)0x80;
 | |
|       buffer++;
 | |
|     }
 | |
| }
 | |
| 
 | |
| void
 | |
| fhandler_dev_dsp::Audio::convert_S16LE_S16BE (unsigned char *buffer,
 | |
| 					      int size_bytes)
 | |
| {
 | |
|   int size_samples = size_bytes / 2;
 | |
|   unsigned char hi, lo;
 | |
|   while (size_samples-- > 0)
 | |
|     {
 | |
|       hi = buffer[0];
 | |
|       lo = buffer[1];
 | |
|       *buffer++ = lo;
 | |
|       *buffer++ = hi;
 | |
|     }
 | |
| }
 | |
| 
 | |
| void
 | |
| fhandler_dev_dsp::Audio::fillFormat (WAVEFORMATEX * format,
 | |
| 				     int rate, int bits, int channels)
 | |
| {
 | |
|   memset (format, 0, sizeof (*format));
 | |
|   format->wFormatTag = WAVE_FORMAT_PCM;
 | |
|   format->wBitsPerSample = bits;
 | |
|   format->nChannels = channels;
 | |
|   format->nSamplesPerSec = rate;
 | |
|   format->nAvgBytesPerSec = format->nSamplesPerSec * format->nChannels
 | |
|     * (bits / 8);
 | |
|   format->nBlockAlign = format->nChannels * (bits / 8);
 | |
| }
 | |
| 
 | |
| // calculate a good block size
 | |
| unsigned
 | |
| fhandler_dev_dsp::Audio::blockSize (int rate, int bits, int channels)
 | |
| {
 | |
|   unsigned blockSize;
 | |
|   blockSize = ((bits / 8) * channels * rate) / 8; // approx 125ms per block
 | |
|   // round up to multiple of 64
 | |
|   blockSize +=  0x3f;
 | |
|   blockSize &= ~0x3f;
 | |
|   return blockSize;
 | |
| }
 | |
| 
 | |
| //=======================================================================
 | |
| void
 | |
| fhandler_dev_dsp::Audio_out::fork_fixup (HANDLE parent)
 | |
| {
 | |
|   /* Null dev_.
 | |
|      It will be necessary to reset the queue, open the device
 | |
|      and create a lock when writing */
 | |
|   debug_printf ("parent=%p", parent);
 | |
|   dev_ = NULL;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool
 | |
| fhandler_dev_dsp::Audio_out::query (int rate, int bits, int channels)
 | |
| {
 | |
|   WAVEFORMATEX format;
 | |
|   MMRESULT rc;
 | |
| 
 | |
|   fillFormat (&format, rate, bits, channels);
 | |
|   rc = waveOutOpen (NULL, WAVE_MAPPER, &format, 0L, 0L, WAVE_FORMAT_QUERY);
 | |
|   debug_printf ("%u = waveOutOpen(freq=%d bits=%d channels=%d)", rc, rate, bits, channels);
 | |
|   return (rc == MMSYSERR_NOERROR);
 | |
| }
 | |
| 
 | |
| bool
 | |
| fhandler_dev_dsp::Audio_out::start ()
 | |
| {
 | |
|   WAVEFORMATEX format;
 | |
|   MMRESULT rc;
 | |
|   unsigned bSize = blockSize (freq_, bits_, channels_);
 | |
| 
 | |
|   if (dev_)
 | |
|     return true;
 | |
| 
 | |
|   /* In case of fork bigwavebuffer may already exist */
 | |
|   if (!bigwavebuffer_)
 | |
|     bigwavebuffer_ = new char[MAX_BLOCKS * bSize];
 | |
| 
 | |
|   if (!isvalid ())
 | |
|     return false;
 | |
| 
 | |
|   fillFormat (&format, freq_, bits_, channels_);
 | |
|   rc = waveOutOpen (&dev_, WAVE_MAPPER, &format, (DWORD_PTR) waveOut_callback,
 | |
| 		     (DWORD_PTR) this, CALLBACK_FUNCTION);
 | |
|   if (rc == MMSYSERR_NOERROR)
 | |
|     init (bSize);
 | |
| 
 | |
|   debug_printf ("%u = waveOutOpen(freq=%d bits=%d channels=%d)", rc, freq_, bits_, channels_);
 | |
| 
 | |
|   return (rc == MMSYSERR_NOERROR);
 | |
| }
 | |
| 
 | |
| void
 | |
| fhandler_dev_dsp::Audio_out::stop (bool immediately)
 | |
| {
 | |
|   MMRESULT rc;
 | |
|   WAVEHDR *pHdr;
 | |
| 
 | |
|   debug_printf ("dev_=%p", dev_);
 | |
|   if (dev_)
 | |
|     {
 | |
|       if (!immediately)
 | |
| 	{
 | |
| 	  sendcurrent ();	// force out last block whatever size..
 | |
| 	  waitforallsent ();	// block till finished..
 | |
| 	}
 | |
| 
 | |
|       rc = waveOutReset (dev_);
 | |
|       debug_printf ("%u = waveOutReset()", rc);
 | |
|       while (Qisr2app_->recv (&pHdr))
 | |
| 	{
 | |
| 	  rc = waveOutUnprepareHeader (dev_, pHdr, sizeof (WAVEHDR));
 | |
| 	  debug_printf ("%u = waveOutUnprepareHeader(%p)", rc, pHdr);
 | |
| 	}
 | |
| 
 | |
|       no_thread_exit_protect for_now (true);
 | |
|       rc = waveOutClose (dev_);
 | |
|       debug_printf ("%u = waveOutClose()", rc);
 | |
| 
 | |
|       Qisr2app_->dellock ();
 | |
|     }
 | |
| }
 | |
| 
 | |
| void
 | |
| fhandler_dev_dsp::Audio_out::init (unsigned blockSize)
 | |
| {
 | |
|   int i;
 | |
| 
 | |
|   // internally queue all of our buffer for later use by write
 | |
|   Qisr2app_->reset ();
 | |
|   for (i = 0; i < MAX_BLOCKS; i++)
 | |
|     {
 | |
|       wavehdr_[i].lpData = &bigwavebuffer_[i * blockSize];
 | |
|       wavehdr_[i].dwUser = (int) blockSize;
 | |
|       wavehdr_[i].dwFlags = 0;
 | |
|       if (!Qisr2app_->send (&wavehdr_[i]))
 | |
| 	{
 | |
| 	  system_printf ("Internal Error i=%d", i);
 | |
| 	  break; // should not happen
 | |
| 	}
 | |
|     }
 | |
|   pHdr_ = NULL;
 | |
| }
 | |
| 
 | |
| int
 | |
| fhandler_dev_dsp::Audio_out::write (const char *pSampleData, int nBytes)
 | |
| {
 | |
|   int bytes_to_write = nBytes;
 | |
|   while (bytes_to_write != 0)
 | |
|     { // Block if all blocks used until at least one is free
 | |
|       if (!waitforspace ())
 | |
| 	{
 | |
| 	  if (bytes_to_write != nBytes)
 | |
| 	    break;
 | |
| 	  return -1;
 | |
| 	}
 | |
| 
 | |
|       int sizeleft = (int)pHdr_->dwUser - bufferIndex_;
 | |
|       if (bytes_to_write < sizeleft)
 | |
| 	{ // all data fits into the current block, with some space left
 | |
| 	  memcpy (&pHdr_->lpData[bufferIndex_], pSampleData, bytes_to_write);
 | |
| 	  bufferIndex_ += bytes_to_write;
 | |
| 	  bytes_to_write = 0;
 | |
| 	  break;
 | |
| 	}
 | |
|       else
 | |
| 	{ // data will fill up the current block
 | |
| 	  memcpy (&pHdr_->lpData[bufferIndex_], pSampleData, sizeleft);
 | |
| 	  bufferIndex_ += sizeleft;
 | |
| 	  sendcurrent ();
 | |
| 	  pSampleData += sizeleft;
 | |
| 	  bytes_to_write -= sizeleft;
 | |
| 	}
 | |
|     }
 | |
|   return nBytes - bytes_to_write;
 | |
| }
 | |
| 
 | |
| void
 | |
| fhandler_dev_dsp::Audio_out::buf_info (audio_buf_info *p,
 | |
| 				       int rate, int bits, int channels)
 | |
| {
 | |
|   p->fragstotal = MAX_BLOCKS;
 | |
|   if (this && dev_)
 | |
|     {
 | |
|       /* If the device is running we use the internal values,
 | |
| 	 possibly set from the wave file. */
 | |
|       p->fragsize = blockSize (freq_, bits_, channels_);
 | |
|       p->fragments = Qisr2app_->query ();
 | |
|       if (pHdr_ != NULL)
 | |
| 	p->bytes = (int)pHdr_->dwUser - bufferIndex_
 | |
| 	  + p->fragsize * p->fragments;
 | |
|       else
 | |
| 	p->bytes = p->fragsize * p->fragments;
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       p->fragsize = blockSize (rate, bits, channels);
 | |
|       p->fragments = MAX_BLOCKS;
 | |
|       p->bytes = p->fragsize * p->fragments;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* This is called on an interupt so use locking.. Note Qisr2app_
 | |
|    is used so we should wrap all references to it in locks. */
 | |
| inline void
 | |
| fhandler_dev_dsp::Audio_out::callback_sampledone (WAVEHDR *pHdr)
 | |
| {
 | |
|   Qisr2app_->send (pHdr);
 | |
| }
 | |
| 
 | |
| bool
 | |
| fhandler_dev_dsp::Audio_out::waitforspace ()
 | |
| {
 | |
|   WAVEHDR *pHdr;
 | |
|   MMRESULT rc = WAVERR_STILLPLAYING;
 | |
| 
 | |
|   if (pHdr_ != NULL)
 | |
|     return true;
 | |
|   while (!Qisr2app_->recv (&pHdr))
 | |
|     {
 | |
|       if (fh->is_nonblocking ())
 | |
| 	{
 | |
| 	  set_errno (EAGAIN);
 | |
| 	  return false;
 | |
| 	}
 | |
|       debug_printf ("100ms");
 | |
|       switch (cygwait (100))
 | |
| 	{
 | |
| 	case WAIT_SIGNALED:
 | |
| 	  if (!_my_tls.call_signal_handler ())
 | |
| 	    {
 | |
| 	      set_errno (EINTR);
 | |
| 	      return false;
 | |
| 	    }
 | |
| 	  break;
 | |
| 	case WAIT_CANCELED:
 | |
| 	  pthread::static_cancel_self ();
 | |
| 	  /*NOTREACHED*/
 | |
| 	default:
 | |
| 	  break;
 | |
| 	}
 | |
|     }
 | |
|   if (pHdr->dwFlags)
 | |
|     {
 | |
|       /* Errors are ignored here. They will probbaly cause a failure
 | |
| 	 in the subsequent PrepareHeader */
 | |
|       rc = waveOutUnprepareHeader (dev_, pHdr, sizeof (WAVEHDR));
 | |
|       debug_printf ("%u = waveOutUnprepareHeader(%p)", rc, pHdr);
 | |
|     }
 | |
|   pHdr_ = pHdr;
 | |
|   bufferIndex_ = 0;
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void
 | |
| fhandler_dev_dsp::Audio_out::waitforallsent ()
 | |
| {
 | |
|   while (Qisr2app_->query () != MAX_BLOCKS)
 | |
|     {
 | |
|       debug_printf ("%d blocks in Qisr2app", Qisr2app_->query ());
 | |
|       Sleep (100);
 | |
|     }
 | |
| }
 | |
| 
 | |
| // send the block described by pHdr_ and bufferIndex_ to wave device
 | |
| bool
 | |
| fhandler_dev_dsp::Audio_out::sendcurrent ()
 | |
| {
 | |
|   WAVEHDR *pHdr = pHdr_;
 | |
|   MMRESULT rc;
 | |
|   debug_printf ("pHdr=%p bytes=%d", pHdr, bufferIndex_);
 | |
| 
 | |
|   if (pHdr_ == NULL)
 | |
|     return false;
 | |
|   pHdr_ = NULL;
 | |
| 
 | |
|   // Sample buffer conversion
 | |
|   (this->*convert_) ((unsigned char *)pHdr->lpData, bufferIndex_);
 | |
| 
 | |
|   // Send internal buffer out to the soundcard
 | |
|   pHdr->dwBufferLength = bufferIndex_;
 | |
|   rc = waveOutPrepareHeader (dev_, pHdr, sizeof (WAVEHDR));
 | |
|   debug_printf ("%u = waveOutPrepareHeader(%p)", rc, pHdr);
 | |
|   if (rc == MMSYSERR_NOERROR)
 | |
|     {
 | |
|       rc = waveOutWrite (dev_, pHdr, sizeof (WAVEHDR));
 | |
|       debug_printf ("%u = waveOutWrite(%p)", rc, pHdr);
 | |
|     }
 | |
|   if (rc == MMSYSERR_NOERROR)
 | |
|     return true;
 | |
| 
 | |
|   /* FIXME: Should we return an error instead ?*/
 | |
|   pHdr->dwFlags = 0; /* avoid calling UnprepareHeader again */
 | |
|   Qisr2app_->send (pHdr);
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| //------------------------------------------------------------------------
 | |
| // Call back routine
 | |
| static void CALLBACK
 | |
| waveOut_callback (HWAVEOUT hWave, UINT msg, DWORD_PTR instance,
 | |
| 		  DWORD_PTR param1, DWORD_PTR param2)
 | |
| {
 | |
|   if (msg == WOM_DONE)
 | |
|     {
 | |
|       fhandler_dev_dsp::Audio_out *ptr =
 | |
| 	(fhandler_dev_dsp::Audio_out *) instance;
 | |
|       ptr->callback_sampledone ((WAVEHDR *) param1);
 | |
|     }
 | |
| }
 | |
| 
 | |
| //------------------------------------------------------------------------
 | |
| // wav file detection..
 | |
| #pragma pack(1)
 | |
| struct wavchunk
 | |
| {
 | |
|   char id[4];
 | |
|   unsigned int len;
 | |
| };
 | |
| struct wavformat
 | |
| {
 | |
|   unsigned short wFormatTag;
 | |
|   unsigned short wChannels;
 | |
|   unsigned int dwSamplesPerSec;
 | |
|   unsigned int dwAvgBytesPerSec;
 | |
|   unsigned short wBlockAlign;
 | |
|   unsigned short wBitsPerSample;
 | |
| };
 | |
| #pragma pack()
 | |
| 
 | |
| bool
 | |
| fhandler_dev_dsp::Audio_out::parsewav (const char * &pData, int &nBytes,
 | |
| 				       int dev_freq, int dev_bits, int dev_channels)
 | |
| {
 | |
|   int len;
 | |
|   const char *end = pData + nBytes;
 | |
|   const char *pDat;
 | |
|   int skip = 0;
 | |
| 
 | |
|   /* Start with default values from the device handler */
 | |
|   freq_ = dev_freq;
 | |
|   bits_ = dev_bits;
 | |
|   channels_ = dev_channels;
 | |
|   setconvert (bits_ == 8 ? AFMT_U8 : AFMT_S16_LE);
 | |
| 
 | |
|   // Check alignment first: A lot of the code below depends on it
 | |
|   if (((uintptr_t)pData & 0x3) != 0)
 | |
|     return false;
 | |
|   if (!(pData[0] == 'R' && pData[1] == 'I'
 | |
| 	&& pData[2] == 'F' && pData[3] == 'F'))
 | |
|     return false;
 | |
|   if (!(pData[8] == 'W' && pData[9] == 'A'
 | |
| 	&& pData[10] == 'V' && pData[11] == 'E'))
 | |
|     return false;
 | |
| 
 | |
|   len = *(int *) &pData[4];
 | |
|   len -= 12;
 | |
|   pDat = pData + 12;
 | |
|   skip = 12;
 | |
|   while ((len > 0) && (pDat + sizeof (wavchunk) < end))
 | |
|     { /* We recognize two kinds of wavchunk:
 | |
| 	 "fmt " for the PCM parameters (only PCM supported here)
 | |
| 	 "data" for the start of PCM data */
 | |
|       wavchunk * pChunk = (wavchunk *) pDat;
 | |
|       int blklen = pChunk-> len;
 | |
|       if (pChunk->id[0] == 'f' && pChunk->id[1] == 'm'
 | |
| 	  && pChunk->id[2] == 't' && pChunk->id[3] == ' ')
 | |
| 	{
 | |
| 	  wavformat *format = (wavformat *) (pChunk + 1);
 | |
| 	  if ((char *) (format + 1) >= end)
 | |
| 	    return false;
 | |
| 	  // We have found the parameter chunk
 | |
| 	  if (format->wFormatTag == 0x0001)
 | |
| 	    { // Micr*s*ft PCM; check if parameters work with our device
 | |
| 	      if (query (format->dwSamplesPerSec, format->wBitsPerSample,
 | |
| 			 format->wChannels))
 | |
| 		{ // return the parameters we found
 | |
| 		  freq_ = format->dwSamplesPerSec;
 | |
| 		  bits_ = format->wBitsPerSample;
 | |
| 		  channels_ = format->wChannels;
 | |
| 		}
 | |
| 	    }
 | |
| 	}
 | |
|       else
 | |
| 	{
 | |
| 	  if (pChunk->id[0] == 'd' && pChunk->id[1] == 'a'
 | |
| 	      && pChunk->id[2] == 't' && pChunk->id[3] == 'a')
 | |
| 	    { // throw away all the header & not output it to the soundcard.
 | |
| 	      skip += sizeof (wavchunk);
 | |
| 	      debug_printf ("Discard %d bytes wave header", skip);
 | |
| 	      pData += skip;
 | |
| 	      nBytes -= skip;
 | |
| 	      setconvert (bits_ == 8 ? AFMT_U8 : AFMT_S16_LE);
 | |
| 	      return true;
 | |
| 	    }
 | |
| 	}
 | |
|       pDat += blklen + sizeof (wavchunk);
 | |
|       skip += blklen + sizeof (wavchunk);
 | |
|       len -= blklen + sizeof (wavchunk);
 | |
|     }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| /* ========================================================================
 | |
|    Buffering concept for Audio_in:
 | |
|    On the first read, we queue all blocks of our bigwavebuffer
 | |
|    for reception and start the wave-in device.
 | |
|    We manage queues of pointers to WAVEHDR
 | |
|    When a block has been filled, the callback puts the corresponding
 | |
|    WAVEHDR pointer into a queue.
 | |
|    The function read() blocks (polled, sigh) until at least one good buffer
 | |
|    has arrived, then the data is copied into the buffer provided to read().
 | |
|    After a buffer has been fully used by read(), it is queued again
 | |
|    to the wave-in device immediately.
 | |
|    The function read() iterates until all data requested has been
 | |
|    received, there is no way to interrupt it */
 | |
| 
 | |
| void
 | |
| fhandler_dev_dsp::Audio_in::fork_fixup (HANDLE parent)
 | |
| {
 | |
|   /* Null dev_.
 | |
|      It will be necessary to reset the queue, open the device
 | |
|      and create a lock when reading */
 | |
|   debug_printf ("parent=%p", parent);
 | |
|   dev_ = NULL;
 | |
| }
 | |
| 
 | |
| bool
 | |
| fhandler_dev_dsp::Audio_in::query (int rate, int bits, int channels)
 | |
| {
 | |
|   WAVEFORMATEX format;
 | |
|   MMRESULT rc;
 | |
| 
 | |
|   fillFormat (&format, rate, bits, channels);
 | |
|   rc = waveInOpen (NULL, WAVE_MAPPER, &format, 0L, 0L, WAVE_FORMAT_QUERY);
 | |
|   debug_printf ("%u = waveInOpen(freq=%d bits=%d channels=%d)", rc, rate, bits, channels);
 | |
|   return (rc == MMSYSERR_NOERROR);
 | |
| }
 | |
| 
 | |
| bool
 | |
| fhandler_dev_dsp::Audio_in::start (int rate, int bits, int channels)
 | |
| {
 | |
|   WAVEFORMATEX format;
 | |
|   MMRESULT rc;
 | |
|   unsigned bSize = blockSize (rate, bits, channels);
 | |
| 
 | |
|   if (dev_)
 | |
|     return true;
 | |
| 
 | |
|   /* In case of fork bigwavebuffer may already exist */
 | |
|   if (!bigwavebuffer_)
 | |
|     bigwavebuffer_ = new char[MAX_BLOCKS * bSize];
 | |
| 
 | |
|   if (!isvalid ())
 | |
|     return false;
 | |
| 
 | |
|   fillFormat (&format, rate, bits, channels);
 | |
|   rc = waveInOpen (&dev_, WAVE_MAPPER, &format, (DWORD_PTR) waveIn_callback,
 | |
| 		   (DWORD_PTR) this, CALLBACK_FUNCTION);
 | |
|   debug_printf ("%u = waveInOpen(rate=%d bits=%d channels=%d)", rc, rate, bits, channels);
 | |
| 
 | |
|   if (rc == MMSYSERR_NOERROR)
 | |
|     {
 | |
|       if (!init (bSize))
 | |
| 	return false;
 | |
|     }
 | |
|   return (rc == MMSYSERR_NOERROR);
 | |
| }
 | |
| 
 | |
| void
 | |
| fhandler_dev_dsp::Audio_in::stop ()
 | |
| {
 | |
|   MMRESULT rc;
 | |
|   WAVEHDR *pHdr;
 | |
| 
 | |
|   debug_printf ("dev_=%p", dev_);
 | |
|   if (dev_)
 | |
|     {
 | |
|       /* Note that waveInReset calls our callback for all incomplete buffers.
 | |
| 	 Since all the win32 wave functions appear to use a common lock,
 | |
| 	 we must not call into the wave API from the callback.
 | |
| 	 Otherwise we end up in a deadlock. */
 | |
|       rc = waveInReset (dev_);
 | |
|       debug_printf ("%u = waveInReset()", rc);
 | |
| 
 | |
|       while (Qisr2app_->recv (&pHdr))
 | |
| 	{
 | |
| 	  rc = waveInUnprepareHeader (dev_, pHdr, sizeof (WAVEHDR));
 | |
| 	  debug_printf ("%u = waveInUnprepareHeader(%p)", rc, pHdr);
 | |
| 	}
 | |
| 
 | |
|       no_thread_exit_protect for_now (true);
 | |
|       rc = waveInClose (dev_);
 | |
|       debug_printf ("%u = waveInClose()", rc);
 | |
| 
 | |
|       Qisr2app_->dellock ();
 | |
|     }
 | |
| }
 | |
| 
 | |
| bool
 | |
| fhandler_dev_dsp::Audio_in::queueblock (WAVEHDR *pHdr)
 | |
| {
 | |
|   MMRESULT rc;
 | |
|   rc = waveInPrepareHeader (dev_, pHdr, sizeof (WAVEHDR));
 | |
|   debug_printf ("%u = waveInPrepareHeader(%p)", rc, pHdr);
 | |
|   if (rc == MMSYSERR_NOERROR)
 | |
|     {
 | |
|       rc = waveInAddBuffer (dev_, pHdr, sizeof (WAVEHDR));
 | |
|       debug_printf ("%u = waveInAddBuffer(%p)", rc, pHdr);
 | |
|     }
 | |
|   if (rc == MMSYSERR_NOERROR)
 | |
|     return true;
 | |
| 
 | |
|   /* FIXME: Should the calling function return an error instead ?*/
 | |
|   pHdr->dwFlags = 0;  /* avoid calling UnprepareHeader again */
 | |
|   pHdr->dwBytesRecorded = 0;  /* no data will have been read */
 | |
|   Qisr2app_->send (pHdr);
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool
 | |
| fhandler_dev_dsp::Audio_in::init (unsigned blockSize)
 | |
| {
 | |
|   MMRESULT rc;
 | |
|   int i;
 | |
| 
 | |
|   // try to queue all of our buffer for reception
 | |
|   Qisr2app_->reset ();
 | |
|   for (i = 0; i < MAX_BLOCKS; i++)
 | |
|     {
 | |
|       wavehdr_[i].lpData = &bigwavebuffer_[i * blockSize];
 | |
|       wavehdr_[i].dwBufferLength = blockSize;
 | |
|       wavehdr_[i].dwFlags = 0;
 | |
|       if (!queueblock (&wavehdr_[i]))
 | |
| 	break;
 | |
|     }
 | |
|   pHdr_ = NULL;
 | |
|   rc = waveInStart (dev_);
 | |
|   debug_printf ("%u = waveInStart(), queued=%d", rc, i);
 | |
|   return (rc == MMSYSERR_NOERROR);
 | |
| }
 | |
| 
 | |
| bool
 | |
| fhandler_dev_dsp::Audio_in::read (char *pSampleData, int &nBytes)
 | |
| {
 | |
|   int bytes_to_read = nBytes;
 | |
|   nBytes = 0;
 | |
|   debug_printf ("pSampleData=%p nBytes=%d", pSampleData, bytes_to_read);
 | |
|   while (bytes_to_read != 0)
 | |
|     { // Block till next sound has been read
 | |
|       if (!waitfordata ())
 | |
| 	{
 | |
| 	  if (nBytes)
 | |
| 	    return true;
 | |
| 	  nBytes = -1;
 | |
| 	  return false;
 | |
| 	}
 | |
| 
 | |
|       // Handle gathering our blocks into smaller or larger buffer
 | |
|       int sizeleft = pHdr_->dwBytesRecorded - bufferIndex_;
 | |
|       if (bytes_to_read < sizeleft)
 | |
| 	{ // The current buffer holds more data than requested
 | |
| 	  memcpy (pSampleData, &pHdr_->lpData[bufferIndex_], bytes_to_read);
 | |
| 	  (this->*convert_) ((unsigned char *)pSampleData, bytes_to_read);
 | |
| 	  nBytes += bytes_to_read;
 | |
| 	  bufferIndex_ += bytes_to_read;
 | |
| 	  debug_printf ("got %d", bytes_to_read);
 | |
| 	  break; // done; use remaining data in next call to read
 | |
| 	}
 | |
|       else
 | |
| 	{ // not enough or exact amount in the current buffer
 | |
| 	  if (sizeleft)
 | |
| 	    { // use up what we have
 | |
| 	      memcpy (pSampleData, &pHdr_->lpData[bufferIndex_], sizeleft);
 | |
| 	      (this->*convert_) ((unsigned char *)pSampleData, sizeleft);
 | |
| 	      nBytes += sizeleft;
 | |
| 	      bytes_to_read -= sizeleft;
 | |
| 	      pSampleData += sizeleft;
 | |
| 	      debug_printf ("got %d", sizeleft);
 | |
| 	    }
 | |
| 	  queueblock (pHdr_); // re-queue this block to ISR
 | |
| 	  pHdr_ = NULL;       // need to wait for a new block
 | |
| 	  // if more samples are needed, we need a new block now
 | |
| 	}
 | |
|     }
 | |
|   debug_printf ("end nBytes=%d", nBytes);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool
 | |
| fhandler_dev_dsp::Audio_in::waitfordata ()
 | |
| {
 | |
|   WAVEHDR *pHdr;
 | |
|   MMRESULT rc;
 | |
| 
 | |
|   if (pHdr_ != NULL)
 | |
|     return true;
 | |
|   while (!Qisr2app_->recv (&pHdr))
 | |
|     {
 | |
|       if (fh->is_nonblocking ())
 | |
| 	{
 | |
| 	  set_errno (EAGAIN);
 | |
| 	  return false;
 | |
| 	}
 | |
|       debug_printf ("100ms");
 | |
|       switch (cygwait (100))
 | |
| 	{
 | |
| 	case WAIT_SIGNALED:
 | |
| 	  if (!_my_tls.call_signal_handler ())
 | |
| 	    {
 | |
| 	      set_errno (EINTR);
 | |
| 	      return false;
 | |
| 	    }
 | |
| 	  break;
 | |
| 	case WAIT_CANCELED:
 | |
| 	  pthread::static_cancel_self ();
 | |
| 	  /*NOTREACHED*/
 | |
| 	default:
 | |
| 	  break;
 | |
| 	}
 | |
|     }
 | |
|   if (pHdr->dwFlags) /* Zero if queued following error in queueblock */
 | |
|     {
 | |
|       /* Errors are ignored here. They will probbaly cause a failure
 | |
| 	 in the subsequent PrepareHeader */
 | |
|       rc = waveInUnprepareHeader (dev_, pHdr, sizeof (WAVEHDR));
 | |
|       debug_printf ("%u = waveInUnprepareHeader(%p)", rc, pHdr);
 | |
|     }
 | |
|   pHdr_ = pHdr;
 | |
|   bufferIndex_ = 0;
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void
 | |
| fhandler_dev_dsp::Audio_in::buf_info (audio_buf_info *p,
 | |
| 				      int rate, int bits, int channels)
 | |
| {
 | |
|   p->fragstotal = MAX_BLOCKS;
 | |
|   p->fragsize = blockSize (rate, bits, channels);
 | |
|   if (this && dev_)
 | |
|     {
 | |
|       p->fragments = Qisr2app_->query ();
 | |
|       if (pHdr_ != NULL)
 | |
| 	p->bytes = pHdr_->dwBytesRecorded - bufferIndex_
 | |
| 	  + p->fragsize * p->fragments;
 | |
|       else
 | |
| 	p->bytes = p->fragsize * p->fragments;
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       p->fragments = 0;
 | |
|       p->bytes = 0;
 | |
|     }
 | |
| }
 | |
| 
 | |
| inline void
 | |
| fhandler_dev_dsp::Audio_in::callback_blockfull (WAVEHDR *pHdr)
 | |
| {
 | |
|   Qisr2app_->send (pHdr);
 | |
| }
 | |
| 
 | |
| static void CALLBACK
 | |
| waveIn_callback (HWAVEIN hWave, UINT msg, DWORD_PTR instance, DWORD_PTR param1,
 | |
| 		 DWORD_PTR param2)
 | |
| {
 | |
|   if (msg == WIM_DATA)
 | |
|     {
 | |
|       fhandler_dev_dsp::Audio_in *ptr =
 | |
| 	(fhandler_dev_dsp::Audio_in *) instance;
 | |
|       ptr->callback_blockfull ((WAVEHDR *) param1);
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| /* ------------------------------------------------------------------------
 | |
|    /dev/dsp handler
 | |
|    ------------------------------------------------------------------------ */
 | |
| fhandler_dev_dsp::fhandler_dev_dsp ():
 | |
|   fhandler_base ()
 | |
| {
 | |
|   audio_in_ = NULL;
 | |
|   audio_out_ = NULL;
 | |
|   dev ().parse (FH_OSS_DSP);
 | |
| }
 | |
| 
 | |
| ssize_t __stdcall
 | |
| fhandler_dev_dsp::write (const void *ptr, size_t len)
 | |
| {
 | |
|   return base ()->_write (ptr, len);
 | |
| }
 | |
| 
 | |
| void __reg3
 | |
| fhandler_dev_dsp::read (void *ptr, size_t& len)
 | |
| {
 | |
|   return base ()->_read (ptr, len);
 | |
| }
 | |
| 
 | |
| int
 | |
| fhandler_dev_dsp::ioctl (unsigned int cmd, void *buf)
 | |
| {
 | |
|   return base ()->_ioctl (cmd, buf);
 | |
| }
 | |
| 
 | |
| void
 | |
| fhandler_dev_dsp::fixup_after_fork (HANDLE parent)
 | |
| {
 | |
|   base ()->_fixup_after_fork (parent);
 | |
| }
 | |
| 
 | |
| void
 | |
| fhandler_dev_dsp::fixup_after_exec ()
 | |
| {
 | |
|   base ()->_fixup_after_exec ();
 | |
| }
 | |
| 
 | |
| 
 | |
| int
 | |
| fhandler_dev_dsp::open (int flags, mode_t mode)
 | |
| {
 | |
|   int ret = 0, err = 0;
 | |
|   UINT num_in = 0, num_out = 0;
 | |
|   set_flags ((flags & ~O_TEXT) | O_BINARY);
 | |
|   // Work out initial sample format & frequency, /dev/dsp defaults
 | |
|   audioformat_ = AFMT_U8;
 | |
|   audiofreq_ = 8000;
 | |
|   audiobits_ = 8;
 | |
|   audiochannels_ = 1;
 | |
|   switch (flags & O_ACCMODE)
 | |
|     {
 | |
|     case O_RDWR:
 | |
|       if ((num_in = waveInGetNumDevs ()) == 0)
 | |
| 	err = ENXIO;
 | |
|       /* Fall through */
 | |
|     case O_WRONLY:
 | |
|       if ((num_out = waveOutGetNumDevs ()) == 0)
 | |
| 	err = ENXIO;
 | |
|       break;
 | |
|     case O_RDONLY:
 | |
|       if ((num_in = waveInGetNumDevs ()) == 0)
 | |
| 	err = ENXIO;
 | |
|       break;
 | |
|     default:
 | |
|       err = EINVAL;
 | |
|     }
 | |
| 
 | |
|   if (err)
 | |
|     set_errno (err);
 | |
|   else
 | |
|     ret = fhandler_base::open (flags, mode);
 | |
| 
 | |
|   debug_printf ("ACCMODE=%y audio_in=%d audio_out=%d, err=%d, ret=%d",
 | |
| 		flags & O_ACCMODE, num_in, num_out, err, ret);
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| #define IS_WRITE() ((get_flags() & O_ACCMODE) != O_RDONLY)
 | |
| #define IS_READ() ((get_flags() & O_ACCMODE) != O_WRONLY)
 | |
| 
 | |
| ssize_t __stdcall
 | |
| fhandler_dev_dsp::_write (const void *ptr, size_t len)
 | |
| {
 | |
|   debug_printf ("ptr=%p len=%ld", ptr, len);
 | |
|   int len_s = len;
 | |
|   const char *ptr_s = static_cast <const char *> (ptr);
 | |
| 
 | |
|   if (audio_out_)
 | |
|     /* nothing to do */;
 | |
|   else if (IS_WRITE ())
 | |
|     {
 | |
|       debug_printf ("Allocating");
 | |
|       if (!(audio_out_ = new Audio_out (this)))
 | |
| 	return -1;
 | |
| 
 | |
|       /* check for wave file & get parameters & skip header if possible. */
 | |
| 
 | |
|       if (audio_out_->parsewav (ptr_s, len_s,
 | |
| 				audiofreq_, audiobits_, audiochannels_))
 | |
| 	debug_printf ("=> ptr_s=%p len_s=%d", ptr_s, len_s);
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       set_errno (EBADF); // device was opened for read?
 | |
|       return -1;
 | |
|     }
 | |
| 
 | |
|   /* Open audio device properly with callbacks.
 | |
|      Private parameters were set in call to parsewav.
 | |
|      This is a no-op when there are successive writes in the same process */
 | |
|   if (!audio_out_->start ())
 | |
|     {
 | |
|       set_errno (EIO);
 | |
|       return -1;
 | |
|     }
 | |
| 
 | |
|   int written = audio_out_->write (ptr_s, len_s);
 | |
|   if (written < 0)
 | |
|     {
 | |
|       if (len - len_s > 0)
 | |
| 	return len - len_s;
 | |
|       return -1;
 | |
|     }
 | |
|   return len - len_s + written;
 | |
| }
 | |
| 
 | |
| void __reg3
 | |
| fhandler_dev_dsp::_read (void *ptr, size_t& len)
 | |
| {
 | |
|   debug_printf ("ptr=%p len=%ld", ptr, len);
 | |
| 
 | |
|   if (audio_in_)
 | |
|     /* nothing to do */;
 | |
|   else if (IS_READ ())
 | |
|     {
 | |
|       debug_printf ("Allocating");
 | |
|       if (!(audio_in_ = new Audio_in (this)))
 | |
| 	{
 | |
| 	  len = (size_t)-1;
 | |
| 	  return;
 | |
| 	}
 | |
|       audio_in_->setconvert (audioformat_);
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       len = (size_t)-1;
 | |
|       set_errno (EBADF); // device was opened for write?
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|   /* Open audio device properly with callbacks.
 | |
|      This is a noop when there are successive reads in the same process */
 | |
|   if (!audio_in_->start (audiofreq_, audiobits_, audiochannels_))
 | |
|     {
 | |
|       len = (size_t)-1;
 | |
|       set_errno (EIO);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|   audio_in_->read ((char *)ptr, (int&)len);
 | |
| }
 | |
| 
 | |
| void __reg1
 | |
| fhandler_dev_dsp::close_audio_in ()
 | |
| {
 | |
|   if (audio_in_)
 | |
|     {
 | |
|       audio_in_->stop ();
 | |
|       delete audio_in_;
 | |
|       audio_in_ = NULL;
 | |
|     }
 | |
| }
 | |
| 
 | |
| void __reg2
 | |
| fhandler_dev_dsp::close_audio_out (bool immediately)
 | |
| {
 | |
|   if (audio_out_)
 | |
|     {
 | |
|       audio_out_->stop (immediately);
 | |
|       delete audio_out_;
 | |
|       audio_out_ = NULL;
 | |
|     }
 | |
| }
 | |
| 
 | |
| int
 | |
| fhandler_dev_dsp::close ()
 | |
| {
 | |
|   debug_printf ("audio_in=%p audio_out=%p", audio_in_, audio_out_);
 | |
|   close_audio_in ();
 | |
|   close_audio_out ();
 | |
|   return fhandler_base::close ();
 | |
| }
 | |
| 
 | |
| int
 | |
| fhandler_dev_dsp::_ioctl (unsigned int cmd, void *buf)
 | |
| {
 | |
|   debug_printf ("audio_in=%p audio_out=%p", audio_in_, audio_out_);
 | |
|   int *intbuf = (int *) buf;
 | |
|   switch (cmd)
 | |
|     {
 | |
| #define CASE(a) case a : debug_printf ("/dev/dsp: ioctl %s", #a);
 | |
| 
 | |
|       CASE (SNDCTL_DSP_RESET)
 | |
| 	close_audio_in ();
 | |
| 	close_audio_out (true);
 | |
| 	return 0;
 | |
| 	break;
 | |
| 
 | |
|       CASE (SNDCTL_DSP_GETBLKSIZE)
 | |
| 	/* This is valid even if audio_X is NULL */
 | |
| 	if (IS_WRITE ())
 | |
| 	  {
 | |
| 	    *intbuf = audio_out_->blockSize (audiofreq_,
 | |
| 					     audiobits_,
 | |
| 					     audiochannels_);
 | |
| 	  }
 | |
| 	else
 | |
| 	  { // I am very sure that IS_READ is valid
 | |
| 	    *intbuf = audio_in_->blockSize (audiofreq_,
 | |
| 					    audiobits_,
 | |
| 					    audiochannels_);
 | |
| 	  }
 | |
| 	return 0;
 | |
| 
 | |
|       CASE (SNDCTL_DSP_SETFMT)
 | |
|       {
 | |
| 	int nBits;
 | |
| 	switch (*intbuf)
 | |
| 	  {
 | |
| 	  case AFMT_QUERY:
 | |
| 	    *intbuf = audioformat_;
 | |
| 	    return 0;
 | |
| 	    break;
 | |
| 	  case AFMT_U16_BE:
 | |
| 	  case AFMT_U16_LE:
 | |
| 	  case AFMT_S16_BE:
 | |
| 	  case AFMT_S16_LE:
 | |
| 	    nBits = 16;
 | |
| 	    break;
 | |
| 	  case AFMT_U8:
 | |
| 	  case AFMT_S8:
 | |
| 	    nBits = 8;
 | |
| 	    break;
 | |
| 	  default:
 | |
| 	    nBits = 0;
 | |
| 	  }
 | |
| 	if (nBits && IS_WRITE ())
 | |
| 	  {
 | |
| 	    close_audio_out ();
 | |
| 	    if (audio_out_->query (audiofreq_, nBits, audiochannels_))
 | |
| 	      {
 | |
| 		audiobits_ = nBits;
 | |
| 		audioformat_ = *intbuf;
 | |
| 	      }
 | |
| 	    else
 | |
| 	      {
 | |
| 		*intbuf = audiobits_;
 | |
| 		return -1;
 | |
| 	      }
 | |
| 	  }
 | |
| 	if (nBits && IS_READ ())
 | |
| 	  {
 | |
| 	    close_audio_in ();
 | |
| 	    if (audio_in_->query (audiofreq_, nBits, audiochannels_))
 | |
| 	      {
 | |
| 		audiobits_ = nBits;
 | |
| 		audioformat_ = *intbuf;
 | |
| 	      }
 | |
| 	    else
 | |
| 	      {
 | |
| 		*intbuf = audiobits_;
 | |
| 		return -1;
 | |
| 	      }
 | |
| 	  }
 | |
| 	return 0;
 | |
|       }
 | |
| 
 | |
|       CASE (SNDCTL_DSP_SPEED)
 | |
| 	if (IS_WRITE ())
 | |
| 	  {
 | |
| 	    close_audio_out ();
 | |
| 	    if (audio_out_->query (*intbuf, audiobits_, audiochannels_))
 | |
| 	      audiofreq_ = *intbuf;
 | |
| 	    else
 | |
| 	      {
 | |
| 		*intbuf = audiofreq_;
 | |
| 		return -1;
 | |
| 	      }
 | |
| 	  }
 | |
| 	if (IS_READ ())
 | |
| 	  {
 | |
| 	    close_audio_in ();
 | |
| 	    if (audio_in_->query (*intbuf, audiobits_, audiochannels_))
 | |
| 	      audiofreq_ = *intbuf;
 | |
| 	    else
 | |
| 	      {
 | |
| 		*intbuf = audiofreq_;
 | |
| 		return -1;
 | |
| 	      }
 | |
| 	  }
 | |
| 	return 0;
 | |
| 
 | |
|       CASE (SNDCTL_DSP_STEREO)
 | |
|       {
 | |
| 	int nChannels = *intbuf + 1;
 | |
| 	int res = _ioctl (SNDCTL_DSP_CHANNELS, &nChannels);
 | |
| 	*intbuf = nChannels - 1;
 | |
| 	return res;
 | |
|       }
 | |
| 
 | |
|       CASE (SNDCTL_DSP_CHANNELS)
 | |
|       {
 | |
| 	int nChannels = *intbuf;
 | |
| 
 | |
| 	if (IS_WRITE ())
 | |
| 	  {
 | |
| 	    close_audio_out ();
 | |
| 	    if (audio_out_->query (audiofreq_, audiobits_, nChannels))
 | |
| 	      audiochannels_ = nChannels;
 | |
| 	    else
 | |
| 	      {
 | |
| 		*intbuf = audiochannels_;
 | |
| 		return -1;
 | |
| 	      }
 | |
| 	  }
 | |
| 	if (IS_READ ())
 | |
| 	  {
 | |
| 	    close_audio_in ();
 | |
| 	    if (audio_in_->query (audiofreq_, audiobits_, nChannels))
 | |
| 	      audiochannels_ = nChannels;
 | |
| 	    else
 | |
| 	      {
 | |
| 		*intbuf = audiochannels_;
 | |
| 		return -1;
 | |
| 	      }
 | |
| 	  }
 | |
| 	return 0;
 | |
|       }
 | |
| 
 | |
|       CASE (SNDCTL_DSP_GETOSPACE)
 | |
|       {
 | |
| 	if (!IS_WRITE ())
 | |
| 	  {
 | |
| 	    set_errno(EBADF);
 | |
| 	    return -1;
 | |
| 	  }
 | |
| 	audio_buf_info *p = (audio_buf_info *) buf;
 | |
| 	audio_out_->buf_info (p, audiofreq_, audiobits_, audiochannels_);
 | |
| 	debug_printf ("buf=%p frags=%d fragsize=%d bytes=%d",
 | |
| 		      buf, p->fragments, p->fragsize, p->bytes);
 | |
| 	return 0;
 | |
|       }
 | |
| 
 | |
|       CASE (SNDCTL_DSP_GETISPACE)
 | |
|       {
 | |
| 	if (!IS_READ ())
 | |
| 	  {
 | |
| 	    set_errno(EBADF);
 | |
| 	    return -1;
 | |
| 	  }
 | |
| 	audio_buf_info *p = (audio_buf_info *) buf;
 | |
| 	audio_in_->buf_info (p, audiofreq_, audiobits_, audiochannels_);
 | |
| 	debug_printf ("buf=%p frags=%d fragsize=%d bytes=%d",
 | |
| 		      buf, p->fragments, p->fragsize, p->bytes);
 | |
| 	return 0;
 | |
|       }
 | |
| 
 | |
|       CASE (SNDCTL_DSP_SETFRAGMENT)
 | |
| 	// Fake!! esound & mikmod require this on non PowerPC platforms.
 | |
| 	//
 | |
| 	return 0;
 | |
| 
 | |
|       CASE (SNDCTL_DSP_GETFMTS)
 | |
| 	*intbuf = AFMT_S16_LE | AFMT_U8; // only native formats returned here
 | |
| 	return 0;
 | |
| 
 | |
|       CASE (SNDCTL_DSP_GETCAPS)
 | |
| 	*intbuf = DSP_CAP_BATCH | DSP_CAP_DUPLEX;
 | |
| 	return 0;
 | |
| 
 | |
|       CASE (SNDCTL_DSP_POST)
 | |
|       CASE (SNDCTL_DSP_SYNC)
 | |
| 	// Stop audio out device
 | |
| 	close_audio_out ();
 | |
| 	// Stop audio in device
 | |
| 	close_audio_in ();
 | |
| 	return 0;
 | |
| 
 | |
|     default:
 | |
|       return fhandler_base::ioctl (cmd, buf);
 | |
|       break;
 | |
| 
 | |
| #undef CASE
 | |
|     }
 | |
| }
 | |
| 
 | |
| void
 | |
| fhandler_dev_dsp::_fixup_after_fork (HANDLE parent)
 | |
| { // called from new child process
 | |
|   debug_printf ("audio_in=%p audio_out=%p",
 | |
| 		audio_in_, audio_out_);
 | |
| 
 | |
|   fhandler_base::fixup_after_fork (parent);
 | |
|   if (audio_in_)
 | |
|     audio_in_->fork_fixup (parent);
 | |
|   if (audio_out_)
 | |
|     audio_out_->fork_fixup (parent);
 | |
| }
 | |
| 
 | |
| void
 | |
| fhandler_dev_dsp::_fixup_after_exec ()
 | |
| {
 | |
|   debug_printf ("audio_in=%p audio_out=%p, close_on_exec %d",
 | |
| 		audio_in_, audio_out_, close_on_exec ());
 | |
|   if (!close_on_exec ())
 | |
|     {
 | |
|       audio_in_ = NULL;
 | |
|       audio_out_ = NULL;
 | |
|     }
 | |
| }
 |