diff options
Diffstat (limited to 'flow/audioiocsl.cc')
-rw-r--r-- | flow/audioiocsl.cc | 640 |
1 files changed, 640 insertions, 0 deletions
diff --git a/flow/audioiocsl.cc b/flow/audioiocsl.cc new file mode 100644 index 0000000..9a9b44e --- /dev/null +++ b/flow/audioiocsl.cc @@ -0,0 +1,640 @@ + /* + + 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., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +/** + * only compile 'csl' AudioIO class if libcsl is present + */ +#ifdef HAVE_LIBCSL +#include <csl/csl.h> + +/* g_newa */ +#include <gsl/gsldefs.h> + +#include <vector> + +#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<CslPollFD> 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<AudioIOCSLFactory *> 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<AudioIOCSLFactory *>::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<CslPollFD>::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<CslPollFD>::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 |