/* fhandler_dev_dsp: code to emulate OSS sound model /dev/dsp Copyright 2001 Red Hat, Inc Written by Andy Younger (andy@snoogie.demon.co.uk) 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 #include #include "cygerrno.h" #include "fhandler.h" #include #include #include #include //------------------------------------------------------------------------ // Simple encapsulation of the win32 audio device. // static void CALLBACK wave_callback (HWAVE hWave, UINT msg, DWORD instance, DWORD param1, DWORD param2); class Audio { public: enum { MAX_BLOCKS = 12, BLOCK_SIZE = 16384, TOT_BLOCK_SIZE = BLOCK_SIZE + sizeof (WAVEHDR) }; Audio (); ~Audio (); bool open (int rate, int bits, int channels, bool bCallback = false); void close (); int getvolume (); void setvolume (int newVolume); bool write (const void *pSampleData, int nBytes); int blocks (); void callback_sampledone (void *pData); void setformat (int format) {formattype_ = format;} int numbytesoutput (); void *operator new (size_t, void *p) {return p;} private: char *initialisebuffer (); void waitforcallback (); bool flush (); HWAVEOUT dev_; volatile int nBlocksInQue_; int nBytesWritten_; char *buffer_; int bufferIndex_; CRITICAL_SECTION lock_; char *freeblocks_[MAX_BLOCKS]; int formattype_; char bigwavebuffer_[MAX_BLOCKS * TOT_BLOCK_SIZE]; }; static char audio_buf[sizeof (class Audio)]; Audio::Audio () { InitializeCriticalSection (&lock_); memset (bigwavebuffer_, 0, sizeof (bigwavebuffer_)); for (int i = 0; i < MAX_BLOCKS; i++) freeblocks_[i] = &bigwavebuffer_[i * TOT_BLOCK_SIZE]; } Audio::~Audio () { if (dev_) close (); DeleteCriticalSection (&lock_); } bool Audio::open (int rate, int bits, int channels, bool bCallback = false) { WAVEFORMATEX format; int nDevices = waveOutGetNumDevs (); nBytesWritten_ = 0L; bufferIndex_ = 0; buffer_ = 0L; debug_printf ("number devices %d\n", nDevices); if (nDevices <= 0) return false; debug_printf ("trying to map device freq %d, bits %d, " "channels %d, callback %d\n", rate, bits, channels, bCallback); int bytesperSample = bits / 8; 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 * bytesperSample; format.nBlockAlign = format.nChannels * bytesperSample; nBlocksInQue_ = 0; HRESULT res = waveOutOpen (&dev_, WAVE_MAPPER, &format, (DWORD) wave_callback, (DWORD) this, bCallback ? CALLBACK_FUNCTION : 0); if (res == S_OK) { debug_printf ("Sucessfully opened!"); return true; } else { debug_printf ("failed to open"); return false; } } void Audio::close () { if (dev_) { flush (); // force out last block whatever size.. while (blocks ()) // block till finished.. waitforcallback (); waveOutReset (dev_); waveOutClose (dev_); dev_ = 0L; } nBytesWritten_ = 0L; } int Audio::numbytesoutput () { return nBytesWritten_; } int Audio::getvolume () { DWORD volume; waveOutGetVolume (dev_, &volume); return ((volume >> 16) + (volume & 0xffff)) >> 1; } void Audio::setvolume (int newVolume) { waveOutSetVolume (dev_, (newVolume << 16) | newVolume); } char * Audio::initialisebuffer () { EnterCriticalSection (&lock_); WAVEHDR *pHeader = 0L; for (int i = 0; i < MAX_BLOCKS; i++) { char *pData = freeblocks_[i]; if (pData) { pHeader = (WAVEHDR *) pData; if (pHeader->dwFlags & WHDR_DONE) { waveOutUnprepareHeader (dev_, pHeader, sizeof (WAVEHDR)); } freeblocks_[i] = 0L; break; } } LeaveCriticalSection (&lock_); if (pHeader) { memset (pHeader, 0, sizeof (WAVEHDR)); pHeader->dwBufferLength = BLOCK_SIZE; pHeader->lpData = (LPSTR) (&pHeader[1]); return (char *) pHeader->lpData; } return 0L; } bool Audio::write (const void *pSampleData, int nBytes) { // split up big blocks into smaller BLOCK_SIZE chunks while (nBytes > BLOCK_SIZE) { write (pSampleData, BLOCK_SIZE); nBytes -= BLOCK_SIZE; pSampleData = (void *) ((char *) pSampleData + BLOCK_SIZE); } // Block till next sound is flushed if (blocks () == MAX_BLOCKS) waitforcallback (); // Allocate new wave buffer if necessary if (buffer_ == 0L) { buffer_ = initialisebuffer (); if (buffer_ == 0L) return false; } // Handle gathering blocks into larger buffer int sizeleft = BLOCK_SIZE - bufferIndex_; if (nBytes < sizeleft) { memcpy (&buffer_[bufferIndex_], pSampleData, nBytes); bufferIndex_ += nBytes; nBytesWritten_ += nBytes; return true; } // flushing when we reach our limit of BLOCK_SIZE memcpy (&buffer_[bufferIndex_], pSampleData, sizeleft); bufferIndex_ += sizeleft; nBytesWritten_ += sizeleft; flush (); // change pointer to rest of sample, and size accordingly pSampleData = (void *) ((char *) pSampleData + sizeleft); nBytes -= sizeleft; // if we still have some sample left over write it out if (nBytes) return write (pSampleData, nBytes); return true; } // return number of blocks back. int Audio::blocks () { EnterCriticalSection (&lock_); int ret = nBlocksInQue_; LeaveCriticalSection (&lock_); return ret; } // This is called on an interupt so use locking.. Note nBlocksInQue_ is // modified by it so we should wrap all references to it in locks. void Audio::callback_sampledone (void *pData) { EnterCriticalSection (&lock_); nBlocksInQue_--; for (int i = 0; i < MAX_BLOCKS; i++) if (!freeblocks_[i]) { freeblocks_[i] = (char *) pData; break; } LeaveCriticalSection (&lock_); } void Audio::waitforcallback () { int n = blocks (); if (!n) return; do { Sleep (250); } while (n == blocks ()); } bool Audio::flush () { if (!buffer_) return false; // Send internal buffer out to the soundcard WAVEHDR *pHeader = ((WAVEHDR *) buffer_) - 1; pHeader->dwBufferLength = bufferIndex_; // Quick bit of sample buffer conversion if (formattype_ == AFMT_S8) { unsigned char *p = ((unsigned char *) buffer_); for (int i = 0; i < bufferIndex_; i++) { p[i] -= 0x7f; } } if (waveOutPrepareHeader (dev_, pHeader, sizeof (WAVEHDR)) == S_OK && waveOutWrite (dev_, pHeader, sizeof (WAVEHDR)) == S_OK) { EnterCriticalSection (&lock_); nBlocksInQue_++; LeaveCriticalSection (&lock_); bufferIndex_ = 0; buffer_ = 0L; return true; } else { EnterCriticalSection (&lock_); for (int i = 0; i < MAX_BLOCKS; i++) if (!freeblocks_[i]) { freeblocks_[i] = (char *) pHeader; break; } LeaveCriticalSection (&lock_); } return false; } //------------------------------------------------------------------------ // Call back routine static void CALLBACK wave_callback (HWAVE hWave, UINT msg, DWORD instance, DWORD param1, DWORD param2) { if (msg == WOM_DONE) { Audio *ptr = (Audio *) instance; ptr->callback_sampledone ((void *) param1); } } //------------------------------------------------------------------------ // /dev/dsp handler static Audio *s_audio; // static instance of the Audio handler //------------------------------------------------------------------------ // 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::setupwav (const char *pData, int nBytes) { int len; const char *end = pData + nBytes; 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]; pData += 12; while (len && pData < end) { wavchunk * pChunk = (wavchunk *) pData; 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; // Open up audio device with correct frequency for wav file // // FIXME: should through away all the header & not output // it to the soundcard. s_audio->close (); if (s_audio->open (format->dwSamplesPerSec, format->wBitsPerSample, format->wChannels) == false) { s_audio->open (audiofreq_, audiobits_, audiochannels_); } else { audiofreq_ = format->dwSamplesPerSec; audiobits_ = format->wBitsPerSample; audiochannels_ = format->wChannels; } return true; } pData += blklen + sizeof (wavchunk); } return false; } //------------------------------------------------------------------------ fhandler_dev_dsp::fhandler_dev_dsp (const char *name): fhandler_base (FH_OSS_DSP, name) { set_cb (sizeof *this); } fhandler_dev_dsp::~fhandler_dev_dsp () { } int fhandler_dev_dsp::open (const char *path, int flags, mode_t mode = 0) { // currently we only support writing if ((flags & (O_WRONLY | O_RDONLY | O_RDWR)) != O_WRONLY) return 0; set_flags (flags); if (!s_audio) s_audio = new (audio_buf) Audio; // Work out initial sample format & frequency if (strcmp (path, "/dev/dsp") == 0L) { // dev/dsp defaults audioformat_ = AFMT_S8; audiofreq_ = 8000; audiobits_ = 8; audiochannels_ = 1; } if (!s_audio->open (audiofreq_, audiobits_, audiochannels_)) debug_printf ("/dev/dsp: failed to open\n"); else { set_open_status (); debug_printf ("/dev/dsp: successfully opened\n"); } return 1; } int fhandler_dev_dsp::write (const void *ptr, size_t len) { if (s_audio->numbytesoutput () == 0) { // check for wave file & setup frequencys properly if possible. setupwav ((const char *) ptr, len); // Open audio device properly with callbacks. s_audio->close (); if (!s_audio->open (audiofreq_, audiobits_, audiochannels_, true)) return 0; } s_audio->write (ptr, len); return len; } int fhandler_dev_dsp::read (void *ptr, size_t len) { return len; } off_t fhandler_dev_dsp::lseek (off_t offset, int whence) { return 0; } int fhandler_dev_dsp::close (void) { s_audio->close (); return 0; } int fhandler_dev_dsp::dup (fhandler_base * child) { 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; switch (cmd) { #define CASE(a) case a : debug_printf("/dev/dsp: ioctl %s\n", #a); CASE (SNDCTL_DSP_RESET) audioformat_ = AFMT_S8; audiofreq_ = 8000; audiobits_ = 8; audiochannels_ = 1; return 0; CASE (SNDCTL_DSP_GETBLKSIZE) *intptr = Audio::BLOCK_SIZE; break; CASE (SNDCTL_DSP_SETFMT) { int nBits = 0; if (*intptr == AFMT_S16_LE) nBits = 16; else if (*intptr == AFMT_U8) nBits = 8; else if (*intptr == AFMT_S8) nBits = 8; if (nBits) { s_audio->setformat (*intptr); s_audio->close (); if (s_audio->open (audiofreq_, nBits, audiochannels_) == true) { audiobits_ = nBits; return 0; } else { s_audio->open (audiofreq_, audiobits_, audiochannels_); return -1; } } } break; CASE (SNDCTL_DSP_SPEED) s_audio->close (); if (s_audio->open (*intptr, audiobits_, audiochannels_) == true) { audiofreq_ = *intptr; return 0; } else { s_audio->open (audiofreq_, audiobits_, audiochannels_); return -1; } break; CASE (SNDCTL_DSP_STEREO) { int nChannels = *intptr + 1; s_audio->close (); if (s_audio->open (audiofreq_, audiobits_, nChannels) == true) { audiochannels_ = nChannels; return 0; } else { s_audio->open (audiofreq_, audiobits_, audiochannels_); return -1; } } break; CASE (SNDCTL_DSP_GETOSPACE) { audio_buf_info *p = (audio_buf_info *) ptr; int nBlocks = s_audio->blocks (); int leftblocks = ((Audio::MAX_BLOCKS - nBlocks) - 1); if (leftblocks < 0) leftblocks = 0; if (leftblocks > 1) leftblocks = 1; int left = leftblocks * Audio::BLOCK_SIZE; p->fragments = leftblocks; p->fragstotal = Audio::MAX_BLOCKS; p->fragsize = Audio::BLOCK_SIZE; p->bytes = left; debug_printf ("ptr %p nblocks %d leftblocks %d left bytes %d ", ptr, nBlocks, leftblocks, left); return 0; } break; CASE (SNDCTL_DSP_SETFRAGMENT) { // Fake!! esound & mikmod require this on non PowerPC platforms. // return 0; } break; default: debug_printf ("/dev/dsp: ioctl not handled yet! FIXME:\n"); break; #undef CASE }; return -1; } void fhandler_dev_dsp::dump () { paranoid_printf ("here, fhandler_dev_dsp"); } void fhandler_dev_dsp::fixup_after_exec (HANDLE) { /* FIXME: Is there a better way to do this? */ s_audio = new (audio_buf) Audio; }