diff options
Diffstat (limited to 'flow/audioiooss.cpp')
-rw-r--r-- | flow/audioiooss.cpp | 485 |
1 files changed, 485 insertions, 0 deletions
diff --git a/flow/audioiooss.cpp b/flow/audioiooss.cpp new file mode 100644 index 0000000..21ce41d --- /dev/null +++ b/flow/audioiooss.cpp @@ -0,0 +1,485 @@ + /* + + Copyright (C) 2000 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 <config.h> +#endif + +#if defined(HAVE_SYS_SOUNDCARD_H) + #include <sys/soundcard.h> + #define COMPILE_AUDIOIO_OSS 1 +#elif defined(HAVE_SOUNDCARD_H) + #include <soundcard.h> + #define COMPILE_AUDIOIO_OSS 1 +#endif + +/** + * only compile 'oss' AudioIO class if sys/soundcard.h or soundcard.h is present + */ +#ifdef COMPILE_AUDIOIO_OSS + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/time.h> +#include <sys/stat.h> + +#ifdef HAVE_SYS_SELECT_H + #include <sys/select.h> // Needed on some systems. +#endif + +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <iostream> +#include <algorithm> +#include <cstring> + +#include "debug.h" +#include "audioio.h" + +namespace Arts { + +class AudioIOOSS : public AudioIO { +protected: + int audio_fd; + int requestedFragmentSize; + int requestedFragmentCount; + + std::string findDefaultDevice(); + int ossBits(int format); + +public: + AudioIOOSS(); + + 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(AudioIOOSS,"oss","Open Sound System"); +} + +using namespace std; +using namespace Arts; + +/* + * Tries to figure out which is the OSS device we should write to + */ +string AudioIOOSS::findDefaultDevice() +{ + static const char *device[] = { + "/dev/dsp", /* The usual device */ + // how does access(2) deal with a directory? + // I don't know, but, since /dev/sound/dsp is a linux bogosity + #if defined(__linux__) + "/dev/sound/dsp", /* Linux with devfs-only installation */ + #else + "/dev/sound", /* NetBSD */ + #endif + "/dev/audio", /* OpenBSD */ + 0 + }; + + for(int i = 0; device[i]; i++) + if(access(device[i],F_OK) == 0) + return device[i]; + + // Should this really return a valid device if we have access to none? + return device[0]; +} + +int AudioIOOSS::ossBits(int format) +{ + arts_return_val_if_fail (format == AFMT_U8 + || format == AFMT_S16_LE + || format == AFMT_S16_BE, 16); + + return (format == AFMT_U8)?8:16; +} + +AudioIOOSS::AudioIOOSS() +{ + /* + * default parameters + */ + param(samplingRate) = 44100; + paramStr(deviceName) = findDefaultDevice(); + requestedFragmentSize = param(fragmentSize) = 1024; + requestedFragmentCount = param(fragmentCount) = 7; + param(channels) = 2; + param(direction) = 2; +} + +bool AudioIOOSS::open() +{ + string& _error = 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 mode; + + if(param(direction) == 3) + mode = O_RDWR|O_NDELAY; + else if(param(direction) == 2) + mode = O_WRONLY|O_NDELAY; + else + { + _error = "invalid direction"; + return false; + } + + audio_fd = ::open(_deviceName.c_str(), mode, 0); + + if(audio_fd == -1) + { + _error = "device "; + _error += _deviceName.c_str(); + _error += " can't be opened ("; + _error += strerror(errno); + _error += ")"; + return false; + } + /* + * check device capabilities + */ + int device_caps; + if(ioctl(audio_fd,SNDCTL_DSP_GETCAPS,&device_caps) == -1) + device_caps=0; + + string caps = ""; + if(device_caps & DSP_CAP_DUPLEX) caps += "duplex "; + if(device_caps & DSP_CAP_REALTIME) caps += "realtime "; + if(device_caps & DSP_CAP_BATCH) caps += "batch "; + if(device_caps & DSP_CAP_COPROC) caps += "coproc "; + if(device_caps & DSP_CAP_TRIGGER) caps += "trigger "; + if(device_caps & DSP_CAP_MMAP) caps += "mmap "; + artsdebug("device capabilities: revision%d %s", + device_caps & DSP_CAP_REVISION, caps.c_str()); + + int requestedFormat = (_format == 8)?AFMT_U8:AFMT_S16_LE; + int gotFormat = requestedFormat; + if (ioctl(audio_fd, SNDCTL_DSP_SETFMT, &gotFormat)==-1) + { + _error = "SNDCTL_DSP_SETFMT failed - "; + _error += strerror(errno); + + close(); + return false; + } + + 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(zbuffer,_fragmentSize); + if(len != _fragmentSize) + { + arts_debug("AudioIOOSS: 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; + } + } + return true; +} + +void AudioIOOSS::close() +{ + ::close(audio_fd); +} + +void AudioIOOSS::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 AudioIOOSS::getParam(AudioParam p) +{ + audio_buf_info info; + switch(p) + { + case canRead: + ioctl(audio_fd, SNDCTL_DSP_GETISPACE, &info); + return info.bytes; + break; + + case canWrite: + ioctl(audio_fd, SNDCTL_DSP_GETOSPACE, &info); + return info.bytes; + break; + + case selectReadFD: + return (param(direction) & directionRead)?audio_fd:-1; + break; + + case selectWriteFD: + return (param(direction) & directionWrite)?audio_fd:-1; + break; + + case autoDetect: + /* OSS works reasonable almost everywhere where it compiles */ + return 10; + break; + + default: + return param(p); + break; + } +} + +int AudioIOOSS::read(void *buffer, int size) +{ + arts_assert(audio_fd != 0); + + int result; + do { + result = ::read(audio_fd,buffer,size); + } while(result == -1 && errno == EINTR); + + return result; +} + +int AudioIOOSS::write(void *buffer, int size) +{ + arts_assert(audio_fd != 0); + + int result; + do { + result = ::write(audio_fd,buffer,size); + } while(result == -1 && errno == EINTR); + + return result; +} + +#endif |