From 42995d7bf396933ee60c5f89c354ea89cf13df0d Mon Sep 17 00:00:00 2001 From: tpearson Date: Tue, 5 Jan 2010 00:01:18 +0000 Subject: Copy of aRts for Trinity modifications git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/dependencies/arts@1070145 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- flow/Makefile.am | 49 + flow/artsflow.idl | 566 ++ flow/asyncschedule.cc | 553 ++ flow/asyncschedule.h | 134 + flow/audioio.cc | 165 + flow/audioio.h | 145 + flow/audioioaix.cc | 390 + flow/audioioalsa.cc | 561 ++ flow/audioioalsa9.cc | 590 ++ flow/audioiocsl.cc | 640 ++ flow/audioioesd.cc | 231 + flow/audioiojack.cc | 346 + flow/audioiolibaudioio.cc | 236 + flow/audioiomas.cc | 619 ++ flow/audioionas.cc | 260 + flow/audioionull.cc | 184 + flow/audioiooss.cc | 485 + flow/audioioossthreaded.cc | 681 ++ flow/audioiosgi.cc | 274 + flow/audioiosun.cc | 442 + flow/audiomanager_impl.cc | 325 + flow/audiosubsys.cc | 645 ++ flow/audiosubsys.h | 236 + flow/audiotobytestream_impl.cc | 223 + flow/bufferqueue.h | 148 + flow/bus.cc | 367 + flow/bus.h | 76 + flow/bytestreamtoaudio_impl.cc | 119 + flow/cache.cc | 274 + flow/cache.h | 92 + flow/cachedwav.h | 81 + flow/convert.cc | 418 + flow/convert.h | 164 + flow/cpuinfo.cc | 232 + flow/cpuinfo.h | 72 + flow/datahandle_impl.cc | 499 + flow/fft.c | 32 + flow/fft.h | 32 + flow/gsl/Makefile.am | 24 + flow/gsl/arts-patches | 73 + flow/gsl/configure.in.in | 224 + flow/gsl/dummy.cc | 3 + flow/gsl/gbsearcharray.h | 292 + flow/gsl/gsl-fftconf.sh | 468 + flow/gsl/gsl-fftgen.pl | 810 ++ flow/gsl/gsl-iplan.txt | 57 + flow/gsl/gsl-mplan.txt | 226 + flow/gsl/gsl.3 | 822 ++ flow/gsl/gsl.gnuplot | 5 + flow/gsl/gslarrows | 30 + flow/gsl/gslartsthreads.cc | 205 + flow/gsl/gslartsthreads.h | 102 + flow/gsl/gslcommon.c | 1651 ++++ flow/gsl/gslcommon.h | 293 + flow/gsl/gslconfig.h.in | 11 + flow/gsl/gslconvert.c | 57 + flow/gsl/gslconvert.h | 43 + flow/gsl/gsldatacache.c | 633 ++ flow/gsl/gsldatacache.h | 87 + flow/gsl/gsldatahandle-lbuffer.c | 11 + flow/gsl/gsldatahandle-mad.c | 711 ++ flow/gsl/gsldatahandle-mad.h | 42 + flow/gsl/gsldatahandle-vorbis.c | 376 + flow/gsl/gsldatahandle-vorbis.h | 41 + flow/gsl/gsldatahandle.c | 1241 +++ flow/gsl/gsldatahandle.h | 143 + flow/gsl/gsldatautils.c | 462 + flow/gsl/gsldatautils.h | 909 ++ flow/gsl/gsldefs.h | 136 + flow/gsl/gslengine.c | 753 ++ flow/gsl/gslengine.h | 206 + flow/gsl/gslfft.c | 9052 +++++++++++++++++ flow/gsl/gslfft.h | 126 + flow/gsl/gslffttest.c | 435 + flow/gsl/gslfilehash.c | 464 + flow/gsl/gslfilehash.h | 77 + flow/gsl/gslfilter.c | 1379 +++ flow/gsl/gslfilter.h | 281 + flow/gsl/gslglib.c | 2400 +++++ flow/gsl/gslglib.h | 858 ++ flow/gsl/gslglibhash.cc | 151 + flow/gsl/gslglibhashtest.cc | 123 + flow/gsl/gslieee754.h | 246 + flow/gsl/gslincluder.c | 13420 ++++++++++++++++++++++++++ flow/gsl/gslloader-gslwave.c | 701 ++ flow/gsl/gslloader-mad.c | 210 + flow/gsl/gslloader-oggvorbis.c | 178 + flow/gsl/gslloader-wav.c | 442 + flow/gsl/gslloader.c | 335 + flow/gsl/gslloader.h | 136 + flow/gsl/gslmagic.c | 711 ++ flow/gsl/gslmagic.h | 72 + flow/gsl/gslmakefile.inc | 87 + flow/gsl/gslmath.c | 1085 +++ flow/gsl/gslmath.h | 518 + flow/gsl/gslmathtest.c | 334 + flow/gsl/gslopmaster.c | 783 ++ flow/gsl/gslopmaster.h | 42 + flow/gsl/gslopnode.h | 247 + flow/gsl/gslopschedule.c | 582 ++ flow/gsl/gslopschedule.h | 79 + flow/gsl/gsloputil.c | 721 ++ flow/gsl/gsloputil.h | 86 + flow/gsl/gsloscillator-aux.c | 214 + flow/gsl/gsloscillator.c | 219 + flow/gsl/gsloscillator.h | 84 + flow/gsl/gslosctable.c | 626 ++ flow/gsl/gslosctable.h | 104 + flow/gsl/gslsignal.c | 258 + flow/gsl/gslsignal.h | 343 + flow/gsl/gsltests.c | 650 ++ flow/gsl/gslwave.header | Bin 0 -> 3342 bytes flow/gsl/gslwavechunk.c | 812 ++ flow/gsl/gslwavechunk.h | 129 + flow/gsl/gslwaveloader.c | 2 + flow/gsl/gslwaveloader.h | 6 + flow/gsl/gslwaveosc-aux.c | 249 + flow/gsl/gslwaveosc.c | 376 + flow/gsl/gslwaveosc.h | 96 + flow/gsl/gslwchunk.c | 279 + flow/gslpp/Makefile.am | 7 + flow/gslpp/datahandle.cpp | 412 + flow/gslpp/datahandle.h | 220 + flow/gslschedule.cc | 1195 +++ flow/gslschedule.h | 262 + flow/mcopclass/DataHandlePlay.mcopclass | 2 + flow/mcopclass/Makefile.am | 15 + flow/mcopclass/Synth_ADD.mcopclass | 3 + flow/mcopclass/Synth_AMAN_PLAY.mcopclass | 3 + flow/mcopclass/Synth_AMAN_RECORD.mcopclass | 3 + flow/mcopclass/Synth_BUS_DOWNLINK.mcopclass | 3 + flow/mcopclass/Synth_BUS_UPLINK.mcopclass | 3 + flow/mcopclass/Synth_FREQUENCY.mcopclass | 3 + flow/mcopclass/Synth_MUL.mcopclass | 3 + flow/mcopclass/Synth_MULTI_ADD.mcopclass | 3 + flow/mcopclass/Synth_PLAY.mcopclass | 3 + flow/mcopclass/Synth_PLAY_WAV.mcopclass | 3 + flow/mcopclass/Synth_RECORD.mcopclass | 3 + flow/mcopclass/Synth_WAVE_SIN.mcopclass | 3 + flow/mcopclass/WaveDataHandle.mcopclass | 2 + flow/pipebuffer.cc | 166 + flow/pipebuffer.h | 75 + flow/resample.cc | 305 + flow/resample.h | 167 + flow/stdsynthmodule.cc | 92 + flow/stdsynthmodule.h | 65 + flow/stereoeffectstack_impl.cc | 179 + flow/stereofftscope_impl.cc | 131 + flow/stereovolumecontrol_impl.cc | 197 + flow/synth_add_impl.cc | 44 + flow/synth_frequency_impl.cc | 70 + flow/synth_mul_impl.cc | 44 + flow/synth_multi_add_impl.cc | 64 + flow/synth_play_impl.cc | 285 + flow/synth_play_wav_impl.cc | 579 ++ flow/synth_record_impl.cc | 184 + flow/synth_wave_sin_impl.cc | 44 + flow/synthschedule.h | 36 + flow/virtualports.cc | 491 + flow/virtualports.h | 87 + 160 files changed, 68997 insertions(+) create mode 100644 flow/Makefile.am create mode 100644 flow/artsflow.idl create mode 100644 flow/asyncschedule.cc create mode 100644 flow/asyncschedule.h create mode 100644 flow/audioio.cc create mode 100644 flow/audioio.h create mode 100644 flow/audioioaix.cc create mode 100644 flow/audioioalsa.cc create mode 100644 flow/audioioalsa9.cc create mode 100644 flow/audioiocsl.cc create mode 100644 flow/audioioesd.cc create mode 100644 flow/audioiojack.cc create mode 100644 flow/audioiolibaudioio.cc create mode 100644 flow/audioiomas.cc create mode 100644 flow/audioionas.cc create mode 100644 flow/audioionull.cc create mode 100644 flow/audioiooss.cc create mode 100644 flow/audioioossthreaded.cc create mode 100644 flow/audioiosgi.cc create mode 100644 flow/audioiosun.cc create mode 100644 flow/audiomanager_impl.cc create mode 100644 flow/audiosubsys.cc create mode 100644 flow/audiosubsys.h create mode 100644 flow/audiotobytestream_impl.cc create mode 100644 flow/bufferqueue.h create mode 100644 flow/bus.cc create mode 100644 flow/bus.h create mode 100644 flow/bytestreamtoaudio_impl.cc create mode 100644 flow/cache.cc create mode 100644 flow/cache.h create mode 100644 flow/cachedwav.h create mode 100644 flow/convert.cc create mode 100644 flow/convert.h create mode 100644 flow/cpuinfo.cc create mode 100644 flow/cpuinfo.h create mode 100644 flow/datahandle_impl.cc create mode 100644 flow/fft.c create mode 100644 flow/fft.h create mode 100644 flow/gsl/Makefile.am create mode 100644 flow/gsl/arts-patches create mode 100644 flow/gsl/configure.in.in create mode 100644 flow/gsl/dummy.cc create mode 100644 flow/gsl/gbsearcharray.h create mode 100755 flow/gsl/gsl-fftconf.sh create mode 100755 flow/gsl/gsl-fftgen.pl create mode 100644 flow/gsl/gsl-iplan.txt create mode 100644 flow/gsl/gsl-mplan.txt create mode 100644 flow/gsl/gsl.3 create mode 100644 flow/gsl/gsl.gnuplot create mode 100644 flow/gsl/gslarrows create mode 100644 flow/gsl/gslartsthreads.cc create mode 100644 flow/gsl/gslartsthreads.h create mode 100644 flow/gsl/gslcommon.c create mode 100644 flow/gsl/gslcommon.h create mode 100644 flow/gsl/gslconfig.h.in create mode 100644 flow/gsl/gslconvert.c create mode 100644 flow/gsl/gslconvert.h create mode 100644 flow/gsl/gsldatacache.c create mode 100644 flow/gsl/gsldatacache.h create mode 100644 flow/gsl/gsldatahandle-lbuffer.c create mode 100644 flow/gsl/gsldatahandle-mad.c create mode 100644 flow/gsl/gsldatahandle-mad.h create mode 100644 flow/gsl/gsldatahandle-vorbis.c create mode 100644 flow/gsl/gsldatahandle-vorbis.h create mode 100644 flow/gsl/gsldatahandle.c create mode 100644 flow/gsl/gsldatahandle.h create mode 100644 flow/gsl/gsldatautils.c create mode 100644 flow/gsl/gsldatautils.h create mode 100644 flow/gsl/gsldefs.h create mode 100644 flow/gsl/gslengine.c create mode 100644 flow/gsl/gslengine.h create mode 100644 flow/gsl/gslfft.c create mode 100644 flow/gsl/gslfft.h create mode 100644 flow/gsl/gslffttest.c create mode 100644 flow/gsl/gslfilehash.c create mode 100644 flow/gsl/gslfilehash.h create mode 100644 flow/gsl/gslfilter.c create mode 100644 flow/gsl/gslfilter.h create mode 100644 flow/gsl/gslglib.c create mode 100644 flow/gsl/gslglib.h create mode 100644 flow/gsl/gslglibhash.cc create mode 100644 flow/gsl/gslglibhashtest.cc create mode 100644 flow/gsl/gslieee754.h create mode 100644 flow/gsl/gslincluder.c create mode 100644 flow/gsl/gslloader-gslwave.c create mode 100644 flow/gsl/gslloader-mad.c create mode 100644 flow/gsl/gslloader-oggvorbis.c create mode 100644 flow/gsl/gslloader-wav.c create mode 100644 flow/gsl/gslloader.c create mode 100644 flow/gsl/gslloader.h create mode 100644 flow/gsl/gslmagic.c create mode 100644 flow/gsl/gslmagic.h create mode 100644 flow/gsl/gslmakefile.inc create mode 100644 flow/gsl/gslmath.c create mode 100644 flow/gsl/gslmath.h create mode 100644 flow/gsl/gslmathtest.c create mode 100644 flow/gsl/gslopmaster.c create mode 100644 flow/gsl/gslopmaster.h create mode 100644 flow/gsl/gslopnode.h create mode 100644 flow/gsl/gslopschedule.c create mode 100644 flow/gsl/gslopschedule.h create mode 100644 flow/gsl/gsloputil.c create mode 100644 flow/gsl/gsloputil.h create mode 100644 flow/gsl/gsloscillator-aux.c create mode 100644 flow/gsl/gsloscillator.c create mode 100644 flow/gsl/gsloscillator.h create mode 100644 flow/gsl/gslosctable.c create mode 100644 flow/gsl/gslosctable.h create mode 100644 flow/gsl/gslsignal.c create mode 100644 flow/gsl/gslsignal.h create mode 100644 flow/gsl/gsltests.c create mode 100644 flow/gsl/gslwave.header create mode 100644 flow/gsl/gslwavechunk.c create mode 100644 flow/gsl/gslwavechunk.h create mode 100644 flow/gsl/gslwaveloader.c create mode 100644 flow/gsl/gslwaveloader.h create mode 100644 flow/gsl/gslwaveosc-aux.c create mode 100644 flow/gsl/gslwaveosc.c create mode 100644 flow/gsl/gslwaveosc.h create mode 100644 flow/gsl/gslwchunk.c create mode 100644 flow/gslpp/Makefile.am create mode 100644 flow/gslpp/datahandle.cpp create mode 100644 flow/gslpp/datahandle.h create mode 100644 flow/gslschedule.cc create mode 100644 flow/gslschedule.h create mode 100644 flow/mcopclass/DataHandlePlay.mcopclass create mode 100644 flow/mcopclass/Makefile.am create mode 100644 flow/mcopclass/Synth_ADD.mcopclass create mode 100644 flow/mcopclass/Synth_AMAN_PLAY.mcopclass create mode 100644 flow/mcopclass/Synth_AMAN_RECORD.mcopclass create mode 100644 flow/mcopclass/Synth_BUS_DOWNLINK.mcopclass create mode 100644 flow/mcopclass/Synth_BUS_UPLINK.mcopclass create mode 100644 flow/mcopclass/Synth_FREQUENCY.mcopclass create mode 100644 flow/mcopclass/Synth_MUL.mcopclass create mode 100644 flow/mcopclass/Synth_MULTI_ADD.mcopclass create mode 100644 flow/mcopclass/Synth_PLAY.mcopclass create mode 100644 flow/mcopclass/Synth_PLAY_WAV.mcopclass create mode 100644 flow/mcopclass/Synth_RECORD.mcopclass create mode 100644 flow/mcopclass/Synth_WAVE_SIN.mcopclass create mode 100644 flow/mcopclass/WaveDataHandle.mcopclass create mode 100644 flow/pipebuffer.cc create mode 100644 flow/pipebuffer.h create mode 100644 flow/resample.cc create mode 100644 flow/resample.h create mode 100644 flow/stdsynthmodule.cc create mode 100644 flow/stdsynthmodule.h create mode 100644 flow/stereoeffectstack_impl.cc create mode 100644 flow/stereofftscope_impl.cc create mode 100644 flow/stereovolumecontrol_impl.cc create mode 100644 flow/synth_add_impl.cc create mode 100644 flow/synth_frequency_impl.cc create mode 100644 flow/synth_mul_impl.cc create mode 100644 flow/synth_multi_add_impl.cc create mode 100644 flow/synth_play_impl.cc create mode 100644 flow/synth_play_wav_impl.cc create mode 100644 flow/synth_record_impl.cc create mode 100644 flow/synth_wave_sin_impl.cc create mode 100644 flow/synthschedule.h create mode 100644 flow/virtualports.cc create mode 100644 flow/virtualports.h (limited to 'flow') diff --git a/flow/Makefile.am b/flow/Makefile.am new file mode 100644 index 0000000..720ca47 --- /dev/null +++ b/flow/Makefile.am @@ -0,0 +1,49 @@ + +SUBDIRS = mcopclass gsl gslpp +INCLUDES = -I$(top_srcdir)/mcop -I$(top_builddir)/mcop $(all_includes) +AM_CXXFLAGS = $(MAS_CFLAGS) $(JACK_CFLAGS) -DQT_CLEAN_NAMESPACE + +####### Files + +lib_LTLIBRARIES = libartsflow_idl.la libartsflow.la + +libartsflow_idl_la_SOURCES = artsflow.cc +libartsflow_idl_la_LDFLAGS = -no-undefined -version-info 1:0 $(all_libraries) +libartsflow_idl_la_LIBADD = $(top_builddir)/mcop/libmcop.la $(LIBPOSIX4) + +libartsflow_la_LIBADD = $(top_builddir)/mcop/libmcop.la libartsflow_idl.la $(top_builddir)/flow/gslpp/libgslpp.la $(LIBAUDIOFILE) $(LIBASOUND) $(LIBAUDIOIO) $(LIBOSSAUDIO) $(LIBAUDIONAS) $(LIBCSL) $(SGILIBAUDIO) $(LIBESD) $(LIBMAS) $(JACK_LIBADD) -lm \ + $(top_builddir)/flow/gsl/libgsl.la +libartsflow_la_LDFLAGS = $(MAS_LDFLAGS) $(JACK_LDFLAGS) $(LIBAUDIOFILE_LDFLAGS) $(LIBAUDIONAS_LDFLAGS) $(LIBESD_LDFLAGS) -no-undefined -version-info 1:0 +libartsflow_la_COMPILE_FIRST = artsflow.h +libartsflow_la_SOURCES = synth_play_impl.cc \ + gslschedule.cc audiosubsys.cc \ + pipebuffer.cc convert.cc synth_wave_sin_impl.cc synth_frequency_impl.cc \ + synth_multi_add_impl.cc synth_add_impl.cc synth_mul_impl.cc \ + synth_play_wav_impl.cc stdsynthmodule.cc cache.cc asyncschedule.cc \ + bytestreamtoaudio_impl.cc stereovolumecontrol_impl.cc \ + stereoeffectstack_impl.cc fft.c stereofftscope_impl.cc virtualports.cc \ + bus.cc audiomanager_impl.cc synth_record_impl.cc resample.cc \ + audioio.cc audioiooss.cc audioioalsa.cc audioioalsa9.cc \ + audioionull.cc audioiolibaudioio.cc audioioesd.cc audioiojack.cc \ + audioiosun.cc audioioaix.cc audioionas.cc cpuinfo.cc \ + audioioossthreaded.cc audiotobytestream_impl.cc audioiosgi.cc \ + audioiocsl.cc audioiomas.cc datahandle_impl.cc + +artsincludedir = $(includedir)/arts +artsinclude_HEADERS = artsflow.h audiosubsys.h cache.h \ + cachedwav.h convert.h pipebuffer.h stdsynthmodule.h \ + synthschedule.h fft.h artsflow.idl audioio.h resample.h \ + cpuinfo.h bufferqueue.h gslschedule.h + +DISTCLEANFILES = artsflow.cc artsflow.h artsflow.mcoptype artsflow.mcopclass + +artsflow.cc artsflow.h: $(top_srcdir)/flow/artsflow.idl $(MCOPIDL) + $(MCOPIDL) -t $(top_srcdir)/flow/artsflow.idl + +artsflow.mcoptype: artsflow.h +artsflow.mcopclass: artsflow.h + +######## install idl typeinfo files + +mcoptypedir = $(libdir)/mcop +mcoptype_DATA = artsflow.mcoptype artsflow.mcopclass diff --git a/flow/artsflow.idl b/flow/artsflow.idl new file mode 100644 index 0000000..44424e0 --- /dev/null +++ b/flow/artsflow.idl @@ -0,0 +1,566 @@ + /* + + 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. + + */ + +/* + * arts.idl - MCOP port. What's missing currently in MCOP? + * + * - namespaces (module) + */ + +module Arts { // analog real time synthesizer + +enum AutoSuspendState { asNoSuspend, asSuspend, asSuspendStop, asSuspendMask = 0x3, + asProducer = 0x10, asConsumer = 0x20, asDirectionMask = 0x30 }; + +/** + * The SynthModule interface is the base for all modules containing streams. + * + * There are two goals achieved by this interface. On one side, there is + * functionality which users of stream carrying modules want to use (which + * is: start streaming, stop streaming). + * + * On the other hand, there is functionality which the flow system will use + * to achieve these goals. + */ +interface SynthModule { + // interface for users of this module + + /** + * This function starts the streaming (e.g. the module will start + * producing samples) - if you write a module, do not reimplement this, + * instead reimplement streamInit/streamStart + */ + void start(); + + /** + * This function stops the streaming - if you write a plugin, do not + * reimplement this, instead reimplement streamEnd + */ + void stop(); + + // interface for people implementing modules + + /** + * this is supposed to be the initialization every module passes after + * all attributes have been set up (e.g. you can see which file to open, + * how to initialize your filter coefficients or whatever) + */ + void streamInit(); + + /** + * starts the I/O of the module + */ + void streamStart(); + + /** + * stop the thing again, and free data possibly allocated in streamInit + */ + void streamEnd(); + + /** + * If you run a mixer desk (without anything connected), no calculations + * need to be done - since the output is silent anyway. For this reason, + * there exists this autosuspend attribute. It allows the flow system + * to detect the idle condition, and start suspending the calculations, + * until something "important" happens again. + * + * There are three possible values: + * + * @li asNoSuspend - this one is appropriate when you have a module that + * is active by itself + * @li asSuspend - this one is appropriate for modules that "do nothing" + * by themselves + * @li asSuspendStop - this one is for modules that should be stopped, when + * the system gets suspended, and restarted when the + * system will start again - an example for this is + * soundcard output + * + * A module should choose asSuspend (or asSuspendStop) only if the + * following conditions are true: + * + * @li given constant inputs (like 3.0 on all ports), the module will + * give constant output after some time + * @li given only 0.0 inputs, the module will give only 0.0 outputs + * after some time + * @li the module does not synchronize itself through signal flow (i.e. + * a midi sequence which "knows" when a second has passed through + * the signal flow breaks when suspension happens) + * @li the module can't be brought to do something with a method + * invocation (i.e. a module which starts generating noise for + * a second whenever the noise() method is called is not suspendable) + * @li the module has no internal state that changes over time when only + * constant inputs are given + * + * Typical examples for suspendable modules are arithmetic operations, + * filters, delay/hall/reverb. + * + * Typical examples for non-suspendable modules are sequences, midi stuff, + * oscillators, sample players,... + * + * To deal with modules which either input data from some external source + * (i.e. soundcard input) or output data to some external destination, + * (i.e. soundcard output) the following flags are available: + * + * @li asProducer - set this flag for modules which fulfill the conditions + * for a suspendable module, but produce non-zero output + * even when left alone + * @li asConsumer - set this flag for modules which write the data to + * some external destination - that is - definitely + * require constant input to be suspended + * + * The suspension algorithm will first divide the graph of modules into + * subgraphs of interconnected modules. A subgraph is suspendable if + * all of its modules are suspendable and the subgraph does not contain + * producer(s) and consumer(s) at the same time. + * + * Finally, our module graph is suspendable if all its subgraphs are. + */ + readonly attribute AutoSuspendState autoSuspend; +}; + +/** + * Plays a stream of audio data to the soundcard + */ +interface Synth_PLAY : SynthModule { + // attribute string channels; + default in audio stream invalue_left,invalue_right; +}; + +/** + * Records a stream of audio data from the soundcard + */ +interface Synth_RECORD : SynthModule { + // attribute string channels; + default out audio stream left,right; +}; + +/** + * A frequency generator + * + * This kind of object is used to create frequencies. Oscillators are connected + * at the output of this object + */ +interface Synth_FREQUENCY : SynthModule { + in audio stream frequency; + out audio stream pos; +}; + +/** + * A sine wave + */ +interface Synth_WAVE_SIN : SynthModule { + in audio stream pos; + out audio stream outvalue; +}; + +/** + * A module which mixes an arbitary number of audio streams + */ +interface Synth_MULTI_ADD : SynthModule { + in multi audio stream invalue; + out audio stream outvalue; +}; + +/** + * A module which adds two audio streams + */ +interface Synth_ADD : SynthModule { + default in audio stream invalue1,invalue2; + out audio stream outvalue; +}; + +/** + * Multiplies two audio streams + */ +interface Synth_MUL : SynthModule { + in audio stream invalue1,invalue2; + out audio stream outvalue; + default invalue1, invalue2; +}; + +/** + * This plays a wave file + */ +interface Synth_PLAY_WAV : SynthModule { + /** + * How fast should it be played? 1.0 = normal speed + */ + attribute float speed; + /** + * Which file should be played + */ + attribute string filename; + /** + * Is true as soon as the file is finished + */ + readonly attribute boolean finished; + + out audio stream left, right; + default left, right; +}; + +/** + * sends data to a bus - busses are dynamic N:M connections - all signals + * from all uplinks are mixed together, and sent to all downlinks + */ +interface Synth_BUS_UPLINK : SynthModule { + /** + * the name of the bus to use + */ + attribute string busname; + + default in audio stream left,right; +}; + +/** + * receives data from a bus - busses are dynamic N:M connections - all signals + * from all uplinks are mixed together, and sent to all downlinks + */ +interface Synth_BUS_DOWNLINK : SynthModule { + /** + * the name of the bus to use + */ + attribute string busname; + + default out audio stream left,right; +}; + + +/** + * Byte stream to audio conversion object + * + * Converts an asynchronous byte stream to a synchronous audio stream + */ +interface ByteStreamToAudio : SynthModule { + attribute long samplingRate; + attribute long channels; + attribute long bits; + + /** + * is conversion currently running, or is it stalled due to the fact + * that there is not enough input input? + */ + readonly attribute boolean running; + + async in byte stream indata; + + out audio stream left,right; + default left; + default right; +}; + +/** + * Audio to Byte stream conversion object + * + * Converts a synchronous audio stream to an asynchronous byte stream + */ +interface AudioToByteStream : SynthModule { + attribute long samplingRate; + attribute long channels; + attribute long bits; + + async out byte stream outdata; + + in audio stream left,right; + default left; + default right; +}; + +/** + * Base interface for all stereo effects + */ +interface StereoEffect : SynthModule { + default in audio stream inleft, inright; + default out audio stream outleft, outright; +}; + +/** + * this is a simple clipping stereo volume control + */ +interface StereoVolumeControl : StereoEffect { + attribute float scaleFactor; + readonly attribute float currentVolumeLeft; + readonly attribute float currentVolumeRight; +}; + +/** + * A funny FFT scope + */ +interface StereoFFTScope : StereoEffect { + readonly attribute sequence scope; +}; + +/** + * A stack of stereo effects + */ +interface StereoEffectStack : StereoEffect { + /** + * inserts an effect at the top side (= directly after the input) + * + * @returns an ID which can be used to remove the effect again + */ + long insertTop(StereoEffect effect, string name); + + /** + * inserts an effect at the bottom (= close to the output) side + * + * @returns an ID which can be used to remove the effect again + */ + long insertBottom(StereoEffect effect, string name); + + /** + * removes an effect again + */ + void remove(long ID); +}; + +/* + * Audio Manager stuff + */ + +enum AudioManagerDirection { amPlay, amRecord }; + +/** + * Information structure for audio manager clients + */ +struct AudioManagerInfo { + long ID; + string destination; + + AudioManagerDirection direction; + string title, autoRestoreID; +}; + +/** + * an audio manager client + */ +interface AudioManagerClient { + readonly attribute long ID; + attribute AudioManagerDirection direction; + attribute string title, autoRestoreID; + + void constructor(AudioManagerDirection direction, string title, + string autoRestoreID); +}; + +/** + * The audio manager interface + */ +interface AudioManager { + /** + * a list of destinations, where you can play/record data to/from + */ + readonly attribute sequence destinations; + + /** + * a list of clients + */ + readonly attribute sequence clients; + + /** + * this is incremented each time a change is made (i.e. new client attached) + * TODO: SHOULD GO AWAY WITH ATTRIBUTE WATCHING + */ + readonly attribute long changes; + + /** + * this is used to route a client to another destination + */ + void setDestination(long ID, string destination); +}; +/** + * This is a virtual output port, which you use to play data. Where exactly + * this data gets played is managed by the audiomanager. + * + * Creation: there are two ways to initialize a Synth_AMAN_PLAY - one is + * to set title and autoRestoreID to sensible (non empty) values. The other + * is to pass an already initialized AudioManagerClient on the constructor. + */ +interface Synth_AMAN_PLAY : SynthModule { + attribute string title, autoRestoreID; + void constructor(AudioManagerClient client); + + default in audio stream left, right; +}; + +/** + * This is a virtual input port, which you use to record data. Where this + * data comes from is in turn managed by the audiomanager. + * + * Creation: there are two ways to initialize a Synth_AMAN_RECORD - one is + * to set title and autoRestoreID to sensible (non empty) values. The other + * is to pass an already initialized AudioManagerClient on the constructor. + */ +interface Synth_AMAN_RECORD : SynthModule { + attribute string title, autoRestoreID; + void constructor(AudioManagerClient client); + + default out audio stream left, right; +}; + +/* --------------------------------------------------------------------- */ + +/** + * Wraps a datahandle. That is an abstraction for a float value array + * which can be directly loaded data from a file or have some + * processing stages in between (caching, reversing, cropping...) + * which are hidden to this interface. + * In contrast to the underlying C++ API, this datahandle is already + * open()ed after creation, so you can access its information (like + * channelCount) without further action. + * A datahandle normally has one more important function: read() which + * is not wrapped in MCOP because of the overhead of the data + * transfer. (If there is need for sth. like that in the future, + * one could maybe find a solution.) + */ +interface DataHandle { + readonly attribute long bitDepth; + readonly attribute long channelCount; + readonly attribute long valueCount; + /** + * error code open() returned + */ + readonly attribute long errorNo; +}; + +/** + * Represents a datahandle which delivers the data from the underlying + * sourceDatahandle in reverse order. + */ +interface ReversedDataHandle : DataHandle { + void init(DataHandle sourceHandle); +}; + +/** + * Represents a datahandle which delivers an "inner" part of the data + * from the underlying sourceDatahandle. You can cut away parts at the + * start and/or the end with this. + */ +interface CroppedDataHandle : DataHandle { + void init(DataHandle sourceHandle, + long headCutValueCount, + long tailCutValueCount); +}; + +/** + * Represents a datahandle which delivers the data from the underlying + * sourceDatahandle without the "inner" part containing the values + * [cutOffset..cutOffset+cutValueCount-1], which will be cut away. + */ +interface CutDataHandle : DataHandle { + void init(DataHandle sourceHandle, + long cutOffset, + long cutValueCount); +}; + +/** + * DataHandlePlay uses a gsl_wave_osc to play back data from a + * DataHandle using sophisticated anti-aliasing filtering and caching + * techniques. (Though not implemented at the time of writing this, it + * will be optimized for cases where the anti-aliasing is not needed + * because the mixerFrequency equals the current soundserver's.) + */ +interface DataHandlePlay : SynthModule { + /** + * Which data should be played? + */ + attribute DataHandle handle; + /** + * What is the normal mixer frequency the data from the handle + * should be played back at? (default: current mixing frequency + * of the soundserver, e.g. 44100) + */ + attribute float mixerFrequency; + /** + * Which channel of the datahandle should by played? + * (defaults to 0 = the first channel) + */ + attribute long channelIndex; + /** + * How fast should the data be played? + * (defaults to 1.0 = normal speed, see mixerFrequency) + */ + attribute float speed; + /** + * Current position while playing, in fact it's the index in the + * datahandle, so 0 <= pos < handle.valueCount + */ + attribute long pos; + /** + * Is true as soon as the file is finished + */ + readonly attribute boolean finished; + /** + * Can be used to pause and/or continue playing + */ + attribute boolean paused; + + default out audio stream outvalue; + + DataHandlePlay clone(); +}; + +/** + * DataHandle which represents sample data loaded from a file. Note + * that the samples from all channels are interleaved, that is, the + * samples of the first channel in a stereo file are found at offsets + * 0,2,4,6,.. etc. + */ +interface WaveDataHandle : DataHandle { + /** + * Properties of the loaded sample data. Note that those + * properties are only available from a WaveDataHandle, but may be + * available from a DataHandle in the future. + */ + readonly attribute float mixerFrequency; + readonly attribute float oscillatorFrequency; + + /** + * Load the first wavechunk from a file and return true on + * success. A more specific error code is not available at the + * moment. + */ + boolean load(string filename); + + /** + * Load a specific wavechunk from a file and return true on + * success. A more specific error code is not available at the + * moment. + */ + boolean load(string filename, + long waveIndex, long chunkIndex); + + /** + * Return true if and only if a wavechunk was successfully loaded + * from a file. + */ + readonly attribute boolean isLoaded; + + /** + * Creates a DataHandlePlay object with the important attributes + * handle, mixerFrequency and channelCount already set to play + * this WaveDataHandle. + */ + DataHandlePlay createPlayer(); +}; + +}; diff --git a/flow/asyncschedule.cc b/flow/asyncschedule.cc new file mode 100644 index 0000000..b3251a7 --- /dev/null +++ b/flow/asyncschedule.cc @@ -0,0 +1,553 @@ + /* + + Copyright (C) 2000,2001 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 +#include "asyncschedule.h" + +using namespace std; +using namespace Arts; + +#include "debug.h" +#include + +/* Since this file is a tad bit more complex, here is some basic documentation: + +1) ASyncPort: There are asynchronous ports which are parts of the standard- + flowsystem schedule nodes. Their lifetime starts whenever an asynchronous + stream gets created by the flow system, and ends when the schedule node + gets destroyed. Basically, an ASyncPort has two functions: + + * it is a "Port", which means that it gets connect(), disconnect() and + other calls from the flowsystem + + * it is a "GenericDataChannel", which means that DataPackets can interact + with it + + Although there will be ASyncPorts which only send data and ASyncPorts which + only receive data (there are none that do both), there are no distinct + classes for this. + +2) Standard case: a DataPacket that gets transported over a datachannel locally: + + 1. the user allocates himself a datapacket "packet" + 2. the user calls "packet->send()", which in turn calls + ASyncPort::sendPacket(packet) + 3. the ASyncPort sends the DataPacket to every subscriber (incrementing the + useCount) over the NotificationManager + 4. the NotificationManager delivers the DataPackets to the receiver + 5. eventually, the receiver confirms using "packet->processed()" + 6. the packet informs the ASyncPort::processedPacket() + 7. the packet is freed + +variant (pulling): + + 1. the user gets told by the ASyncPort: produce some data, here is a "packet" + 2. the user calls "packet->send()", which in turn calls + ASyncPort::sendPacket(packet) + 3. the ASyncPort sends the DataPacket to every subscriber (incrementing the + useCount) over the NotificationManager + 4. the NotificationManager delivers the DataPackets to the receiver + 5. eventually, the receiver confirms using "packet->processed()" + 6. the packet informs the ASyncPort::processedPacket() + 7. the ASyncPort restarts with 1. + +3) Remote case: the remote case follows from the local case by adding two extra +things: one object that converts packets from their packet form to a message +(ASyncNetSend), and one object that converts packets from the message form +to a packet again. Effectively, the sending of a single packet looks like +this, then: + + 1-S. the user allocates himself a datapacket "packet" + 2-S. the user calls "packet->send()", which in turn calls + ASyncPort::sendPacket(packet) + 3-S. the ASyncPort sends the DataPacket to every subscriber (incrementing the + useCount) over the NotificationManager + 4-S. the NotificationManager delivers the DataPackets to the ASyncNetSend + 5-S. the ASyncNetSend::notify method gets called, which in turn converts + the packet to a network message + + ... network transfer ... + + 6-R. the ASyncNetReceive::receive method gets called - the method creates + a new data packet, and sends it using the NotificationManager again + 7-R. the NotificationManager delivers the DataPacket to the receiver + 8-R. eventually, the receiver confirms using "packet->processed()" + 9-R. the packet informs the ASyncNetReceive::processedPacket() which + frees the packet and tells the (remote) sender that it went all right + + ... network transfer ... + + 10-S. eventually, ASyncNetSend::processed() gets called, and confirms + the packet using "packet->processed()" + 11-S. the packet informs the ASyncPort::processedPacket() + 12-S. the packet is freed + +variant(pulling): + + works the same as in the local case by exchanging steps 1-S and 12-S + +4) ownership: + + * ASyncPort: is owned by the Object which it is a part of, if the object + dies, ASyncPort dies unconditionally + + * DataPacket: is owned by the GenericDataChannel they are propagated over, + that is, the ASyncPort normally - however if the DataPacket is still in + use (i.e. in state 5 of the local case), it will take responsibility to + free itself once all processed() calls have been collected + + * ASyncNetSend, ASyncNetReceive: own each other, so that if the sender dies, + the connection will die as well, and if the receiver dies, the same happens + +*/ + +#undef DEBUG_ASYNC_TRANSFER + +ASyncPort::ASyncPort(const std::string& name, void *ptr, long flags, + StdScheduleNode* parent) : Port(name, ptr, flags, parent), pull(false) +{ + stream = (GenericAsyncStream *)ptr; + stream->channel = this; + stream->_notifyID = notifyID = parent->object()->_mkNotifyID(); +} + +ASyncPort::~ASyncPort() +{ + /* + * tell all outstanding packets that we don't exist any longer, so that + * if they feel like they need to confirm they have been processed now, + * they don't talk to an no longer existing object about it + */ + while(!sent.empty()) + { + sent.front()->channel = 0; + sent.pop_front(); + } + + /* disconnect remote connections (if present): the following things will + * need to be ensured here, since we are being deleted: + * + * - the senders should not talk to us after our destructor + * - all of our connections need to be disconnected + * - every connection needs to be closed exactly once + * + * (closing a connection can cause reentrancy due to mcop communication) + */ + while(!netSenders.empty()) + netSenders.front()->disconnect(); + + FlowSystemReceiver receiver = netReceiver; + if(!receiver.isNull()) + receiver.disconnect(); +} + +//-------------------- GenericDataChannel interface ------------------------- + +void ASyncPort::setPull(int packets, int capacity) +{ + pullNotification.receiver = parent->object(); + pullNotification.ID = notifyID; + pullNotification.internal = 0; + pull = true; + + for(int i=0;icreatePacket(capacity); + packet->useCount = 0; + pullNotification.data = packet; + NotificationManager::the()->send(pullNotification); + } +} + +void ASyncPort::endPull() +{ + pull = false; + // TODO: maybe remove all pending pull packets here +} + +void ASyncPort::processedPacket(GenericDataPacket *packet) +{ + int count = 0; + list::iterator i = sent.begin(); + while(i != sent.end()) + { + if(*i == packet) + { + count++; + i = sent.erase(i); + } + else i++; + } + assert(count == 1); + +#ifdef DEBUG_ASYNC_TRANSFER + cout << "port::processedPacket" << endl; +#endif + assert(packet->useCount == 0); + if(pull) + { + pullNotification.data = packet; + NotificationManager::the()->send(pullNotification); + } + else + { + stream->freePacket(packet); + } +} + +void ASyncPort::sendPacket(GenericDataPacket *packet) +{ + bool sendOk = false; + +#ifdef DEBUG_ASYNC_TRANSFER + cout << "port::sendPacket" << endl; +#endif + + if(packet->size > 0) + { + vector::iterator i; + for(i=subscribers.begin(); i != subscribers.end(); i++) + { + Notification n = *i; + n.data = packet; + packet->useCount++; +#ifdef DEBUG_ASYNC_TRANSFER + cout << "sending notification " << n.ID << endl; +#endif + NotificationManager::the()->send(n); + sendOk = true; + } + } + + if(sendOk) + sent.push_back(packet); + else + stream->freePacket(packet); +} + +//----------------------- Port interface ------------------------------------ + +void ASyncPort::connect(Port *xsource) +{ + arts_debug("port(%s)::connect",_name.c_str()); + + ASyncPort *source = xsource->asyncPort(); + assert(source); + addAutoDisconnect(xsource); + + Notification n; + n.receiver = parent->object(); + n.ID = notifyID; + n.internal = 0; + source->subscribers.push_back(n); +} + +void ASyncPort::disconnect(Port *xsource) +{ + arts_debug("port::disconnect"); + + ASyncPort *source = xsource->asyncPort(); + assert(source); + removeAutoDisconnect(xsource); + + // remove our subscription from the source object + vector::iterator si; + for(si = source->subscribers.begin(); si != source->subscribers.end(); si++) + { + if(si->receiver == parent->object()) + { + source->subscribers.erase(si); + return; + } + } + + // there should have been exactly one, so this shouldn't be reached + assert(false); +} + +ASyncPort *ASyncPort::asyncPort() +{ + return this; +} + +GenericAsyncStream *ASyncPort::receiveNetCreateStream() +{ + return stream->createNewStream(); +} + +NotificationClient *ASyncPort::receiveNetObject() +{ + return parent->object(); +} + +long ASyncPort::receiveNetNotifyID() +{ + return notifyID; +} + +// Network transparency +void ASyncPort::addSendNet(ASyncNetSend *netsend) +{ + Notification n; + n.receiver = netsend; + n.ID = netsend->notifyID(); + n.internal = 0; + subscribers.push_back(n); + netSenders.push_back(netsend); +} + +void ASyncPort::removeSendNet(ASyncNetSend *netsend) +{ + arts_return_if_fail(netsend != 0); + netSenders.remove(netsend); + + vector::iterator si; + for(si = subscribers.begin(); si != subscribers.end(); si++) + { + if(si->receiver == netsend) + { + subscribers.erase(si); + return; + } + } + arts_warning("Failed to remove ASyncNetSend (%p) from ASyncPort", netsend); +} + +void ASyncPort::setNetReceiver(ASyncNetReceive *receiver) +{ + arts_return_if_fail(receiver != 0); + + FlowSystemReceiver r = FlowSystemReceiver::_from_base(receiver->_copy()); + netReceiver = r; +} + +void ASyncPort::disconnectRemote(const string& dest) +{ + list::iterator i; + + for(i = netSenders.begin(); i != netSenders.end(); i++) + { + if((*i)->dest() == dest) + { + (*i)->disconnect(); + return; + } + } + arts_warning("failed to disconnect %s in ASyncPort", dest.c_str()); +} + +ASyncNetSend::ASyncNetSend(ASyncPort *ap, const std::string& dest) : ap(ap) +{ + _dest = dest; + ap->addSendNet(this); +} + +ASyncNetSend::~ASyncNetSend() +{ + while(!pqueue.empty()) + { + pqueue.front()->processed(); + pqueue.pop(); + } + if(ap) + { + ap->removeSendNet(this); + ap = 0; + } +} + +long ASyncNetSend::notifyID() +{ + return 1; +} + +void ASyncNetSend::notify(const Notification& notification) +{ + // got a packet? + assert(notification.ID == notifyID()); + GenericDataPacket *dp = (GenericDataPacket *)notification.data; + pqueue.push(dp); + + /* + * since packets are delivered asynchronously, and since disconnection + * involves communication, it might happen that we get a packet without + * actually being connected any longer - in that case, silently forget it + */ + if(!receiver.isNull()) + { + // put it into a custom data message and send it to the receiver + Buffer *buffer = receiver._allocCustomMessage(receiveHandlerID); + dp->write(*buffer); + receiver._sendCustomMessage(buffer); + } +} + +void ASyncNetSend::processed() +{ + assert(!pqueue.empty()); + pqueue.front()->processed(); + pqueue.pop(); +} + +void ASyncNetSend::setReceiver(FlowSystemReceiver newReceiver) +{ + receiver = newReceiver; + receiveHandlerID = newReceiver.receiveHandlerID(); +} + +void ASyncNetSend::disconnect() +{ + /* since disconnection will cause destruction (most likely immediate), + * we'll reference ourselves ... */ + _copy(); + + if(!receiver.isNull()) + { + FlowSystemReceiver r = receiver; + receiver = FlowSystemReceiver::null(); + r.disconnect(); + } + if(ap) + { + ap->removeSendNet(this); + ap = 0; + } + + _release(); +} + +string ASyncNetSend::dest() +{ + return _dest; +} + +/* dispatching function for custom message */ + +static void _dispatch_ASyncNetReceive_receive(void *object, Buffer *buffer) +{ + ((ASyncNetReceive *)object)->receive(buffer); +} + +ASyncNetReceive::ASyncNetReceive(ASyncPort *port, FlowSystemSender sender) +{ + port->setNetReceiver(this); + stream = port->receiveNetCreateStream(); + stream->channel = this; + this->sender = sender; + /* stream->_notifyID = _mkNotifyID(); */ + + gotPacketNotification.ID = port->receiveNetNotifyID(); + gotPacketNotification.receiver = port->receiveNetObject(); + gotPacketNotification.internal = 0; + _receiveHandlerID = + _addCustomMessageHandler(_dispatch_ASyncNetReceive_receive,this); +} + +ASyncNetReceive::~ASyncNetReceive() +{ + /* tell outstanding packets that we don't exist any longer */ + while(!sent.empty()) + { + sent.front()->channel = 0; + sent.pop_front(); + } + delete stream; +} + +long ASyncNetReceive::receiveHandlerID() +{ + return _receiveHandlerID; +} + +void ASyncNetReceive::receive(Buffer *buffer) +{ + GenericDataPacket *dp = stream->createPacket(512); + dp->read(*buffer); + dp->useCount = 1; + gotPacketNotification.data = dp; + NotificationManager::the()->send(gotPacketNotification); + sent.push_back(dp); +} + +/* + * It will happen that this routine is called in time critical situations, + * such as: while audio calculation is running, and must be finished in + * time. The routine is mostly harmless, because sender->processed() is + * a oneway function, which just queues the buffer for sending and returns + * back, so it should return at once. + * + * However there is an exception upon first call: when sender->processed() + * is called for the first time, the method processed has still to be looked + * up. Thus, a synchronous call to _lookupMethod is made. That means, upon + * first call, the method will send out an MCOP request and block until the + * remote process tells that id. + */ +void ASyncNetReceive::processedPacket(GenericDataPacket *packet) +{ + /* + * HACK! Upon disconnect, strange things will happen. One of them is + * that we might, for the reason of not being referenced any longer, + * cease to exist without warning. Another is that our nice "sender" + * reference will get a null reference without warning, see disconnect + * code (which will cause the attached stub to also disappear). As + * those objects (especially the stub) are not prepared for not + * being there any more in the middle of whatever they do, we here + * explicitly reference us, and them, *again*, so that no evil things + * will happen. A general solution for this would be garbage collection + * in a timer, but until this is implemented (if it ever will become + * implemented), we'll live with this hack. + */ + _copy(); + sent.remove(packet); + stream->freePacket(packet); + if(!sender.isNull()) + { + FlowSystemSender xsender = sender; + xsender.processed(); + } + _release(); +} + +void ASyncNetReceive::disconnect() +{ + if(!sender.isNull()) + { + FlowSystemSender s = sender; + sender = FlowSystemSender::null(); + s.disconnect(); + } +} + +void ASyncNetReceive::sendPacket(GenericDataPacket *) +{ + assert(false); +} + +void ASyncNetReceive::setPull(int, int) +{ + assert(false); +} + +void ASyncNetReceive::endPull() +{ + assert(false); +} diff --git a/flow/asyncschedule.h b/flow/asyncschedule.h new file mode 100644 index 0000000..0477c9a --- /dev/null +++ b/flow/asyncschedule.h @@ -0,0 +1,134 @@ + /* + + 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 ASYNCSCHEDULE_H +#define ASYNCSCHEDULE_H + +#include "gslschedule.h" +#include "datapacket.h" +#include "weakreference.h" + +#include + +/* + * BC - Status (2002-03-08): ASyncNetSend, ASyncNetReceive, ASyncPort. + * + * None of these classes is considered part of the public API. Do NOT use it + * in your apps. These are part of the implementation of libartsflow's + * StdFlowSystem, and subject to change with the needs of it. + */ + +namespace Arts { + +class ASyncPort; +class ASyncNetSend : public FlowSystemSender_skel +{ +protected: + ASyncPort *ap; + std::queue pqueue; + FlowSystemReceiver receiver; + long receiveHandlerID; + std::string _dest; + +public: + ASyncNetSend(ASyncPort *ap, const std::string& dest); + ~ASyncNetSend(); + long notifyID(); + std::string dest(); + + /* this overwrites the Object::notify function */ + void notify(const Notification& notification); + void processed(); + void setReceiver(FlowSystemReceiver receiver); + void disconnect(); +}; + +class ASyncNetReceive : public FlowSystemReceiver_skel, + public GenericDataChannel +{ +protected: + GenericAsyncStream *stream; + FlowSystemSender sender; + Notification gotPacketNotification; + std::list sent; + long _receiveHandlerID; + +public: + ASyncNetReceive(ASyncPort *port, FlowSystemSender sender); + ~ASyncNetReceive(); + + // GenericDataChannel interface + void processedPacket(GenericDataPacket *packet); + void sendPacket(GenericDataPacket *packet); + void setPull(int packets, int capacity); + void endPull(); + + // FlowSystemReceiver interface + + long receiveHandlerID(); + void disconnect(); + void receive(Buffer *buffer); // custom data receiver +}; + +class ASyncPort :public Port, public GenericDataChannel { +protected: + long notifyID; + std::vector subscribers; + std::list sent; + std::list netSenders; + WeakReference netReceiver; + + GenericAsyncStream *stream; + + bool pull; + Notification pullNotification; + +public: + // GenericDataChannel interface + void processedPacket(GenericDataPacket *packet); + void sendPacket(GenericDataPacket *packet); + void setPull(int packets, int capacity); + void endPull(); + + // Port interface + ASyncPort(const std::string& name, void *ptr, long flags, + StdScheduleNode* parent); + ~ASyncPort(); + + void connect(Port *port); + void disconnect(Port *port); + ASyncPort *asyncPort(); + + // Network transparency + void addSendNet(ASyncNetSend *netsend); // send + void removeSendNet(ASyncNetSend *netsend); + void disconnectRemote(const std::string& dest); + + long receiveNetNotifyID(); // receive + GenericAsyncStream *receiveNetCreateStream(); + NotificationClient *receiveNetObject(); + void setNetReceiver(ASyncNetReceive *receiver); +}; + +} + +#endif /* ASYNCSCHEDULE_H */ diff --git a/flow/audioio.cc b/flow/audioio.cc new file mode 100644 index 0000000..e5924ab --- /dev/null +++ b/flow/audioio.cc @@ -0,0 +1,165 @@ + /* + + 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 "audioio.h" +#include +#include +#include +#include + +using namespace Arts; +using namespace std; + +class Arts::AudioIOPrivate { +public: + map paramMap; + map paramStrMap; +}; + +AudioIO::AudioIO() :d(new AudioIOPrivate) +{ +} + +AudioIO::~AudioIO() +{ + delete d; +} + +int& AudioIO::param(AudioParam param) +{ + /* + * returns d->paramMap[param], initializes new elements with -1 (for those + * parameters not handled explicitly by the derived AudioIO* class). + */ + + map::iterator pi = d->paramMap.find(param); + if(pi == d->paramMap.end()) + { + int& result = d->paramMap[param]; + result = -1; + return result; + } + else return pi->second; +} + +string& AudioIO::paramStr(AudioParam param) +{ + return d->paramStrMap[param]; +} + +void AudioIO::setParamStr(AudioParam p, const char *value) +{ + paramStr(p) = value; +} + +void AudioIO::setParam(AudioParam p, int& value) +{ + param(p) = value; +} + +int AudioIO::getParam(AudioParam p) +{ + return param(p); +} + +const char *AudioIO::getParamStr(AudioParam p) +{ + return paramStr(p).c_str(); +} + +/* ---- factories ---- */ + +static list *audioIOFactories = 0; + +AudioIO *AudioIO::createAudioIO(const char *name) +{ + if(audioIOFactories) + { + list::iterator i; + for(i = audioIOFactories->begin(); i != audioIOFactories->end(); i++) + { + AudioIOFactory *factory = *i; + + if(strcmp(factory->name(),name) == 0) + return factory->createAudioIO(); + } + } + return 0; +} + +int AudioIO::queryAudioIOCount() +{ + return audioIOFactories->size(); +} + +int AudioIO::queryAudioIOParam(int /*audioIO*/, AudioParam /*p*/) +{ + return 0; +} + +const char *AudioIO::queryAudioIOParamStr(int audioIO, AudioParam p) +{ + list::iterator i = audioIOFactories->begin(); + + while(audioIO && i != audioIOFactories->end()) { i++; audioIO--; } + if(i == audioIOFactories->end()) return 0; + + switch(p) + { + case name: + return (*i)->name(); + case fullName: + return (*i)->fullName(); + default: + return 0; + } +} + +void AudioIO::addFactory(AudioIOFactory *factory) +{ + if(!audioIOFactories) + audioIOFactories = new list; + + audioIOFactories->push_back(factory); +} + +void AudioIO::removeFactory(AudioIOFactory *factory) +{ + assert(audioIOFactories); + + audioIOFactories->remove(factory); + if(audioIOFactories->empty()) + { + delete audioIOFactories; + audioIOFactories = 0; + } +} + +void AudioIOFactory::startup() +{ + AudioIO::addFactory(this); +} + +void AudioIOFactory::shutdown() +{ + AudioIO::removeFactory(this); +} diff --git a/flow/audioio.h b/flow/audioio.h new file mode 100644 index 0000000..ade96e2 --- /dev/null +++ b/flow/audioio.h @@ -0,0 +1,145 @@ + /* + + 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 ARTS_AUDIOIO_H +#define ARTS_AUDIOIO_H + +#include +#include +#include "arts_export.h" +/* + * BC - Status (2002-03-08): AudioIO, AudioIOFactory + * + * Right now, these classes are considered an implementation detail. No binary + * compatibility guaranteed, its safe to add virtual methods when required. + */ + +/* + * The AudioIO class - it is used as follows: + * + * 1. check the settings with getParam, modify what you don't like + * 2. call open -> print getInfo(lastError) if it failed (returned false) + * 3. check whether the settings are still what you expected them to + * be (as the AudioIO class will maybe only know after open if the + * requested parameters can be achieved) + */ + +namespace Arts { + +class AudioIOPrivate; +class AudioIOFactory; + +class ARTS_EXPORT AudioIO { +private: + class AudioIOPrivate *d; + +public: + enum AudioParam { +/* the following has to be supported */ + samplingRate = 1, /* usually 4000..48000 (Hz) */ + channels = 2, /* usually 1 (mono) or 2 (stereo) */ + format = 3, /* 8 = 8bit unsigned + * 16 = 16bit signed little endian + * 17 = 16bit signed big endian + * 32 = 32bit float */ + +/* the following -can- be supported (value=-1 if they are not) */ + direction = 101, /* 1 = read, 2 = write, 3 = read|write */ + fragmentCount = 102,/* usually 3..16 (to control latency) */ + fragmentSize = 103, /* usually 256,512,...8192 (a 2^N value) */ + canRead = 104, /* the amount of bytes that can be read */ + canWrite = 105, /* the amount of bytes that can be written */ + selectReadFD = 106, /* filedescriptor to be select()ed on for reading */ + selectWriteFD = 107,/* filedescriptor to be select()ed on for writing */ + autoDetect = 108, /* 0 = don't use this as default + * 1 or higher = preference of using this as default + * + * if nothing else is specified, aRts asks all + * available AudioIO classes for the autoDetect + * value and chooses the one which returned the + * highest number */ + +/* string parameters that have to be supported */ + lastError = 201, /* the last error message as human readable text */ + +/* string parameters that -can- be supported */ + deviceName = 301, /* name of the device to open */ + +/* class parameters: same semantics as above */ + name = 1201, /* name of the driver (i.e. oss) */ + fullName = 1202 /* full name (i.e. Open Sound System) */ + }; + + enum { + directionRead = 1, + directionWrite = 2, + directionReadWrite = 3 + }; + + AudioIO(); + virtual ~AudioIO(); + + virtual void setParamStr(AudioParam param, const char *value); + virtual void setParam(AudioParam param, int& value); + virtual int getParam(AudioParam param); + virtual const char *getParamStr(AudioParam param); + + virtual bool open() = 0; + virtual void close() = 0; + virtual int read(void *buffer, int size) = 0; + virtual int write(void *buffer, int size) = 0; + +/* ---- factory querying stuff ---- */ + static int queryAudioIOCount(); + static int queryAudioIOParam(int audioIO, AudioParam param); + static const char *queryAudioIOParamStr(int audioIO, AudioParam param); + +/* ---- factory stuff ---- */ + static AudioIO *createAudioIO(const char *name); + static void addFactory(AudioIOFactory *factory); + static void removeFactory(AudioIOFactory *factory); + +protected: + int& param(AudioParam param); + std::string& paramStr(AudioParam param); +}; + +class ARTS_EXPORT AudioIOFactory : public StartupClass { +public: + void startup(); + void shutdown(); + virtual AudioIO *createAudioIO() = 0; + virtual const char *name() = 0; + virtual const char *fullName() = 0; +}; + +} + +#define REGISTER_AUDIO_IO(impl,implName,implFullName) \ + static class AudioIOFactory ## impl : public AudioIOFactory { \ + public: \ + AudioIO *createAudioIO() { return new impl(); } \ + virtual const char *name() { return implName; } \ + virtual const char *fullName() { return implFullName; } \ + } The_ ## impl ## _Factory /* <- add semicolon when calling this macro */ + +#endif /* ARTS_AUDIOIO_H */ diff --git a/flow/audioioaix.cc b/flow/audioioaix.cc new file mode 100644 index 0000000..f36e9db --- /dev/null +++ b/flow/audioioaix.cc @@ -0,0 +1,390 @@ +/* + + Copyright (C) 2001 Carsten Griwodz + griff@ifi.uio.no + + 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 +#endif + +#ifdef _AIX + +/* + * The audio header files exist even if there is not soundcard the + * the AIX machine. You won't be able to compile this code on AIX3 + * which had ACPA support, so /dev/acpa is not checked here. + * I have no idea whether the Ultimedia Audio Adapter is actually + * working or what it is right now. + * For PCI machines including PowerSeries 850, baud or paud should + * work. The DSP (MWave?) of the 850 laptops may need microcode + * download. This is not implemented. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#undef BIG_ENDIAN +#include + +#ifndef AUDIO_BIG_ENDIAN +#define AUDIO_BIG_ENDIAN BIG_ENDIAN +#endif + +#include "debug.h" +#include "audioio.h" + +namespace Arts { + +class AudioIOAIX : public AudioIO { + int openDevice(); + +protected: + int audio_fd; + +public: + AudioIOAIX(); + + 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(AudioIOAIX,"paud","Personal Audio Device"); +}; + +using namespace std; +using namespace Arts; + +int AudioIOAIX::openDevice() +{ + char devname[14]; + int fd; + for ( int dev=0; dev<4; dev++ ) + { + for ( int chan=1; chan<8; chan++ ) + { + sprintf(devname,"/dev/paud%d/%d",dev,chan); + fd = ::open (devname, O_WRONLY, 0); + if ( fd >= 0 ) + { + paramStr(deviceName) = devname; + return fd; + } + sprintf(devname,"/dev/baud%d/%d",dev,chan); + fd = ::open (devname, O_WRONLY, 0); + if ( fd >= 0 ) + { + paramStr(deviceName) = devname; + return fd; + } + } + } + return -1; +} + +AudioIOAIX::AudioIOAIX() +{ + int fd = openDevice(); + if( fd >= 0 ) + { + audio_status audioStatus; + memset( &audioStatus, 0, sizeof(audio_status) ); + ioctl(fd, AUDIO_STATUS, &audioStatus); + + audio_buffer audioBuffer; + memset( &audioBuffer, 0, sizeof(audio_buffer) ); + ioctl(fd, AUDIO_BUFFER, &audioBuffer); + + ::close( fd ); + + /* + * default parameters + */ + param(samplingRate) = audioStatus.srate; + param(fragmentSize) = audioStatus.bsize; + param(fragmentCount) = audioBuffer.write_buf_cap / audioStatus.bsize; + param(channels) = audioStatus.channels; + param(direction) = 2; + + param(format) = ( audioStatus.bits_per_sample==8 ) ? 8 + : ( ( audioStatus.flags & AUDIO_BIG_ENDIAN ) ? 17 : 16 ); + } +} + +bool AudioIOAIX::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; + + switch( param(direction) ) + { + case 1 : mode = O_RDONLY | O_NDELAY; break; + case 2 : mode = O_WRONLY | O_NDELAY; break; + case 3 : + _error = "open device twice to RDWR"; + return false; + default : + _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; + } + + if( (_channels!=1) && (_channels!=2) ) + { + _error = "internal error; set channels to 1 (mono) or 2 (stereo)"; + + close(); + return false; + } + + // int requeststereo = stereo; + + // int speed = _samplingRate; + + audio_init audioInit; + memset( &audioInit, 0, sizeof(audio_init) ); + audioInit.srate = _samplingRate; + audioInit.bits_per_sample = ((_format==8)?8:16); + audioInit.bsize = _fragmentSize; + audioInit.mode = PCM; + audioInit.channels = _channels; + audioInit.flags = 0; + audioInit.flags |= (_format==17) ? AUDIO_BIG_ENDIAN : 0; + audioInit.flags |= (_format==8) ? 0 : SIGNED; + audioInit.operation = (param(direction)==1) ? RECORD : PLAY; + + if ( ioctl(audio_fd, AUDIO_INIT, &audioInit) < 0 ) + { + _error = "AUDIO_INIT failed - "; + _error += strerror(errno); + switch ( audioInit.rc ) + { + case 1 : + _error += "Couldn't set audio format: DSP can't do play requests"; + break; + case 2 : + _error += "Couldn't set audio format: DSP can't do record requests"; + break; + case 4 : + _error += "Couldn't set audio format: request was invalid"; + break; + case 5 : + _error += "Couldn't set audio format: conflict with open's flags"; + break; + case 6 : + _error += "Couldn't set audio format: out of DSP MIPS or memory"; + break; + default : + _error += "Couldn't set audio format: not documented in sys/audio.h"; + break; + } + + close(); + return false; + } + + if (audioInit.channels != _channels) + { + _error = "audio device doesn't support number of requested channels"; + close(); + return false; + } + + switch( _format ) + { + case 8 : + if (audioInit.flags&AUDIO_BIG_ENDIAN==1) + { + _error = "setting little endian format failed"; + close(); + return false; + } + if (audioInit.flags&SIGNED==1) + { + _error = "setting unsigned format failed"; + close(); + return false; + } + break; + case 16 : + if (audioInit.flags&AUDIO_BIG_ENDIAN==1) + { + _error = "setting little endian format failed"; + close(); + return false; + } + if (audioInit.flags&SIGNED==0) + { + _error = "setting signed format failed"; + close(); + return false; + } + break; + case 17 : + if (audioInit.flags&AUDIO_BIG_ENDIAN==0) + { + _error = "setting big endian format failed"; + close(); + return false; + } + if (audioInit.flags&SIGNED==0) + { + _error = "setting signed format failed"; + close(); + return false; + } + break; + default : + break; + } + + /* + * 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(audioInit.srate - _samplingRate) > tolerance) + { + _error = "can't set requested samplingrate"; + + char details[80]; + sprintf(details," (requested rate %d, got rate %ld)", + _samplingRate, audioInit.srate); + _error += details; + + close(); + return false; + } + _samplingRate = audioInit.srate; + + _fragmentSize = audioInit.bsize; + _fragmentCount = audioInit.bsize / audioInit.bits_per_sample; + + audio_buffer buffer_info; + ioctl(audio_fd, AUDIO_BUFFER, &buffer_info); + _fragmentCount = buffer_info.write_buf_cap / audioInit.bsize; + + + 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); + + return true; +} + +void AudioIOAIX::close() +{ + ::close(audio_fd); +} + +void AudioIOAIX::setParam(AudioParam p, int& value) +{ + param(p) = value; +} + +int AudioIOAIX::getParam(AudioParam p) +{ + audio_buffer info; + switch(p) + { + case canRead: + ioctl(audio_fd, AUDIO_BUFFER, &info); + return (info.read_buf_cap - info.read_buf_size); + break; + + case canWrite: + ioctl(audio_fd, AUDIO_BUFFER, &info); + return (info.write_buf_cap - info.write_buf_size); + break; + + case selectReadFD: + return (param(direction) & directionRead)?audio_fd:-1; + break; + + case selectWriteFD: + return (param(direction) & directionWrite)?audio_fd:-1; + break; + + case autoDetect: + /* You may prefer OSS if it works, e.g. on 43P 240 + * or you may prefer UMS, if anyone bothers to write + * a module for it. + */ + return 2; + break; + + default: + return param(p); + break; + } +} + +int AudioIOAIX::read(void *buffer, int size) +{ + arts_assert(audio_fd != 0); + return ::read(audio_fd,buffer,size); +} + +int AudioIOAIX::write(void *buffer, int size) +{ + arts_assert(audio_fd != 0); + return ::write(audio_fd,buffer,size); +} + +#endif + diff --git a/flow/audioioalsa.cc b/flow/audioioalsa.cc new file mode 100644 index 0000000..303db16 --- /dev/null +++ b/flow/audioioalsa.cc @@ -0,0 +1,561 @@ + /* + + Copyright (C) 2000,2001 Jozef Kosoru + jozef.kosoru@pobox.sk + (C) 2000,2001 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 +#endif + +/** + * only compile 'alsa' AudioIO class if configure thinks it is a good idea + */ +#ifdef HAVE_LIBASOUND + +#ifdef HAVE_ALSA_ASOUNDLIB_H +#include +#elif defined(HAVE_SYS_ASOUNDLIB_H) +#include +#endif + +#include +#include +#include +#include + +#ifdef HAVE_SYS_SELECT_H +#include // Needed on some systems. +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "debug.h" +#include "audioio.h" + +namespace Arts { + +class AudioIOALSA : public AudioIO { +protected: + int audio_read_fd; + int audio_write_fd; + int requestedFragmentSize; + int requestedFragmentCount; + + enum BufferMode{block, stream}; + int m_card; + int m_device; + int m_format; + BufferMode m_bufferMode; + + snd_pcm_t *m_pcm_handle; + snd_pcm_channel_info_t m_cinfo; + snd_pcm_format_t m_cformat; + snd_pcm_channel_params_t m_params; + snd_pcm_channel_setup_t m_setup; + + int setPcmParams(const int channel); + void checkCapabilities(); + +public: + AudioIOALSA(); + + 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(AudioIOALSA,"alsa","Advanced Linux Sound Architecture"); +}; + +using namespace std; +using namespace Arts; + +AudioIOALSA::AudioIOALSA() +{ + param(samplingRate) = 44100; + paramStr(deviceName) = "/dev/dsp"; //!! alsa doesn't need this + requestedFragmentSize = param(fragmentSize) = 1024; + requestedFragmentCount = param(fragmentCount) = 7; + param(channels) = 2; + param(direction) = directionWrite; + + /* + * default parameters + */ + m_card = snd_defaults_pcm_card(); //!! need interface !! + m_device = snd_defaults_pcm_device(); //!! +#ifdef WORDS_BIGENDIAN + m_format = SND_PCM_SFMT_S16_BE; +#else + m_format = SND_PCM_SFMT_S16_LE; +#endif + m_bufferMode = block; //block/stream (stream mode doesn't work yet) + + if(m_card >= 0) { + char* cardname = 0; + + if(snd_card_get_name(m_card, &cardname) == 0 && cardname != 0) + { + //!! thats not what devicename is intended to do + //!! devicename is an input information into + //!! the "driver", to select which card to use + //!! not an output information + paramStr(deviceName) = cardname; + free(cardname); + } + } +} + +bool AudioIOALSA::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& _direction = param(direction); + int& _format = param(format); + + /* + * initialize format - TODO: implement fallback (i.e. if no format given, + * it should try 16bit first, then fall back to 8bit) + */ + switch(_format) + { + default: _format = 16; + + case 16: // 16bit, signed little endian + m_format = SND_PCM_SFMT_S16_LE; + break; + + case 17: // 16bit, signed big endian + m_format = SND_PCM_SFMT_S16_BE; + break; + + case 8: // 8bit, unsigned + m_format = SND_PCM_SFMT_U8; + break; + } + + /* open pcm device */ + int mode = SND_PCM_OPEN_NONBLOCK; + + if(_direction == directionReadWrite) + mode |= SND_PCM_OPEN_DUPLEX; + else if(_direction == directionWrite) + mode |= SND_PCM_OPEN_PLAYBACK; + else + { + _error = "invalid direction"; + return false; + } + + int err; + if((err = snd_pcm_open(&m_pcm_handle, m_card, m_device, mode)) < 0) { + _error = "device: "; + _error += _deviceName.c_str(); + _error += " can't be opened ("; + _error += snd_strerror(err); + _error += ")"; + return false; + } + else { + artsdebug("ALSA driver: %s", _deviceName.c_str()); + } + + snd_pcm_nonblock_mode(m_pcm_handle, 0); + + /* flush buffers */ + (void)snd_pcm_capture_flush(m_pcm_handle); + if(_direction & directionRead) + (void)snd_pcm_channel_flush(m_pcm_handle, SND_PCM_CHANNEL_CAPTURE); + if(_direction & directionWrite) + (void)snd_pcm_channel_flush(m_pcm_handle, SND_PCM_CHANNEL_PLAYBACK); + + /* check device capabilities */ + checkCapabilities(); + + /* set the fragment settings to what the user requested */ + _fragmentSize = requestedFragmentSize; + _fragmentCount = requestedFragmentCount; + + /* set PCM communication parameters */ + if((_direction & directionRead) && setPcmParams(SND_PCM_CHANNEL_CAPTURE)) + return false; + if((_direction & directionWrite) && setPcmParams(SND_PCM_CHANNEL_PLAYBACK)) + return false; + + /* prepare channel */ + if((_direction & directionRead) && + snd_pcm_channel_prepare(m_pcm_handle, SND_PCM_CHANNEL_CAPTURE) < 0) + { + _error = "Unable to prepare capture channel!"; + return false; + } + if((_direction & directionWrite) && + snd_pcm_channel_prepare(m_pcm_handle, SND_PCM_CHANNEL_PLAYBACK) < 0) + { + _error = "Unable to prepare playback channel!"; + return false; + } + + /* obtain current PCM setup (may differ from requested one) */ + (void)memset(&m_setup, 0, sizeof(m_setup)); + + m_setup.channel = SND_PCM_CHANNEL_PLAYBACK; + if(snd_pcm_channel_setup(m_pcm_handle, &m_setup) < 0) { + _error = "Unable to obtain channel setup!"; + return false; + } + + /* check samplerate */ + const int tolerance = _samplingRate/10+1000; + if(abs(m_setup.format.rate-_samplingRate) > tolerance) + { + _error = "Can't set requested sampling rate!"; + char details[80]; + sprintf(details," (requested rate %d, got rate %d)", + _samplingRate, m_setup.format.rate); + _error += details; + return false; + } + _samplingRate = m_setup.format.rate; + + /* check format */ + if(m_setup.format.format != m_format) { + _error = "Can't set requested format:"; + _error += snd_pcm_get_format_name(m_format); + return false; + } + + /* check voices */ + if(m_setup.format.voices != _channels) { + _error = "Audio device doesn't support number of requested channels!"; + return false; + } + + /* update fragment settings with what we got */ + switch(m_bufferMode) { + case block: + _fragmentSize = m_setup.buf.block.frag_size; + _fragmentCount = m_setup.buf.block.frags_max-1; + break; + case stream: + _fragmentSize = m_setup.buf.stream.queue_size; + _fragmentCount = 1; + break; + } + + 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); + + /* obtain PCM file descriptor(s) */ + audio_read_fd = audio_write_fd = -1; + + if(_direction & directionRead) + audio_read_fd = snd_pcm_file_descriptor(m_pcm_handle, + SND_PCM_CHANNEL_CAPTURE); + if(_direction & directionWrite) + audio_write_fd = snd_pcm_file_descriptor(m_pcm_handle, + SND_PCM_CHANNEL_PLAYBACK); + + /* start recording */ + if((_direction & directionRead) && snd_pcm_capture_go(m_pcm_handle)) { + _error = "Can't start recording!"; + return false; + } + + return true; +} + +void AudioIOALSA::close() +{ + int& _direction = param(direction); + if(_direction & directionRead) + (void)snd_pcm_channel_flush(m_pcm_handle, SND_PCM_CHANNEL_CAPTURE); + if(_direction & directionWrite) + (void)snd_pcm_channel_flush(m_pcm_handle, SND_PCM_CHANNEL_PLAYBACK); + (void)snd_pcm_close(m_pcm_handle); +} + +void AudioIOALSA::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 AudioIOALSA::getParam(AudioParam p) +{ + snd_pcm_channel_status_t status; + (void)memset(&status, 0, sizeof(status)); + + switch(p) + { + case canRead: + status.channel = SND_PCM_CHANNEL_CAPTURE; + if(snd_pcm_channel_status(m_pcm_handle, &status) < 0) { + arts_warning("Capture channel status error!"); + return -1; + } + return status.free; + break; + + case canWrite: + status.channel = SND_PCM_CHANNEL_PLAYBACK; + if(snd_pcm_channel_status(m_pcm_handle, &status) < 0) { + arts_warning("Playback channel status error!"); + return -1; + } + return status.free; + break; + + case selectReadFD: + return audio_read_fd; + break; + + case selectWriteFD: + return audio_write_fd; + break; + + case autoDetect: + /* + * that the ALSA driver could be compiled doesn't say anything + * about whether it will work (the user might be using an OSS + * kernel driver) so we'll use a value less than the OSS one + * here, because OSS will most certainly work (ALSA's OSS emu) + */ + return 5; + break; + + default: + return param(p); + break; + } +} + +int AudioIOALSA::read(void *buffer, int size) +{ + int length; + do { + length = snd_pcm_read(m_pcm_handle, buffer, size); + } while (length == -EINTR); + if(length == -EPIPE) { + snd_pcm_channel_status_t status; + (void)memset(&status, 0, sizeof(status)); + status.channel = SND_PCM_CHANNEL_CAPTURE; + if(snd_pcm_channel_status(m_pcm_handle, &status) < 0) { + arts_info("Capture channel status error!"); + return -1; + } + else if(status.status == SND_PCM_STATUS_RUNNING) { + length = 0; + } + else if(status.status == SND_PCM_STATUS_OVERRUN) { + artsdebug("Overrun at position: %d" ,status.scount); + if(snd_pcm_channel_prepare(m_pcm_handle, SND_PCM_CHANNEL_CAPTURE)<0) + { + arts_info("Overrun: capture prepare error!"); + return -1; + } + length = 0; + } + else { + arts_info("Unknown capture error!"); + return -1; + } + } + else if(length < 0) { + arts_info("Capture error: %s", snd_strerror(length)); + return -1; + } + return length; +} + +int AudioIOALSA::write(void *buffer, int size) +{ + int length; + while((length = snd_pcm_write(m_pcm_handle, buffer, size)) != size) { + if (length == -EINTR) + continue; // Try again + snd_pcm_channel_status_t status; + (void)memset(&status, 0, sizeof(status)); + status.channel = SND_PCM_CHANNEL_PLAYBACK; + + if(snd_pcm_channel_status(m_pcm_handle, &status) < 0) { + arts_warning("Playback channel status error!"); + return -1; + } + else if(status.status == SND_PCM_STATUS_UNDERRUN) { + artsdebug("Underrun at position: %d", status.scount); + if(snd_pcm_channel_prepare(m_pcm_handle, SND_PCM_CHANNEL_PLAYBACK) + < 0) { + arts_warning("Underrun: playback prepare error!"); + return -1; + } + } + else { + arts_warning("Unknown playback error!"); + return -1; + } + } + return size; +} + +int AudioIOALSA::setPcmParams(const int channel) +{ + int &_samplingRate = param(samplingRate); + int &_channels = param(channels); + int &_fragmentSize = param(fragmentSize); + int &_fragmentCount = param(fragmentCount); + + (void)memset(&m_cformat, 0, sizeof(m_cformat)); + m_cformat.interleave = 1; + m_cformat.format = m_format; + m_cformat.rate = _samplingRate; + m_cformat.voices = _channels; + + (void)memset(&m_params, 0, sizeof(m_params)); + switch(m_bufferMode){ + case stream: + m_params.mode=SND_PCM_MODE_STREAM; + break; + case block: + m_params.mode=SND_PCM_MODE_BLOCK; + break; + } + m_params.channel=channel; + (void)memcpy(&m_params.format, &m_cformat, sizeof(m_cformat)); + if(channel==SND_PCM_CHANNEL_CAPTURE){ + m_params.start_mode=SND_PCM_START_GO; + m_params.stop_mode=SND_PCM_STOP_ROLLOVER; + } + else{ //SND_PCM_CHANNEL_PLAYBACK + m_params.start_mode= (m_bufferMode==block) ? SND_PCM_START_FULL : SND_PCM_START_DATA; + m_params.stop_mode=SND_PCM_STOP_ROLLOVER; // SND_PCM_STOP_STOP + //use this ^^^ if you want to track underruns + } + + switch(m_bufferMode){ + case stream: + m_params.buf.stream.queue_size=1024*1024; //_fragmentSize*_fragmentCount; + m_params.buf.stream.fill=SND_PCM_FILL_SILENCE_WHOLE; + m_params.buf.stream.max_fill=1024; + break; + case block: + m_params.buf.block.frag_size=_fragmentSize; + if(channel==SND_PCM_CHANNEL_CAPTURE){ + m_params.buf.block.frags_max=1; + m_params.buf.block.frags_min=1; + } + else{ //SND_PCM_CHANNEL_PLAYBACK + m_params.buf.block.frags_max=_fragmentCount+1; + m_params.buf.block.frags_min=1; + } + } + if(snd_pcm_channel_params(m_pcm_handle, &m_params)<0){ + paramStr(lastError) = "Unable to set channel params!"; + return 1; + } + else { + return 0; + } +} + +void AudioIOALSA::checkCapabilities() +{ + snd_pcm_info_t info; + (void)memset(&info, 0, sizeof(info)); + if(!snd_pcm_info(m_pcm_handle, &info)) { + string flags = ""; + if(info.flags & SND_PCM_INFO_PLAYBACK) flags += "playback "; + if(info.flags & SND_PCM_INFO_CAPTURE) flags += "capture "; + if(info.flags & SND_PCM_INFO_DUPLEX) flags += "duplex "; + if(info.flags & SND_PCM_INFO_DUPLEX_RATE) flags += "duplex_rate "; + artsdebug(" type:%d id:%s\n" + " flags:%s\n" + " playback_subdevices:%d capture_subdevices:%d", + info.type, info.id, + flags.c_str(), + info.playback+1, info.capture+1); + } + else { + arts_warning("Can't get device info!"); //not fatal error + } + + (void)memset(&m_cinfo, 0, sizeof(m_cinfo)); + m_cinfo.channel = SND_PCM_CHANNEL_PLAYBACK; + if(!snd_pcm_channel_info(m_pcm_handle, &m_cinfo)) { + string flags = ""; + if(m_cinfo.flags & SND_PCM_CHNINFO_MMAP) flags += "mmap "; + if(m_cinfo.flags & SND_PCM_CHNINFO_STREAM) flags += "stream "; + if(m_cinfo.flags & SND_PCM_CHNINFO_BLOCK) flags += "block "; + if(m_cinfo.flags & SND_PCM_CHNINFO_BATCH) flags += "batch "; + if(m_cinfo.flags & SND_PCM_CHNINFO_INTERLEAVE) flags += "interleave "; + if(m_cinfo.flags & SND_PCM_CHNINFO_NONINTERLEAVE) flags += "noninterleave "; + if(m_cinfo.flags & SND_PCM_CHNINFO_BLOCK_TRANSFER) flags += "block_transfer "; + if(m_cinfo.flags & SND_PCM_CHNINFO_OVERRANGE) flags += "overrange "; + if(m_cinfo.flags & SND_PCM_CHNINFO_MMAP_VALID) flags += "mmap_valid "; + if(m_cinfo.flags & SND_PCM_CHNINFO_PAUSE) flags += "pause "; + + artsdebug(" subdevice:%d\n" + " flags:%s\n" + " min_rate:%d max_rate:%d\n" + " buffer_size:%d min_fragment_size:%d max_fragment_size:%d\n" + " fragment_align:%d fifo_size:%d transfer_block_size:%d\n" + " mmap_size:%d", + m_cinfo.subdevice, + flags.c_str(), + m_cinfo.min_rate, m_cinfo.max_rate, + m_cinfo.buffer_size, m_cinfo.min_fragment_size, m_cinfo.max_fragment_size, + m_cinfo.fragment_align, m_cinfo.fifo_size, m_cinfo.transfer_block_size, + m_cinfo.mmap_size); + } + else { + arts_warning("Can't get channel info!"); //not fatal error + } +} + +#endif /* HAVE_LIBASOUND */ diff --git a/flow/audioioalsa9.cc b/flow/audioioalsa9.cc new file mode 100644 index 0000000..8367da6 --- /dev/null +++ b/flow/audioioalsa9.cc @@ -0,0 +1,590 @@ + /* + + Copyright (C) 2001 Takashi Iwai + Copyright (C) 2004 Allan Sandfeld Jensen + + based on audioalsa.cc: + Copyright (C) 2000,2001 Jozef Kosoru + jozef.kosoru@pobox.sk + (C) 2000,2001 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 +#endif + +/** + * only compile 'alsa' AudioIO class if configure thinks it is a good idea + */ +#ifdef HAVE_LIBASOUND2 + +#ifdef HAVE_ALSA_ASOUNDLIB_H +#include +#elif defined(HAVE_SYS_ASOUNDLIB_H) +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "debug.h" +#include "audioio.h" +#include "audiosubsys.h" +#include "dispatcher.h" +#include "iomanager.h" + +namespace Arts { + +class AudioIOALSA : public AudioIO, public IONotify { +protected: + // List of file descriptors + struct poll_descriptors { + poll_descriptors() : nfds(0), pfds(0) {}; + int nfds; + struct pollfd *pfds; + } audio_write_pds, audio_read_pds; + + snd_pcm_t *m_pcm_playback; + snd_pcm_t *m_pcm_capture; + snd_pcm_format_t m_format; + int m_period_size, m_periods; + + void startIO(); + int setPcmParams(snd_pcm_t *pcm); + static int poll2iomanager(int pollTypes); + static int iomanager2poll(int ioTypes); + void getDescriptors(snd_pcm_t *pcm, poll_descriptors *pds); + void watchDescriptors(poll_descriptors *pds); + + void notifyIO(int fd, int types); + + int xrun(snd_pcm_t *pcm); +#ifdef HAVE_SND_PCM_RESUME + int resume(snd_pcm_t *pcm); +#endif + +public: + AudioIOALSA(); + + 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(AudioIOALSA,"alsa","Advanced Linux Sound Architecture"); +} + +using namespace std; +using namespace Arts; + +AudioIOALSA::AudioIOALSA() +{ + param(samplingRate) = 44100; + paramStr(deviceName) = "default"; // ALSA pcm device name - not file name + param(fragmentSize) = 1024; + param(fragmentCount) = 7; + param(channels) = 2; + param(direction) = directionWrite; + param(format) = 16; + /* + * default parameters + */ + m_format = SND_PCM_FORMAT_S16_LE; + m_pcm_playback = NULL; + m_pcm_capture = NULL; +} + +bool AudioIOALSA::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& _direction = param(direction); + int& _format = param(format); + + m_pcm_playback = NULL; + m_pcm_capture = NULL; + + /* initialize format */ + switch(_format) { + case 16: // 16bit, signed little endian + m_format = SND_PCM_FORMAT_S16_LE; + break; + case 17: // 16bit, signed big endian + m_format = SND_PCM_FORMAT_S16_BE; + break; + case 8: // 8bit, unsigned + m_format = SND_PCM_FORMAT_U8; + break; + default: // test later + m_format = SND_PCM_FORMAT_UNKNOWN; + break; + } + + /* open pcm device */ + int err; + if (_direction & directionWrite) { + if ((err = snd_pcm_open(&m_pcm_playback, _deviceName.c_str(), + SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + _error = "device: "; + _error += _deviceName.c_str(); + _error += " can't be opened for playback ("; + _error += snd_strerror(err); + _error += ")"; + return false; + } + snd_pcm_nonblock(m_pcm_playback, 0); + } + if (_direction & directionRead) { + if ((err = snd_pcm_open(&m_pcm_capture, _deviceName.c_str(), + SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + _error = "device: "; + _error += _deviceName.c_str(); + _error += " can't be opened for capture ("; + _error += snd_strerror(err); + _error += ")"; + snd_pcm_close(m_pcm_playback); + return false; + } + snd_pcm_nonblock(m_pcm_capture, 0); + } + + artsdebug("ALSA driver: %s", _deviceName.c_str()); + + /* check device capabilities */ + // checkCapabilities(); + + /* set PCM communication parameters */ + if (((_direction & directionWrite) && setPcmParams(m_pcm_playback)) || + ((_direction & directionRead) && setPcmParams(m_pcm_capture))) { + snd_pcm_close(m_pcm_playback); + snd_pcm_close(m_pcm_capture); + return false; + } + + 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); + + + startIO(); + /* restore the format value */ + switch (m_format) { + case SND_PCM_FORMAT_S16_LE: + _format = 16; + break; + case SND_PCM_FORMAT_S16_BE: + _format = 17; + break; + case SND_PCM_FORMAT_U8: + _format = 8; + break; + default: + _error = "Unknown PCM format"; + return false; + } + + /* start recording */ + if (_direction & directionRead) + snd_pcm_start(m_pcm_capture); + + return true; +} + +void AudioIOALSA::close() +{ + arts_debug("Closing ALSA-driver"); + int& _direction = param(direction); + if ((_direction & directionRead) && m_pcm_capture) { + (void)snd_pcm_drop(m_pcm_capture); + (void)snd_pcm_close(m_pcm_capture); + m_pcm_capture = NULL; + } + if ((_direction & directionWrite) && m_pcm_playback) { + (void)snd_pcm_drop(m_pcm_playback); + (void)snd_pcm_close(m_pcm_playback); + m_pcm_playback = NULL; + } + Dispatcher::the()->ioManager()->remove(this, IOType::all); + + delete[] audio_read_pds.pfds; + delete[] audio_write_pds.pfds; + audio_read_pds.pfds = NULL; audio_write_pds.pfds = NULL; + audio_read_pds.nfds = 0; audio_write_pds.nfds = 0; +} + +void AudioIOALSA::setParam(AudioParam p, int& value) +{ + param(p) = value; + if (m_pcm_playback != NULL) { + setPcmParams(m_pcm_playback); + } + if (m_pcm_capture != NULL) { + setPcmParams(m_pcm_capture); + } +} + +int AudioIOALSA::getParam(AudioParam p) +{ + snd_pcm_sframes_t avail; + switch(p) { + + case canRead: + if (! m_pcm_capture) return -1; + while ((avail = snd_pcm_avail_update(m_pcm_capture)) < 0) { + if (avail == -EPIPE) + avail = xrun(m_pcm_capture); +#ifdef HAVE_SND_PCM_RESUME + else if (avail == -ESTRPIPE) + avail = resume(m_pcm_capture); +#endif + if (avail < 0) { + arts_info("Capture error: %s", snd_strerror(avail)); + return -1; + } + } + return snd_pcm_frames_to_bytes(m_pcm_capture, avail); + + case canWrite: + if (! m_pcm_playback) return -1; + while ((avail = snd_pcm_avail_update(m_pcm_playback)) < 0) { + if (avail == -EPIPE) + avail = xrun(m_pcm_playback); +#ifdef HAVE_SND_PCM_RESUME + else if (avail == -ESTRPIPE) + avail = resume(m_pcm_playback); +#endif + if (avail < 0) { + arts_info("Playback error: %s", snd_strerror(avail)); + return -1; + } + } + return snd_pcm_frames_to_bytes(m_pcm_playback, avail); + + case selectReadFD: + return -1; + + case selectWriteFD: + return -1; + + case autoDetect: + { + /* + * that the ALSA driver could be compiled doesn't say anything + * about whether it will work (the user might be using an OSS + * kernel driver). + * If we can open the device, it'll work - and we'll have to use + * a higher number than OSS to avoid buggy OSS emulation being used. + */ + int card = -1; + if (snd_card_next(&card) < 0 || card < 0) { + // No ALSA drivers in use... + return 0; + } + return 15; + } + + default: + return param(p); + } +} + +void AudioIOALSA::startIO() +{ + /* get & watch PCM file descriptor(s) */ + if (m_pcm_playback) { + getDescriptors(m_pcm_playback, &audio_write_pds); + watchDescriptors(&audio_write_pds); + } + if (m_pcm_capture) { + getDescriptors(m_pcm_capture, &audio_read_pds); + watchDescriptors(&audio_read_pds); + } + +} + +int AudioIOALSA::poll2iomanager(int pollTypes) +{ + int types = 0; + + if(pollTypes & POLLIN) + types |= IOType::read; + if(pollTypes & POLLOUT) + types |= IOType::write; + if(pollTypes & POLLERR) + types |= IOType::except; + + return types; +} + +int AudioIOALSA::iomanager2poll(int ioTypes) +{ + int types = 0; + + if(ioTypes & IOType::read) + types |= POLLIN; + if(ioTypes & IOType::write) + types |= POLLOUT; + if(ioTypes & IOType::except) + types |= POLLERR; + + return types; +} + +void AudioIOALSA::getDescriptors(snd_pcm_t *pcm, poll_descriptors *pds) +{ + pds->nfds = snd_pcm_poll_descriptors_count(pcm); + pds->pfds = new struct pollfd[pds->nfds]; + + if (snd_pcm_poll_descriptors(pcm, pds->pfds, pds->nfds) != pds->nfds) { + arts_info("Cannot get poll descriptor(s)\n"); + } + +} + +void AudioIOALSA::watchDescriptors(poll_descriptors *pds) +{ + for(int i=0; infds; i++) { + // Check in which direction this handle is supposed to be watched + int types = poll2iomanager(pds->pfds[i].events); + Dispatcher::the()->ioManager()->watchFD(pds->pfds[i].fd, types, this); + } +} + +int AudioIOALSA::xrun(snd_pcm_t *pcm) +{ + int err; + artsdebug("xrun!!\n"); + if ((err = snd_pcm_prepare(pcm)) < 0) + return err; + if (pcm == m_pcm_capture) + snd_pcm_start(pcm); // ignore error here.. + return 0; +} + +#ifdef HAVE_SND_PCM_RESUME +int AudioIOALSA::resume(snd_pcm_t *pcm) +{ + int err; + artsdebug("resume!\n"); + while ((err = snd_pcm_resume(pcm)) == -EAGAIN) + sleep(1); /* wait until suspend flag is not released */ + if (err < 0) { + if ((err = snd_pcm_prepare(pcm)) < 0) + return err; + if (pcm == m_pcm_capture) + snd_pcm_start(pcm); // ignore error here.. + } + return 0; +} +#endif + +int AudioIOALSA::read(void *buffer, int size) +{ + int frames = snd_pcm_bytes_to_frames(m_pcm_capture, size); + int length; + while ((length = snd_pcm_readi(m_pcm_capture, buffer, frames)) < 0) { + if (length == -EINTR) + continue; // Try again + else if (length == -EPIPE) + length = xrun(m_pcm_capture); +#ifdef HAVE_SND_PCM_RESUME + else if (length == -ESTRPIPE) + length = resume(m_pcm_capture); +#endif + if (length < 0) { + arts_info("Capture error: %s", snd_strerror(length)); + return -1; + } + } + return snd_pcm_frames_to_bytes(m_pcm_capture, length); +} + +int AudioIOALSA::write(void *buffer, int size) +{ + int frames = snd_pcm_bytes_to_frames(m_pcm_playback, size); + int length; + while ((length = snd_pcm_writei(m_pcm_playback, buffer, frames)) < 0) { + if (length == -EINTR) + continue; // Try again + else if (length == -EPIPE) + length = xrun(m_pcm_playback); +#ifdef HAVE_SND_PCM_RESUME + else if (length == -ESTRPIPE) + length = resume(m_pcm_playback); +#endif + if (length < 0) { + arts_info("Playback error: %s", snd_strerror(length)); + return -1; + } + } + + // Start the sink if it needs it + if (snd_pcm_state( m_pcm_playback ) == SND_PCM_STATE_PREPARED) + snd_pcm_start(m_pcm_playback); + + if (length == frames) // Sometimes the fragments are "odd" in alsa + return size; + else + return snd_pcm_frames_to_bytes(m_pcm_playback, length); +} + +void AudioIOALSA::notifyIO(int fd, int type) +{ + int todo = 0; + + // Translate from iomanager-types to poll-types, + // inorder to fake a snd_pcm_poll_descriptors_revents call. + if(m_pcm_playback) { + for(int i=0; i < audio_write_pds.nfds; i++) { + if(fd == audio_write_pds.pfds[i].fd) { + audio_write_pds.pfds[i].revents = iomanager2poll(type); + todo |= AudioSubSystem::ioWrite; + } + } + if (todo & AudioSubSystem::ioWrite) { + unsigned short revents; + snd_pcm_poll_descriptors_revents(m_pcm_playback, + audio_write_pds.pfds, + audio_write_pds.nfds, + &revents); + if (! (revents & POLLOUT)) todo &= ~AudioSubSystem::ioWrite; + } + } + if(m_pcm_capture) { + for(int i=0; i < audio_read_pds.nfds; i++) { + if(fd == audio_read_pds.pfds[i].fd) { + audio_read_pds.pfds[i].revents = iomanager2poll(type); + todo |= AudioSubSystem::ioRead; + } + } + if (todo & AudioSubSystem::ioRead) { + unsigned short revents; + snd_pcm_poll_descriptors_revents(m_pcm_capture, + audio_read_pds.pfds, + audio_read_pds.nfds, + &revents); + if (! (revents & POLLIN)) todo &= ~AudioSubSystem::ioRead; + } + } + + if (type & IOType::except) todo |= AudioSubSystem::ioExcept; + + if (todo != 0) AudioSubSystem::the()->handleIO(todo); +} + +int AudioIOALSA::setPcmParams(snd_pcm_t *pcm) +{ + int &_samplingRate = param(samplingRate); + int &_channels = param(channels); + int &_fragmentSize = param(fragmentSize); + int &_fragmentCount = param(fragmentCount); + string& _error = paramStr(lastError); + + snd_pcm_hw_params_t *hw; + snd_pcm_hw_params_alloca(&hw); + snd_pcm_hw_params_any(pcm, hw); + + if (snd_pcm_hw_params_set_access(pcm, hw, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) { + _error = "Unable to set interleaved!"; + return 1; + } + if (m_format == SND_PCM_FORMAT_UNKNOWN) { + // test the available formats + // try 16bit first, then fall back to 8bit + if (! snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_S16_LE)) + m_format = SND_PCM_FORMAT_S16_LE; + else if (! snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_S16_BE)) + m_format = SND_PCM_FORMAT_S16_BE; + else if (! snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_U8)) + m_format = SND_PCM_FORMAT_U8; + else + m_format = SND_PCM_FORMAT_UNKNOWN; + } + if (snd_pcm_hw_params_set_format(pcm, hw, m_format) < 0) { + _error = "Unable to set format!"; + return 1; + } + + unsigned int rate = snd_pcm_hw_params_set_rate_near(pcm, hw, _samplingRate, 0); + const unsigned int tolerance = _samplingRate/10+1000; + if (abs((int)rate - (int)_samplingRate) > (int)tolerance) { + _error = "Can't set requested sampling rate!"; + char details[80]; + sprintf(details," (requested rate %d, got rate %d)", + _samplingRate, rate); + _error += details; + return 1; + } + _samplingRate = rate; + + if (snd_pcm_hw_params_set_channels(pcm, hw, _channels) < 0) { + _error = "Unable to set channels!"; + return 1; + } + + m_period_size = _fragmentSize; + if (m_format != SND_PCM_FORMAT_U8) + m_period_size <<= 1; + if (_channels > 1) + m_period_size /= _channels; + if ((m_period_size = snd_pcm_hw_params_set_period_size_near(pcm, hw, m_period_size, 0)) < 0) { + _error = "Unable to set period size!"; + return 1; + } + m_periods = _fragmentCount; + if ((m_periods = snd_pcm_hw_params_set_periods_near(pcm, hw, m_periods, 0)) < 0) { + _error = "Unable to set periods!"; + return 1; + } + + if (snd_pcm_hw_params(pcm, hw) < 0) { + _error = "Unable to set hw params!"; + return 1; + } + + _fragmentSize = m_period_size; + _fragmentCount = m_periods; + if (m_format != SND_PCM_FORMAT_U8) + _fragmentSize >>= 1; + if (_channels > 1) + _fragmentSize *= _channels; + + return 0; // ok, we're ready.. +} + +#endif /* HAVE_LIBASOUND2 */ 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 +#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 diff --git a/flow/audioioesd.cc b/flow/audioioesd.cc new file mode 100644 index 0000000..a3fa4b5 --- /dev/null +++ b/flow/audioioesd.cc @@ -0,0 +1,231 @@ + /* + + Copyright (C) 2001 Jochen Hoenicke + jochen@gnu.org + + Copyright (C) 2003 Ian Chiew + ian@snork.net + + 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 +#endif + +/** + * only compile esound AudioIO class if libesd was detected during + * configure + */ +#ifdef HAVE_LIBESD + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "audioio.h" +#include "audiosubsys.h" +#include "iomanager.h" +#include "dispatcher.h" + +#include + +#ifdef __BIG_ENDIAN__ +#define _format_bits 17 +#else +#define _format_bits 16 +#endif + +namespace Arts { + +class AudioIOESD : public AudioIO, public IONotify { +protected: + int server_fd, read_fd, write_fd; + +public: + AudioIOESD(); + + void setParam(AudioParam param, int& value); + int getParam(AudioParam param); + void notifyIO(int fd, int type); + + bool open(); + void close(); + void run(); + int read(void *buffer, int size); + int write(void *buffer, int size); +}; + +REGISTER_AUDIO_IO(AudioIOESD,"esd","Enlightened Sound Daemon"); +} + +using namespace std; +using namespace Arts; + +AudioIOESD::AudioIOESD() +{ + /* + * default parameters + */ + param(samplingRate) = 44100; + paramStr(deviceName) = "null"; + param(fragmentSize) = 1024; + param(fragmentCount) = 7; + param(format) = _format_bits; + param(channels) = 2; + param(direction) = 2; + + server_fd = -1; + read_fd = -1; + write_fd = -1; +} + +bool AudioIOESD::open() +{ + int& _channels = param(channels); + int& _direction = param(direction); + int& _samplingRate = param(samplingRate); + int& _format = param(format); + string& _error = paramStr(lastError); + + if ((server_fd = esd_open_sound(NULL)) == -1) + { + _error = "Couldn't connect to server"; + return false; + } + + esd_server_info_t *server_info = esd_get_server_info(server_fd); + + if (server_info == NULL) + { + _error = "Unable to query EsounD server"; + return false; + } + + esd_format_t server_format = server_info->format; + + _samplingRate = server_info->rate; + _channels = (server_format & ESD_STEREO) ? 2 : 1; + _format = (server_format & ESD_BITS16) ? _format_bits : 8; + + esd_free_server_info(server_info); + + if (_direction & directionRead) + { + read_fd = esd_record_stream(server_format, _samplingRate, + NULL, "aRts"); + if (read_fd == -1) + { + _error = "Couldn't create read uflow"; + return false; + } + } + + if (_direction & directionWrite) + { + write_fd = esd_play_stream(server_format, _samplingRate, + NULL, "aRts"); + if (write_fd == -1) + { + _error = "Couldn't create write flow"; + return false; + } + } + + return true; +} + +void AudioIOESD::notifyIO(int, int) +{ +} + +void AudioIOESD::close() +{ + if (write_fd != -1) + esd_close(write_fd); + + if (read_fd != -1) + esd_close(read_fd); + + if (server_fd != -1) + esd_close(server_fd); +} + +void AudioIOESD::setParam(AudioParam p, int& value) +{ + switch(p) + { + case channels: + case format: + case direction: + case samplingRate: + param(p) = value; + close(); + open(); + return; + + default: + param(p) = value; + return; + } +} + +int AudioIOESD::getParam(AudioParam p) +{ + switch(p) + { + case selectReadFD: + return read_fd; + + case selectWriteFD: + return write_fd; + + case canRead: + return ESD_BUF_SIZE; + + case canWrite: + return ESD_BUF_SIZE; + + // ESD handles are actually socket descriptors, and I know not + // of any portable way to peek at the socket's send or receive + // buffers. + + default: + return param(p); + } +} + +int AudioIOESD::read(void *buffer, int size) +{ + return ::read(read_fd, buffer, size); +} + +int AudioIOESD::write(void *buffer, int size) +{ + return ::write(write_fd, buffer, size); +} + +#endif diff --git a/flow/audioiojack.cc b/flow/audioiojack.cc new file mode 100644 index 0000000..787ac03 --- /dev/null +++ b/flow/audioiojack.cc @@ -0,0 +1,346 @@ +/* + Copyright (C) 2004 Matthias Kretz + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + 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 +#endif + +#ifdef HAVE_LIBJACK + +#include +#include + +#include "debug.h" +#include "audioio.h" +#include "audiosubsys.h" +#include "iomanager.h" +#include "dispatcher.h" + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#undef DEBUG_WAVEFORM +#ifdef DEBUG_WAVEFORM +#include +#endif + +#include +#include + +namespace Arts { + +class AudioIOJack : public AudioIO, public TimeNotify { +private: +#ifdef DEBUG_WAVEFORM + std::ofstream plotfile; +#endif + char * processBuffer; + size_t buffersize; + +protected: + jack_client_t *jack; + jack_port_t *outleft, *outright; + jack_port_t *inleft, *inright; + jack_ringbuffer_t *olb, *orb, *ilb, *irb; + +public: + AudioIOJack(); + + void notifyTime(); + + void setParam( AudioParam p, int & val ); + int getParam(AudioParam param); + + static int jackCallback( jack_nframes_t, void * ); + + bool open(); + void close(); + int read(void *buffer, int size); + int write(void *buffer, int size); +}; + +REGISTER_AUDIO_IO(AudioIOJack,"jack","Jack Audio Connection Kit"); +} + +using namespace std; +using namespace Arts; + +AudioIOJack::AudioIOJack() + : +#ifdef DEBUG_WAVEFORM + plotfile( "/dev/shm/audioiojack.plot" ), +#endif + jack( 0 ) + , outleft( 0 ) + , outright( 0 ) + , inleft( 0 ) + , inright( 0 ) +{ + /* + * default parameters + */ + param( samplingRate ) = 44100; + paramStr( deviceName ) = "jack"; + param( fragmentSize ) = 512; + param( fragmentCount ) = 2; + param( channels ) = 2; + param( direction ) = 2; + param( format ) = 32; +} + +bool AudioIOJack::open() +{ + string& _error = paramStr( lastError ); + jack = jack_client_new( "artsd" ); + if( jack == 0 ) + { + _error = "Couldn't connect to jackd"; + return false; + } + + int& _sampleRate = param(samplingRate); + _sampleRate = jack_get_sample_rate( jack ); + int& _fragmentSize = param(fragmentSize); + int& _fragmentCount = param(fragmentCount); + + /* + * don't allow unreasonable large fragmentSize/Count combinations, + * because "real" hardware also doesn't + */ + + if(_fragmentSize > 1024*8) _fragmentSize = 1024*8; + + while(_fragmentSize * _fragmentCount > 1024*128) + _fragmentCount--; + + jack_set_process_callback( jack, Arts::AudioIOJack::jackCallback, this ); + + if( param( direction ) & directionWrite ) + { + outleft = jack_port_register( jack, "out_1", + JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 ); + outright = jack_port_register( jack, "out_2", + JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 ); + olb = jack_ringbuffer_create( + sizeof( jack_default_audio_sample_t ) * + _fragmentSize * _fragmentCount ); + orb = jack_ringbuffer_create( + sizeof( jack_default_audio_sample_t ) * + _fragmentSize * _fragmentCount ); + } + if( param( direction ) & directionRead ) + { + inleft = jack_port_register( jack, "in_1", + JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0 ); + inright = jack_port_register( jack, "in_2", + JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0 ); + ilb = jack_ringbuffer_create( + sizeof( jack_default_audio_sample_t ) * + 1024 * 64 ); + irb = jack_ringbuffer_create( + sizeof( jack_default_audio_sample_t ) * + 1024 * 64 ); + } + + if( jack_activate( jack ) ) + { + _error = "Activating as jack client failed."; + return false; + } + + const char **ports; + if( param( direction ) & directionRead ) + { + ports = jack_get_ports( jack, 0, 0, JackPortIsPhysical + | JackPortIsOutput ); + if( ports == 0 ) + { + arts_warning( "Cannot find any capture ports to" + " connect to. You need to manually connect" + " the capture ports in jack" ); + } + else + { + if( ports[ 0 ] != 0 ) + jack_connect( jack, ports[ 0 ], + jack_port_name( inleft ) ); + if( ports[ 1 ] != 0 ) + jack_connect( jack, ports[ 1 ], + jack_port_name( inright ) ); + free( ports ); + } + } + if( param( direction ) & directionWrite ) + { + ports = jack_get_ports( jack, 0, 0, JackPortIsPhysical + | JackPortIsInput ); + if( ports == 0 ) + { + arts_warning( "Cannot find any playback ports to" + " connect to. You need to manually connect" + " the playback ports in jack" ); + } + else + { + if( ports[ 0 ] != 0 ) + jack_connect( jack, jack_port_name( outleft ), + ports[ 0 ] ); + if( ports[ 1 ] != 0 ) + jack_connect( jack, jack_port_name( outright ), + ports[ 1 ] ); + free( ports ); + } + } + + // Install the timer + Dispatcher::the()->ioManager()->addTimer(10, this); + + return true; +} + +void AudioIOJack::close() +{ + jack_client_close( jack ); + Dispatcher::the()->ioManager()->removeTimer(this); +} + +int AudioIOJack::jackCallback( jack_nframes_t nframes, void * args ) +{ + AudioIOJack * that = static_cast( args ); + + that->buffersize = nframes * sizeof( jack_default_audio_sample_t ); + if( that->outleft ) + { + if( jack_ringbuffer_read_space( that->olb ) < that->buffersize ) + { + that->processBuffer = static_cast( + jack_port_get_buffer( that->outleft, nframes ) ); + memset( that->processBuffer, 0, that->buffersize ); + that->processBuffer = static_cast( + jack_port_get_buffer( that->outright, nframes ) ); + memset( that->processBuffer, 0, that->buffersize ); + } + else + { + that->processBuffer = static_cast( + jack_port_get_buffer( that->outleft, nframes ) ); + jack_ringbuffer_read( that->olb, that->processBuffer, that->buffersize ); + that->processBuffer = static_cast( + jack_port_get_buffer( that->outright, nframes ) ); + jack_ringbuffer_read( that->orb, that->processBuffer, that->buffersize ); + } + } + if( that->inleft ) + { + that->processBuffer = static_cast( + jack_port_get_buffer( that->inleft, nframes ) ); + jack_ringbuffer_write( that->ilb, that->processBuffer, that->buffersize ); + that->processBuffer = static_cast( + jack_port_get_buffer( that->inright, nframes ) ); + jack_ringbuffer_write( that->irb, that->processBuffer, that->buffersize ); + } + return 0; +} + +void AudioIOJack::setParam( AudioParam p, int& val ) +{ + // don't change the format - jack only supports 32 bit float + if( p == format ) + return; + AudioIO::setParam( p, val ); +} + +int AudioIOJack::getParam(AudioParam p) +{ + switch(p) + { + case canRead: + return MIN( jack_ringbuffer_read_space( ilb ), jack_ringbuffer_read_space( irb ) ) * param( channels ); + case canWrite: + return MIN( jack_ringbuffer_write_space( olb ), jack_ringbuffer_write_space( orb ) ) * param( channels ); + default: + return AudioIO::getParam( p ); + } +} + +int AudioIOJack::read(void *buffer, int size) +{ + float * floatbuffer = static_cast( buffer ); + if( param( channels ) == 2 ) + { + float * end = ( float * )( static_cast( buffer ) + size ); + while( floatbuffer < end ) + { + jack_ringbuffer_read( ilb, ( char* )( floatbuffer++ ), sizeof( float ) ); +#ifdef DEBUG_WAVEFORM + plotfile << *( floatbuffer - 1 ) << "\n"; +#endif + jack_ringbuffer_read( irb, ( char* )( floatbuffer++ ), sizeof( float ) ); + } + } + else if( param( channels ) == 1 ) + { + jack_ringbuffer_read( ilb, ( char* )( floatbuffer ), size ); + } + return size; +} + +int AudioIOJack::write(void *buffer, int size) +{ + float * floatbuffer = static_cast( buffer ); + if( param( channels ) == 2 ) + { + float * end = ( float * )( static_cast( buffer ) + size ); + while( floatbuffer < end ) + { + jack_ringbuffer_write( olb, ( char* )( floatbuffer++ ), sizeof( float ) ); + jack_ringbuffer_write( orb, ( char* )( floatbuffer++ ), sizeof( float ) ); + } + } + else if( param( channels ) == 1 ) + { + jack_ringbuffer_write( olb, ( char* )( floatbuffer ), size ); + } + return size; +} + +void AudioIOJack::notifyTime() +{ + int& _direction = param(direction); + int& _fragmentSize = param(fragmentSize); + + for( ;; ) + { + int todo = 0; + if( ( _direction & directionRead ) && ( getParam( canRead ) >= _fragmentSize ) ) + todo |= AudioSubSystem::ioRead; + + if( ( _direction & directionWrite ) && ( getParam( canWrite ) >= _fragmentSize ) ) + todo |= AudioSubSystem::ioWrite; + + if( ! todo ) + return; + + AudioSubSystem::the()->handleIO( todo ); + } +} + +#endif +// vim: sw=4 ts=4 diff --git a/flow/audioiolibaudioio.cc b/flow/audioiolibaudioio.cc new file mode 100644 index 0000000..e7c545e --- /dev/null +++ b/flow/audioiolibaudioio.cc @@ -0,0 +1,236 @@ + /* + + Copyright (C) 2001 Robert Lunnon + bob@yarrabee.net.au + Copyright (C) 2001 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 +#endif + +/** + * only compile 'LibAudioIO' AudioIO class if libaudio was detected during + * configure + */ +#ifdef HAVE_LIBAUDIOIO + +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_SELECT_H +#include // Needed on some systems. +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "debug.h" +#include "audioio.h" + +namespace Arts { + +class AudioIOLibAudioIO : public AudioIO { +protected: + int audio_fd; + int requestedFragmentSize; + int requestedFragmentCount; + SampleSpec_t spec; + +public: + AudioIOLibAudioIO(); + + 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(AudioIOLibAudioIO,"libaudioio"," Portable Audio Library"); +}; + +using namespace std; +using namespace Arts; + +AudioIOLibAudioIO::AudioIOLibAudioIO() +{ + /* + * default parameters + */ + param(samplingRate) = spec.rate=44100; + paramStr(deviceName) = "/dev/Audio"; + requestedFragmentSize = param(fragmentSize) = 4096; + requestedFragmentCount =spec.max_blocks= param(fragmentCount) = 7; + param(channels) = spec.channels=2; + param(direction) = 2; +} + +bool AudioIOLibAudioIO::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; + spec.channels=_channels; + spec.max_blocks= param(fragmentCount); + spec.rate= _samplingRate; + spec.encoding= ENCODE_PCM; + spec.precision=16 ; + spec.endian=ENDIAN_NATURAL; + spec.disable_threads=1; + + 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 = ::AudioIOOpenX( mode, &spec,&spec ); + if(audio_fd == -1) + { + _error = "device "; + _error += _deviceName.c_str(); + _error += " can't be opened ("; + _error += strerror(errno); + _error += ")"; + return false; + } + + + /* + * since we use spec.endian=ENDIAN_NATURAL we'll have little endian audio + * on little endian machines and big endian audio on big endian machines: + */ +#ifdef WORDS_BIGENDIAN + _format = 17; +#else + _format = 16; +#endif + + spec.channels=_channels; + + spec.rate = _samplingRate; + + + _fragmentSize = requestedFragmentSize; + spec.max_blocks=_fragmentCount = requestedFragmentCount; + + + 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); + + return(true); +} + +void AudioIOLibAudioIO::close() +{ + ::AudioIOClose(); +} + +void AudioIOLibAudioIO::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 AudioIOLibAudioIO::getParam(AudioParam p) +{ + switch(p) + { + case canRead: + return AudioIOCheckRead(); + break; + + case canWrite: + return (AudioIOCheckWriteReady()) ? (16*1024) : 0; + // Assume if writable can write 16K + // Arts Really doesn't care + break; + + case selectReadFD: + return (param(direction) & directionRead)?audio_fd:-1; + break; + + case selectWriteFD: + return (param(direction) & directionWrite)?audio_fd:-1; + break; + + case autoDetect: + /* + * if there is a "native" aRts driver, we'll rather use this + * than the generic libaudioio one, because the native one + * is likely to be better optimized to the needs of aRts than + * a generic driver, so keep the value small + */ + return 3; + break; + + default: + return param(p); + break; + } +} + +int AudioIOLibAudioIO::read(void *buffer, int size) +{ + arts_assert(audio_fd != 0); + return ::AudioIORead(buffer,size); +} + +int AudioIOLibAudioIO::write(void *buffer, int size) +{ + arts_assert(audio_fd != 0); + return ::AudioIOWrite(buffer,size); +} + +#endif diff --git a/flow/audioiomas.cc b/flow/audioiomas.cc new file mode 100644 index 0000000..e49fec2 --- /dev/null +++ b/flow/audioiomas.cc @@ -0,0 +1,619 @@ + /* + + Copyright (C) 2001-2003 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 +#endif + +/* + * Only compile this AudioIO class if we have MAS + */ +#ifdef HAVE_LIBMAS + +extern "C" { +#include +#include +#include +} + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "debug.h" +#include "audioio.h" +#include "audiosubsys.h" +#include "iomanager.h" +#include "dispatcher.h" + +namespace Arts { + + class AudioIOMAS : public AudioIO, public TimeNotify { + protected: + mas_channel_t audio_channel; + mas_port_t mix_sink; + mas_port_t srate_source, srate_sink; + mas_port_t audio_source, audio_sink; + mas_port_t endian_sink, endian_source; + mas_port_t sbuf_source, sbuf_sink; + mas_port_t squant_sink, squant_source; + mas_port_t open_source; /* (!) */ + mas_device_t endian; + mas_device_t srate; + mas_device_t squant; + mas_device_t sbuf; + mas_data *data; + mas_package package; + int32 mas_error; + + std::list allocated_channels; + std::list allocated_ports; + std::list allocated_devices; + + double lastUpdate, bytesPerSec; + int readBufferAvailable; + int writeBufferAvailable; + + double currentTime(); + void updateBufferSizes(); + +#ifdef WORDS_BIGENDIAN + static const int defaultFormat = 17; +#else + static const int defaultFormat = 16; +#endif + bool close_with_error(const std::string& text); + public: + AudioIOMAS(); + + // Timer callback + void notifyTime(); + + 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(AudioIOMAS,"mas","MAS Audio Input/Output"); + +}; + +using namespace std; +using namespace Arts; + +AudioIOMAS::AudioIOMAS() +{ + /* + * default parameters + */ + param(samplingRate) = 44100; + paramStr(deviceName) = ""; // TODO + param(fragmentSize) = 4096; + param(fragmentCount) = 7; + param(channels) = 2; + param(direction) = 2; + param(format) = defaultFormat; +} + +namespace { + int masInitCount = 0; +} + +// Opens the audio device +bool AudioIOMAS::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); + + /* FIXME: do we need to free what we allocate with mas_init() in close() */ + if (!masInitCount) + { + mas_error = mas_init(); + + if (mas_error < 0) + return close_with_error("error connecting to MAS server"); + } + masInitCount++; + + if (param(direction) != 2) + { + _error = "unsupported direction (currently no full duplex support)"; + return false; + } + + /* + * data path + * + * audio_sink + * audio_channel: data_channel ("artsd") + * audio_source + * | + * V + * endian_sink + * endian: instantiate_device ("endian") + * open_source = endian_source + * | + * V + * [squant_sink] + * [squant] + * [squant_source] + * | + * V + * [srate_sink] + * [srate] + * [srate_source] + * | + * V + * sbuf_sink + * sbuf + * sbuf_source + * | + * V + * mix_sink: port ("default_mix_sink") + */ + + // audio_channel, source & sink + mas_error = mas_make_data_channel("artsd", &audio_channel, &audio_source, &audio_sink); + if (mas_error < 0) + return close_with_error("error initializing MAS data channel"); + + allocated_channels.push_back(audio_channel); + allocated_ports.push_back(audio_source); + allocated_ports.push_back(audio_sink); + + // endian, source & sink + mas_error = mas_asm_instantiate_device( "endian", 0, 0, &endian ); + if ( mas_error < 0 ) + return close_with_error("error initantiating MAS endian device"); + + allocated_devices.push_back(endian); + + mas_error = mas_asm_get_port_by_name( endian, "sink", &endian_sink ); + if ( mas_error < 0 ) + return close_with_error("error getting MAS endian device sink port"); + + allocated_ports.push_back(endian_sink); + + mas_error = mas_asm_get_port_by_name( endian, "source", &endian_source ); + if ( mas_error < 0 ) + return close_with_error("error getting MAS endian device source port"); + + allocated_ports.push_back(endian_source); + + char ratestring[16], resolutionstring[16]; + sprintf (ratestring, "%u", _samplingRate); + sprintf (resolutionstring, "%u", _format); + + mas_data_characteristic* dc; + + dc = (mas_data_characteristic *)MAS_NEW( dc ); + masc_setup_dc( dc, 6 ); + masc_append_dc_key_value( dc, "format", (_format==8) ? "ulinear":"linear" ); + + masc_append_dc_key_value( dc, "resolution", resolutionstring ); + masc_append_dc_key_value( dc, "sampling rate", ratestring ); + masc_append_dc_key_value( dc, "channels", "2" ); + masc_append_dc_key_value( dc, "endian", "little" ); + + mas_error = mas_asm_connect_source_sink( audio_source, endian_sink, dc ); + if ( mas_error < 0 ) + return close_with_error("error connecting MAS net audio source to endian sink"); + + /* The next device is 'if needed' only. After the following if() + statement, open_source will contain the current unconnected + source in the path (will be either endian_source or + squant_source in this case) + */ + open_source = endian_source; + + if ( _format != 16 ) + { + arts_debug("MAS output: Sample resolution is not 16 bit/sample, instantiating squant device."); + + // squant, source & sink + mas_error = mas_asm_instantiate_device( "squant", 0, 0, &squant ); + if ( mas_error < 0 ) + return close_with_error("error creating MAS squant device"); + + allocated_devices.push_back(squant); + + mas_error = mas_asm_get_port_by_name( squant, "sink", &squant_sink ); + if ( mas_error < 0 ) + return close_with_error("error getting MAS squant device sink port"); + + allocated_ports.push_back(squant_sink); + + mas_error = mas_asm_get_port_by_name( squant, "source", &squant_source ); + if ( mas_error < 0 ) + return close_with_error("error getting MAS squant device source port"); + + allocated_ports.push_back(squant_source); + + arts_debug( "MAS output: Connecting endian -> squant."); + + masc_strike_dc( dc ); + masc_setup_dc( dc, 6 ); + masc_append_dc_key_value( dc,"format",(_format==8) ? "ulinear":"linear" ); + masc_append_dc_key_value( dc, "resolution", resolutionstring ); + masc_append_dc_key_value( dc, "sampling rate", ratestring ); + masc_append_dc_key_value( dc, "channels", "2" ); + masc_append_dc_key_value( dc, "endian", "host" ); + + mas_error = mas_asm_connect_source_sink( endian_source, squant_sink, dc ); + if ( mas_error < 0 ) + return close_with_error("error connecting MAS endian output to squant device"); + + /* sneaky: the squant device is optional -> pretend it isn't there */ + open_source = squant_source; + } + + + /* Another 'if necessary' device, as above */ + if ( _samplingRate != 44100 ) + { + arts_debug ("MAS output: Sample rate is not 44100, instantiating srate device."); + + // srate, source & sink + mas_error = mas_asm_instantiate_device( "srate", 0, 0, &srate ); + if ( mas_error < 0 ) + return close_with_error("error initantiating MAS srate device"); + + allocated_devices.push_back(srate); + + mas_error = mas_asm_get_port_by_name( srate, "sink", &srate_sink ); + if ( mas_error < 0 ) + return close_with_error("error getting MAS srate sink port"); + + allocated_ports.push_back(srate_sink); + + mas_error = mas_asm_get_port_by_name( srate, "source", &srate_source ); + if ( mas_error < 0 ) + return close_with_error("error getting MAS srate source port"); + + allocated_ports.push_back(srate_source); + + arts_debug( "MAS output: Connecting to srate."); + + masc_strike_dc( dc ); + masc_setup_dc( dc, 6 ); + masc_append_dc_key_value( dc, "format", "linear" ); + masc_append_dc_key_value( dc, "resolution", "16" ); + masc_append_dc_key_value( dc, "sampling rate", ratestring ); + masc_append_dc_key_value( dc, "channels", "2" ); + masc_append_dc_key_value( dc, "endian", "host" ); + + mas_error = mas_asm_connect_source_sink( open_source, srate_sink, dc ); + if ( mas_error < 0 ) + return close_with_error("error connecting to MAS srate device"); + + open_source = srate_source; + } + + // sbuf, source & sink + mas_error = mas_asm_instantiate_device( "sbuf", 0, 0, &sbuf ); + if ( mas_error < 0 ) + return close_with_error("error initantiating MAS sbuf device"); + + allocated_devices.push_back(sbuf); + + mas_error = mas_asm_get_port_by_name( sbuf, "sink", &sbuf_sink ); + if ( mas_error < 0 ) + return close_with_error("error getting MAS sbuf device sink port"); + + allocated_ports.push_back(sbuf_sink); + + mas_error = mas_asm_get_port_by_name( sbuf, "source", &sbuf_source ); + if ( mas_error < 0 ) + return close_with_error("error getting MAS sbuf device source port"); + + allocated_ports.push_back(sbuf_source); + + masc_strike_dc( dc ); + masc_setup_dc( dc, 6 ); + + masc_append_dc_key_value( dc, "format", "linear" ); + masc_append_dc_key_value( dc, "resolution", "16" ); + masc_append_dc_key_value( dc, "sampling rate", "44100" ); + masc_append_dc_key_value( dc, "channels", "2" ); + masc_append_dc_key_value( dc, "endian", "host" ); + + arts_debug("MAS output: Connecting to sbuf."); + + mas_error = mas_asm_connect_source_sink( open_source, sbuf_sink, dc ); + if ( mas_error < 0 ) + return close_with_error("error connecting to MAS mixer device"); + + /* configure sbuf */ + + float BUFTIME_MS = _fragmentSize * _fragmentCount; + BUFTIME_MS *= 1000.0; + BUFTIME_MS /= (float)_channels; + if (_format > 8) + BUFTIME_MS /= 2.0; + BUFTIME_MS /= (float)_samplingRate; + + arts_debug("MAS output: BUFTIME_MS = %f", BUFTIME_MS); + + masc_setup_package( &package, NULL, 0, 0 ); + masc_pushk_uint32( &package, "buftime_ms", (uint32) BUFTIME_MS ); + masc_finalize_package( &package ); + mas_set( sbuf, "buftime_ms", &package ); + masc_strike_package( &package ); + + masc_setup_package( &package, NULL, 0, 0 ); + masc_pushk_int32( &package, "mc_clkid", 9 ); + masc_finalize_package( &package ); + mas_set( sbuf, "mc_clkid", &package ); + masc_strike_package( &package ); + + mas_source_play( sbuf ); + + // mix_sink + mas_error = mas_asm_get_port_by_name( 0, "default_mix_sink", &mix_sink ); + if (mas_error < 0) + return close_with_error("error finding MAS default sink"); + + allocated_ports.push_back(mix_sink); + + arts_debug("MAS output: Connecting sbuf to mix_sink."); + + mas_error = mas_asm_connect_source_sink( sbuf_source, mix_sink, dc ); + if ( mas_error < 0 ) + return close_with_error("error connecting to MAS mixer device"); + + data = (mas_data *)MAS_NEW( data ); + masc_setup_data( data, _fragmentSize ); /* we can reuse this */ + data->length = _fragmentSize; + data->allocated_length = data->length; + data->header.type = 10; + + arts_debug("MAS output: playing."); + + // Install the timer + Dispatcher::the()->ioManager()->addTimer(10, this); + + bytesPerSec = _channels * _samplingRate; + if (_format > 8) + bytesPerSec *= 2; + + lastUpdate = 0; + + return true; +} + +double AudioIOMAS::currentTime() +{ + timeval tv; + gettimeofday(&tv,0); + + return (double)tv.tv_sec + (double)tv.tv_usec/1000000.0; +} + +bool AudioIOMAS::close_with_error(const string& text) +{ + string& error = paramStr(lastError); + error = text; + error += masc_strmerror (mas_error); + return false; +} + +void AudioIOMAS::close() +{ + list::iterator pi; + for (pi = allocated_ports.begin(); pi != allocated_ports.end(); pi++) + mas_free_port (*pi); + allocated_ports.clear(); + + list::iterator ci; + for (ci = allocated_channels.begin(); ci != allocated_channels.end(); ci++) + mas_free_channel (*ci); + allocated_channels.clear(); + + list::iterator di; + for (di = allocated_devices.begin(); di != allocated_devices.end(); di++) + { + mas_device_t device = *di; + mas_error = mas_asm_terminate_device_instance(device, 0); + if (mas_error < 0) + arts_warning ("MAS output: error while closing device: %s", masc_strmerror(mas_error)); + + mas_free_device(device); + } + allocated_devices.clear(); + + Dispatcher::the()->ioManager()->removeTimer(this); +} + +void AudioIOMAS::updateBufferSizes() +{ + double time = currentTime(); + double waterMark = param(fragmentSize); + waterMark *= 1.3; + + if ((time - lastUpdate) * bytesPerSec < waterMark) + return; + + lastUpdate = time; + + uint32 inbuf_ms; + int32 mas_error; + + mas_error = mas_get( sbuf, "inbuf_ms", 0 , &package ); + if ( mas_error < 0 ) + arts_fatal ("MAS output: error getting size of buffer: %s", masc_strmerror(mas_error)); + + masc_pull_uint32( &package, &inbuf_ms ); + masc_strike_package( &package ); + + //arts_debug(" inbuf_ms = %u", inbuf_ms); + + float bytes = inbuf_ms; + bytes /= 1000.0; + bytes *= param(samplingRate); + bytes *= param(channels); + if(param(format) > 8) + bytes *= 2; + + int bytesFree = param(fragmentSize) * param(fragmentCount) - (int)bytes; + + if (bytesFree < param(fragmentSize)) + bytesFree = 0; + + writeBufferAvailable = bytesFree; + + arts_debug ("MAS output buffer: %6d / %6d bytes used => %6d bytes free", + (int)bytes, param(fragmentSize) * param(fragmentCount), writeBufferAvailable); +} + +// This is called on each timer tick +void AudioIOMAS::notifyTime() +{ + updateBufferSizes(); + + int& _direction = param(direction); + int& _fragmentSize = param(fragmentSize); + + for (;;) { + int todo = 0; + + if ((_direction & directionRead) && (getParam(canRead) >= _fragmentSize)) + todo |= AudioSubSystem::ioRead; + + if ((_direction & directionWrite) && (getParam(canWrite) >= _fragmentSize)) + todo |= AudioSubSystem::ioWrite; + + if (!todo) + return; + + AudioSubSystem::the()->handleIO(todo); + } +} + +void AudioIOMAS::setParam(AudioParam p, int& value) +{ + switch(p) { +#if 0 + case fragmentSize: + param(p) = requestedFragmentSize = value; + break; + case fragmentCount: + param(p) = requestedFragmentCount = value; + break; +#endif + default: + param(p) = value; + break; + } +} + +int AudioIOMAS::getParam(AudioParam p) +{ + int bytes; + int count; + + switch(p) + { +#if 0 + case canRead: + if (ioctl(audio_fd, AUDIO_GETINFO, &auinfo) < 0) + return (0); + bytes = (auinfo.record.samples * bytesPerSample) - bytesRead; + if (bytes < 0) { + printf("Error: bytes %d < 0, samples=%u, bytesRead=%u\n", + bytes, auinfo.record.samples, bytesRead); + bytes = 0; + } + return bytes; + + case canWrite: + if (ioctl(audio_fd, AUDIO_GETINFO, &auinfo) < 0) + return (0); + count = SUN_MAX_BUFFER_SIZE - + (bytesWritten - (auinfo.play.samples * bytesPerSample)); + return count; +#endif + case canWrite: + return writeBufferAvailable; + + case autoDetect: + /* + * Fairly small priority, for we haven't tested this a lot + */ + return 3; + + default: + return param(p); + } +} + +int AudioIOMAS::read(void *buffer, int size) +{ +#if 0 + size = ::read(audio_fd, buffer, size); + if (size < 0) + return 0; + + bytesRead += size; + return size; +#endif + return 0; +} + +int AudioIOMAS::write(void *buffer, int size) +{ + static int ts = 0; + static int seq = 0; + data->header.sequence = seq++; + data->header.media_timestamp = ts; + ts += size / 4; + + assert(size == data->length); + memcpy(data->segment, buffer, size); + + int32 mas_error = mas_send( audio_channel , data ); + if (mas_error < 0) + arts_fatal ("MAS output: problem during mas_send: %s", masc_strmerror(mas_error)); + + writeBufferAvailable -= size; + return size; +} + +#endif /* HAVE_LIBMAS */ diff --git a/flow/audioionas.cc b/flow/audioionas.cc new file mode 100644 index 0000000..3106ade --- /dev/null +++ b/flow/audioionas.cc @@ -0,0 +1,260 @@ + /* + + Copyright (C) 2001 Jochen Hoenicke + jochen@gnu.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. + + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +/** + * only compile nas AudioIO class if libaudio was detected during + * configure + */ +#ifdef HAVE_LIBAUDIONAS + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "audioio.h" +#include "audiosubsys.h" +#include "iomanager.h" +#include "dispatcher.h" + +#include