| /* fhandler_dev_dsp: code to emulate OSS sound model /dev/dsp |
| |
| 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); |
| static 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); |
| static void default_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); |
| static void default_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) |
| { |
| if (dev_) |
| { |
| /* If the device is running we use the internal values, |
| possibly set from the wave file. */ |
| p->fragstotal = MAX_BLOCKS; |
| 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 |
| { |
| default_buf_info(p, rate, bits, channels); |
| } |
| } |
| |
| void fhandler_dev_dsp::Audio_out::default_buf_info (audio_buf_info *p, |
| int rate, int bits, int channels) |
| { |
| p->fragstotal = MAX_BLOCKS; |
| 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::default_buf_info (audio_buf_info *p, |
| int rate, int bits, int channels) |
| { |
| p->fragstotal = MAX_BLOCKS; |
| p->fragsize = blockSize (rate, bits, channels); |
| p->fragments = 0; |
| p->bytes = 0; |
| } |
| |
| void |
| fhandler_dev_dsp::Audio_in::buf_info (audio_buf_info *p, |
| int rate, int bits, int channels) |
| { |
| if (dev_) |
| { |
| p->fragstotal = MAX_BLOCKS; |
| p->fragsize = blockSize (rate, bits, channels); |
| p->fragments = Qisr2app_->query (); |
| if (pHdr_ != NULL) |
| p->bytes = pHdr_->dwBytesRecorded - bufferIndex_ |
| + p->fragsize * p->fragments; |
| else |
| p->bytes = p->fragsize * p->fragments; |
| } |
| else |
| { |
| default_buf_info(p, rate, bits, channels); |
| } |
| } |
| |
| 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) |
| { |
| 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 = open_null (flags); |
| |
| 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; |
| if (audio_out_) { |
| audio_out_->buf_info (p, audiofreq_, audiobits_, audiochannels_); |
| } else { |
| Audio_out::default_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; |
| if (audio_in_) { |
| audio_in_->buf_info (p, audiofreq_, audiobits_, audiochannels_); |
| } else { |
| Audio_in::default_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; |
| } |
| } |