1533 lines
37 KiB
C++
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_);
|
|
}
|
|
|
|
|