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