diff options
Diffstat (limited to 'artsc')
-rw-r--r-- | artsc/Makefile.am | 32 | ||||
-rwxr-xr-x | artsc/artsc-config.in | 56 | ||||
-rw-r--r-- | artsc/artsc.c | 226 | ||||
-rw-r--r-- | artsc/artsc.h | 246 | ||||
-rw-r--r-- | artsc/artsc_export.h.in | 52 | ||||
-rw-r--r-- | artsc/artscbackend.cc | 805 | ||||
-rw-r--r-- | artsc/artsdsp.c | 675 | ||||
-rwxr-xr-x | artsc/artsdsp.in | 106 | ||||
-rw-r--r-- | artsc/stdioemu.c | 96 |
9 files changed, 2294 insertions, 0 deletions
diff --git a/artsc/Makefile.am b/artsc/Makefile.am new file mode 100644 index 0000000..7487633 --- /dev/null +++ b/artsc/Makefile.am @@ -0,0 +1,32 @@ +AM_CFLAGS = -DARTSC_BACKEND='"$(libdir)/libartscbackend.la"' +AM_CPPFLAGS = -DCOMPILING_ARTSC +lib_LTLIBRARIES = libartsc.la libartscbackend.la libartsdsp.la libartsdsp_st.la +FLOWLIBS = $(top_builddir)/flow/libartsflow.la + +INCLUDES = -I$(top_srcdir)/mcop -I$(top_builddir)/mcop -I$(top_srcdir)/flow \ + -I$(top_builddir)/flow -I$(top_builddir)/soundserver \ + -I$(top_srcdir)/libltdl $(all_includes) + +bin_SCRIPTS = artsc-config artsdsp + +libartsdsp_la_SOURCES = artsdsp.c +libartsdsp_la_LDFLAGS = -no-undefined -module $(all_libraries) +libartsdsp_la_LIBADD = libartsc.la + +libartsdsp_st_la_SOURCES = artsc.c artsdsp.c +libartsdsp_st_la_LDFLAGS = -no-undefined -module $(all_libraries) +libartsdsp_st_la_LIBADD = $(top_builddir)/libltdl/libltdlc.la + +libartsc_la_SOURCES = artsc.c +libartsc_la_LDFLAGS = -no-undefined $(USE_THREADS) $(all_libraries) +libartsc_la_LIBADD = $(top_builddir)/libltdl/libltdlc.la $(LIBPTHREAD) + +libartscbackend_la_SOURCES = artscbackend.cc +libartscbackend_la_LDFLAGS = -no-undefined -module $(KDE_RPATH) $(all_libraries) +libartscbackend_la_LIBADD = $(FLOWLIBS) \ + $(top_builddir)/soundserver/libsoundserver_idl.la + +artscincludedir = $(includedir)/artsc +artscinclude_HEADERS = artsc.h artsc_export.h + +artscbackend.lo: $(top_builddir)/soundserver/soundserver.h ../flow/artsflow.h diff --git a/artsc/artsc-config.in b/artsc/artsc-config.in new file mode 100755 index 0000000..8b9b578 --- /dev/null +++ b/artsc/artsc-config.in @@ -0,0 +1,56 @@ +#!/bin/sh + +usage() +{ + echo "usage: $0 [OPTIONS]" +cat << EOH + +options: + [--libs] + [--cflags] + [--version] + [--arts-version] + [--arts-prefix] +EOH + exit 1; +} + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ +libdl=@LIBDL@ + +flags="" + +while test $# -gt 0 +do + case $1 in + --libs) + flags="$flags -L$libdir $libdl -lartsc @USE_THREADS@ @LIBPTHREAD@ @GLIB_LDFLAGS@ @GLIB_LIBADD@" + ;; + --cflags) + flags="$flags -I$includedir/artsc @GLIB_CFLAGS@" + ;; + --version) + echo 0.9.5 + ;; + --arts-version) + echo @ARTS_VERSION@ + ;; + --arts-prefix) + echo $prefix + ;; + *) + echo "$0: unknown option $1" + echo + usage + ;; + esac + shift +done + +if test -n "$flags" +then + echo $flags +fi diff --git a/artsc/artsc.c b/artsc/artsc.c new file mode 100644 index 0000000..61a02b6 --- /dev/null +++ b/artsc/artsc.c @@ -0,0 +1,226 @@ + /* + + 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., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ + +#include "artsc.h" +#include "ltdl.h" +#include <assert.h> + +typedef int (*backend_init_ptr)(); +typedef int (*backend_suspend_ptr)(); +typedef int (*backend_suspended_ptr)(); +typedef void (*backend_free_ptr)(); +typedef arts_stream_t (*backend_play_stream_ptr)(int,int,int,const char*); +typedef arts_stream_t (*backend_record_stream_ptr)(int,int,int,const char*); +typedef void (*backend_close_stream_ptr)(arts_stream_t); +typedef int (*backend_read_ptr)(arts_stream_t,void*,int); +typedef int (*backend_write_ptr)(arts_stream_t,const void*,int); +typedef int (*backend_stream_set_ptr)(arts_stream_t, arts_parameter_t, int); +typedef int (*backend_stream_get_ptr)(arts_stream_t, arts_parameter_t); + +static struct arts_backend { + int available; + int refcnt; + lt_dlhandle handle; + + backend_init_ptr init; + backend_suspend_ptr suspend; + backend_suspended_ptr suspended; + backend_free_ptr free; + backend_play_stream_ptr play_stream; + backend_record_stream_ptr record_stream; + backend_close_stream_ptr close_stream; + backend_read_ptr read; + backend_write_ptr write; + backend_stream_set_ptr stream_set; + backend_stream_get_ptr stream_get; +} backend = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0 }; + +static void arts_backend_ref() +{ + if(backend.refcnt == 0) + { + lt_dlinit(); + backend.handle = lt_dlopen(ARTSC_BACKEND); + + if(backend.handle) + { + backend.init = (backend_init_ptr) + lt_dlsym(backend.handle, "arts_backend_init"); + backend.suspend = (backend_suspend_ptr) + lt_dlsym(backend.handle, "arts_backend_suspend"); + backend.suspended = (backend_suspended_ptr) + lt_dlsym(backend.handle, "arts_backend_suspended"); + backend.free = (backend_free_ptr) + lt_dlsym(backend.handle, "arts_backend_free"); + backend.play_stream = (backend_play_stream_ptr) + lt_dlsym(backend.handle, "arts_backend_play_stream"); + backend.record_stream = (backend_record_stream_ptr) + lt_dlsym(backend.handle, "arts_backend_record_stream"); + backend.close_stream = (backend_close_stream_ptr) + lt_dlsym(backend.handle, "arts_backend_close_stream"); + backend.write = (backend_write_ptr) + lt_dlsym(backend.handle, "arts_backend_write"); + backend.read = (backend_read_ptr) + lt_dlsym(backend.handle, "arts_backend_read"); + backend.stream_set = (backend_stream_set_ptr) + lt_dlsym(backend.handle, "arts_backend_stream_set"); + backend.stream_get = (backend_stream_get_ptr) + lt_dlsym(backend.handle, "arts_backend_stream_get"); + } + + if(backend.handle && backend.init && backend.free && backend.play_stream + && backend.record_stream && backend.close_stream && backend.write + && backend.read && backend.stream_set && backend.stream_get + && backend.suspend) + backend.available = 1; + else + backend.available = 0; + } + backend.refcnt++; +} + +static void arts_backend_release() +{ + assert(backend.refcnt > 0); + backend.refcnt--; + if(backend.refcnt == 0) + { + if(backend.available) + { + backend.available = 0; + + if(backend.handle) lt_dlclose(backend.handle); + } + lt_dlexit(); + } +} + +int arts_init() +{ + int rc = ARTS_E_NOBACKEND; + + arts_backend_ref(); + if(backend.available) rc = backend.init(); + + /* init failed: the user may not call other arts_xxx functions now */ + if(rc < 0) arts_backend_release(); + + return rc; +} + +int arts_suspend() +{ + int rc = ARTS_E_NOBACKEND; + + if(backend.available) rc = backend.suspend(); + return rc; +} + +int arts_suspended() +{ + int rc = ARTS_E_NOBACKEND; + + if(backend.available && backend.suspended) rc = backend.suspended(); + return rc; +} + +void arts_free() +{ + if(backend.available) + { + backend.free(); + arts_backend_release(); + } +} + +arts_stream_t arts_play_stream(int rate, int bits, int channels, const char *name) +{ + arts_stream_t rc = 0; + + if(backend.available) rc = backend.play_stream(rate,bits,channels,name); + return rc; +} + +arts_stream_t arts_record_stream(int rate, int bits, int channels, const char *name) +{ + arts_stream_t rc = 0; + + if(backend.available) rc = backend.record_stream(rate,bits,channels,name); + return rc; +} + +void arts_close_stream(arts_stream_t stream) +{ + if(backend.available) backend.close_stream(stream); +} + +int arts_read(arts_stream_t stream, void *buffer, int count) +{ + int rc = ARTS_E_NOBACKEND; + + if(backend.available) rc = backend.read(stream,buffer,count); + return rc; +} + +int arts_write(arts_stream_t stream, const void *buffer, int count) +{ + int rc = ARTS_E_NOBACKEND; + + if(backend.available) rc = backend.write(stream,buffer,count); + return rc; +} + +int arts_stream_set(arts_stream_t stream, arts_parameter_t param, int value) +{ + int rc = ARTS_E_NOBACKEND; + + if(backend.available) rc = backend.stream_set(stream,param,value); + return rc; +} + +int arts_stream_get(arts_stream_t stream, arts_parameter_t param) +{ + int rc = ARTS_E_NOBACKEND; + + if(backend.available) rc = backend.stream_get(stream,param); + return rc; +} + +const char *arts_error_text(int errorcode) +{ + switch(errorcode) { + case 0: + return "success"; + case ARTS_E_NOSERVER: + return "can't connect to aRts soundserver"; + case ARTS_E_NOBACKEND: + return "loading the aRts backend \"" + ARTSC_BACKEND "\" failed"; + case ARTS_E_NOIMPL: + return "this aRts function is not yet implemented"; + case ARTS_E_NOINIT: + return "need to use arts_init() before using other functions"; + case ARTS_E_NOSTREAM: + return "you passed no valid aRts stream to a function"; + } + return "unknown arts error happened"; +} diff --git a/artsc/artsc.h b/artsc/artsc.h new file mode 100644 index 0000000..5cdf8f1 --- /dev/null +++ b/artsc/artsc.h @@ -0,0 +1,246 @@ + /* + + 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., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ + +#ifndef ARTSC_H +#define ARTSC_H + +#include "artsc_export.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @libdoc aRts plain C API + * + * The aRts plain C API aims at easily writing/porting plain C apps to the + * arts sound server. What is provided is streaming functionality, in a + * blocking way. So for most apps, you simply remove the few system calls + * that deal with your audio device, and replace them with the appropriate + * arts calls. + */ + +/** + * the type of streams (simply treat these as black boxes) + */ +typedef void *arts_stream_t; + +/* error codes */ + +#define ARTS_E_NOSERVER ( -1 ) +#define ARTS_E_NOBACKEND ( -2 ) +#define ARTS_E_NOSTREAM ( -3 ) +#define ARTS_E_NOINIT ( -4 ) +#define ARTS_E_NOIMPL ( -5 ) + +/** + * the values for stream parameters + * + * @see arts_parameter_t + */ +enum arts_parameter_t_enum { + ARTS_P_BUFFER_SIZE = 1, + ARTS_P_BUFFER_TIME = 2, + ARTS_P_BUFFER_SPACE = 3, + ARTS_P_SERVER_LATENCY = 4, + ARTS_P_TOTAL_LATENCY = 5, + ARTS_P_BLOCKING = 6, + ARTS_P_PACKET_SIZE = 7, + ARTS_P_PACKET_COUNT = 8, + ARTS_P_PACKET_SETTINGS = 9 +}; + +/** + * parameters for streams + * + * @li ARTS_P_BUFFER_SIZE (rw) + * The size of the internal buffers used for streaming to the server - this + * directly affects the latency that will occur. If you never set it + * explicitly, this value defaults to at least 65536 (64kb). Generally, + * it is important to know that the server itself gives some constraints + * which makes buffer sizes below a certain value impossible. So setting + * this parameter will always result in either getting what you wanted, + * or a larger streaming buffer due to server limitations. + * + * @li ARTS_P_BUFFER_TIME (rw) + * The time the buffer used for streaming to the server takes to play in + * milliseconds. This is just a more human readable method to set the buffer + * size, so setting ARTS_P_BUFFER_SIZE affects this parameter and the other + * way round. As aRts chooses reasonable buffer sizes for streaming (rather + * 3*16kb than 40234 bytes), setting this parameter will often end up with + * a slightly larger value than you requested. + * + * @li ARTS_P_BUFFER_SPACE (r) + * The amount of bytes that can be read/written without blocking (depending + * whether this is a record or play stream). As requesting this parameter + * does a few system calls (but no remote invocation) to verify that it is + * up-to-date, don't overuse it. + * + * @li ARTS_P_SERVER_LATENCY (r) + * The amount of latency the server creates (due to hardware buffering) + * in milliseconds. + * + * @li ARTS_P_TOTAL_LATENCY (r) + * The overall latency in milliseconds it takes (at most), from the time + * when you write a byte into a stream, until it gets played on the + * soundcard. This is simply a shortcut to the sum of ARTS_P_BUFFER_TIME + * and ARTS_P_SERVER_LATENCY. + * + * @li ARTS_P_BLOCKING (rw) + * If this parameter is 1 (the default), arts_read/arts_write will block + * when not all data can be read/written successfully, and wait until it + * works. If this parameter is 0, arts_read/arts_write will return + * the number of successfully read/written bytes immediately. + * + * @li ARTS_P_PACKET_SIZE (r) + * This returns the size of the packets used for buffering. The optimal + * size for arts_stream_write is always writing one packet. The buffering of + * streams works with audio packets. So the ARTS_P_BUFFER_SIZE parameter of + * streams (which specifies how many bytes of a stream are prebuffered), + * really consists of (ARTS_P_PACKET_SIZE) * (ARTS_P_PACKET_COUNT). + * + * @li ARTS_P_PACKET_COUNT (r) + * This returns the number of the packets are used for buffering. See + * ARTS_P_PACKET_SIZE for more. + * + * @li ARTS_P_PACKET_SETTINGS (rw) + * This is a way to configure packet size & packet count at the same time. + * The format is 0xCCCCSSSS, where 2^SSSS is the packet size, and CCCC is + * the packet count. Note that when writing this, you don't necessarily + * get the settings you requested. + */ +typedef enum arts_parameter_t_enum arts_parameter_t; + +/** + * initializes the aRts C API, and connects to the sound server + * + * @return 0 if everything is all right, an error code otherwise + */ + +ARTSC_EXPORT int arts_init(void); + +/** + * disconnects from the sound server and frees the aRts C API internals + */ +ARTSC_EXPORT void arts_free(void); + +/** + * asks aRtsd to free the DSP device and return 1 if it was successful, + * 0 if there were active non-suspendable modules + */ +ARTSC_EXPORT int arts_suspend(void); + +/** + * asks aRtsd if the DSP device is free and return 1 if it is, + * 0 if not + */ +ARTSC_EXPORT int arts_suspended(void); + + +/** + * converts an error code to a human readable error message + * + * @param errorcode the errorcode (from another arts function that failed) + * @returns a text string with the error message + */ +ARTSC_EXPORT const char *arts_error_text(int errorcode); + +/** + * open a stream for playing + * + * @param rate the sampling rate (something like 44100) + * @param bits how many bits each sample has (8 or 16) + * @param channels how many channels, 1 is mono, 2 is stereo + * @param name the name of the stream (these will be used so that the user can + * assign streams to effects/mixer channels and similar) + * + * @return a stream + */ +ARTSC_EXPORT arts_stream_t arts_play_stream(int rate, int bits, int channels, const char *name); + +/** + * open a stream for recording + * + * @param rate the sampling rate (something like 44100) + * @param bits how many bits each sample has (8 or 16) + * @param channels how many channels, 1 is mono, 2 is stereo + * @param name the name of the stream (these will be used so that the user can + * assign streams to effects/mixer channels and similar) + * + * @return a stream + */ +ARTSC_EXPORT arts_stream_t arts_record_stream(int rate, int bits, int channels, const char *name); + +/** + * close a stream + */ +ARTSC_EXPORT void arts_close_stream(arts_stream_t stream); + +/** + * read samples from stream + * + * @param stream a previously opened record stream + * @param buffer a buffer with sample data + * @param count the number of bytes contained in the buffer + * + * @returns number of read bytes on success or error code + */ +ARTSC_EXPORT int arts_read(arts_stream_t stream, void *buffer, int count); + +/** + * write samples to to stream + * + * @param stream a previously opened play stream + * @param buffer a buffer with sample data + * @param count the number of bytes contained in the buffer + * + * @returns number of written bytes on success or error code + */ +ARTSC_EXPORT int arts_write(arts_stream_t stream, const void *buffer, int count); + +/** + * configure a parameter of a stream + * + * @param stream an opened record or play stream + * @param parameter the parameter you want to modify + * @param value the new value + * + * @returns the new value of the parameter (which may or may not be the value + * you wanted to have), or an error code if something went wrong + */ +ARTSC_EXPORT int arts_stream_set(arts_stream_t stream, arts_parameter_t param, int value); + +/** + * query a parameter of a stream + * + * @param stream an opened record or play stream + * @param parameter the parameter you want to query + * + * @returns the value of the parameter, or an error code + */ +ARTSC_EXPORT int arts_stream_get(arts_stream_t stream, arts_parameter_t param); + +#ifdef __cplusplus +} +#endif + +#endif /* ARTSC_H */ diff --git a/artsc/artsc_export.h.in b/artsc/artsc_export.h.in new file mode 100644 index 0000000..85fd6ef --- /dev/null +++ b/artsc/artsc_export.h.in @@ -0,0 +1,52 @@ +/* This file is part of the KDE libraries + Copyright (c) 2002-2003 KDE Team + + 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. +*/ + +#ifndef ARTSC_EXPORT_H +#define ARTSC_EXPORT_H + +#undef __KDE_HAVE_GCC_VISIBILITY +/** + * The ARTS_NO_EXPORT macro marks the symbol of the given variable + * to be hidden. A hidden symbol is stripped during the linking step, + * so it can't be used from outside the resulting library, which is similar + * to static. However, static limits the visibility to the current + * compilation unit. hidden symbols can still be used in multiple compilation + * units. + * + * \code + * int ARTSC_NO_EXPORT foo; + * int ARTSC_EXPORT bar; + * \end + */ + +#if defined(__KDE_HAVE_GCC_VISIBILITY) +/* Visibility is available for GCC newer than 3.4. + * See: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=9283 + */ +#define ARTSC_NO_EXPORT __attribute__ ((visibility("hidden"))) +#define ARTSC_EXPORT __attribute__ ((visibility("default"))) +#elif defined(_WIN32) +#define ARTSC_NO_EXPORT +#define ARTSC_EXPORT __declspec(dllexport) +#else +#define ARTSC_NO_EXPORT +#define ARTSC_EXPORT +#endif + +#endif /* ARTSC_EXPORTS */ diff --git a/artsc/artscbackend.cc b/artsc/artscbackend.cc new file mode 100644 index 0000000..a9f3a91 --- /dev/null +++ b/artsc/artscbackend.cc @@ -0,0 +1,805 @@ + /* + + Copyright (C) 2000 Stefan Westerfeld + stefan@space.twc.de + 2001 Matthias Kretz + kretz@kde.org + + 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. + + */ + +#include "artsc.h" +#include "soundserver.h" +#include "stdsynthmodule.h" + +#include <iostream> +#include <algorithm> +#include <queue> + +#include <stdio.h> +#include <cstring> +#include <unistd.h> +#include <fcntl.h> +#include <math.h> +#include <assert.h> +#include "arts_export.h" + +#define arts_backend_debug(x) ; + +using namespace std; +using namespace Arts; + +/** + * Base class for streams + */ +class Stream +{ +protected: + SoundServer server; + float serverBufferTime; + + bool _finished, isAttached; + int _samplingRate, _bits, _channels, pos; + string _name; + queue< DataPacket<mcopbyte>* > streamqueue; + + int packetCount, packetCapacity; + int blockingIO; + + /** + * returns the amount of bytes that will be played in a given amount of + * time in milliseconds + */ + int timeToBytes(float time) + { + float playSpeed = _channels * _samplingRate * _bits / 8; + return (int)(playSpeed * (time / 1000.0)); + } + + /** + * returns the time in milliseconds it takes with the current parameters + * to play a given amount of bytes + */ + float bytesToTime(int size) + { + float playSpeed = _channels * _samplingRate * _bits / 8; + return (1000.0 * ((float)size) / playSpeed); + } + + int bufferSize() { + return packetCount * packetCapacity; + } + + float bufferTime() { + return bytesToTime(bufferSize()); + } + + int bufferSpace() { + int space = 0; + + attach(); + + /* make sure that our information is up-to-date */ + Dispatcher::the()->ioManager()->processOneEvent(false); + + if(!streamqueue.empty()) + { + space += packetCapacity - pos; /* the first, half filled packet */ + + if(streamqueue.size() > 1) /* and the other, empty packets */ + space += (streamqueue.size()-1)*packetCapacity; + } + return space; + } + + int setBufferSize(int size) + { + /* don't change sizes when already streaming */ + if(isAttached) + return ARTS_E_NOIMPL; + + /* + * these parameters are usually a bad idea ;-) however we have to start + * somewhere, and maybe in two years, with a highly optimized kernel + * this is possible - for now, don't request the impossible or don't + * complain if it doesn't work + */ + packetCount = 3; + packetCapacity = 128; + + /* + * - do not configure stream buffers smaller than the server + * recommended value + * - try to get more or less close to the value the application + * wants + */ + int needSize = max(size, timeToBytes(server.minStreamBufferTime())); + + while(bufferSize() < needSize) + { + packetCount++; + if(packetCount == 8) + { + packetCount /= 2; + packetCapacity *= 2; + } + } + + return bufferSize(); + } + + int packetSettings() + { + int settings = 0; + + int cap = packetCapacity; + while(cap > 1) + { + settings++; + cap /= 2; + } + + settings |= packetCount << 16; + return settings; + } + + int setPacketSettings(int settings) + { + /* don't change sizes when already streaming */ + if(isAttached) + return ARTS_E_NOIMPL; + + packetCount = settings >> 16; + + packetCapacity = 1; + int c = settings & 0xffff; + while(c > 0) { + packetCapacity *= 2; + c--; + } + + /* + * - do not configure stream buffers smaller than the server + * recommended value + * - keep the packetSize the applications specified + */ + int needSize = timeToBytes(server.minStreamBufferTime()); + + while(bufferSize() < needSize) + packetCount++; + + return packetSettings(); + } + + /** + * the stream has to attach itself + */ + virtual void attach() = 0; + +public: + Stream(SoundServer server, int rate, int bits, int channels, + string name) : server(server), _finished(false), isAttached(false), + _samplingRate(rate), _bits(bits), _channels(channels), pos(0), + _name(name) + { + serverBufferTime = server.serverBufferTime(); + stream_set(ARTS_P_BUFFER_SIZE,64*1024); + stream_set(ARTS_P_BLOCKING,1); + } + virtual ~Stream() + { + // + } + + virtual int stream_set(arts_parameter_t param, int value) + { + int result; + + switch(param) { + case ARTS_P_BUFFER_SIZE: + return setBufferSize(value); + + case ARTS_P_BUFFER_TIME: + result = setBufferSize(timeToBytes(value)); + if(result < 0) return result; + return (int)bufferTime(); + + case ARTS_P_PACKET_SETTINGS: + return setPacketSettings(value); + + case ARTS_P_BLOCKING: + if(value != 0 && value != 1) return ARTS_E_NOIMPL; + + blockingIO = value; + return blockingIO; + /* + * maybe ARTS_P_TOTAL_LATENCY _could_ be made writeable, the + * others are of course useless + */ + case ARTS_P_BUFFER_SPACE: + case ARTS_P_SERVER_LATENCY: + case ARTS_P_TOTAL_LATENCY: + case ARTS_P_PACKET_SIZE: + case ARTS_P_PACKET_COUNT: + return ARTS_E_NOIMPL; + } + return ARTS_E_NOIMPL; + } + + virtual int stream_get(arts_parameter_t param) + { + switch(param) { + case ARTS_P_BUFFER_SIZE: + return bufferSize(); + + case ARTS_P_BUFFER_TIME: + return (int)bufferTime(); + + case ARTS_P_BUFFER_SPACE: + return bufferSpace(); + + case ARTS_P_PACKET_SETTINGS: + return packetSettings(); + + case ARTS_P_SERVER_LATENCY: + return (int)serverBufferTime; + + case ARTS_P_TOTAL_LATENCY: + return stream_get(ARTS_P_SERVER_LATENCY) + + stream_get(ARTS_P_BUFFER_TIME); + + case ARTS_P_BLOCKING: + return blockingIO; + + case ARTS_P_PACKET_SIZE: + return packetCapacity; + + case ARTS_P_PACKET_COUNT: + return packetCount; + } + return ARTS_E_NOIMPL; + } + + virtual int write(const mcopbyte * /*data*/, int /*size*/) + { + return ARTS_E_NOIMPL; + } + + virtual int read(mcopbyte * /*data*/, int /*size*/) + { + return ARTS_E_NOIMPL; + } + + virtual void close() = 0; + + int suspend() + { + if(isAttached) + { + return server.suspend(); + } + return 0; + } + + int suspended() + { + if(isAttached) + { + return 0; + } + return server.suspended(); + } +}; + +class Receiver : public ByteSoundReceiver_skel, + public StdSynthModule, + virtual public Stream +{ + /* + * FIXME: bsWrapper is a more or less ugly trick to be able to use + * this object although not using smartwrappers to access it + */ + ByteSoundReceiver bsWrapper; + +protected: + virtual void attach() + { + if(!isAttached) + { + isAttached = true; + + server.attachRecorder(bsWrapper); + start(); + + /* + * TODO: this processOneEvent looks a bit strange here... it is + * there since StdIOManager does block 5 seconds on the first + * arts_write if it isn't - although notifications are pending + * + * Probably the real solution is to rewrite the + * StdIOManager::processOneEvent function. (And maybe drop the + * assumption that aRts will not block when an infinite amount + * of notifications is pending - I mean: will it ever happen?) + */ + Dispatcher::the()->ioManager()->processOneEvent(false); + } + } + +public: + Receiver(SoundServer server, int rate, int bits, int channels, + string name) : Stream( server, rate, bits, channels, name) + { + bsWrapper = ByteSoundReceiver::_from_base(this); + } + + virtual ~Receiver() { + // + } + + long samplingRate() { return _samplingRate; } + long channels() { return _channels; } + long bits() { return _bits; } + bool finished() { return _finished; } + string title() { return _name; } + + void process_indata(DataPacket<mcopbyte> *packet) + { + streamqueue.push(packet); + } + + void close() + { + if(isAttached) + { + /* remove all packets from the streamqueue */ + while(!streamqueue.empty()) + { + DataPacket<mcopbyte> *packet = streamqueue.front(); + packet->processed(); + streamqueue.pop(); + } + + server.detachRecorder(bsWrapper); + } + // similar effect like "delete this;" + bsWrapper = ByteSoundReceiver::null(); + } + + int read(mcopbyte *data, int size) + { + attach(); + + int remaining = size; + while(remaining) + { + if(blockingIO) + { + /* C API blocking style read */ + while(streamqueue.empty()) + Dispatcher::the()->ioManager()->processOneEvent(true); + } + else + { + /* non blocking I/O */ + if(streamqueue.empty()) + Dispatcher::the()->ioManager()->processOneEvent(false); + + /* still no more packets to read? */ + if(streamqueue.empty()) + return size - remaining; + } + + /* get a packet */ + DataPacket<mcopbyte> *packet = streamqueue.front(); + + /* copy some data from there */ + int tocopy = min(remaining,packet->size-pos); + memcpy(data,&packet->contents[pos],tocopy); + pos += tocopy; + data += tocopy; + remaining -= tocopy; + + /* have we read the whole packet? then get rid of it */ + if(pos == packet->size) + { + packet->processed(); + streamqueue.pop(); + pos = 0; + } + } + + /* no possible error conditions */ + return size; + } +}; + +class Sender : public ByteSoundProducerV2_skel, + public StdSynthModule, + virtual public Stream +{ + /* + * FIXME: bsWrapper is a more or less ugly trick to be able to use + * this object although not using smartwrappers to access it + */ + ByteSoundProducerV2 bsWrapper; + +protected: + virtual void attach() + { + if(!isAttached) + { + isAttached = true; + + server.attach(bsWrapper); + start(); + + /* + * TODO: this processOneEvent looks a bit strange here... it is + * there since StdIOManager does block 5 seconds on the first + * arts_write if it isn't - although notifications are pending + * + * Probably the real solution is to rewrite the + * StdIOManager::processOneEvent function. (And maybe drop the + * assumption that aRts will not block when an infinite amount + * of notifications is pending - I mean: will it ever happen?) + */ + Dispatcher::the()->ioManager()->processOneEvent(false); + } + } + +public: + Sender(SoundServer server, int rate, int bits, int channels, + string name) : Stream( server, rate, bits, channels, name) + { + bsWrapper = ByteSoundProducerV2::_from_base(this); + } + + virtual ~Sender() { + // + } + + long samplingRate() { return _samplingRate; } + long channels() { return _channels; } + long bits() { return _bits; } + bool finished() { return _finished; } + string title() { return _name; } + + void streamStart() + { + /* + * start streaming + */ + outdata.setPull(packetCount, packetCapacity); + } + + void request_outdata(DataPacket<mcopbyte> *packet) + { + streamqueue.push(packet); + } + + void close() + { + if(isAttached) + { + if(pos != 0) + { + /* send the last half-filled packet */ + DataPacket<mcopbyte> *packet = streamqueue.front(); + packet->size = pos; + packet->send(); + streamqueue.pop(); + } + outdata.endPull(); + + /* remove all packets from the streamqueue */ + while(!streamqueue.empty()) + { + DataPacket<mcopbyte> *packet = streamqueue.front(); + packet->size = 0; + packet->send(); + streamqueue.pop(); + } + + server.detach(bsWrapper); + } + // similar effect like "delete this;" + Arts::ByteSoundProducerV2_base* x = _copy(); + bsWrapper = ByteSoundProducerV2::null(); + x->_release(); + } + + int write(const mcopbyte *data, int size) + { + attach(); + + int remaining = size; + while(remaining) + { + if(blockingIO) + { + /* C API blocking style write */ + /* we're not waiting for any data here, but rather for + * DataPackets that we can fill */ + while(streamqueue.empty()) + Dispatcher::the()->ioManager()->processOneEvent(true); + } + else + { + /* non blocking I/O */ + if(streamqueue.empty()) + Dispatcher::the()->ioManager()->processOneEvent(false); + + /* still no more space to write? */ + if(streamqueue.empty()) + return size - remaining; + } + + /* get a packet */ + DataPacket<mcopbyte> *packet = streamqueue.front(); + + /* copy some data there */ + int tocopy = min(remaining,packetCapacity-pos); + memcpy(&packet->contents[pos],data,tocopy); + pos += tocopy; + data += tocopy; + remaining -= tocopy; + + /* have we filled up the packet? then send it */ + if(pos == packetCapacity) + { + packet->size = packetCapacity; + packet->send(); + streamqueue.pop(); + pos = 0; + } + } + + /* no possible error conditions */ + return size; + } +}; + +class ArtsCApi { +protected: + static ArtsCApi *instance; + int refcnt; + + Dispatcher dispatcher; + SoundServer server; + + ArtsCApi() : refcnt(1), server(Reference("global:Arts_SoundServer")) + { + // + } + +public: +// C Api commands + int init() { + if(server.isNull()) + return ARTS_E_NOSERVER; + + return 0; + } + + int suspend() { + if(!server.isNull()) + return server.suspend()? 1:0; + return ARTS_E_NOSERVER; + } + + int suspended() { + if(!server.isNull()) + return server.suspended()? 1:0; + return ARTS_E_NOSERVER; + } + + void free() { + // nothing to do + } + + arts_stream_t play_stream(int rate, int bits, int channels, const char *name) + { + if(server.isNull()) + return 0; + + return (arts_stream_t)static_cast<Stream *>(new Sender(server,rate,bits,channels,name)); + } + + arts_stream_t record_stream(int rate, int bits, int channels, const char *name) + { + if(server.isNull()) + return 0; + + return (arts_stream_t)static_cast<Stream *>(new Receiver(server,rate,bits,channels,name)); + } + + void close_stream(arts_stream_t stream) + { + if(server.isNull()) + return; + + if(!stream) + return; + + static_cast<Stream *>(stream)->close(); + } + + int write(arts_stream_t stream, const void *data, int size) + { + if(server.isNull()) + return ARTS_E_NOSERVER; + + if(!stream) + return ARTS_E_NOSTREAM; + + return static_cast<Stream *>(stream)->write((const mcopbyte *)data,size); + } + + int read(arts_stream_t stream, void *data, int size) + { + if(server.isNull()) + return ARTS_E_NOSERVER; + + if(!stream) + return ARTS_E_NOSTREAM; + + return static_cast<Stream *>(stream)->read((mcopbyte *)data,size); + } + + int stream_set(arts_stream_t stream, arts_parameter_t param, int value) + { + if(server.isNull()) + return ARTS_E_NOSERVER; + + if(!stream) + return ARTS_E_NOSTREAM; + + return static_cast<Stream *>(stream)->stream_set(param,value); + } + + int stream_get(arts_stream_t stream, arts_parameter_t param) + { + if(server.isNull()) + return ARTS_E_NOSERVER; + + if(!stream) + return ARTS_E_NOSTREAM; + + return static_cast<Stream *>(stream)->stream_get(param); + } + +// allocation and freeing of the class + static ArtsCApi *the() { + return instance; + } + static void ref() { + if(!instance) + instance = new ArtsCApi(); + else + instance->refcnt++; + } + static void release() { + assert(instance); + assert(instance->refcnt > 0); + instance->refcnt--; + if(instance->refcnt == 0) + { + delete instance; + instance = 0; + } + } +}; + +//----------------------------- static members ------------------------------- + +ArtsCApi *ArtsCApi::instance = 0; + +//------------------ wrappers from C to C++ class ---------------------------- + +extern "C" ARTSC_EXPORT int arts_backend_init() +{ + arts_backend_debug("arts_backend_init"); + ArtsCApi::ref(); + + // if init fails, don't expect free, and don't expect that the user + // continues using other API functions + int rc = ArtsCApi::the()->init(); + if(rc < 0) ArtsCApi::release(); + return rc; +} + +extern "C" ARTSC_EXPORT int arts_backend_suspend() +{ + if(!ArtsCApi::the()) return ARTS_E_NOINIT; + arts_backend_debug("arts_backend_suspend"); + return ArtsCApi::the()->suspend(); +} + +extern "C" ARTSC_EXPORT int arts_backend_suspended() +{ + if(!ArtsCApi::the()) return ARTS_E_NOINIT; + arts_backend_debug("arts_backend_suspended"); + return ArtsCApi::the()->suspended(); +} + +extern "C" ARTSC_EXPORT void arts_backend_free() +{ + if(!ArtsCApi::the()) return; + + arts_backend_debug("arts_backend_free"); + ArtsCApi::the()->free(); + ArtsCApi::release(); +} + +extern "C" ARTSC_EXPORT arts_stream_t arts_backend_play_stream(int rate, int bits, int channels, const char *name) +{ + if(!ArtsCApi::the()) return 0; + + arts_backend_debug("arts_backend_play_stream"); + return ArtsCApi::the()->play_stream(rate,bits,channels,name); +} + +extern "C" ARTSC_EXPORT arts_stream_t arts_backend_record_stream(int rate, int bits, int channels, const char *name) +{ + if(!ArtsCApi::the()) return 0; + + arts_backend_debug("arts_backend_record_stream"); + return ArtsCApi::the()->record_stream(rate,bits,channels,name); +} + +extern "C" ARTSC_EXPORT void arts_backend_close_stream(arts_stream_t stream) +{ + if(!ArtsCApi::the()) return; + + arts_backend_debug("arts_backend_close_stream"); + ArtsCApi::the()->close_stream(stream); +} + +extern "C" ARTSC_EXPORT int arts_backend_read(arts_stream_t stream, void *buffer, int count) +{ + if(!ArtsCApi::the()) return ARTS_E_NOINIT; + + arts_backend_debug("arts_backend_read"); + return ArtsCApi::the()->read(stream,buffer,count); +} + +extern "C" ARTSC_EXPORT int arts_backend_write(arts_stream_t stream, const void *buffer, + int count) +{ + if(!ArtsCApi::the()) return ARTS_E_NOINIT; + + arts_backend_debug("arts_backend_write"); + return ArtsCApi::the()->write(stream,buffer,count); +} + +extern "C" ARTSC_EXPORT int arts_backend_stream_set(arts_stream_t stream, + arts_parameter_t param, int value) +{ + if(!ArtsCApi::the()) return ARTS_E_NOINIT; + + arts_backend_debug("arts_stream_set"); + return ArtsCApi::the()->stream_set(stream,param,value); +} + +extern "C" ARTSC_EXPORT int arts_backend_stream_get(arts_stream_t stream, + arts_parameter_t param) +{ + if(!ArtsCApi::the()) return ARTS_E_NOINIT; + + arts_backend_debug("arts_stream_get"); + return ArtsCApi::the()->stream_get(stream,param); +} diff --git a/artsc/artsdsp.c b/artsc/artsdsp.c new file mode 100644 index 0000000..368b15f --- /dev/null +++ b/artsc/artsdsp.c @@ -0,0 +1,675 @@ +/* Evil evil evil hack to get OSS apps to cooperate with artsd + * This is based on the original esddsp, which esd uses to do the same. + * + * Copyright (C) 1998 Manish Singh <yosh@gimp.org> + * Copyright (C) 2000 Stefan Westerfeld <stefan@space.twc.de> (aRts port) + * + * 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; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#define _GNU_SOURCE 1 + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef HAVE_SYS_SOUNDCARD_H +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <stdarg.h> +#include <sys/types.h> +#include <sys/soundcard.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +/* #include <sys/mman.h> */ +#include <fcntl.h> + +#include <artsc.h> +#include <dlfcn.h> + +#include "arts_export.h" +/* + * NOTE: + * + * To truly support non-blocking I/O, there is some stuff missing. Namely, + * select should be trapped and redirected, as well as the means for making + * a stream non-blocking and so on. Maybe poll, too. + * + * Currently, only apps that are doing blocking I/O will probably work right. + */ + +/** + * the stream status: sndfd is -1 when unused, otherwise it is a useless fd + * which points to /dev/null, to ensure compatibility with more weird + * operations on streams + * + * settings contains what has already been set (speed, bits, channels), and + * is 7 when all of these are true + * + * stream contains an aRts stream or 0 + */ +static int sndfd = -1; +static int settings; +static int arts_init_done = 0; +static arts_stream_t stream = 0; +static arts_stream_t record_stream = 0; +static int bits = 0; +static int speed = 0; +static int channels = 0; +static int frags; + +#if defined(HAVE_IOCTL_INT_INT_DOTS) +typedef int ioctl_request_t; +#elif defined(HAVE_IOCTL_INT_ULONG_DOTS) +typedef unsigned long ioctl_request_t; +#elif defined(HAVE_IOCTL_INT_ULONGINT_DOTS) +typedef unsigned long int ioctl_request_t; +#else +#error "unknown ioctl type (check config.h, adapt configure test)..." +#endif + +/* + * memory mapping emulation + */ +static int mmapemu = 0; +static count_info mmapemu_ocount; +static char *mmapemu_obuffer = 0; +static int mmapemu_osize = 0; + +/* + * original C library functions + */ +typedef int (*orig_open_ptr)(const char *pathname, int flags, ...); +typedef int (*orig_close_ptr)(int fd); +typedef int (*orig_ioctl_ptr)(int fd, ioctl_request_t request, ...); +typedef ssize_t (*orig_write_ptr)(int fd, const void *buf, size_t count); +typedef ssize_t (*orig_read_ptr)(int fd, void *buf, size_t count); +typedef void* (*orig_mmap_ptr)(void *start, size_t length, int prot, + int flags, int fd, off_t offset); +typedef int (*orig_munmap_ptr)(void *start, size_t length); +typedef FILE* (*orig_fopen_ptr)(const char *path, const char *mode); +typedef int (*orig_access_ptr)(const char *pathname, int mode); + +static orig_open_ptr orig_open; +static orig_close_ptr orig_close; +static orig_ioctl_ptr orig_ioctl; +static orig_write_ptr orig_write; +static orig_read_ptr orig_read; +static orig_mmap_ptr orig_mmap; +static orig_munmap_ptr orig_munmap; +static orig_fopen_ptr orig_fopen; +static orig_access_ptr orig_access; + +static int artsdsp_debug = 0; +static int artsdsp_init = 0; + +void *mmap(void *start, size_t length, int prot, int flags, + int fd, off_t offset); +int munmap(void *start, size_t length); +#define CHECK_INIT() if(!artsdsp_init) artsdsp_doinit(); + +/* + * Initialization - maybe this should be either be a startup only called + * routine, or use pthread locks to prevent strange effects in multithreaded + * use (however it seems highly unlikely that an application would create + * multiple threads before even using one of redirected the system functions + * once). + */ + +static void artsdsp_doinit() +{ + const char *env; + artsdsp_init = 1; + + /* debugging? */ + env = getenv("ARTSDSP_VERBOSE"); + artsdsp_debug = env && !strcmp(env,"1"); + + /* mmap emulation? */ + env = getenv("ARTSDSP_MMAP"); + mmapemu = env && !strcmp(env,"1"); + + /* resolve original symbols */ + orig_open = (orig_open_ptr)dlsym(RTLD_NEXT,"open"); + orig_close = (orig_close_ptr)dlsym(RTLD_NEXT,"close"); + orig_write = (orig_write_ptr)dlsym(RTLD_NEXT,"write"); + orig_read = (orig_read_ptr)dlsym(RTLD_NEXT,"read"); + orig_ioctl = (orig_ioctl_ptr)dlsym(RTLD_NEXT,"ioctl"); + orig_mmap = (orig_mmap_ptr)dlsym(RTLD_NEXT,"mmap"); + orig_munmap = (orig_munmap_ptr)dlsym(RTLD_NEXT,"munmap"); + orig_fopen = (orig_fopen_ptr)dlsym(RTLD_NEXT,"fopen"); + orig_access = (orig_access_ptr)dlsym(RTLD_NEXT,"access"); +} + +static void +#ifdef __GNUC__ + __attribute__( ( format ( printf, 1, 2 ) ) ) +#endif +artsdspdebug(const char *fmt,...) +{ + CHECK_INIT(); + + if(artsdsp_debug) + { + va_list ap; + va_start(ap, fmt); + (void) vfprintf(stderr, fmt, ap); + va_end(ap); + } +} + +static void mmapemu_flush() +{ + int space; + + if (mmapemu_osize == 0) { + return; + } + + space = arts_stream_get(stream, ARTS_P_BUFFER_SPACE); + artsdspdebug("space = %d\n",space); + while(space >= 4096) + { + arts_write(stream,&mmapemu_obuffer[mmapemu_ocount.ptr],4096); + space -= 4096; + mmapemu_ocount.ptr += 4096; + mmapemu_ocount.ptr %= mmapemu_osize; + mmapemu_ocount.blocks++; + mmapemu_ocount.bytes += 4096; + } +} + +/* returns 1 if the filename points to a sound device */ +static int is_sound_device(const char *pathname) +{ + if(!pathname) return 0; + if(strcmp(pathname,"/dev/dsp") == 0) return 1; + if(strcmp(pathname,"/dev/sound/dsp") == 0) return 1; + return 0; +} + +int open (const char *pathname, int flags, ...) +{ + va_list args; + mode_t mode = 0; + + CHECK_INIT(); + + /* + * After the documentation, va_arg is not safe if there is no argument to + * get "random errors will occur", so only get it in case O_CREAT is set, + * and hope that passing 0 to the orig_open function in all other cases + * will work. + */ + va_start(args,flags); + if(flags & O_CREAT) { + /* The compiler will select one of these at compile-tyime if -O is used. + * Otherwise, it may be deferred until runtime. + */ + if (sizeof(int) >= sizeof(mode_t)) { + mode = va_arg(args, int); + } else { + mode = va_arg(args, mode_t); + } + } + va_end(args); + + if (!is_sound_device(pathname)) /* original open for anything but sound */ + return orig_open (pathname, flags, mode); + + settings = 0; + frags = 0; + stream = 0; + record_stream = 0; + + artsdspdebug ("aRts: hijacking /dev/dsp open...\n"); + + sndfd = orig_open("/dev/null",flags,mode); + if(sndfd >= 0) + { + if(!arts_init_done) { + int rc = arts_init(); + if(rc < 0) { + artsdspdebug("error on aRts init: %s\n", arts_error_text(rc)); + orig_close(sndfd); + sndfd = -1; + return orig_open (pathname, flags, mode); + } + else arts_init_done = 1; + } + } + + /* success */ + return sndfd; +} + +int ioctl (int fd, ioctl_request_t request, ...) +{ + int space, size, latency, odelay; + + /* + * FreeBSD needs ioctl with varargs. However I have no idea how to "forward" + * the variable args ioctl to the orig_ioctl routine. So I expect the ioctl + * to have exactly one pointer-like parameter and forward that, hoping that + * it works + */ + va_list args; + void *argp; + va_start(args,request); + argp = va_arg(args, void *); + va_end(args); + + CHECK_INIT(); + + if (fd != sndfd ) + return orig_ioctl (fd, request, argp); + else if (sndfd != -1) + { + int *arg = (int *) argp; + artsdspdebug("aRts: hijacking /dev/dsp ioctl (%d : %x - %p)\n", + ( int ) fd, ( int ) request, ( void* ) argp); + + switch (request) + { + struct audio_buf_info *audiop; +#ifdef SNDCTL_DSP_RESET + case SNDCTL_DSP_RESET: /* _SIO ('P', 0) */ + artsdspdebug("aRts: SNDCTL_DSP_RESET unsupported\n"); + break; +#endif + +#ifdef SNDCTL_DSP_SYNC + case SNDCTL_DSP_SYNC: /* _SIO ('P', 1) */ + artsdspdebug("aRts: SNDCTL_DSP_SYNC unsupported\n"); + break; +#endif + +#ifdef SNDCTL_DSP_SPEED + case SNDCTL_DSP_SPEED: /* _SIOWR('P', 2, int) */ + speed = *arg; + settings |= 2; + break; +#endif + +#ifdef SNDCTL_DSP_STEREO + case SNDCTL_DSP_STEREO: /* _SIOWR('P', 3, int) */ + channels = (*arg)?2:1; + settings |= 4; + break; +#endif + +#ifdef SNDCTL_DSP_GETBLKSIZE + case SNDCTL_DSP_GETBLKSIZE: /* _SIOWR('P', 4, int) */ + if(mmapemu) + *arg = 4096; + else if(stream) + *arg = arts_stream_get(stream,ARTS_P_PACKET_SIZE); + else + { + int fragSize = frags & 0x7fff; + if(fragSize > 1 && fragSize < 16) + *arg = (1 << fragSize); + else + *arg = 16384; /* no idea ;-) */ + } + break; +#endif + +#ifdef SNDCTL_DSP_SETFMT + case SNDCTL_DSP_SETFMT: /* _SIOWR('P',5, int) */ + bits = (*arg & 0x30) ? 16 : 8; + settings |= 1; + break; +#endif + +#ifdef SNDCTL_DSP_CHANNELS + case SNDCTL_DSP_CHANNELS: /* _SIOWR('P', 6, int) */ + channels = (*arg); + settings |= 4; + break; +#endif + +#ifdef SOUND_PCM_WRITE_FILTER + case SOUND_PCM_WRITE_FILTER: /* _SIOWR('P', 7, int) */ + artsdspdebug("aRts: SNDCTL_DSP_WRITE_FILTER(%d) unsupported\n",*arg); + break; +#endif + +#ifdef SNDCTL_DSP_POST + case SNDCTL_DSP_POST: /* _SIO ('P', 8) */ + artsdspdebug("aRts: SNDCTL_DSP_POST unsupported\n"); + break; +#endif + +#ifdef SNDCTL_DSP_SUBDIVIDE + case SNDCTL_DSP_SUBDIVIDE: /* _SIOWR('P', 9, int) */ + artsdspdebug("aRts: SNDCTL_DSP_SUBDIVIDE(%d) unsupported\n",*arg); + break; +#endif + +#ifdef SNDCTL_DSP_SETFRAGMENT + case SNDCTL_DSP_SETFRAGMENT: /* _SIOWR('P',10, int) */ + artsdspdebug("aRts: SNDCTL_DSP_SETFRAGMENT(%x) partially supported\n", + *arg); + frags = *arg; + break; +#endif + +#ifdef SNDCTL_DSP_GETFMTS + case SNDCTL_DSP_GETFMTS: /* _SIOR ('P',11, int) */ + *arg = 8 | 16; + break; +#endif + +#if defined(SNDCTL_DSP_GETOSPACE) && defined(SNDCTL_DSP_GETISPACE) + case SNDCTL_DSP_GETOSPACE: /* _SIOR ('P',12, audio_buf_info) */ + case SNDCTL_DSP_GETISPACE: /* _SIOR ('P',13, audio_buf_info) */ + audiop = argp; + if(mmapemu) + { + audiop->fragstotal = 16; + audiop->fragsize = 4096; + audiop->bytes = 0; /* FIXME: is this right? */ + audiop->fragments = 0; + } + else + { + audiop->fragstotal = + stream?arts_stream_get(stream, ARTS_P_PACKET_COUNT):10; + audiop->fragsize = + stream?arts_stream_get(stream, ARTS_P_PACKET_SIZE):16384; + audiop->bytes = + stream?arts_stream_get(stream, ARTS_P_BUFFER_SPACE):16384; + audiop->fragments = audiop->bytes / audiop->fragsize; + } + break; +#endif + +#ifdef SNDCTL_DSP_NONBLOCK + case SNDCTL_DSP_NONBLOCK: /* _SIO ('P',14) */ + artsdspdebug("aRts: SNDCTL_DSP_NONBLOCK unsupported\n"); + break; +#endif + +#ifdef SNDCTL_DSP_GETCAPS + case SNDCTL_DSP_GETCAPS: /* _SIOR ('P',15, int) */ + if(mmapemu) + *arg = DSP_CAP_MMAP | DSP_CAP_TRIGGER | DSP_CAP_REALTIME | DSP_CAP_DUPLEX; + else + *arg = DSP_CAP_DUPLEX; + break; +#endif + +#ifdef SNDCTL_DSP_GETTRIGGER + case SNDCTL_DSP_GETTRIGGER: /* _SIOR ('P',16, int) */ + artsdspdebug("aRts: SNDCTL_DSP_GETTRIGGER unsupported\n"); + break; +#endif + +#ifdef SNDCTL_DSP_SETTRIGGER + case SNDCTL_DSP_SETTRIGGER: /* _SIOW ('P',16, int) */ + artsdspdebug("aRts: SNDCTL_DSP_SETTRIGGER unsupported\n"); + break; +#endif + +#ifdef SNDCTL_DSP_GETIPTR + case SNDCTL_DSP_GETIPTR: /* _SIOR ('P',17, count_info) */ + artsdspdebug("aRts: SNDCTL_DSP_GETIPTR unsupported\n"); + break; +#endif + +#ifdef SNDCTL_DSP_GETOPTR + case SNDCTL_DSP_GETOPTR: /* _SIOR ('P',18, count_info) */ + if(mmapemu) + { + mmapemu_flush(); + *((count_info *)arg) = mmapemu_ocount; + mmapemu_ocount.blocks = 0; + } + else + { + artsdspdebug("aRts: SNDCTL_DSP_GETOPTR unsupported\n"); + } + break; +#endif + +#ifdef SNDCTL_DSP_MAPINBUF + case SNDCTL_DSP_MAPINBUF: /* _SIOR ('P', 19, buffmem_desc) */ + artsdspdebug("aRts: SNDCTL_DSP_MAPINBUF unsupported\n"); + break; +#endif + +#ifdef SNDCTL_DSP_MAPOUTBUF + case SNDCTL_DSP_MAPOUTBUF: /* _SIOR ('P', 20, buffmem_desc) */ + artsdspdebug("aRts: SNDCTL_DSP_MAPOUTBUF unsupported\n"); + break; +#endif + +#ifdef SNDCTL_DSP_SETSYNCRO + case SNDCTL_DSP_SETSYNCRO: /* _SIO ('P', 21) */ + artsdspdebug("aRts: SNDCTL_DSP_SETSYNCHRO unsupported\n"); + break; +#endif + +#ifdef SNDCTL_DSP_SETDUPLEX + case SNDCTL_DSP_SETDUPLEX: /* _SIO ('P', 22) */ + artsdspdebug("aRts: SNDCTL_DSP_SETDUPLEX unsupported\n"); + break; +#endif + +#ifdef SNDCTL_DSP_GETODELAY + case SNDCTL_DSP_GETODELAY: /* _SIOR ('P', 23, int) */ + space = arts_stream_get(stream, ARTS_P_BUFFER_SPACE); + size = arts_stream_get(stream, ARTS_P_BUFFER_SIZE); + latency = arts_stream_get(stream, ARTS_P_SERVER_LATENCY); + odelay = size - space + (latency * speed * bits * channels) / 8000; + artsdspdebug("aRts: SNDCTL_DSP_GETODELAY returning %d\n", odelay); + *arg = odelay; + break; +#endif + + default: + artsdspdebug("aRts: unhandled /dev/dsp ioctl (%lx - %p)\n",request, argp); + break; + } + + if (settings == 7 && !stream) + { + const char *name = getenv("ARTSDSP_NAME"); + int fragSize = (frags & 0x7fff); + int fragCount = (frags & 0x7fff0000) >> 16; + + artsdspdebug ("aRts: creating stream...\n"); + stream = arts_play_stream(speed,bits,channels,name?name:"artsdsp"); + + if(fragSize > 1 && fragSize < 16) + { + /* + * if fragment settings are way too large (unrealistic), we + * will assume that the user didn't mean it, and let the C API + * choose a convenient number >= 3 + */ + if(fragCount < 2 || fragCount > 8192 + || (fragCount * (1 << fragSize)) > 128*1024) + { + frags = 0x00030000+fragSize; + } + + arts_stream_set(stream,ARTS_P_PACKET_SETTINGS,frags); + } + if(mmapemu) + { + arts_stream_set(stream,ARTS_P_PACKET_SETTINGS,0x0002000c); + mmapemu_ocount.ptr=mmapemu_ocount.blocks=mmapemu_ocount.bytes=0; + artsdspdebug("aRts: total latency = %dms, buffer latency = %dms\n", + arts_stream_get(stream,ARTS_P_TOTAL_LATENCY), + arts_stream_get(stream, ARTS_P_BUFFER_TIME)); + } + } + + return 0; + } + + return 0; +} + +int close(int fd) +{ + CHECK_INIT(); + + if (fd != sndfd) + return orig_close (fd); + else if (sndfd != -1) + { + artsdspdebug ("aRts: /dev/dsp close...\n"); + if(stream) + { + arts_close_stream(stream); + stream = 0; + } + if (record_stream) + { + arts_close_stream(record_stream); + record_stream = 0; + } + if(mmapemu && mmapemu_obuffer) + { + free(mmapemu_obuffer); + mmapemu_obuffer = 0; + } + + /* + * there are problems with library unloading in conjunction with X11, + * (Arts::X11GlobalComm), so we don't unload stuff again here + */ + + /* arts_free(); */ + + orig_close(sndfd); + sndfd = -1; + } + return 0; +} + +int access(const char *pathname, int mode) +{ + CHECK_INIT(); + + if (!is_sound_device(pathname)) /* original open for anything but sound */ + return orig_access (pathname, mode); + else + artsdspdebug ("aRts: /dev/dsp access...\n"); + return 0; +} + +ssize_t write (int fd, const void *buf, size_t count) +{ + CHECK_INIT(); + + if(fd != sndfd) + return orig_write(fd,buf,count); + else if(sndfd != -1) + { + artsdspdebug ("aRts: /dev/dsp write...\n"); + if(stream != 0) + { + return arts_write(stream,buf,count); + } + } + return 0; +} + +ssize_t read (int fd, void *buf, size_t count) +{ + CHECK_INIT(); + + if (fd != sndfd) + return orig_read(fd,buf,count); + else if (sndfd == -1) + return 0; + + if (!record_stream) + record_stream = arts_record_stream(speed, bits, channels, "artsdsp"); + artsdspdebug ( "aRts: /dev/dsp read...\n" ); + return arts_read(record_stream, buf, count); +} + +void *mmap(void *start, size_t length, int prot, int flags, + int fd, off_t offset) +{ + CHECK_INIT(); + + if(fd != sndfd || sndfd == -1) + return orig_mmap(start,length,prot,flags,fd,offset); + else + { + artsdspdebug ("aRts: mmap - start = %p, length = %zd, prot = %d\n", + start, length, prot); + artsdspdebug (" flags = %d, fd = %d, offset = %ld\n",flags, fd,offset); + + if(mmapemu && length > 0) + { + mmapemu_osize = length; + mmapemu_obuffer = malloc(length); + mmapemu_ocount.ptr = mmapemu_ocount.blocks = mmapemu_ocount.bytes = 0; + return mmapemu_obuffer; + } + else artsdspdebug ("aRts: /dev/dsp mmap (unsupported, try -m option)...\n"); + } + return (void *)-1; +} + +int munmap(void *start, size_t length) +{ + CHECK_INIT(); + + if(start != mmapemu_obuffer || mmapemu_obuffer == 0) + return orig_munmap(start,length); + + artsdspdebug ("aRts: /dev/dsp munmap...\n"); + mmapemu_obuffer = 0; + free(start); + + return 0; +} + +#ifdef HAVE_ARTSDSP_STDIO_EMU + +/* + * use #include rather than linking, because we want to keep everything + * static inside artsdsp to be sure that we don't override application symbols + */ +#include "stdioemu.c" + +FILE* fopen(const char *path, const char *mode) +{ + CHECK_INIT(); + + if (!is_sound_device(path)) /* original open for anything but sound */ + return orig_fopen (path, mode); + + artsdspdebug ("aRts: hijacking /dev/dsp fopen...\n"); + + return fake_fopen(path, mode); +} +#endif + +#endif +/* + * vim:ts=4 + */ diff --git a/artsc/artsdsp.in b/artsc/artsdsp.in new file mode 100755 index 0000000..084a716 --- /dev/null +++ b/artsc/artsdsp.in @@ -0,0 +1,106 @@ +#!/bin/sh +# artsdsp - wrapper script to allow *some* binary only programs to use artsd +# based on the esddsp script + +# keep this in sync with artsversion.h +version="@ARTS_VERSION@" + +# default values for script variables +verbose=0 +set_name=0 +single_thread=0 + +# check for artsdsp options +while test $# -gt 0; do + + case "$1" in + + -h|--help) + echo "artsdsp - attempt to reroute audio device to artsd" + echo " " + echo "artsdsp [options] application arguments" + echo " " + echo "options:" + echo "-h, --help show brief help" + echo "-n, --name=NAME use name to identify player to artsd" + echo "-m, --mmap emulate memory mapping (i.e. for quake)" + echo "-s, --single-threaded use the single-threaded version" + echo "-v, --verbose show parameters" + echo "-V, --version show version" + exit 0 + ;; + + -n) + shift + if test $# -gt 0; then + export ARTSDSP_NAME=$1 + else + echo "no player name specified" + exit 1 + fi + shift + set_name=1 + ;; + + --name*) + export ARTSDSP_NAME=`echo $1 | sed -e 's/^[^=]*=//g'` + set_name=1 + shift + ;; + + -v|--verbose) + verbose=1 + shift + ;; + + -V|--version) + echo "artsdsp $version" + exit + ;; + + -m|--mmap) + export ARTSDSP_MMAP=1 + shift + ;; + + -s|--single-threaded) + single_thread=1 + shift + ;; + + *) + # no more artsdsp options, get on with life + break + ;; + esac +done + +# echo options if verbose specified +if test "$verbose" = 1; then + ARTSDSP_VERBOSE=1 + export ARTSDSP_VERBOSE + echo "artsdsp: $version" + echo "name: $ARTSDSP_NAME" + echo "command line: $@" + if test "$single_thread" = 1; then + echo "threaded: no" + else + echo "threaded: yes" + fi +fi + +prefix=@prefix@ +exec_prefix=@exec_prefix@ + +if test "$single_thread" = 1; then + LD_PRELOAD=${exec_prefix}/\$LIB/libartsdsp_st.so.0 +else + LD_PRELOAD=${exec_prefix}/\$LIB/libartsdsp.so.0:${exec_prefix}/\$LIB/libartsc.so.0 +fi +if test -f ${exec_prefix}/\$LIB/libdl.so.2; then + LD_PRELOAD=$LD_PRELOAD:${exec_prefix}/\$LIB/libdl.so.2 +fi +export LD_PRELOAD + +# invoke the program with the args given +exec "$@" diff --git a/artsc/stdioemu.c b/artsc/stdioemu.c new file mode 100644 index 0000000..2b3bc1c --- /dev/null +++ b/artsc/stdioemu.c @@ -0,0 +1,96 @@ + /* + + 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., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ + +/* + * This source only exists because some very special programs think that + * it is a very special idea to access /dev/dsp by the means of stdio, so + * we need to fake FILE* access for artsdsp as well. + * + * To do so, it relies on glibc internals, so that it will probably not work + * on other systems - but then again, it might not be necessary on other + * systems, when fopen properly calls open, it might as well work unchanged. + */ + + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <stdio.h> +#include <libio.h> + +struct fd_cookie { + int fd; +}; + +static ssize_t fdc_read (void *cookie, char *buffer, size_t size) +{ + struct fd_cookie *fdc = (struct fd_cookie *)cookie; + return read(fdc->fd, buffer, size); +} + +static ssize_t fdc_write (void *cookie, const char *buffer, size_t size) +{ + struct fd_cookie *fdc = (struct fd_cookie *)cookie; + return write(fdc->fd, buffer, size); +} + +static int fdc_seek (void* cookie, off64_t* position, int whence) +{ + return -1; +} + +static int fdc_clean (void *cookie) +{ + struct fd_cookie *fdc = (struct fd_cookie *)cookie; + int result = close(fdc->fd); + free(cookie); + return result; +} + +static FILE *fake_fopen(const char *path, const char *mode) +{ + cookie_io_functions_t fns = { fdc_read, fdc_write, fdc_seek, fdc_clean }; + struct fd_cookie *fdc = + (struct fd_cookie *)malloc(sizeof(struct fd_cookie)); + const char *mptr; + int open_mode = 0; + FILE *result = 0; + + for(mptr = mode; *mptr; mptr++) + { + if(*mptr == 'r') open_mode |= 1; /* 1 = read */ + if(*mptr == 'w') open_mode |= 2; /* 2 = write */ + if(*mptr == '+') open_mode |= 3; /* 3 = readwrite */ + if(*mptr == 'a') open_mode |= 2; /* append -> write */ + } + if(open_mode == 1) fdc->fd = open(path,O_RDONLY,0666); + if(open_mode == 2) fdc->fd = open(path,O_WRONLY,0666); + if(open_mode == 3) fdc->fd = open(path,O_RDWR,0666); + + if(open_mode && fdc->fd > 0) + { + result = fopencookie (fdc,"w", fns); + result->_fileno = fdc->fd; /* ugly patchy slimy kludgy hack */ + } + return result; +} |