/* Copyright (C) 2000-2002 Stefan Westerfeld stefan@space.twc.de This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include #endif /** * only compile 'csl' AudioIO class if libcsl is present */ #ifdef HAVE_LIBCSL #include /* g_newa */ #include #include #include "audioio.h" #include "audiosubsys.h" #include "debug.h" #include "dispatcher.h" #include "iomanager.h" namespace Arts { class AudioIOCSL : public AudioIO, public IONotify { protected: CslPcmStream *inputStream, *outputStream; CslDriver *cslDriver; int requestedFragmentSize; int requestedFragmentCount; const char *cslDriverName; std::vector cslFds, cslOldFds; int csl2iomanager(int cslTypes); void updateFds(); void notifyIO(int fd, int types); static void handleRead(void *user_data, CslPcmStream *stream); static void handleWrite(void *user_data, CslPcmStream *stream); public: AudioIOCSL(const char *driverName = 0); void setParam(AudioParam param, int& value); int getParam(AudioParam param); bool open(); void close(); int read(void *buffer, int size); int write(void *buffer, int size); }; REGISTER_AUDIO_IO(AudioIOCSL,"csl","Common Sound Layer"); class AudioIOCSLFactory : public AudioIOFactory { protected: const char *driverName; std::string _name; std::string _fullName; public: AudioIOCSLFactory(const char *driverName) : driverName(driverName) { _name = "csl-"; _name += driverName; _fullName = "Common Sound Layer ("; _fullName += driverName; _fullName += ")"; } virtual ~AudioIOCSLFactory() { } AudioIO *createAudioIO() { return new AudioIOCSL(driverName); } virtual const char *name() { return _name.c_str(); } virtual const char *fullName() { return _fullName.c_str(); } }; static class AudioIOCSLInit { protected: std::list factories; public: AudioIOCSLInit() { unsigned int i,n; const char **drivers = csl_list_drivers(&n); for(i = 0; i < n; i++) factories.push_back(new AudioIOCSLFactory(drivers[i])); } ~AudioIOCSLInit() { std::list::iterator i; for(i = factories.begin(); i != factories.end(); i++) delete (*i); factories.clear(); } } aci; }; using namespace std; using namespace Arts; AudioIOCSL::AudioIOCSL(const char *driverName) : cslDriverName(driverName) { /* * default parameters */ param(samplingRate) = 44100; paramStr(deviceName) = "todo"; requestedFragmentSize = param(fragmentSize) = 1024; requestedFragmentCount = param(fragmentCount) = 7; param(channels) = 2; param(direction) = 2; #ifdef WORDS_BIGENDIAN param(format) = 17; #else param(format) = 16; #endif } bool AudioIOCSL::open() { string& errorMsg = paramStr(lastError); string& _deviceName = paramStr(deviceName); int& _channels = param(channels); int& _fragmentSize = param(fragmentSize); int& _fragmentCount = param(fragmentCount); int& _samplingRate = param(samplingRate); int& _format = param(format); int fmt = 0; char *env = 0; if(cslDriverName && strcmp(cslDriverName, "arts") == 0) env = arts_strdup_printf("ARTS_SERVER=%s",_deviceName.c_str()); if(env) { putenv(env); arts_debug("AudioIOCsl: set %s\n",env); } CslErrorType error; error = csl_driver_init(cslDriverName, &cslDriver); /* choose backend */ if(env) { putenv("ARTS_SERVER"); free(env); } if (error) { errorMsg = "unable to initialize CSL driver: "; errorMsg += csl_strerror(error); return false; } if(_format == 8) fmt = CSL_PCM_FORMAT_U8; else if(_format == 16) fmt = CSL_PCM_FORMAT_S16_LE; else if(_format == 17) fmt = CSL_PCM_FORMAT_S16_BE; inputStream = outputStream = 0; if(param(direction) & directionRead) { /* open PCM output stream */ error = csl_pcm_open_output(cslDriver, "artsd output", _samplingRate, _channels, fmt, &inputStream); if (error) { errorMsg = "failed to open CSL input stream: "; errorMsg += csl_strerror(error); return false; } csl_pcm_set_callback(inputStream, handleRead, 0, 0); } if(param(direction) & directionWrite) { /* open PCM output stream */ error = csl_pcm_open_output(cslDriver, "artsd output", _samplingRate, _channels, fmt, &outputStream); if (error) { close(); errorMsg = "failed to open CSL output stream: "; errorMsg += csl_strerror(error); return false; } csl_pcm_set_callback(outputStream, handleWrite, 0, 0); } #if 0 if (_format && (ossBits(gotFormat) != ossBits(requestedFormat))) { char details[80]; sprintf(details," (_format = %d, asked driver to give %d, got %d)", _format, requestedFormat, gotFormat); _error = "Can't set playback format"; _error += details; close(); return false; } if(gotFormat == AFMT_U8) _format = 8; else if(gotFormat == AFMT_S16_LE) _format = 16; else if(gotFormat == AFMT_S16_BE) _format = 17; else { char details[80]; sprintf(details," (_format = %d, asked driver to give %d, got %d)", _format, requestedFormat, gotFormat); _error = "unknown format given by driver"; _error += details; close(); return false; } int stereo=-1; /* 0=mono, 1=stereo */ if(_channels == 1) { stereo = 0; } if(_channels == 2) { stereo = 1; } if(stereo == -1) { _error = "internal error; set channels to 1 (mono) or 2 (stereo)"; close(); return false; } int requeststereo = stereo; if (ioctl(audio_fd, SNDCTL_DSP_STEREO, &stereo)==-1) { _error = "SNDCTL_DSP_STEREO failed - "; _error += strerror(errno); close(); return false; } if (requeststereo != stereo) { _error = "audio device doesn't support number of requested channels"; close(); return false; } int speed = _samplingRate; if (ioctl(audio_fd, SNDCTL_DSP_SPEED, &speed)==-1) { _error = "SNDCTL_DSP_SPEED failed - "; _error += strerror(errno); close(); return false; } /* * Some soundcards seem to be able to only supply "nearly" the requested * sampling rate, especially PAS 16 cards seem to quite radical supplying * something different than the requested sampling rate ;) * * So we have a quite large tolerance here (when requesting 44100 Hz, it * will accept anything between 38690 Hz and 49510 Hz). Most parts of the * aRts code will do resampling where appropriate, so it shouldn't affect * sound quality. */ int tolerance = _samplingRate/10+1000; if (abs(speed-_samplingRate) > tolerance) { _error = "can't set requested samplingrate"; char details[80]; sprintf(details," (requested rate %d, got rate %d)", _samplingRate, speed); _error += details; close(); return false; } _samplingRate = speed; /* * set the fragment settings to what the user requested */ _fragmentSize = requestedFragmentSize; _fragmentCount = requestedFragmentCount; /* * lower 16 bits are the fragment size (as 2^S) * higher 16 bits are the number of fragments */ int frag_arg = 0; int size = _fragmentSize; while(size > 1) { size /= 2; frag_arg++; } frag_arg += (_fragmentCount << 16); if(ioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, &frag_arg) == -1) { char buffer[1024]; _error = "can't set requested fragments settings"; sprintf(buffer,"size%d:count%d\n",_fragmentSize,_fragmentCount); close(); return false; } /* * now see what we really got as cards aren't required to supply what * we asked for */ audio_buf_info info; if(ioctl(audio_fd,SNDCTL_DSP_GETOSPACE, &info) == -1) { _error = "can't retrieve fragment settings"; close(); return false; } // update fragment settings with what we got _fragmentSize = info.fragsize; _fragmentCount = info.fragstotal; artsdebug("buffering: %d fragments with %d bytes " "(audio latency is %1.1f ms)", _fragmentCount, _fragmentSize, (float)(_fragmentSize*_fragmentCount) / (float)(2.0 * _samplingRate * _channels)*1000.0); /* * Workaround for broken kernel drivers: usually filling up the audio * buffer is _only_ required if _fullDuplex is true. However, there * are kernel drivers around (especially everything related to ES1370/1371) * which will not trigger select()ing the file descriptor unless we have * written something first. */ char *zbuffer = (char *)calloc(sizeof(char), _fragmentSize); if(_format == 8) for(int zpos = 0; zpos < _fragmentSize; zpos++) zbuffer[zpos] |= 0x80; for(int fill = 0; fill < _fragmentCount; fill++) { int len = ::write(audio_fd,zbuffer,_fragmentSize); if(len != _fragmentSize) { arts_debug("AudioIOCSL: failed prefilling audio buffer (might cause synchronization problems in conjunction with full duplex)"); fill = _fragmentCount+1; } } free(zbuffer); /* * Triggering - the original aRts code did this for full duplex: * * - stop audio i/o using SETTRIGGER(~(PCM_ENABLE_INPUT|PCM_ENABLE_OUTPUT)) * - fill buffer (see zbuffer code two lines above) * - start audio i/o using SETTRIGGER(PCM_ENABLE_INPUT|PCM_ENABLE_OUTPUT) * * this should guarantee synchronous start of input/output. Today, it * seems there are too many broken drivers around for this. */ if(device_caps & DSP_CAP_TRIGGER) { int enable_bits = 0; if(param(direction) & 1) enable_bits |= PCM_ENABLE_INPUT; if(param(direction) & 2) enable_bits |= PCM_ENABLE_OUTPUT; if(ioctl(audio_fd,SNDCTL_DSP_SETTRIGGER, &enable_bits) == -1) { _error = "can't start sound i/o"; close(); return false; } } #endif updateFds(); return true; } void AudioIOCSL::close() { if(inputStream) { csl_pcm_close(inputStream); inputStream = 0; } if(outputStream) { csl_pcm_close(outputStream); outputStream = 0; } updateFds(); } void AudioIOCSL::setParam(AudioParam p, int& value) { switch(p) { case fragmentSize: param(p) = requestedFragmentSize = value; break; case fragmentCount: param(p) = requestedFragmentCount = value; break; default: param(p) = value; break; } } int AudioIOCSL::getParam(AudioParam p) { CslErrorType error; CslPcmStatus status; switch(p) { case canRead: error = csl_pcm_get_status (inputStream, &status); if (error) /* FIXME */ arts_fatal("unable to obtain csl stream status: %s", csl_strerror (error)); updateFds(); return status.n_bytes_available; break; case canWrite: error = csl_pcm_get_status(outputStream, &status); if (error) /* FIXME */ arts_fatal("unable to obtain csl stream status: %s", csl_strerror (error)); updateFds(); return status.n_bytes_available; break; case autoDetect: /* CSL is pretty experimental currently */ return 1; break; default: return param(p); break; } } int AudioIOCSL::read(void *buffer, int size) { arts_assert(inputStream != 0); int result = csl_pcm_read(inputStream, size, buffer); updateFds(); return result; } void AudioIOCSL::handleRead(void *, CslPcmStream *) { AudioSubSystem::the()->handleIO(AudioSubSystem::ioRead); } int AudioIOCSL::write(void *buffer, int size) { arts_assert(outputStream != 0); int result = csl_pcm_write(outputStream, size, buffer); updateFds(); return result; } void AudioIOCSL::handleWrite(void *, CslPcmStream *) { AudioSubSystem::the()->handleIO(AudioSubSystem::ioWrite); } /* mainloop integration: make CSL callbacks work inside the aRts mainloop */ int AudioIOCSL::csl2iomanager(int cslTypes) { /* FIXME: doublecheck this list */ int types = 0; if(cslTypes & CSL_POLLIN) types |= IOType::read; if(cslTypes & CSL_POLLOUT) types |= IOType::write; if(cslTypes & CSL_POLLERR) types |= IOType::except; return types; } void AudioIOCSL::updateFds() { unsigned int n_fds = csl_poll_count_fds(cslDriver); CslPollFD *newFds = g_newa(CslPollFD, n_fds); unsigned int have_fds = csl_poll_get_fds(cslDriver, n_fds, newFds); arts_assert(have_fds == n_fds); cslFds.clear(); unsigned int i; for(i = 0; i < have_fds; i++) cslFds.push_back(newFds[i]); /* FIXME: if csl provided a flag for this, we could save some work here */ bool fdsChanged; if(cslFds.size() == cslOldFds.size()) { fdsChanged = false; for(i = 0; i < have_fds; i++) { if(cslFds[i].events != cslOldFds[i].events) fdsChanged = true; if(cslFds[i].fd != cslOldFds[i].fd) fdsChanged = true; } } else { fdsChanged = true; } if(!fdsChanged) return; vector::iterator ci; /* remove old watches */ /* * UGLY! due to broken API, we can only remove all watches here, and not * do anything selectively - its not a problem for the code here, but it * might be a problem elsewhere. Unfortunately, it can't be fixed without * breaking BC. */ Dispatcher::the()->ioManager()->remove(this, IOType::all); arts_debug("AudioIOCSL::updateFds(): removing watches"); /* add new watches */ for(ci = cslFds.begin(); ci < cslFds.end(); ci++) { int types = csl2iomanager(ci->events); if(types) { Dispatcher::the()->ioManager()->watchFD(ci->fd, types, this); arts_debug("AudioIOCSL::updateFds(): adding watch on %d", ci->fd); } } cslOldFds = cslFds; } void AudioIOCSL::notifyIO(int fd, int type) { vector::iterator fi; for(fi = cslFds.begin(); fi != cslFds.end(); fi++) { if(fi->fd == fd) { int ftype = csl2iomanager(fi->events); fi->revents = 0; if(type & ftype & IOType::read) fi->revents |= CSL_POLLIN; if(type & ftype & IOType::write) fi->revents |= CSL_POLLOUT; if(type & ftype & IOType::except) fi->revents |= CSL_POLLERR; if(fi->revents) csl_poll_handle_fds(cslDriver, 1, &(*fi)); } } updateFds(); } #endif