libc/winsup/cygwin/fhandler_dsp.cc

1533 lines
37 KiB
C++

/* fhandler_dev_dsp: code to emulate OSS sound model /dev/dsp
Copyright 2001, 2002, 2003, 2004 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 <stdio.h>
#include <windows.h>
#include <sys/soundcard.h>
#include <mmsystem.h>
#include "cygerrno.h"
#include "security.h"
#include "path.h"
#include "fhandler.h"
/*------------------------------------------------------------------------
Simple encapsulation of the win32 audio device.
Implementation Notes
1. Audio buffers are created dynamically 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.
2. Every open call creates a new instance of the handler. To cope
with the fact that only a single wave device exists, the static
variable open_count tracks opens for one process. After a
successful open, every subsequent open from the same process
to the device fails with EBUSY.
If different processes open the audio device simultaneously,
the results are unpredictable - usually the first one wins.
3. The wave device is reserved within a process from the time that
the first read or write call has been successful until /dev/dsp
has been closed by that process. During this reservation period
child processes that use the same file descriptor cannot
do read, write or ioctls that change the device properties.
This means that a parent can open the device, do some ioctl,
spawn children, and any one of them can do the data read/write
*/
class fhandler_dev_dsp::Audio
{ // This class contains functionality common to Audio_in and Audio_out
public:
Audio ();
~Audio ();
class queue;
bool denyAccess ();
void fork_fixup (HANDLE parent);
inline DWORD getOwner () { return owner_; }
void setOwner () { owner_ = GetCurrentProcessId (); }
inline void clearOwner () { owner_ = 0L; }
void setformat (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);
inline void lock () { EnterCriticalSection (&lock_); }
inline void unlock () { LeaveCriticalSection (&lock_); }
private:
DWORD owner_; /* Process ID when wave operation started, else 0 */
CRITICAL_SECTION lock_;
};
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
int query (); // return number of items queued
private:
int head_;
int tail_;
int depth_, depth1_;
WAVEHDR **storage_;
};
static void CALLBACK waveOut_callback (HWAVEOUT hWave, UINT msg, DWORD instance,
DWORD param1, DWORD param2);
class fhandler_dev_dsp::Audio_out: public Audio
{
public:
Audio_out ();
~Audio_out ();
bool query (int rate, int bits, int channels);
bool start (int rate, int bits, int channels);
void stop (bool immediately = false);
bool 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 ();
void waitforspace ();
bool sendcurrent ();
int emptyblocks ();
enum { MAX_BLOCKS = 12 };
queue *Qapp2app_; // empty and unprepared blocks
HWAVEOUT dev_; // The wave device
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_; // empty blocks passed from wave callback
};
static void CALLBACK waveIn_callback (HWAVEIN hWave, UINT msg, DWORD instance,
DWORD param1, DWORD param2);
class fhandler_dev_dsp::Audio_in: public Audio
{
public:
Audio_in ();
~Audio_in ();
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);
void waitfordata (); // blocks until we have a good pHdr_
enum { MAX_BLOCKS = 12 }; // read ahead of 1.5 seconds
queue *Qapp2app_; // filled and unprepared blocks
HWAVEIN dev_;
int bufferIndex_; // offset into pHdr_->lpData
WAVEHDR *pHdr_; // successfully recorded data
WAVEHDR wavehdr_[MAX_BLOCKS];
char *bigwavebuffer_; // audio samples
// Member variables below must be locked
queue *Qisr2app_; // filled blocks passed from wave callback
};
/* --------------------------------------------------------------------
Implementation */
// Simple fixed length FIFO queue implementation for audio buffer management
fhandler_dev_dsp::Audio::queue::queue (int depth)
{
head_ = 0;
tail_ = 0;
depth_ = depth;
depth1_ = depth + 1;
// allow space for one extra object in the queue
// so we can distinguish full and empty status
storage_ = new WAVEHDR *[depth1_];
}
fhandler_dev_dsp::Audio::queue::~queue ()
{
delete[] storage_;
}
bool
fhandler_dev_dsp::Audio::queue::send (WAVEHDR *x)
{
if (query () == depth_)
return false;
storage_[tail_] = x;
tail_++;
if (tail_ == depth1_)
tail_ = 0;
return true;
}
bool
fhandler_dev_dsp::Audio::queue::recv (WAVEHDR **x)
{
if (query () == 0)
return false;
*x = storage_[head_];
head_++;
if (head_ == depth1_)
head_ = 0;
return true;
}
int
fhandler_dev_dsp::Audio::queue::query ()
{
int n = tail_ - head_;
if (n < 0)
n += depth1_;
return n;
}
// Audio class implements functionality need for both read and write
fhandler_dev_dsp::Audio::Audio ()
{
InitializeCriticalSection (&lock_);
convert_ = &fhandler_dev_dsp::Audio::convert_none;
owner_ = 0L;
}
fhandler_dev_dsp::Audio::~Audio ()
{
DeleteCriticalSection (&lock_);
}
void
fhandler_dev_dsp::Audio::fork_fixup (HANDLE parent)
{
debug_printf ("parent=0x%08x", parent);
InitializeCriticalSection (&lock_);
}
bool
fhandler_dev_dsp::Audio::denyAccess ()
{
if (owner_ == 0L)
return false;
return (GetCurrentProcessId () != owner_);
}
void
fhandler_dev_dsp::Audio::setformat (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;
}
//=======================================================================
fhandler_dev_dsp::Audio_out::Audio_out (): Audio ()
{
bigwavebuffer_ = NULL;
Qisr2app_ = new queue (MAX_BLOCKS);
Qapp2app_ = new queue (MAX_BLOCKS);
}
fhandler_dev_dsp::Audio_out::~Audio_out ()
{
stop ();
delete Qapp2app_;
delete Qisr2app_;
}
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 ("freq=%d bits=%d channels=%d %s", rate, bits, channels,
(rc != MMSYSERR_NOERROR) ? "FAIL" : "OK");
return (rc == MMSYSERR_NOERROR);
}
bool
fhandler_dev_dsp::Audio_out::start (int rate, int bits, int channels)
{
WAVEFORMATEX format;
MMRESULT rc;
unsigned bSize = blockSize (rate, bits, channels);
bigwavebuffer_ = new char[MAX_BLOCKS * bSize];
if (bigwavebuffer_ == NULL)
return false;
int nDevices = waveOutGetNumDevs ();
debug_printf ("number devices=%d, blocksize=%d", nDevices, bSize);
if (nDevices <= 0)
return false;
fillFormat (&format, rate, bits, channels);
rc = waveOutOpen (&dev_, WAVE_MAPPER, &format, (DWORD) waveOut_callback,
(DWORD) this, CALLBACK_FUNCTION);
if (rc == MMSYSERR_NOERROR)
{
setOwner ();
init (bSize);
}
debug_printf ("freq=%d bits=%d channels=%d %s", rate, bits, channels,
(rc != MMSYSERR_NOERROR) ? "FAIL" : "OK");
return (rc == MMSYSERR_NOERROR);
}
void
fhandler_dev_dsp::Audio_out::stop (bool immediately)
{
MMRESULT rc;
WAVEHDR *pHdr;
bool gotblock;
debug_printf ("dev_=%08x pid=%d owner=%d", (int)dev_,
GetCurrentProcessId (), getOwner ());
if (getOwner () && !denyAccess ())
{
if (!immediately)
{
sendcurrent (); // force out last block whatever size..
waitforallsent (); // block till finished..
}
rc = waveOutReset (dev_);
debug_printf ("waveOutReset rc=%d", rc);
do
{
lock ();
gotblock = Qisr2app_->recv (&pHdr);
unlock ();
if (gotblock)
{
rc = waveOutUnprepareHeader (dev_, pHdr, sizeof (WAVEHDR));
debug_printf ("waveOutUnprepareHeader Block 0x%08x %s", pHdr,
(rc != MMSYSERR_NOERROR) ? "FAIL" : "OK");
}
}
while (gotblock);
while (Qapp2app_->recv (&pHdr))
/* flush queue */;
rc = waveOutClose (dev_);
debug_printf ("waveOutClose rc=%d", rc);
clearOwner ();
}
if (bigwavebuffer_)
{
delete[] bigwavebuffer_;
bigwavebuffer_ = NULL;
}
}
void
fhandler_dev_dsp::Audio_out::init (unsigned blockSize)
{
int i;
// internally queue all of our buffer for later use by write
for (i = 0; i < MAX_BLOCKS; i++)
{
wavehdr_[i].lpData = &bigwavebuffer_[i * blockSize];
(int)wavehdr_[i].dwUser = blockSize;
if (!Qapp2app_->send (&wavehdr_[i]))
{
debug_printf ("Internal Error i=%d", i);
break; // should not happen
}
}
pHdr_ = NULL;
}
bool
fhandler_dev_dsp::Audio_out::write (const char *pSampleData, int nBytes)
{
while (nBytes != 0)
{ // Block if all blocks used until at least one is free
waitforspace ();
int sizeleft = (int)pHdr_->dwUser - bufferIndex_;
if (nBytes < sizeleft)
{ // all data fits into the current block, with some space left
memcpy (&pHdr_->lpData[bufferIndex_], pSampleData, nBytes);
bufferIndex_ += nBytes;
break;
}
else
{ // data will fill up the current block
memcpy (&pHdr_->lpData[bufferIndex_], pSampleData, sizeleft);
bufferIndex_ += sizeleft;
sendcurrent ();
pSampleData += sizeleft;
nBytes -= sizeleft;
}
}
return true;
}
// return number of (completely) empty blocks back.
int
fhandler_dev_dsp::Audio_out::emptyblocks ()
{
int n;
lock ();
n = Qisr2app_->query ();
unlock ();
n += Qapp2app_->query ();
return n;
}
void
fhandler_dev_dsp::Audio_out::buf_info (audio_buf_info *p,
int rate, int bits, int channels)
{
p->fragstotal = MAX_BLOCKS;
p->fragsize = blockSize (rate, bits, channels);
if (getOwner ())
{
p->fragments = emptyblocks ();
if (pHdr_ != NULL)
p->bytes = (int)pHdr_->dwUser - bufferIndex_
+ p->fragsize * p->fragments;
else
p->bytes = p->fragsize * p->fragments;
}
else
{
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. */
void
fhandler_dev_dsp::Audio_out::callback_sampledone (WAVEHDR *pHdr)
{
lock ();
Qisr2app_->send (pHdr);
unlock ();
}
void
fhandler_dev_dsp::Audio_out::waitforspace ()
{
WAVEHDR *pHdr;
bool gotblock;
MMRESULT rc = WAVERR_STILLPLAYING;
if (pHdr_ != NULL)
return;
while (Qapp2app_->recv (&pHdr) == false)
{
lock ();
gotblock = Qisr2app_->recv (&pHdr);
unlock ();
if (gotblock)
{
if ((pHdr->dwFlags & WHDR_DONE)
&& ((rc = waveOutUnprepareHeader (dev_, pHdr, sizeof (WAVEHDR)))
== MMSYSERR_NOERROR))
{
Qapp2app_->send (pHdr);
}
else
{
debug_printf ("error UnprepareHeader 0x%08x, rc=%d, 100ms",
pHdr, rc);
lock ();
Qisr2app_->send (pHdr);
unlock ();
Sleep (100);
}
}
else
{
debug_printf ("100ms");
Sleep (100);
}
}
pHdr_ = pHdr;
bufferIndex_ = 0;
}
void
fhandler_dev_dsp::Audio_out::waitforallsent ()
{
while (emptyblocks () != MAX_BLOCKS)
{
debug_printf ("100ms Qisr=%d Qapp=%d",
Qisr2app_->query (), Qapp2app_->query ());
Sleep (100);
}
}
// send the block described by pHdr_ and bufferIndex_ to wave device
bool
fhandler_dev_dsp::Audio_out::sendcurrent ()
{
WAVEHDR *pHdr = pHdr_;
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_;
pHdr->dwFlags = 0;
if (waveOutPrepareHeader (dev_, pHdr, sizeof (WAVEHDR)) == MMSYSERR_NOERROR)
{
if (waveOutWrite (dev_, pHdr, sizeof (WAVEHDR)) == MMSYSERR_NOERROR)
{
debug_printf ("waveOutWrite bytes=%d", bufferIndex_);
return true;
}
else
{
debug_printf ("waveOutWrite failed");
lock ();
Qisr2app_->send (pHdr);
unlock ();
}
}
else
{
debug_printf ("waveOutPrepareHeader failed");
Qapp2app_->send (pHdr);
}
return false;
}
//------------------------------------------------------------------------
// Call back routine
static void CALLBACK
waveOut_callback (HWAVEOUT hWave, UINT msg, DWORD instance, DWORD param1,
DWORD 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 &rate, int &bits, int &channels)
{
int len;
const char *end = pData + nBytes;
const char *pDat;
int skip = 0;
// Check alignment first: A lot of the code below depends on it
if (((int)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
rate = 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;
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. We need a second queue to distinguish
blocks with data from blocks that have been unprepared and are ready
to be used by read().
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 */
fhandler_dev_dsp::Audio_in::Audio_in () : Audio ()
{
bigwavebuffer_ = NULL;
Qisr2app_ = new queue (MAX_BLOCKS);
Qapp2app_ = new queue (MAX_BLOCKS);
}
fhandler_dev_dsp::Audio_in::~Audio_in ()
{
stop ();
delete Qapp2app_;
delete Qisr2app_;
}
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 ("freq=%d bits=%d channels=%d %s", rate, bits, channels,
(rc != MMSYSERR_NOERROR) ? "FAIL" : "OK");
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);
bigwavebuffer_ = new char[MAX_BLOCKS * bSize];
if (bigwavebuffer_ == NULL)
return false;
int nDevices = waveInGetNumDevs ();
debug_printf ("number devices=%d, blocksize=%d", nDevices, bSize);
if (nDevices <= 0)
return false;
fillFormat (&format, rate, bits, channels);
rc = waveInOpen (&dev_, WAVE_MAPPER, &format, (DWORD) waveIn_callback,
(DWORD) this, CALLBACK_FUNCTION);
if (rc == MMSYSERR_NOERROR)
{
setOwner ();
if (!init (bSize))
{
stop ();
return false;
}
}
debug_printf ("freq=%d bits=%d channels=%d %s", rate, bits, channels,
(rc != MMSYSERR_NOERROR) ? "FAIL" : "OK");
return (rc == MMSYSERR_NOERROR);
}
void
fhandler_dev_dsp::Audio_in::stop ()
{
MMRESULT rc;
WAVEHDR *pHdr;
bool gotblock;
debug_printf ("dev_=%08x pid=%d owner=%d", (int)dev_,
GetCurrentProcessId (), getOwner ());
if (getOwner () && !denyAccess ())
{
rc = waveInReset (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. */
debug_printf ("waveInReset rc=%d", rc);
do
{
lock ();
gotblock = Qisr2app_->recv (&pHdr);
unlock ();
if (gotblock)
{
rc = waveInUnprepareHeader (dev_, pHdr, sizeof (WAVEHDR));
debug_printf ("waveInUnprepareHeader Block 0x%08x %s", pHdr,
(rc != MMSYSERR_NOERROR) ? "FAIL" : "OK");
}
}
while (gotblock);
while (Qapp2app_->recv (&pHdr))
/* flush queue */;
rc = waveInClose (dev_);
debug_printf ("waveInClose rc=%d", rc);
clearOwner ();
}
if (bigwavebuffer_)
{
delete[] bigwavebuffer_;
bigwavebuffer_ = NULL;
}
}
bool
fhandler_dev_dsp::Audio_in::queueblock (WAVEHDR *pHdr)
{
MMRESULT rc;
pHdr->dwFlags = 0;
rc = waveInPrepareHeader (dev_, pHdr, sizeof (WAVEHDR));
if (rc == MMSYSERR_NOERROR)
rc = waveInAddBuffer (dev_, pHdr, sizeof (WAVEHDR));
debug_printf ("waveInAddBuffer Block 0x%08x %s", pHdr,
(rc != MMSYSERR_NOERROR) ? "FAIL" : "OK");
return (rc == MMSYSERR_NOERROR);
}
bool
fhandler_dev_dsp::Audio_in::init (unsigned blockSize)
{
MMRESULT rc;
int i;
// try to queue all of our buffer for reception
for (i = 0; i < MAX_BLOCKS; i++)
{
wavehdr_[i].lpData = &bigwavebuffer_[i * blockSize];
wavehdr_[i].dwBufferLength = blockSize;
if (!queueblock (&wavehdr_[i]))
break;
}
pHdr_ = NULL;
rc = waveInStart (dev_);
debug_printf ("waveInStart=%d %s queued=%d",
rc, (rc != MMSYSERR_NOERROR) ? "FAIL" : "OK", 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=%08x nBytes=%d", pSampleData, bytes_to_read);
while (bytes_to_read != 0)
{ // Block till next sound has been read
waitfordata ();
// 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;
}
void
fhandler_dev_dsp::Audio_in::waitfordata ()
{
WAVEHDR *pHdr;
bool gotblock;
MMRESULT rc;
if (pHdr_ != NULL)
return;
while (Qapp2app_->recv (&pHdr) == false)
{
lock ();
gotblock = Qisr2app_->recv (&pHdr);
unlock ();
if (gotblock)
{
rc = waveInUnprepareHeader (dev_, pHdr, sizeof (WAVEHDR));
if (rc == MMSYSERR_NOERROR)
Qapp2app_->send (pHdr);
else
debug_printf ("error UnprepareHeader 0x%08x", pHdr);
}
else
{
debug_printf ("100ms");
Sleep (100);
}
}
pHdr_ = pHdr;
bufferIndex_ = 0;
}
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 (getOwner ())
{
lock ();
p->fragments = Qisr2app_->query ();
unlock ();
p->fragments += Qapp2app_->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;
}
}
// This is called on an interrupt so use locking..
void
fhandler_dev_dsp::Audio_in::callback_blockfull (WAVEHDR *pHdr)
{
lock ();
Qisr2app_->send (pHdr);
unlock ();
}
static void CALLBACK
waveIn_callback (HWAVEIN hWave, UINT msg, DWORD instance, DWORD param1,
DWORD 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
------------------------------------------------------------------------
instances of the handler statics */
int fhandler_dev_dsp::open_count = 0;
fhandler_dev_dsp::fhandler_dev_dsp ():
fhandler_base ()
{
debug_printf ("0x%08x", (int)this);
audio_in_ = NULL;
audio_out_ = NULL;
}
fhandler_dev_dsp::~fhandler_dev_dsp ()
{
close ();
debug_printf ("0x%08x end", (int)this);
}
int
fhandler_dev_dsp::open (int flags, mode_t mode)
{
open_count++;
if (open_count > 1)
{
set_errno (EBUSY);
return 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_WRONLY:
audio_out_ = new Audio_out;
if (!audio_out_->query (audiofreq_, audiobits_, audiochannels_))
{
delete audio_out_;
audio_out_ = NULL;
}
break;
case O_RDONLY:
audio_in_ = new Audio_in;
if (!audio_in_->query (audiofreq_, audiobits_, audiochannels_))
{
delete audio_in_;
audio_in_ = NULL;
}
break;
case O_RDWR:
audio_out_ = new Audio_out;
if (audio_out_->query (audiofreq_, audiobits_, audiochannels_))
{
audio_in_ = new Audio_in;
if (!audio_in_->query (audiofreq_, audiobits_, audiochannels_))
{
delete audio_in_;
audio_in_ = NULL;
audio_out_->stop ();
delete audio_out_;
audio_out_ = NULL;
}
}
else
{
delete audio_out_;
audio_out_ = NULL;
}
break;
default:
set_errno (EINVAL);
return 0;
} // switch (flags & O_ACCMODE)
int rc;
if (audio_in_ || audio_out_)
{ /* All tried query () succeeded */
rc = 1;
set_open_status ();
need_fork_fixup (true);
close_on_exec (true);
}
else
{ /* One of the tried query () failed */
rc = 0;
set_errno (EIO);
}
debug_printf ("ACCMODE=0x%08x audio_in=%08x audio_out=%08x, rc=%d",
flags & O_ACCMODE, (int)audio_in_, (int)audio_out_, rc);
return rc;
}
#define RETURN_ERROR_WHEN_BUSY(audio)\
if ((audio)->denyAccess ()) \
{\
set_errno (EBUSY);\
return -1;\
}
int
fhandler_dev_dsp::write (const void *ptr, size_t len)
{
int len_s = len;
const char *ptr_s = static_cast <const char *> (ptr);
debug_printf ("ptr=%08x len=%d", ptr, len);
if (!audio_out_)
{
set_errno (EACCES); // device was opened for read?
return -1;
}
RETURN_ERROR_WHEN_BUSY (audio_out_);
if (audio_out_->getOwner () == 0L)
{ // No owner yet, lets do it
// check for wave file & get parameters & skip header if possible.
if (audio_out_->parsewav (ptr_s, len_s,
audiofreq_, audiobits_, audiochannels_))
{ // update our format conversion
debug_printf ("=> ptr_s=%08x len_s=%d", ptr_s, len_s);
audioformat_ = ((audiobits_ == 8) ? AFMT_U8 : AFMT_S16_LE);
audio_out_->setformat (audioformat_);
}
// Open audio device properly with callbacks.
if (!audio_out_->start (audiofreq_, audiobits_, audiochannels_))
{
set_errno (EIO);
return -1;
}
}
audio_out_->write (ptr_s, len_s);
return len;
}
void __stdcall
fhandler_dev_dsp::read (void *ptr, size_t& len)
{
debug_printf ("ptr=%08x len=%d", ptr, len);
if (!audio_in_)
{
len = (size_t)-1;
set_errno (EACCES); // device was opened for write?
return;
}
if (audio_in_->denyAccess ())
{
len = (size_t)-1;
set_errno (EBUSY);
return;
}
if (audio_in_->getOwner () == 0L)
{ // No owner yet. Let's take it
// Open audio device properly with callbacks.
if (!audio_in_->start (audiofreq_, audiobits_, audiochannels_))
{
len = (size_t)-1;
set_errno (EIO);
return;
}
}
audio_in_->read ((char *)ptr, (int&)len);
return;
}
_off64_t
fhandler_dev_dsp::lseek (_off64_t offset, int whence)
{
return 0;
}
int
fhandler_dev_dsp::close (void)
{
debug_printf ("audio_in=%08x audio_out=%08x",
(int)audio_in_, (int)audio_out_);
if (audio_in_)
{
delete audio_in_;
audio_in_ = NULL;
}
if (audio_out_)
{
if (exit_state != ES_NOT_EXITING)
{ // emergency close due to call to exit() or Ctrl-C:
// do not wait for all pending audio to be played
audio_out_->stop (true);
}
delete audio_out_;
audio_out_ = NULL;
}
if (open_count > 0)
open_count--;
return 0;
}
int
fhandler_dev_dsp::dup (fhandler_base * child)
{
debug_printf ("");
fhandler_dev_dsp *fhc = (fhandler_dev_dsp *) child;
fhc->set_flags (get_flags ());
fhc->audiochannels_ = audiochannels_;
fhc->audiobits_ = audiobits_;
fhc->audiofreq_ = audiofreq_;
fhc->audioformat_ = audioformat_;
return 0;
}
int
fhandler_dev_dsp::ioctl (unsigned int cmd, void *ptr)
{
int *intptr = (int *) ptr;
debug_printf ("audio_in=%08x audio_out=%08x",
(int)audio_in_, (int)audio_out_);
switch (cmd)
{
#define CASE(a) case a : debug_printf ("/dev/dsp: ioctl %s", #a);
CASE (SNDCTL_DSP_RESET)
if (audio_out_)
{
RETURN_ERROR_WHEN_BUSY (audio_out_);
audio_out_->stop (true);
}
if (audio_in_)
{
RETURN_ERROR_WHEN_BUSY (audio_in_);
audio_in_->stop ();
}
return 0;
break;
CASE (SNDCTL_DSP_GETBLKSIZE)
if (audio_out_)
{
*intptr = audio_out_->blockSize (audiofreq_,
audiobits_,
audiochannels_);
}
else
{ // I am very sure that audio_in_ is valid
*intptr = audio_in_->blockSize (audiofreq_,
audiobits_,
audiochannels_);
}
return 0;
break;
CASE (SNDCTL_DSP_SETFMT)
{
int nBits;
switch (*intptr)
{
case AFMT_QUERY:
*intptr = 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 && audio_out_)
{
RETURN_ERROR_WHEN_BUSY (audio_out_);
audio_out_->stop ();
audio_out_->setformat (*intptr);
if (audio_out_->query (audiofreq_, nBits, audiochannels_))
{
audiobits_ = nBits;
audioformat_ = *intptr;
}
else
{
*intptr = audiobits_;
return -1;
}
}
if (nBits && audio_in_)
{
RETURN_ERROR_WHEN_BUSY (audio_in_);
audio_in_->stop ();
audio_in_->setformat (*intptr);
if (audio_in_->query (audiofreq_, nBits, audiochannels_))
{
audiobits_ = nBits;
audioformat_ = *intptr;
}
else
{
*intptr = audiobits_;
return -1;
}
}
return 0;
}
break;
CASE (SNDCTL_DSP_SPEED)
{
if (audio_out_)
{
RETURN_ERROR_WHEN_BUSY (audio_out_);
audio_out_->stop ();
if (audio_out_->query (*intptr, audiobits_, audiochannels_))
audiofreq_ = *intptr;
else
{
*intptr = audiofreq_;
return -1;
}
}
if (audio_in_)
{
RETURN_ERROR_WHEN_BUSY (audio_in_);
audio_in_->stop ();
if (audio_in_->query (*intptr, audiobits_, audiochannels_))
audiofreq_ = *intptr;
else
{
*intptr = audiofreq_;
return -1;
}
}
return 0;
}
break;
CASE (SNDCTL_DSP_STEREO)
{
int nChannels = *intptr + 1;
if (audio_out_)
{
RETURN_ERROR_WHEN_BUSY (audio_out_);
audio_out_->stop ();
if (audio_out_->query (audiofreq_, audiobits_, nChannels))
audiochannels_ = nChannels;
else
{
*intptr = audiochannels_ - 1;
return -1;
}
}
if (audio_in_)
{
RETURN_ERROR_WHEN_BUSY (audio_in_);
audio_in_->stop ();
if (audio_in_->query (audiofreq_, audiobits_, nChannels))
audiochannels_ = nChannels;
else
{
*intptr = audiochannels_ - 1;
return -1;
}
}
return 0;
}
break;
CASE (SNDCTL_DSP_CHANNELS)
{
int nChannels = *intptr;
if (audio_out_)
{
RETURN_ERROR_WHEN_BUSY (audio_out_);
audio_out_->stop ();
if (audio_out_->query (audiofreq_, audiobits_, nChannels))
audiochannels_ = nChannels;
else
{
*intptr = audiochannels_;
return -1;
}
}
if (audio_in_)
{
RETURN_ERROR_WHEN_BUSY (audio_in_);
audio_in_->stop ();
if (audio_in_->query (audiofreq_, audiobits_, nChannels))
audiochannels_ = nChannels;
else
{
*intptr = audiochannels_;
return -1;
}
}
return 0;
}
break;
CASE (SNDCTL_DSP_GETOSPACE)
{
audio_buf_info *p = (audio_buf_info *) ptr;
if (audio_out_)
{
RETURN_ERROR_WHEN_BUSY (audio_out_);
audio_out_->buf_info (p, audiofreq_, audiobits_, audiochannels_);
debug_printf ("ptr=%p frags=%d fragsize=%d bytes=%d",
ptr, p->fragments, p->fragsize, p->bytes);
}
return 0;
}
break;
CASE (SNDCTL_DSP_GETISPACE)
{
audio_buf_info *p = (audio_buf_info *) ptr;
if (audio_in_)
{
RETURN_ERROR_WHEN_BUSY (audio_in_);
audio_in_->buf_info (p, audiofreq_, audiobits_, audiochannels_);
debug_printf ("ptr=%p frags=%d fragsize=%d bytes=%d",
ptr, p->fragments, p->fragsize, p->bytes);
}
return 0;
}
break;
CASE (SNDCTL_DSP_SETFRAGMENT)
{
// Fake!! esound & mikmod require this on non PowerPC platforms.
//
return 0;
}
break;
CASE (SNDCTL_DSP_GETFMTS)
{
*intptr = AFMT_S16_LE | AFMT_U8; // only native formats returned here
return 0;
}
break;
CASE (SNDCTL_DSP_GETCAPS)
{
*intptr = DSP_CAP_BATCH | DSP_CAP_DUPLEX;
return 0;
}
break;
CASE (SNDCTL_DSP_POST)
CASE (SNDCTL_DSP_SYNC)
{
if (audio_out_)
{
// Stop audio out device
RETURN_ERROR_WHEN_BUSY (audio_out_);
audio_out_->stop ();
}
if (audio_in_)
{
// Stop audio in device
RETURN_ERROR_WHEN_BUSY (audio_in_);
audio_in_->stop ();
}
return 0;
}
break;
default:
debug_printf ("/dev/dsp: ioctl 0x%08x not handled yet! FIXME:", cmd);
break;
#undef CASE
};
set_errno (EINVAL);
return -1;
}
void
fhandler_dev_dsp::dump ()
{
paranoid_printf ("here");
}
void
fhandler_dev_dsp::fixup_after_fork (HANDLE parent)
{ // called from new child process
debug_printf ("audio_in=%08x audio_out=%08x",
(int)audio_in_, (int)audio_out_);
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=%08x audio_out=%08x",
(int)audio_in_, (int)audio_out_);
}