diff --git a/common/JackAudioAdapterFactory.cpp b/common/JackAudioAdapterFactory.cpp index 9ceeee04..f94dc84c 100644 --- a/common/JackAudioAdapterFactory.cpp +++ b/common/JackAudioAdapterFactory.cpp @@ -35,6 +35,11 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. #define JackPlatformAdapter JackAlsaAdapter #endif +#ifdef __QNX__ +#include "JackIoAudioAdapter.h" +#define JackPlatformAdapter JackIoAudioAdapter +#endif + #if defined(__sun__) || defined(sun) #include "JackOSSAdapter.h" #define JackPlatformAdapter JackOSSAdapter diff --git a/common/JackControlAPI.cpp b/common/JackControlAPI.cpp index a733f5d9..fe3b873c 100644 --- a/common/JackControlAPI.cpp +++ b/common/JackControlAPI.cpp @@ -661,7 +661,12 @@ jackctl_setup_signals( sigfillset(&allsignals); action.sa_handler = signal_handler; action.sa_mask = allsignals; +#ifdef __QNX__ + // SA_RESTART is not supported in QNX 6.6.0 + action.sa_flags = SA_RESETHAND; +#else action.sa_flags = SA_RESTART|SA_RESETHAND; +#endif for (i = 1; i < NSIG; i++) { diff --git a/common/jack/systemdeps.h b/common/jack/systemdeps.h index 1f62cb64..1f2d6014 100644 --- a/common/jack/systemdeps.h +++ b/common/jack/systemdeps.h @@ -103,7 +103,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. #endif /* _WIN32 && !__CYGWIN__ && !GNU_WIN32 */ -#if defined(__APPLE__) || defined(__linux__) || defined(__sun__) || defined(sun) || defined(__unix__) || defined(__CYGWIN__) || defined(GNU_WIN32) +#if defined(__APPLE__) || defined(__linux__) || defined(__QNX__) || defined(__sun__) || defined(sun) || defined(__unix__) || defined(__CYGWIN__) || defined(GNU_WIN32) #if defined(__CYGWIN__) || defined(GNU_WIN32) #include diff --git a/common/ringbuffer.c b/common/ringbuffer.c index 9de844f6..aad38b42 100644 --- a/common/ringbuffer.c +++ b/common/ringbuffer.c @@ -67,21 +67,16 @@ size_t jack_ringbuffer_write_space(const jack_ringbuffer_t *rb); LIB_EXPORT jack_ringbuffer_t * jack_ringbuffer_create (size_t sz) { - int power_of_two; jack_ringbuffer_t *rb; if ((rb = (jack_ringbuffer_t *) malloc (sizeof (jack_ringbuffer_t))) == NULL) { return NULL; } - for (power_of_two = 1; 1 << power_of_two < sz; power_of_two++); + rb->buf = NULL; + jack_ringbuffer_reset_size( rb, sz ); - rb->size = 1 << power_of_two; - rb->size_mask = rb->size; - rb->size_mask -= 1; - rb->write_ptr = 0; - rb->read_ptr = 0; - if ((rb->buf = (char *) malloc (rb->size)) == NULL) { + if (rb->buf == NULL) { free (rb); return NULL; } @@ -129,17 +124,38 @@ jack_ringbuffer_reset (jack_ringbuffer_t * rb) memset(rb->buf, 0, rb->size); } -/* Reset the read and write pointers to zero. This is not thread - safe. */ +/* Reset the size of the ringbuffer. + + Reallocates the internal buffer using the next power-of-two size up from + the requested size. + + If the reallocation fails, the previous buffer is left intact. + */ LIB_EXPORT void jack_ringbuffer_reset_size (jack_ringbuffer_t * rb, size_t sz) { - rb->size = sz; - rb->size_mask = rb->size; - rb->size_mask -= 1; - rb->read_ptr = 0; - rb->write_ptr = 0; + /* Attempt to reallocate buffer to size sz */ + int power_of_two; + for (power_of_two = 1; 1 << power_of_two < sz; power_of_two++); + size_t newsz = 1 << power_of_two; + + void* newbuf = realloc (rb->buf, newsz); + if (NULL != newbuf) + { + rb->buf = newbuf; + rb->size = newsz; + rb->size_mask = rb->size; + rb->size_mask -= 1; + rb->read_ptr = 0; + rb->write_ptr = 0; + } + else + { + /* Reallocation failed. Ringbuffer is left in initial state + * so application must check ringbuffer size to see if + * the resize succeeded. */ + } } /* Return the number of bytes available for reading. This is the diff --git a/common/shm.c b/common/shm.c index c52dfa50..36839425 100644 --- a/common/shm.c +++ b/common/shm.c @@ -49,7 +49,9 @@ #include #include #include +#ifndef USE_POSIX_SHM #include +#endif #include #include @@ -176,6 +178,49 @@ semaphore_init () {return 0;} static int semaphore_add (int value) {return 0;} +#elif defined(__QNX__) +#include + +static sem_t sem; + +/* all semaphore errors are fatal -- issue message, but do not return */ +static void +semaphore_error (char *msg) +{ + jack_error ("JACK semaphore error: %s (%s)", + msg, strerror (errno)); +} + +static int +semaphore_init () +{ + semid = sem_init(&sem, JACK_SEMAPHORE_KEY, 1); + + if( semid == -1) + { + semaphore_error("semaphore_init()"); + } + + + return 0; +} + +static inline int +semaphore_add (int value) +{ + while( value > 0 ) + { + sem_post( &sem ); + --value; + } + while( value < 0 ) + { + sem_wait( &sem ); + ++value; + } + return 0; +} + #else /* all semaphore errors are fatal -- issue message, but do not return */ static void diff --git a/common/wscript b/common/wscript index fcf1d495..3d5390db 100644 --- a/common/wscript +++ b/common/wscript @@ -26,6 +26,8 @@ def create_jack_process_obj(bld, target, sources, uselib = None): env_includes = ['../macosx', '../posix', '../macosx/coreaudio'] if bld.env['IS_LINUX']: env_includes = ['../linux', '../posix', '../linux/alsa'] + if bld.env['IS_QNX']: + env_includes = ['../qnx', '../posix', '../qnx/ioaudio'] if bld.env['IS_SUN']: env_includes = ['../solaris', '../posix', '../solaris/oss'] if bld.env['IS_WINDOWS']: @@ -36,6 +38,8 @@ def create_jack_process_obj(bld, target, sources, uselib = None): process.source = sources if bld.env['IS_LINUX']: process.env.append_value("CPPFLAGS", "-fvisibility=hidden") + if bld.env['IS_QNX']: + process.env.append_value("CPPFLAGS", "-fvisibility=hidden") if bld.env['IS_MACOSX']: process.env.append_value("CPPFLAGS", "-mmacosx-version-min=10.4 -arch i386 -arch ppc -arch x86_64") #process.env.append_value("LINKFLAGS", "-arch i386 -arch ppc -arch x86_64") @@ -93,6 +97,22 @@ def build(bld): uselib.append('RT') uselib.append('DL') + if bld.env['IS_QNX']: + common_libsources += [ + 'JackDebugClient.cpp', + 'timestamps.c', + '../posix/JackPosixThread.cpp', + '../posix/JackPosixSemaphore.cpp', + '../posix/JackPosixProcessSync.cpp', + '../posix/JackPosixMutex.cpp', + '../posix/JackSocket.cpp', + '../qnx/JackQnxTime.c', + ] + includes = ['../qnx', '../posix'] + includes + uselib.append('RT') + uselib.append('DL') + uselib.append('SOCKET') + if bld.env['IS_SUN']: common_libsources += [ 'JackDebugClient.cpp', @@ -158,6 +178,12 @@ def build(bld): '../posix/JackPosixServerLaunch.cpp', ] + if bld.env['IS_QNX']: + clientlib.source += [ + '../posix/JackSocketClientChannel.cpp', + '../posix/JackPosixServerLaunch.cpp', + ] + if bld.env['IS_SUN']: clientlib.source += [ '../posix/JackSocketClientChannel.cpp', @@ -183,6 +209,9 @@ def build(bld): if bld.env['IS_LINUX']: clientlib.env.append_value("CPPFLAGS", "-fvisibility=hidden") + if bld.env['IS_QNX']: + clientlib.env.append_value("CPPFLAGS", "-fvisibility=hidden") + if bld.env['IS_MACOSX']: clientlib.env.append_value("CPPFLAGS", "-fvisibility=hidden") clientlib.env.append_value("CPPFLAGS", "-mmacosx-version-min=10.4 -arch i386 -arch ppc -arch x86_64") @@ -252,6 +281,14 @@ def build(bld): '../posix/JackNetUnixSocket.cpp', ] + if bld.env['IS_QNX']: + serverlib.source += [ + '../posix/JackSocketServerChannel.cpp', + '../posix/JackSocketNotifyChannel.cpp', + '../posix/JackSocketServerNotifyChannel.cpp', + '../posix/JackNetUnixSocket.cpp', + ] + if bld.env['IS_SUN']: serverlib.source += [ '../posix/JackSocketServerChannel.cpp', @@ -283,6 +320,9 @@ def build(bld): if bld.env['IS_LINUX']: serverlib.env.append_value("CPPFLAGS", "-fvisibility=hidden") + if bld.env['IS_QNX']: + serverlib.env.append_value("CPPFLAGS", "-fvisibility=hidden") + if bld.env['IS_MACOSX']: serverlib.env.append_value("CPPFLAGS", "-fvisibility=hidden") serverlib.env.append_value("CPPFLAGS", "-mmacosx-version-min=10.4 -arch i386 -arch ppc -arch x86_64") @@ -321,6 +361,10 @@ def build(bld): netlib.source += ['../posix/JackNetUnixSocket.cpp','../posix/JackPosixThread.cpp', '../posix/JackPosixMutex.cpp', '../linux/JackLinuxTime.c'] netlib.env.append_value("CPPFLAGS", "-fvisibility=hidden") + if bld.env['IS_QNX']: + netlib.source += ['../posix/JackNetUnixSocket.cpp','../posix/JackPosixThread.cpp','../posix/JackPosixMutex.cpp','../qnx/JackQnxTime.c'] + netlib.env.append_value("CPPFLAGS", "-fvisibility=hidden") + if bld.env['IS_SUN']: netlib.source += ['../posix/JackNetUnixSocket.cpp','../posix/JackPosixThread.cpp', '../posix/JackPosixMutex.cpp', '../solaris/JackSolarisTime.c'] netlib.env.append_value("CPPFLAGS", "-fvisibility=hidden") @@ -370,6 +414,11 @@ def build(bld): process = create_jack_process_obj(bld, 'audioadapter', audio_adapter_sources, serverlib) process.use = ['ALSA', 'SAMPLERATE'] + if bld.env['BUILD_ADAPTER'] and bld.env['IS_QNX']: + audio_adapter_sources += ['../qnx/ioaudio/JackIoAudioAdapter.cpp'] + process = create_jack_process_obj(bld, 'audioadapter', audio_adapter_sources, serverlib) + process.use = ['IOAUDIO', 'SAMPLERATE'] + if bld.env['BUILD_ADAPTER'] and bld.env['IS_SUN']: audio_adapter_sources += ['../solaris/oss/JackOSSAdapter.cpp', 'memops.c'] process = create_jack_process_obj(bld, 'audioadapter', audio_adapter_sources, serverlib) diff --git a/dbus/wscript b/dbus/wscript index ece9198f..d9b7da5f 100644 --- a/dbus/wscript +++ b/dbus/wscript @@ -41,6 +41,8 @@ def build(bld): sysdeps_dbus_include = ['../linux', '../posix'] if bld.env['IS_MACOSX']: sysdeps_dbus_include = ['../macosx', '../posix'] + if bld.env['IS_QNX']: + sysdeps_dbus_include = ['../qnx', '../posix'] obj.includes = sysdeps_dbus_include + ['.', '../', '../common', '../common/jack'] obj.defines = ['HAVE_CONFIG_H','SERVER_SIDE'] @@ -66,6 +68,8 @@ def build(bld): obj.use += ['PTHREAD', 'DL', 'RT', 'DBUS-1', 'EXPAT', 'STDC++'] if bld.env['IS_MACOSX']: obj.use += ['PTHREAD', 'DL', 'DBUS-1', 'EXPAT'] + if bld.env['IS_QNX']: + obj.use += ['PTHREAD', 'DL', 'DBUS-1', 'EXPAT', 'STDC++'] obj.target = 'jackdbus' # process org.jackaudio.service.in -> org.jackaudio.service diff --git a/example-clients/wscript b/example-clients/wscript index ba67614e..33875011 100644 --- a/example-clients/wscript +++ b/example-clients/wscript @@ -47,6 +47,8 @@ def build(bld): os_incdir = ['../linux', '../posix'] if bld.env['IS_MACOSX']: os_incdir = ['../macosx', '../posix'] + if bld.env['IS_QNX']: + os_incdir = ['../qnx', '../posix'] if bld.env['IS_SUN']: os_incdir = ['../solaris', '../posix'] if bld.env['IS_WINDOWS']: @@ -75,11 +77,12 @@ def build(bld): prog.env.append_value("LINKFLAGS", "") if bld.env['IS_LINUX']: prog.use += ['RT', 'M'] + if bld.env['IS_QNX']: + prog.use += ['M','SOCKET'] if bld.env['IS_SUN']: prog.use += ['M'] prog.target = example_program - if bld.env['BUILD_EXAMPLE_CLIENT_TRANSPORT']: prog = bld(features = 'c cprogram') prog.includes = os_incdir + ['../common/jack', '../common'] @@ -95,6 +98,8 @@ def build(bld): prog.use += ['READLINE'] if bld.env['IS_WINDOWS']: prog.use += ['READLINE'] + if bld.env['IS_QNX']: + prog.use += ['READLINE'] prog.target = 'jack_transport' if bld.env['BUILD_EXAMPLE_CLIENT_REC']: @@ -109,6 +114,8 @@ def build(bld): prog.use += ['SNDFILE'] if bld.env['IS_LINUX']: prog.use += ['RT', 'SNDFILE'] + if bld.env['IS_QNX']: + prog.use += ['SNDFILE'] if bld.env['IS_SUN']: prog.use += ['RT', 'SNDFILE'] if bld.env['IS_WINDOWS']: diff --git a/posix/JackNetUnixSocket.cpp b/posix/JackNetUnixSocket.cpp index d3aeb4ce..d34f3a37 100644 --- a/posix/JackNetUnixSocket.cpp +++ b/posix/JackNetUnixSocket.cpp @@ -22,6 +22,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. #include #include +#include using namespace std; diff --git a/posix/JackPosixSemaphore.cpp b/posix/JackPosixSemaphore.cpp index e9503536..96e946b3 100644 --- a/posix/JackPosixSemaphore.cpp +++ b/posix/JackPosixSemaphore.cpp @@ -33,9 +33,9 @@ void JackPosixSemaphore::BuildName(const char* client_name, const char* server_n char ext_client_name[SYNC_MAX_NAME_SIZE + 1]; JackTools::RewriteName(client_name, ext_client_name); if (getenv("JACK_PROMISCUOUS_SERVER")) { - snprintf(res, size, "jack_sem.%s_%s", server_name, ext_client_name); + snprintf(res, size, "/jack_sem.%s_%s", server_name, ext_client_name); } else { - snprintf(res, size, "jack_sem.%d_%s_%s", JackTools::GetUID(), server_name, ext_client_name); + snprintf(res, size, "/jack_sem.%d_%s_%s", JackTools::GetUID(), server_name, ext_client_name); } } diff --git a/qnx/JackAtomic_os.h b/qnx/JackAtomic_os.h new file mode 100644 index 00000000..61ffc7e0 --- /dev/null +++ b/qnx/JackAtomic_os.h @@ -0,0 +1,86 @@ +/* +Copyright (C) 2004-2008 Grame + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __JackAtomic_qnx__ +#define __JackAtomic_qnx__ + +#include "JackTypes.h" + +#ifdef __PPC__ + +static inline int CAS(register UInt32 value, register UInt32 newvalue, register volatile void* addr) +{ + register int result; + register UInt32 tmp; + asm volatile ( + "# CAS \n" + " lwarx %4, 0, %1 \n" // creates a reservation on addr + " cmpw %4, %2 \n" // test value at addr + " bne- 1f \n" + " sync \n" // synchronize instructions + " stwcx. %3, 0, %1 \n" // if the reservation is not altered + // stores the new value at addr + " bne- 1f \n" + " li %0, 1 \n" + " b 2f \n" + "1: \n" + " li %0, 0 \n" + "2: \n" + : "=r" (result) + : "r" (addr), "r" (value), "r" (newvalue), "r" (tmp) + ); + return result; +} + +#endif + +#if defined(__i386__) || defined(__x86_64__) + +#define LOCK "lock ; " + +static inline char CAS(volatile UInt32 value, UInt32 newvalue, volatile void* addr) +{ + register char ret; + __asm__ __volatile__ ( + "# CAS \n\t" + LOCK "cmpxchg %2, (%1) \n\t" + "sete %0 \n\t" + : "=a" (ret) + : "c" (addr), "d" (newvalue), "a" (value) + ); + return ret; +} + +#endif + + + + +#if !defined(__i386__) && !defined(__x86_64__) && !defined(__PPC__) + + +static inline char CAS(volatile UInt32 value, UInt32 newvalue, volatile void* addr) +{ + return __sync_bool_compare_and_swap ((UInt32*)addr, value, newvalue); +} +#endif + + +#endif + diff --git a/qnx/JackPlatformPlug_os.h b/qnx/JackPlatformPlug_os.h new file mode 100644 index 00000000..0f401451 --- /dev/null +++ b/qnx/JackPlatformPlug_os.h @@ -0,0 +1,90 @@ +/* +Copyright (C) 2004-2008 Grame + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __JackPlatformPlug_qnx__ +#define __JackPlatformPlug_qnx__ + +#define jack_server_dir "/dev/shmem" +#define jack_client_dir "/dev/shmem" +#define JACK_DEFAULT_DRIVER "ioaudio" + +namespace Jack +{ + struct JackRequest; + struct JackResult; + + class JackPosixMutex; + class JackPosixThread; + class JackFifo; + class JackSocketServerChannel; + class JackSocketClientChannel; + class JackSocketServerNotifyChannel; + class JackSocketNotifyChannel; + class JackClientSocket; + class JackNetUnixSocket; +} + +/* __JackPlatformMutex__ */ +#include "JackPosixMutex.h" +namespace Jack {typedef JackPosixMutex JackMutex; } + +/* __JackPlatformThread__ */ +#include "JackPosixThread.h" +namespace Jack { typedef JackPosixThread JackThread; } + +/* __JackPlatformSynchro__ client activation */ +/* +#include "JackFifo.h" +namespace Jack { typedef JackFifo JackSynchro; } +*/ + +#include "JackPosixSemaphore.h" +namespace Jack { typedef JackPosixSemaphore JackSynchro; } + +/* __JackPlatformChannelTransaction__ */ +/* +#include "JackSocket.h" +namespace Jack { typedef JackClientSocket JackChannelTransaction; } +*/ + +/* __JackPlatformProcessSync__ */ +#include "JackPosixProcessSync.h" +namespace Jack { typedef JackPosixProcessSync JackProcessSync; } + +/* __JackPlatformServerChannel__ */ +#include "JackSocketServerChannel.h" +namespace Jack { typedef JackSocketServerChannel JackServerChannel; } + +/* __JackPlatformClientChannel__ */ +#include "JackSocketClientChannel.h" +namespace Jack { typedef JackSocketClientChannel JackClientChannel; } + +/* __JackPlatformServerNotifyChannel__ */ +#include "JackSocketServerNotifyChannel.h" +namespace Jack { typedef JackSocketServerNotifyChannel JackServerNotifyChannel; } + +/* __JackPlatformNotifyChannel__ */ +#include "JackSocketNotifyChannel.h" +namespace Jack { typedef JackSocketNotifyChannel JackNotifyChannel; } + +/* __JackPlatformNetSocket__ */ +#include "JackNetUnixSocket.h" +namespace Jack { typedef JackNetUnixSocket JackNetSocket; } + +#endif diff --git a/qnx/JackQnxTime.c b/qnx/JackQnxTime.c new file mode 100644 index 00000000..371e9f0d --- /dev/null +++ b/qnx/JackQnxTime.c @@ -0,0 +1,216 @@ +/* +Copyright (C) 2001-2003 Paul Davis +Copyright (C) 2005 Jussi Laako +Copyright (C) 2004-2008 Grame + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "JackConstants.h" +#include "JackTime.h" +#include "JackTypes.h" +#include "JackError.h" +#include "cycles.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +jack_time_t (*_jack_get_microseconds)(void) = 0; + +#if defined(__gnu_linux__) && (defined(__i386__) || defined(__x86_64__)) +#define HPET_SUPPORT +#define HPET_MMAP_SIZE 1024 +#define HPET_CAPS 0x000 +#define HPET_PERIOD 0x004 +#define HPET_COUNTER 0x0f0 +#define HPET_CAPS_COUNTER_64BIT (1 << 13) +#if defined(__x86_64__) +typedef uint64_t hpet_counter_t; +#else +typedef uint32_t hpet_counter_t; +#endif +static int hpet_fd; +static unsigned char *hpet_ptr; +static uint32_t hpet_period; /* period length in femto secs */ +static uint64_t hpet_offset = 0; +static uint64_t hpet_wrap; +static hpet_counter_t hpet_previous = 0; +#endif /* defined(__gnu_linux__) && (__i386__ || __x86_64__) */ + +#ifdef HPET_SUPPORT + +static int jack_hpet_init () +{ + uint32_t hpet_caps; + + hpet_fd = open("/dev/hpet", O_RDONLY); + if (hpet_fd < 0) { + jack_error ("This system has no accessible HPET device (%s)", strerror (errno)); + return -1; + } + + hpet_ptr = (unsigned char *) mmap(NULL, HPET_MMAP_SIZE, + PROT_READ, MAP_SHARED, hpet_fd, 0); + if (hpet_ptr == MAP_FAILED) { + jack_error ("This system has no mappable HPET device (%s)", strerror (errno)); + close (hpet_fd); + return -1; + } + + /* this assumes period to be constant. if needed, + it can be moved to the clock access function + */ + hpet_period = *((uint32_t *) (hpet_ptr + HPET_PERIOD)); + hpet_caps = *((uint32_t *) (hpet_ptr + HPET_CAPS)); + hpet_wrap = ((hpet_caps & HPET_CAPS_COUNTER_64BIT) && + (sizeof(hpet_counter_t) == sizeof(uint64_t))) ? + 0 : ((uint64_t) 1 << 32); + + return 0; +} + +static jack_time_t jack_get_microseconds_from_hpet (void) +{ + hpet_counter_t hpet_counter; + long double hpet_time; + + hpet_counter = *((hpet_counter_t *) (hpet_ptr + HPET_COUNTER)); + if (hpet_counter < hpet_previous) + hpet_offset += hpet_wrap; + hpet_previous = hpet_counter; + hpet_time = (long double) (hpet_offset + hpet_counter) * + (long double) hpet_period * (long double) 1e-9; + return ((jack_time_t) (hpet_time + 0.5)); +} + +#else + +static int jack_hpet_init () +{ + jack_error ("This version of JACK or this computer does not have HPET support.\n" + "Please choose a different clock source."); + return -1; +} + +static jack_time_t jack_get_microseconds_from_hpet (void) +{ + /* never called */ + return 0; +} + +#endif /* HPET_SUPPORT */ + +#define HAVE_CLOCK_GETTIME 1 + +#ifndef HAVE_CLOCK_GETTIME + +static jack_time_t jack_get_microseconds_from_system (void) +{ + jack_time_t jackTime; + struct timeval tv; + + gettimeofday (&tv, NULL); + jackTime = (jack_time_t) tv.tv_sec * 1000000 + (jack_time_t) tv.tv_usec; + return jackTime; +} + +#else + +static jack_time_t jack_get_microseconds_from_system (void) +{ + jack_time_t jackTime; + struct timespec time; + + clock_gettime(CLOCK_MONOTONIC, &time); + jackTime = (jack_time_t) time.tv_sec * 1e6 + + (jack_time_t) time.tv_nsec / 1e3; + return jackTime; +} + +#endif /* HAVE_CLOCK_GETTIME */ + + +SERVER_EXPORT void JackSleep(long usec) +{ + usleep(usec); +} + +SERVER_EXPORT void InitTime() +{ + /* nothing to do on a generic system - we use the system clock */ +} + +SERVER_EXPORT void EndTime() +{} + +void SetClockSource(jack_timer_type_t source) +{ + jack_log("Clock source : %s", ClockSourceName(source)); + + switch (source) + { + case JACK_TIMER_HPET: + if (jack_hpet_init () == 0) { + _jack_get_microseconds = jack_get_microseconds_from_hpet; + } else { + _jack_get_microseconds = jack_get_microseconds_from_system; + } + break; + + case JACK_TIMER_SYSTEM_CLOCK: + default: + _jack_get_microseconds = jack_get_microseconds_from_system; + break; + } +} + +const char* ClockSourceName(jack_timer_type_t source) +{ + switch (source) { + case JACK_TIMER_HPET: + return "hpet"; + case JACK_TIMER_SYSTEM_CLOCK: + #ifdef HAVE_CLOCK_GETTIME + return "system clock via clock_gettime"; + #else + return "system clock via gettimeofday"; + #endif + } + + /* what is wrong with gcc ? */ + return "unknown"; +} + +SERVER_EXPORT jack_time_t GetMicroSeconds() +{ + return _jack_get_microseconds(); +} + +SERVER_EXPORT jack_time_t jack_get_microseconds() +{ + return _jack_get_microseconds(); +} + diff --git a/qnx/cycles.h b/qnx/cycles.h new file mode 100644 index 00000000..ae721166 --- /dev/null +++ b/qnx/cycles.h @@ -0,0 +1,30 @@ +/* + Copyright (C) 2001 Paul Davis + Code derived from various headers from the Linux kernel + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: cycles.h,v 1.4.2.1 2006/06/20 14:44:00 letz Exp $ +*/ + +#ifndef __jack_cycles_h__ +#define __jack_cycles_h__ + +#include + +typedef uint64_t cycles_t; +#define get_cycles() ClockCycles() + +#endif /* __jack_cycles_h__ */ diff --git a/qnx/ioaudio/JackIoAudioAdapter.cpp b/qnx/ioaudio/JackIoAudioAdapter.cpp new file mode 100644 index 00000000..b71a417e --- /dev/null +++ b/qnx/ioaudio/JackIoAudioAdapter.cpp @@ -0,0 +1,1209 @@ +/* + Copyright (C) 2008 Grame + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + */ + +#if defined(HAVE_CONFIG_H) +#include "config.h" +#endif + +#include "JackIoAudioAdapter.h" +#include "JackGlobals.h" +#include "JackEngineControl.h" + +#define check_error(err) if (err) { jack_error("%s:%d, io-audio error %d : %s", __FILE__, __LINE__, err, snd_strerror(err)); return err; } +#define check_error_msg(err,msg) if (err) { jack_error("%s:%d, %s : %s(%d)", __FILE__, __LINE__, msg, snd_strerror(err), err); return err; } +#define display_error_msg(err,msg) if (err) { jack_error("%s:%d, %s : %s(%d)", __FILE__, __LINE__, msg, snd_strerror(err), err); } + +namespace Jack +{ + + template + int snd_pcm_malloc_struct( + T** ptr ) + { + T* tmp = (T*)malloc( sizeof(T) ); + if( NULL == tmp ) + { + *ptr = NULL; + return -1; + } + *ptr = tmp; + memset( tmp, + 0, + sizeof(T) ); + return 0; + } + + template + int snd_pcm_free_struct( + T** ptr ) + { + if( NULL != *ptr ) + { + free( *ptr ); + *ptr = NULL; + } + + return 0; + } + + inline void* aligned_calloc( + size_t nmemb, + size_t size ) + { + return (void*)calloc( nmemb, + size ); + } + + AudioInterface::AudioInterface( + const AudioParam& ap ) : + fParams( ap ) + { + AudioInterface_common(); + } + + AudioInterface::AudioInterface( + jack_nframes_t buffer_size, + jack_nframes_t sample_rate ) : + fParams( buffer_size, + sample_rate ) + { + AudioInterface_common(); + } + + int AudioInterface::AudioInterface_common() + { + fInputDevice = NULL; + check_error( snd_pcm_malloc_struct( &fInputParams ) ); + check_error( snd_pcm_malloc_struct( &fInputSetup ) ); + fInputCardBuffer = NULL; + fJackInputBuffers = + (jack_default_audio_sample_t**)calloc( NUM_BUFFERS, + sizeof(jack_default_audio_sample_t*) ); + + fOutputDevice = NULL; + check_error( snd_pcm_malloc_struct( &fOutputParams ) ); + check_error( snd_pcm_malloc_struct( &fOutputSetup ) ); + fOutputCardBuffer = NULL; + fJackOutputBuffers = + (jack_default_audio_sample_t**)calloc( NUM_BUFFERS, + sizeof(jack_default_audio_sample_t*) ); + + return EOK; + } + + AudioInterface::~AudioInterface() + { + snd_pcm_free_struct( &fInputParams ); + snd_pcm_free_struct( &fInputSetup ); + free( fJackInputBuffers ); + + snd_pcm_free_struct( &fOutputParams ); + snd_pcm_free_struct( &fOutputSetup ); + free( fJackOutputBuffers ); + } + + /** + * Open the audio interface + */ + int AudioInterface::open() + { + /////////////////////////////////////////////////////////////////////// + // Declare Local Variables + /////////////////////////////////////////////////////////////////////// + int err; + int card; + int device; + + /////////////////////////////////////////////////////////////////////// + // Input Device Setup + /////////////////////////////////////////////////////////////////////// + + if( strncmp( fParams.fInputCardName, + "none", + 4 ) != 0 ) + { + + check_error( + snd_pcm_open_name( &fInputDevice, + fParams.fInputCardName, + SND_PCM_OPEN_CAPTURE ) ); + + // Disable plugin conversions for this device + err = snd_pcm_plugin_set_disable( fInputDevice, + PLUGIN_BUFFER_PARTIAL_BLOCKS ); + err = snd_pcm_plugin_set_disable( fInputDevice, + PLUGIN_MMAP ); + err = snd_pcm_plugin_set_disable( fInputDevice, + PLUGIN_ROUTING ); + err = snd_pcm_plugin_set_disable( fInputDevice, + PLUGIN_CONVERSION ); + + // get channel parameters + snd_pcm_channel_info_t inputInfo; + memset( &inputInfo, + 0, + sizeof( inputInfo ) ); + inputInfo.channel = SND_PCM_CHANNEL_CAPTURE; + check_error( snd_pcm_channel_info( fInputDevice, + &inputInfo ) ); + + //get hardware input parameters + fInputParams->mode = SND_PCM_MODE_BLOCK; + fInputParams->channel = SND_PCM_CHANNEL_CAPTURE; + //fInputParams->sync = Not Supported by io-audio + + // Check supported formats, preferring in order: float, sint32, sint16, plugin converted + if( inputInfo.formats & SND_PCM_FMT_FLOAT ) + { + fInputParams->format.format = SND_PCM_SFMT_FLOAT; + } + else if( inputInfo.formats & SND_PCM_FMT_S32_LE ) + { + fInputParams->format.format = SND_PCM_SFMT_S32_LE; + } + else if( inputInfo.formats & SND_PCM_FMT_S16_LE ) + { + fInputParams->format.format = SND_PCM_SFMT_S16_LE; + } + else + { + // Re-enable format conversion plugin if device can't accept float, sint32 or sint16 + snd_pcm_plugin_set_enable( fInputDevice, + PLUGIN_CONVERSION ); + fInputParams->format.format = SND_PCM_SFMT_FLOAT; + } + + fInputParams->format.interleave = 1; + fInputParams->format.rate = fParams.fFrequency; + fInputParams->format.voices = fNumInputPorts; + //fInputParams->digital = Not currently implemented in io-audio + fInputParams->start_mode = SND_PCM_START_DATA; + fInputParams->stop_mode = SND_PCM_STOP_STOP; + //fInputParams->time = 1; // If set, the driver offers the time when the transfer began (gettimeofday() format) + //fInputParams->ust_time = 1; // If set, the driver offers the time when the transfer began (UST format) + fInputParams->buf.block.frag_size = + snd_pcm_format_size( fInputParams->format.format, + fParams.fBuffering * fNumInputPorts ); + ; + fInputParams->buf.block.frags_max = fParams.fPeriod; + fInputParams->buf.block.frags_min = 1; + strcpy( fInputParams->sw_mixer_subchn_name, + "Jack Capture Channel" ); + + //set params record with initial values + if( ( err = snd_pcm_channel_params( fInputDevice, + fInputParams ) ) != 0 ) + { + jack_error( "%s:%d, io-audio error %d : %s, why_failed = %d", + __FILE__, + __LINE__, + err, + snd_strerror( err ), + fInputParams->why_failed ); + return err; + } + + //get params record with actual values + fInputSetup->channel = SND_PCM_CHANNEL_CAPTURE; + check_error( snd_pcm_channel_setup( fInputDevice, + fInputSetup ) ); + + //set hardware buffers + fInputFormat = fInputSetup->format; + fNumInputPorts = fInputFormat.voices; + ssize_t frameSize = snd_pcm_format_size( fInputFormat.format, + fInputFormat.voices ); + fInputBufferFrames = fInputSetup->buf.block.frag_size + / ( frameSize ); + + size_t nfrags = fInputSetup->buf.block.frags_max; + size_t nframes = fInputBufferFrames; + size_t framesize = frameSize; + + fInputCardBuffer = aligned_calloc( nfrags, + nframes * framesize ); + + //create floating point buffers needed by the JACK + for( unsigned int i = 0; i < fNumInputPorts; i++ ) + { + fJackInputBuffers[i] = + (jack_default_audio_sample_t*)aligned_calloc( + fInputBufferFrames, + sizeof(jack_default_audio_sample_t) ); + for( unsigned int j = 0; j < fInputBufferFrames; j++ ) + fJackInputBuffers[i][j] = 0.0; + } + } + /////////////////////////////////////////////////////////////////////// + // Output Device Setup + /////////////////////////////////////////////////////////////////////// + + if( strncmp( fParams.fOutputCardName, + "none", + 4 ) != 0 ) + { + + check_error( + snd_pcm_open_name( &fOutputDevice, + fParams.fOutputCardName, + SND_PCM_OPEN_PLAYBACK ) ); + + // Disable plugin conversions for this device + err = snd_pcm_plugin_set_disable( fOutputDevice, + PLUGIN_BUFFER_PARTIAL_BLOCKS ); + err = snd_pcm_plugin_set_disable( fOutputDevice, + PLUGIN_MMAP ); + err = snd_pcm_plugin_set_disable( fOutputDevice, + PLUGIN_ROUTING ); + err = snd_pcm_plugin_set_disable( fOutputDevice, + PLUGIN_CONVERSION ); + + snd_pcm_channel_info_t outputInfo; + memset( &outputInfo, + 0, + sizeof( outputInfo ) ); + outputInfo.channel = SND_PCM_CHANNEL_PLAYBACK; + check_error( snd_pcm_channel_info( fOutputDevice, + &outputInfo ) ); + + //get hardware output parameters + check_error( snd_pcm_malloc_struct( &fOutputParams ) ); + fOutputParams->channel = SND_PCM_CHANNEL_PLAYBACK; + fOutputParams->mode = SND_PCM_MODE_BLOCK; + //fOutputParams->sync = Not Supported by io-audio + + // Check supported formats, preferring in order: float, sint32, sint16, plugin converted + if( outputInfo.formats & SND_PCM_FMT_FLOAT ) + { + fOutputParams->format.format = SND_PCM_SFMT_FLOAT; + } + else if( outputInfo.formats & SND_PCM_FMT_S32_LE ) + { + fOutputParams->format.format = SND_PCM_SFMT_S32_LE; + } + else if( outputInfo.formats & SND_PCM_FMT_S16_LE ) + { + fOutputParams->format.format = SND_PCM_SFMT_S16_LE; + } + else + { + // Re-enable format conversion plugin if device can't accept float, sint32 or sint16 + snd_pcm_plugin_set_enable( fOutputDevice, + PLUGIN_CONVERSION ); + fOutputParams->format.format = SND_PCM_SFMT_FLOAT; + } + + fOutputParams->format.interleave = 1; + fOutputParams->format.rate = fParams.fFrequency; + fOutputParams->format.voices = fNumOutputPorts; + //fOutputParams->digital = Not currently implemented by io-audio + fOutputParams->start_mode = SND_PCM_START_DATA; + fOutputParams->stop_mode = SND_PCM_STOP_STOP; + //fOutputParams->time = 1; // If set, the driver offers the time when the transfer began (gettimeofday() format) + //fOutputParams->ust_time = 1; // If set, the driver offers the time when the transfer began (UST Format) + fOutputParams->buf.block.frag_size = + snd_pcm_format_size( fOutputParams->format.format, + fParams.fBuffering * fNumOutputPorts ); + fOutputParams->buf.block.frags_max = fParams.fPeriod; + fOutputParams->buf.block.frags_min = 1; + strcpy( fOutputParams->sw_mixer_subchn_name, + "Jack Playback Channel" ); + + //set params record with initial values + if( ( err = snd_pcm_channel_params( fOutputDevice, + fOutputParams ) ) != 0 ) + { + jack_error( "%s:%d, io-audio error %d : %s, why_failed = %d", + __FILE__, + __LINE__, + err, + snd_strerror( err ), + fOutputParams->why_failed ); + return err; + } + + //get params record with actual values + check_error( snd_pcm_channel_setup( fOutputDevice, + fOutputSetup ) ); + + //set hardware buffers + fOutputFormat = fOutputSetup->format; + + fNumOutputPorts = fOutputFormat.voices; + ssize_t frameSize = snd_pcm_format_size( fOutputFormat.format, + fOutputFormat.voices ); + fOutputBufferFrames = fOutputSetup->buf.block.frag_size + / ( frameSize ); + + size_t nfrags = fOutputSetup->buf.block.frags_max; + size_t nframes = fOutputBufferFrames; + size_t framesize = frameSize; + + fOutputCardBuffer = aligned_calloc( nfrags, + nframes * framesize ); + + //create floating point buffers needed by the JACK + for( unsigned int i = 0; i < fNumOutputPorts; i++ ) + { + fJackOutputBuffers[i] = + (jack_default_audio_sample_t*)aligned_calloc( + fOutputBufferFrames, + sizeof(jack_default_audio_sample_t) ); + for( unsigned int j = 0; j < fOutputBufferFrames; j++ ) + fJackOutputBuffers[i][j] = 0.0; + } + } + + return 0; + } + + int AudioInterface::close() + { + snd_pcm_close( fInputDevice ); + snd_pcm_close( fOutputDevice ); + + for( unsigned int i = 0; i < fNumInputPorts; i++ ) + if( fJackInputBuffers[i] ) + free( fJackInputBuffers[i] ); + + for( unsigned int i = 0; i < fNumOutputPorts; i++ ) + if( fJackOutputBuffers[i] ) + free( fJackOutputBuffers[i] ); + + if( fInputCardBuffer ) + free( fInputCardBuffer ); + if( fOutputCardBuffer ) + free( fOutputCardBuffer ); + + return 0; + } + + ssize_t AudioInterface::interleavedBufferSize( + snd_pcm_channel_params_t* params ) + { + return params->buf.block.frag_size * params->format.voices; + } + + ssize_t AudioInterface::noninterleavedBufferSize( + snd_pcm_channel_params_t* params ) + { + return params->buf.block.frag_size; + } + + /** + * Read audio samples from the audio card. Convert samples to floats and take + * care of interleaved buffers + */ + int AudioInterface::read() + { + ssize_t count; + unsigned int s; + unsigned int c; + if( NULL != fInputDevice ) + { + switch( fInputFormat.interleave ) + { + case 1: + count = snd_pcm_read( fInputDevice, + fInputCardBuffer, + fInputBufferFrames ); + if( count < 0 ) + { + display_error_msg( count, + "reading interleaved samples" ); + check_error_msg( + snd_pcm_channel_prepare( fInputDevice, + SND_PCM_CHANNEL_CAPTURE ), + "preparing input stream" ); + } + jack_log( "JackIoAudioAdapter read %ld interleaved bytes", + count ); + if( fInputFormat.format == SND_PCM_SFMT_S16 ) + { + int16_t* buffer16b = (int16_t*)fInputCardBuffer; + for( s = 0; s < fInputBufferFrames; s++ ) + for( c = 0; c < fNumInputPorts; c++ ) + fJackInputBuffers[c][s] = + jack_default_audio_sample_t( + buffer16b[c + + s + * fNumInputPorts] ) + * ( jack_default_audio_sample_t( 1.0 ) + / jack_default_audio_sample_t( + SHRT_MAX ) ); + } + else // SND_PCM_SFMT_S32 + { + int32_t* buffer32b = (int32_t*)fInputCardBuffer; + for( s = 0; s < fInputBufferFrames; s++ ) + for( c = 0; c < fNumInputPorts; c++ ) + fJackInputBuffers[c][s] = + jack_default_audio_sample_t( + buffer32b[c + + s + * fNumInputPorts] ) + * ( jack_default_audio_sample_t( 1.0 ) + / jack_default_audio_sample_t( INT_MAX ) ); + } + break; + case 0: + count = snd_pcm_read( fInputDevice, + fInputCardBuffer, + fInputBufferFrames ); + if( count < 0 ) + { + display_error_msg( count, + "reading non-interleaved samples" ); + check_error_msg( + snd_pcm_channel_prepare( fInputDevice, + SND_PCM_CHANNEL_CAPTURE ), + "preparing input stream" ); + } + jack_log( "JackIoAudioAdapter read %ld non-interleaved bytes", + count ); + if( fInputFormat.format == SND_PCM_SFMT_S16 ) + { + int16_t* buffer16b; + for( c = 0; c < fNumInputPorts; c++ ) + { + buffer16b = (int16_t*)fInputCardBuffer; + for( s = 0; s < fInputBufferFrames; s++ ) + fJackInputBuffers[c][s] = + jack_default_audio_sample_t( buffer16b[s] ) + * ( jack_default_audio_sample_t( 1.0 ) + / jack_default_audio_sample_t( + SHRT_MAX ) ); + } + } + else // SND_PCM_SFMT_S32 + { + int32_t* buffer32b; + for( c = 0; c < fNumInputPorts; c++ ) + { + buffer32b = (int32_t*)fInputCardBuffer; + for( s = 0; s < fInputBufferFrames; s++ ) + fJackInputBuffers[c][s] = + jack_default_audio_sample_t( + buffer32b[s] ) + * ( jack_default_audio_sample_t( 1.0 ) + / jack_default_audio_sample_t( INT_MAX ) ); + } + } + break; + default: + check_error_msg( -10000, + "unknown access mode" ) + ; + break; + } + } + return 0; + } + + /** + * write the output soft channels to the audio card. Convert sample + * format and interleaves buffers when needed + */ + int AudioInterface::write() + { + ssize_t amount; + ssize_t count; + unsigned int f; + unsigned int c; + if( NULL != fOutputDevice ) + { + recovery: switch( fOutputFormat.interleave ) + { + case 1: + if( fOutputFormat.format == SND_PCM_SFMT_S16 ) + { + int16_t* buffer16b = (int16_t*)fOutputCardBuffer; + for( f = 0; f < fOutputBufferFrames; f++ ) + { + for( c = 0; c < fNumOutputPorts; c++ ) + { + jack_default_audio_sample_t x = + fJackOutputBuffers[c][f]; + buffer16b[c + ( f * fNumOutputPorts )] = + short( + max(min (x, jack_default_audio_sample_t(1.0)), jack_default_audio_sample_t(-1.0)) + * jack_default_audio_sample_t( + SHRT_MAX ) ); + } + } + } + else // SND_PCM_FORMAT_S32 + { + int32_t* buffer32b = (int32_t*)fOutputCardBuffer; + for( f = 0; f < fOutputBufferFrames; f++ ) + { + for( c = 0; c < fNumOutputPorts; c++ ) + { + jack_default_audio_sample_t x = + fJackOutputBuffers[c][f]; + buffer32b[c + f * fNumOutputPorts] = + int32_t( + max(min(x, jack_default_audio_sample_t(1.0)), jack_default_audio_sample_t(-1.0)) + * jack_default_audio_sample_t( + INT_MAX ) ); + } + } + } + amount = snd_pcm_format_size( fOutputFormat.format, + fOutputBufferFrames + * fNumOutputPorts ); + count = snd_pcm_write( fOutputDevice, + fOutputCardBuffer, + amount ); + if( count <= 0 ) + { + display_error_msg( count, + "writing interleaved" ); + int err = + snd_pcm_channel_prepare( fOutputDevice, + SND_PCM_CHANNEL_PLAYBACK ); + check_error_msg( err, + "preparing output stream" ); + goto recovery; + } + jack_log( "JackIoAudioAdapter wrote %ld interleaved bytes", + count ); + break; + case 0: + if( fOutputFormat.format == SND_PCM_SFMT_S16 ) + { + int16_t* buffer16b = (int16_t*)fOutputCardBuffer; + for( c = 0; c < fNumOutputPorts; c++ ) + { + for( f = 0; f < fOutputBufferFrames; f++ ) + { + jack_default_audio_sample_t x = + fJackOutputBuffers[c][f]; + buffer16b[f] = + short( + max(min (x, jack_default_audio_sample_t(1.0)), jack_default_audio_sample_t(-1.0)) + * jack_default_audio_sample_t( + SHRT_MAX ) ); + } + } + } + else // SND_PCM_FORMAT_S32 + { + int32_t* buffer32b = (int32_t*)fOutputCardBuffer; + for( c = 0; c < fNumOutputPorts; c++ ) + { + for( f = 0; f < fOutputBufferFrames; f++ ) + { + jack_default_audio_sample_t x = + fJackOutputBuffers[c][f]; + buffer32b[f + ( c * fNumOutputPorts )] = + int32_t( + max(min(x, jack_default_audio_sample_t(1.0)), jack_default_audio_sample_t(-1.0)) + * jack_default_audio_sample_t( + INT_MAX ) ); + } + } + } + amount = snd_pcm_format_size( fOutputFormat.format, + fOutputBufferFrames + * fNumOutputPorts ); + count = snd_pcm_write( fOutputDevice, + fOutputCardBuffer, + fOutputBufferFrames ); + if( count <= 0 ) + { + display_error_msg( count, + "writing non-interleaved" ); + int err = + snd_pcm_channel_prepare( fOutputDevice, + SND_PCM_CHANNEL_PLAYBACK ); + check_error_msg( err, + "preparing output stream" ); + goto recovery; + } + jack_log( "JackIoAudioAdapter wrote %ld non-interleaved bytes", + count ); + break; + default: + check_error_msg( -10000, + "unknown access mode" ) + ; + break; + } + } + return 0; + } + + /** + * print short information on the audio device + */ + int AudioInterface::shortinfo() + { + snd_ctl_hw_info_t card_info; + snd_ctl_t* ctl_handle; + + int card = snd_card_name( fParams.fInputCardName ); + if( card < 0 ) + return card; + + check_error( snd_ctl_open( &ctl_handle, + card ) ); + check_error( snd_ctl_hw_info( ctl_handle, + &card_info ) ); + jack_info( "%s|%d|%d|%d|%d|%s", + card_info.name, + fNumInputPorts, + fNumOutputPorts, + fParams.fFrequency, + fOutputBufferFrames, + snd_pcm_get_format_name( fInputFormat.format ) ); + snd_ctl_close( ctl_handle ); + + return 0; + } + + /** + * print more detailled information on the audio device + */ + int AudioInterface::longinfo() + { + snd_ctl_hw_info_t card_info; + snd_ctl_t* ctl_handle; + + //display info + jack_info( "Audio Interface Description :" ); + jack_info( + "Sampling Frequency : %d, Sample Format : %s, buffering : %d, nperiod : %d", + fParams.fFrequency, + snd_pcm_get_format_name( fInputFormat.format ), + fOutputBufferFrames, + fParams.fPeriod ); + jack_info( "Software inputs : %2d, Software outputs : %2d", + fNumInputPorts, + fNumOutputPorts ); + jack_info( "Hardware inputs : %2d, Hardware outputs : %2d", + fNumInputPorts, + fNumOutputPorts ); + + //get audio card info and display + int card = snd_card_name( fParams.fInputCardName ); + check_error( snd_ctl_open( &ctl_handle, + card ) ); + check_error( snd_ctl_hw_info( ctl_handle, + &card_info ) ); + printCardInfo( &card_info ); + + //display input/output streams info + if( fNumInputPorts > 0 ) + printHWParams( fInputParams ); + if( fNumOutputPorts > 0 ) + printHWParams( fOutputParams ); + snd_ctl_close( ctl_handle ); + return 0; + } + + void AudioInterface::printCardInfo( + snd_ctl_hw_info_t* ci ) + { + jack_info( "Card info (address : %p)", + ci ); + jack_info( "\tID = %s", + ci->id ); + jack_info( "\tDriver = %s", + ci->abbreviation ); + jack_info( "\tName = %s", + ci->name ); + jack_info( "\tLongName = %s", + ci->longname ); + jack_info( "\tMixerName = %s", + ci ); + jack_info( "\tComponents = %s", + ci ); + jack_info( "--------------" ); + } + + void AudioInterface::printHWParams( + snd_pcm_channel_params_t* params ) + { + jack_info( "HW Params info (address : %p)\n", + params ); + jack_info( "\tChannels = %d", + params->format.voices ); + jack_info( "\tFormat = %s", + snd_pcm_get_format_name( params->format.format ) ); + jack_info( "\tAccess = %s", + "" ); + jack_info( "\tRate = %d", + params->format.rate ); + jack_info( "\tPeriods = %d", + 0 ); + jack_info( "\tPeriod size = %d", + 0 ); + jack_info( "\tPeriod time = %d", + 0 ); + jack_info( "\tBuffer size = %d", + 0 ); + jack_info( "\tBuffer time = %d", + 0 ); + jack_info( "--------------" ); + } + + JackIoAudioAdapter::JackIoAudioAdapter( + jack_nframes_t buffer_size, + jack_nframes_t sample_rate, + const JSList* params ) : + JackAudioAdapterInterface( buffer_size, + sample_rate ), + fThread( this ), + fAudioInterface( + buffer_size, + sample_rate ) + { + const JSList* node; + const jack_driver_param_t* param; + + fCaptureChannels = 2; + fPlaybackChannels = 2; + + fAudioInterface.fParams.fPeriod = 2; + + for( node = params; node; node = jack_slist_next( node ) ) + { + param = (const jack_driver_param_t*)node->data; + + switch( param->character ) + { + case 'i': + jack_info( "fCardInputVoices = %d", + param->value.ui ); + fAudioInterface.fParams.fCardInputVoices = param->value.ui; + break; + case 'o': + jack_info( "fCardOutputVoices = %d", + param->value.ui ); + fAudioInterface.fParams.fCardOutputVoices = param->value.ui; + break; + case 'C': + jack_info( "fInputCardName = %s", + param->value.str ); + fAudioInterface.fParams.fInputCardName = + strdup( param->value.str ); + break; + case 'P': + jack_info( "fOutputCardName = %s", + param->value.str ); + fAudioInterface.fParams.fOutputCardName = + strdup( param->value.str ); + break; + case 'D': + break; + case 'n': + jack_info( "fPeriod = %d", + param->value.ui ); + fAudioInterface.fParams.fPeriod = param->value.ui; + break; + case 'r': + jack_info( "fFrequency = %d", + param->value.ui ); + fAudioInterface.fParams.fFrequency = param->value.ui; + SetAdaptedSampleRate( param->value.ui ); + break; + case 'p': + jack_info( "fBuffering = %d", + param->value.ui ); + fAudioInterface.fParams.fBuffering = param->value.ui; + SetAdaptedBufferSize( param->value.ui ); + break; + case 'q': + jack_info( "fQuality = %d", + param->value.ui ); + fQuality = param->value.ui; + break; + case 'g': + jack_info( "fRingbufferCurSize = %d", + param->value.ui ); + fRingbufferCurSize = param->value.ui; + fAdaptative = false; + break; + } + } + } + + int JackIoAudioAdapter::Open() + { + //open audio interface + if( fAudioInterface.open() ) + return -1; + + //start adapter thread + if( fThread.StartSync() < 0 ) + { + jack_error( "Cannot start audioadapter thread" ); + return -1; + } + + //display card info + fAudioInterface.longinfo(); + + //turn the thread realtime + fThread.AcquireRealTime( GetEngineControl()->fClientPriority ); + return 0; + } + + int JackIoAudioAdapter::Close() + { +#ifdef JACK_MONITOR + fTable.Save(fHostBufferSize, fHostSampleRate, fAdaptedSampleRate, fAdaptedBufferSize); +#endif + switch( fThread.GetStatus() ) + { + + // Kill the thread in Init phase + case JackThread::kStarting: + case JackThread::kIniting: + if( fThread.Kill() < 0 ) + { + jack_error( "Cannot kill thread" ); + return -1; + } + break; + + // Stop when the thread cycle is finished + case JackThread::kRunning: + if( fThread.Stop() < 0 ) + { + jack_error( "Cannot stop thread" ); + return -1; + } + break; + + default: + break; + } + return fAudioInterface.close(); + } + + void JackIoAudioAdapter::Create() + { + int err; + snd_pcm_channel_info_t channel_info; + snd_pcm_t* device; + + jack_info( "Fixed ringbuffer size = %d frames", + fRingbufferCurSize ); + + if( strncmp( fAudioInterface.fParams.fInputCardName, + "none", + 4 ) != 0 ) + { + err = snd_pcm_open_name( &device, + fAudioInterface.fParams.fInputCardName, + SND_PCM_OPEN_CAPTURE ); + + // get channel parameters + memset( &channel_info, + 0, + sizeof( channel_info ) ); + channel_info.channel = SND_PCM_CHANNEL_CAPTURE; + err = snd_pcm_channel_info( device, + &channel_info ); + snd_pcm_close( device ); + + // Determine number of capture channels from card or parameter + if( 0 == fAudioInterface.fParams.fCardInputVoices ) + { + fCaptureChannels = channel_info.max_voices; + } + else + { + fCaptureChannels = + max( channel_info.min_voices, + (int )fAudioInterface.fParams.fCardInputVoices ); + fCaptureChannels = min( channel_info.max_voices, + fCaptureChannels ); + } + + fAudioInterface.fNumInputPorts = fCaptureChannels; + + // Create capture channel ringbuffers + fCaptureRingBuffer = new JackResampler*[fCaptureChannels]; + for( int i = 0; i < fCaptureChannels; i++ ) + { + fCaptureRingBuffer[i] = new JackResampler(); + fCaptureRingBuffer[i]->Reset( fRingbufferCurSize ); + } + jack_log( "ReadSpace = %ld", + fCaptureRingBuffer[0]->ReadSpace() ); + } + else + { + fCaptureChannels = 0; + fCaptureRingBuffer = NULL; + fAudioInterface.fNumInputPorts = 0; + } + + if( strncmp( fAudioInterface.fParams.fOutputCardName, + "none", + 4 ) != 0 ) + { + int err; + + err = snd_pcm_open_name( &device, + fAudioInterface.fParams.fOutputCardName, + SND_PCM_OPEN_PLAYBACK ); + + // get channel parameters + memset( &channel_info, + 0, + sizeof( channel_info ) ); + channel_info.channel = SND_PCM_CHANNEL_PLAYBACK; + err = snd_pcm_channel_info( device, + &channel_info ); + snd_pcm_close( device ); + + // Determine number of playback channels from card or parameter + if( 0 == fAudioInterface.fParams.fCardOutputVoices ) + { + fPlaybackChannels = channel_info.max_voices; + } + else + { + fPlaybackChannels = + max( channel_info.min_voices, + (int )fAudioInterface.fParams.fCardOutputVoices ); + fPlaybackChannels = + min( channel_info.max_voices, + fPlaybackChannels ); + } + + fAudioInterface.fNumOutputPorts = fPlaybackChannels; + + // Create playback channel ringbuffers + fPlaybackRingBuffer = new JackResampler*[fPlaybackChannels]; + for( int i = 0; i < fPlaybackChannels; i++ ) + { + fPlaybackRingBuffer[i] = new JackResampler(); + fPlaybackRingBuffer[i]->Reset( fRingbufferCurSize ); + } + jack_log( "ReadSpace = %ld", + fPlaybackRingBuffer[0]->ReadSpace() ); + } + else + { + fPlaybackChannels = 0; + fPlaybackRingBuffer = NULL; + fAudioInterface.fNumOutputPorts = 0; + } + } + + void JackIoAudioAdapter::Destroy() + { + for( int i = 0; i < fCaptureChannels; i++ ) + { + delete ( fCaptureRingBuffer[i] ); + } + for( int i = 0; i < fPlaybackChannels; i++ ) + { + delete ( fPlaybackRingBuffer[i] ); + } + + delete[] fCaptureRingBuffer; + delete[] fPlaybackRingBuffer; + } + + bool JackIoAudioAdapter::Init() + { + //fill the hardware buffers + for( unsigned int i = 0; i < fAudioInterface.fParams.fPeriod; i++ ) + fAudioInterface.write(); + return true; + } + + bool JackIoAudioAdapter::Execute() + { + //read data from audio interface + if( fAudioInterface.read() < 0 ) + return false; + + PushAndPull( fAudioInterface.fJackInputBuffers, + fAudioInterface.fJackOutputBuffers, + fAdaptedBufferSize ); + + //write data to audio interface + if( fAudioInterface.write() < 0 ) + return false; + + return true; + } + + int JackIoAudioAdapter::SetSampleRate( + jack_nframes_t sample_rate ) + { + JackAudioAdapterInterface::SetHostSampleRate( sample_rate ); + Close(); + return Open(); + } + + int JackIoAudioAdapter::SetBufferSize( + jack_nframes_t buffer_size ) + { + JackAudioAdapterInterface::SetHostBufferSize( buffer_size ); + Close(); + return Open(); + } + +} // namespace + +#ifdef __cplusplus +extern "C" +{ +#endif + + SERVER_EXPORT jack_driver_desc_t* jack_get_descriptor() + { + jack_driver_desc_t * desc; + jack_driver_desc_filler_t filler; + jack_driver_param_value_t value; + + desc = + jack_driver_descriptor_construct( "audioadapter", + JackDriverNone, + "netjack audio <==> net backend adapter", + &filler ); + + strcpy( value.str, + "pcmPreferredc" ); + jack_driver_descriptor_add_parameter( desc, + &filler, + "capture", + 'C', + JackDriverParamString, + &value, + NULL, + "Provide capture ports for this io-audio device", + "Opens the given io-audio device for capture and provides ports for its voices.\n" + "Pass \"none\" to disable." ); + + strcpy( value.str, + "pcmPreferredp" ); + jack_driver_descriptor_add_parameter( desc, + &filler, + "playback", + 'P', + JackDriverParamString, + &value, + NULL, + "Provide playback ports for this io-audio device", + "Opens the given io-audio device for playback and provides ports for its voices.\n" + "Pass \"none\" to disable." ); + + value.ui = 48000U; + jack_driver_descriptor_add_parameter( desc, + &filler, + "rate", + 'r', + JackDriverParamUInt, + &value, + NULL, + "Sample rate", + NULL ); + + value.ui = 512U; + jack_driver_descriptor_add_parameter( desc, + &filler, + "periodsize", + 'p', + JackDriverParamUInt, + &value, + NULL, + "Period size", + NULL ); + + value.ui = 2U; + jack_driver_descriptor_add_parameter( desc, + &filler, + "nperiods", + 'n', + JackDriverParamUInt, + &value, + NULL, + "Number of periods of playback latency", + NULL ); + + value.i = true; + jack_driver_descriptor_add_parameter( desc, + &filler, + "duplex", + 'D', + JackDriverParamBool, + &value, + NULL, + "Provide both capture and playback ports", + NULL ); + + value.i = 0; + jack_driver_descriptor_add_parameter( desc, + &filler, + "in-channels", + 'i', + JackDriverParamInt, + &value, + NULL, + "Number of capture voices (defaults to hardware max)", + "Provides up to this many input ports corresponding to voices on the capture device." ); + + jack_driver_descriptor_add_parameter( desc, + &filler, + "out-channels", + 'o', + JackDriverParamInt, + &value, + NULL, + "Number of playback voices (defaults to hardware max)", + "Provides up to this many output ports corresponding to voices on the playback device." ); + + value.ui = 0; + jack_driver_descriptor_add_parameter( desc, + &filler, + "quality", + 'q', + JackDriverParamUInt, + &value, + NULL, + "Resample algorithm quality (0 - 4)", + NULL ); + + value.ui = 32768; + jack_driver_descriptor_add_parameter( desc, + &filler, + "ring-buffer", + 'g', + JackDriverParamUInt, + &value, + NULL, + "Fixed ringbuffer size", + "Fixed ringbuffer size (if not set => automatic adaptative)" ); + + return desc; + } + +#ifdef __cplusplus +} +#endif + diff --git a/qnx/ioaudio/JackIoAudioAdapter.h b/qnx/ioaudio/JackIoAudioAdapter.h new file mode 100644 index 00000000..3e9b9081 --- /dev/null +++ b/qnx/ioaudio/JackIoAudioAdapter.h @@ -0,0 +1,257 @@ +/* + Copyright (C) 2008 Grame + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + */ + +#ifndef __JackIoAudioAdapter__ +#define __JackIoAudioAdapter__ + +#include +#include +#include +#include +#include "JackAudioAdapterInterface.h" +#include "JackPlatformPlug.h" +#include "JackError.h" +#include "jack.h" +#include "jslist.h" + +namespace Jack + { + +#define max(x,y) (((x)>(y)) ? (x) : (y)) +#define min(x,y) (((x)<(y)) ? (x) : (y)) +#define NUM_BUFFERS 256 + + /** + * A convenient class to pass parameters to AudioInterface + */ + class AudioParam + { + public: + const char* fInputCardName; + const char* fOutputCardName; + unsigned int fFrequency; + int fBuffering; + + unsigned int fCardInputVoices; + unsigned int fCardOutputVoices; + + unsigned int fPeriod; + + public: + AudioParam( + jack_nframes_t buffer_size = 512, + jack_nframes_t sample_rate = 44100, + const char* input_card = "pcmPreferredc", + int input_ports = 2, + const char* output_card = "pcmPreferredp", + int output_ports = 2, + int periods = 2) : + fInputCardName(input_card), + fOutputCardName(output_card), + fFrequency(sample_rate), + fBuffering(buffer_size), + fCardInputVoices(input_ports), + fCardOutputVoices(output_ports), + fPeriod(periods) + { + } + + AudioParam& inputCardName(const char* n) + { + fInputCardName = n; + return *this; + } + + AudioParam& outputCardName(const char* n) + { + fOutputCardName = n; + return *this; + } + + AudioParam& frequency(int f) + { + fFrequency = f; + return *this; + } + + AudioParam& buffering(int fpb) + { + fBuffering = fpb; + return *this; + } + + void setInputs(int inputs) + { + fCardInputVoices = inputs; + } + + AudioParam& inputs(int n) + { + fCardInputVoices = n; + return *this; + } + + void setOutputs(int outputs) + { + fCardOutputVoices = outputs; + } + + AudioParam& outputs(int n) + { + fCardOutputVoices = n; + return *this; + } + }; + + /** + * An io-audio client interface + */ + class AudioInterface + { + public: + AudioParam fParams; + + //device info + snd_pcm_t* fOutputDevice; + snd_pcm_t* fInputDevice; + snd_pcm_channel_params_t *fInputParams; + snd_pcm_channel_params_t *fOutputParams; + snd_pcm_channel_setup_t *fInputSetup; + snd_pcm_channel_setup_t *fOutputSetup; + + //samples info + snd_pcm_format_t fInputFormat; + snd_pcm_format_t fOutputFormat; + + unsigned int fNumInputPorts; + unsigned int fNumOutputPorts; + + //Number of frames for one voice/port + jack_nframes_t fInputBufferFrames; + jack_nframes_t fOutputBufferFrames; + + // audiocard buffers + void* fInputCardBuffer; + void* fOutputCardBuffer; + + // floating point JACK buffers + jack_default_audio_sample_t** fJackInputBuffers; + jack_default_audio_sample_t** fJackOutputBuffers; + + //public methods --------------------------------------------------------- + + AudioInterface(const AudioParam& ap = AudioParam()); + + AudioInterface(jack_nframes_t buffer_size, jack_nframes_t sample_rate); + + ~AudioInterface(); + + /** + * Open the audio interface + */ + int open(); + + int close(); + + int setAudioParams(snd_pcm_t* stream, snd_pcm_channel_params_t* params); + + ssize_t interleavedBufferSize(snd_pcm_channel_params_t* params); + + ssize_t noninterleavedBufferSize(snd_pcm_channel_params_t* params); + + /** + * Read audio samples from the audio card. Convert samples to floats and take + * care of interleaved buffers + */ + int read(); + + /** + * write the output soft channels to the audio card. Convert sample + * format and interleaves buffers when needed + */ + int write(); + + /** + * print short information on the audio device + */ + int shortinfo(); + + /** + * print more detailled information on the audio device + */ + int longinfo(); + + void printCardInfo(snd_ctl_hw_info_t* ci); + + void printHWParams(snd_pcm_channel_params_t* params); + + private: + int AudioInterface_common(); + }; + + /*! + \brief Audio adapter using io-audio API. + */ + + class JackIoAudioAdapter: public JackAudioAdapterInterface, + public JackRunnableInterface + { + + private: + JackThread fThread; + AudioInterface fAudioInterface; + + public: + JackIoAudioAdapter( + jack_nframes_t buffer_size, + jack_nframes_t sample_rate, + const JSList* params); + ~JackIoAudioAdapter() + { + } + + virtual int Open(); + virtual int Close(); + + virtual void Create(); + virtual void Destroy(); + + virtual int SetSampleRate(jack_nframes_t sample_rate); + virtual int SetBufferSize(jack_nframes_t buffer_size); + + virtual bool Init(); + virtual bool Execute(); + + }; + + } + +#ifdef __cplusplus +extern "C" + { +#endif +#include "JackCompilerDeps.h" +#include "driver_interface.h" + SERVER_EXPORT jack_driver_desc_t* jack_get_descriptor(); + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/qnx/ioaudio/ioaudio_driver.c b/qnx/ioaudio/ioaudio_driver.c new file mode 100644 index 00000000..44c2354a --- /dev/null +++ b/qnx/ioaudio/ioaudio_driver.c @@ -0,0 +1,749 @@ +/* + * ioaudio_driver.c + * + * Copyright 2015 Garmin + */ + +#define HW_CONTEXT_T struct jack_card +#define PCM_SUBCHN_CONTEXT_T struct subchn + +#include + +#include + +#include +#include +#include + +#include + +static const char* PORTNAME_FMT = "playback_%d"; +typedef jack_default_audio_sample_t sample_t; + +struct subchn +{ + ado_pcm_subchn_t *pcm_subchn; + ado_pcm_config_t *pcm_config; + int32_t pcm_offset; /* holds offset of data populated in PCM subchannel buffer */ + uint8_t go; /* indicates if trigger GO has been issue by client for data transfer */ + void *strm; /* pointer back to parent stream structure */ +}; + +typedef struct jack_card +{ + /** + * PCM device instance + */ + ado_pcm_t* pcm; + + /** + * Audio Card Instance + */ + ado_card_t* card; + + /** + * Audio Hardware Mutex + */ + ado_mutex_t hw_lock; + + /** + * Audio Mixer Instance + */ + ado_mixer_t* mixer; + + /** + * PCM Device Capabilities + */ + ado_pcm_cap_t caps; + + /** + * PCM Device Callbacks + */ + ado_pcm_hw_t funcs; + + /** + * PCM Subchannel used by client + */ + struct subchn subchn; + + /** + * Name of JACK server to communicate with + */ + char* server; + + /** + * Name of this client + */ + char* name; + + /** + * Handle to this client + */ + jack_client_t* client; + + /** + * SND_PCM_CHANNEL_CAPTURE or SND_PCM_CHANNEL_PLAYBACK + */ + int channel_type; + + /** + * Number of voices / ports + */ + int voices; + + /** + * Ringbuffer between io-audio and JACK + */ + jack_ringbuffer_t ringbuffer; + + /** + * Number of audio frames per fragment + */ + size_t fragsize; + + /** + * Array of ports between io-audio and JACK + * + * size of array is 'voices' + */ + jack_port_t** ports; + + /** + * Mutex controlling JACK processing + */ + pthread_mutex_t process_lock; +} jack_card_t; + +static int parse_commandline( + jack_card_t* jack_card, + char* theArgs ) +{ + int opt = 0; + char* value; + char* argdup = ado_strdup( theArgs ); + char* args = argdup; + char* opts[] = { "server", "name", "type", "voices", NULL }; + + while( ( NULL != args ) && ( '\0' != args[0] ) ) + { + opt = getsubopt( &args, + opts, + &value ); + + if( opt >= 0 ) + { + switch( opt ) + { + case 0: /* server: JACK server name */ + if( NULL == value ) + { + ado_error( "server option given without value; ignoring" ); + jack_card->server = NULL; + } + else + { + jack_card->server = ado_strdup( value ); + } + break; + case 1: /* name: JACK client name */ + if( NULL == value ) + { + ado_error( "name option given without value; ignoring" ); + jack_card->name = ado_strdup( "jack_ioaudio" ); + } + else + { + jack_card->name = ado_strdup( value ); + } + break; + case 2: /* type: io-audio channel type */ + if( NULL != value ) + { + if( value[0] == 'P' || value[0] == 'p' ) + { + jack_card->channel_type = SND_PCM_CHANNEL_PLAYBACK; + } + else if( value[0] == 'C' || value[0] == 'c' ) + { + jack_card->channel_type = SND_PCM_CHANNEL_CAPTURE; + } + } + else + { + ado_error( + "type option value not 'c' or 'p'; defaulting to 'p'" ); + jack_card->channel_type = SND_PCM_CHANNEL_PLAYBACK; + } + break; + case 3: /* voices: number of voices in io-audio channel and JACK ports */ + if( NULL != value ) + { + char* endptr; + int val; + errno = 0; /* To distinguish success/failure after call */ + val = strtol( value, + &endptr, + 0 ); + + /* Check for various possible errors */ + + if( ( errno == ERANGE + && ( val == LONG_MAX || val == LONG_MIN ) ) + || ( errno != 0 && val == 0 ) ) + { + ado_error( "voices option value out of range" ); + return errno; + } + + if( endptr == value ) + { + ado_error( "voices option value contains no digits" ); + return EINVAL; + } + + jack_card->voices = val; + } + else + { + ado_error( "voices option given without value; failing" ); + return EINVAL; + } + break; + default: + break; + } + } + else + { + ado_debug( DB_LVL_DRIVER, + "deva-ctrl-jack: found unknown argument %s", + value ); + } + } + ado_free( argdup ); + return EOK; +} + +int32_t cb_capabilities( + HW_CONTEXT_T * mcasp_card, + ado_pcm_t *pcm, + snd_pcm_channel_info_t * info ) +{ + ado_debug( DB_LVL_DRIVER, + "deva-ctrl-jack: capabilities()" ); + return EOK; +} + +int32_t cb_aquire( + HW_CONTEXT_T *jack_card, + PCM_SUBCHN_CONTEXT_T **pc, + ado_pcm_config_t *config, + ado_pcm_subchn_t *subchn, + uint32_t *why_failed ) +{ + ado_debug( DB_LVL_DRIVER, + "deva-ctrl-jack: aquire()" ); + if( jack_card->subchn.pcm_subchn ) + { + *why_failed = SND_PCM_PARAMS_NO_CHANNEL; + return ( EAGAIN ); + } + + /* + * Create shared memory region for ringbuffer + */ + config->dmabuf.addr = ado_shm_alloc( config->dmabuf.size, + config->dmabuf.name, + ADO_SHM_DMA_SAFE, + &config->dmabuf.phys_addr ); + + /* + * Set up JACK ringbuffer structure to use SHM region instead of own + * buffer. + */ + jack_card->ringbuffer.buf = (char*)config->dmabuf.addr; + jack_card->ringbuffer.size = config->dmabuf.size; + jack_card->ringbuffer.size_mask = config->dmabuf.size - 1; + jack_ringbuffer_reset( &jack_card->ringbuffer ); + + /* + * Store parameters for future use + */ + jack_card->subchn.pcm_config = config; + jack_card->subchn.pcm_subchn = subchn; + + /* + * Set output parameters + */ + *pc = &jack_card->subchn; + + return EOK; +} + +int32_t cb_release( + HW_CONTEXT_T *jack_card, + PCM_SUBCHN_CONTEXT_T *pc, + ado_pcm_config_t *config ) +{ + ado_debug( DB_LVL_DRIVER, + "deva-ctrl-jack: release()" ); + + pthread_mutex_lock( &jack_card->process_lock ); + + jack_card->subchn.go = 0; + + jack_ringbuffer_reset( &jack_card->ringbuffer ); + + ado_shm_free( pc->pcm_config->dmabuf.addr, + pc->pcm_config->dmabuf.size, + pc->pcm_config->dmabuf.name ); + pc->pcm_subchn = NULL; + + pthread_mutex_unlock( &jack_card->process_lock ); + return EOK; +} + +int32_t cb_prepare( + HW_CONTEXT_T *jack_card, + PCM_SUBCHN_CONTEXT_T *pc, + ado_pcm_config_t *config ) +{ + ado_debug( DB_LVL_DRIVER, + "deva-ctrl-jack: prepare()" ); + jack_ringbuffer_reset( &jack_card->ringbuffer ); + return EOK; +} + +int32_t cb_trigger( + HW_CONTEXT_T *jack_card, + PCM_SUBCHN_CONTEXT_T *pc, + uint32_t cmd ) +{ + ado_debug( DB_LVL_DRIVER, + "deva-ctrl-jack: trigger( %d )", + cmd ); + + if( ADO_PCM_TRIGGER_STOP == cmd ) + { + jack_card->subchn.go = 0; + ado_debug( DB_LVL_DRIVER, + "deva-ctrl-jack: ADO_PCM_TRIGGER_STOP" ); + } + else if( ADO_PCM_TRIGGER_GO == cmd ) + { + /* + * Signal readiness to JACK and ADO + */ + jack_card->subchn.go = 1; + ado_debug( DB_LVL_DRIVER, + "deva-ctrl-jack: ADO_PCM_TRIGGER_GO" ); + } + else + { + jack_card->subchn.go = 0; + ado_debug( DB_LVL_DRIVER, + "deva-ctrl-jack: ADO_PCM_TRIGGER_SYNC_AND_GO" ); + } + return EOK; +} + +uint32_t cb_position( + HW_CONTEXT_T *jack_card, + PCM_SUBCHN_CONTEXT_T *pc, + ado_pcm_config_t *config ) +{ + ado_debug( DB_LVL_DRIVER, + "deva-ctrl-jack: position()" ); + return EOK; +} + +void irq_handler( + HW_CONTEXT_T *jack_card, + int32_t event ) +{ + ado_debug( DB_LVL_DRIVER, + "deva-ctrl-jack: irq_handler()" ); +} + +/** + * Prototype for the client supplied function that is called + * by the engine anytime there is work to be done. + * + * @pre nframes == jack_get_buffer_size() + * @pre nframes == pow(2,x) + * + * @param nframes number of frames to process + * @param arg pointer to a client supplied structure + * + * @return zero on success, non-zero on error + */ +int jack_process( + jack_nframes_t nframes, + void *arg ) +{ + int v; + jack_card_t* jack_card = (jack_card_t*)arg; + + size_t total_size = nframes * sizeof(sample_t); + size_t size_completed; + + if( ( jack_card->fragsize != total_size ) ) + { + ado_error( + "deva_ctrl-jack: mismatch of fragment size with number of frames requested" ); + } + + /* + * Try to lock the process mutex. If the lock can't be acquired, assume something is happening with the subchannel + * and write zeroes to JACK instead. + */ + if( jack_card->subchn.go + && ( 0 == pthread_mutex_trylock( &jack_card->process_lock ) ) ) + { + int voices = jack_card->subchn.pcm_config->format.voices; + size_t size_per_voice = total_size / voices; + + void* jack_buf[voices]; + for( v = 0; v < voices; ++v ) + { + jack_buf[v] = jack_port_get_buffer( jack_card->ports[v], + nframes ); + } + + for( size_completed = 0; size_completed < total_size; size_completed += + size_per_voice ) + { + for( v = 0; v < voices; ++v ) + { + /* + * Advance ringbuffer write pointer on the assumption that io-audio has filled in nframes of data + */ + jack_ringbuffer_write_advance( &jack_card->ringbuffer, + size_per_voice ); + + jack_ringbuffer_data_t read_buf[2]; + jack_ringbuffer_get_read_vector( &jack_card->ringbuffer, + read_buf ); + + if( SND_PCM_SFMT_FLOAT_LE + == jack_card->subchn.pcm_config->format.format ) + { + jack_ringbuffer_read( &jack_card->ringbuffer, + jack_buf[v], + size_per_voice ); + } + else if( SND_PCM_SFMT_S32_LE + == jack_card->subchn.pcm_config->format.format ) + { + int s; + size_t remaining = size_per_voice / sizeof(sample_t); + read_buf[0].len /= sizeof(sample_t); + read_buf[1].len /= sizeof(sample_t); + + int32_t* src = (sample_t*)read_buf[0].buf; + sample_t* dest = (sample_t*)jack_buf[v]; + size_t amt = min( read_buf[0].len, + remaining ); + for( s = 0; s < amt; s += sizeof(sample_t) ) + { + dest[s] = ( (sample_t)src[s] ) * ( (sample_t)1.0 ) + / ( (sample_t)INT_MAX ); + } + remaining -= amt; + if( remaining > 0 ) + { + src = (int32_t*)read_buf[1].buf; + dest += amt; + amt = min( read_buf[1].len, + remaining ); + for( s = 0; s < amt; s += sizeof(sample_t) ) + { + dest[s] = ( (sample_t)src[s] ) * ( (sample_t)1.0 ) + / ( (sample_t)INT_MAX ); + } + } + jack_ringbuffer_read_advance( &jack_card->ringbuffer, + size_per_voice ); + } + + jack_buf[v] += size_per_voice; + + } + dma_interrupt( jack_card->subchn.pcm_subchn ); + } + pthread_mutex_unlock( &jack_card->process_lock ); + } + else + { + /* + * In the case where there is no subchannel or we can't aquire the ringbuffer lock, + * write zeroes to the JACK buffers rather than let stale data sit in them. + */ + for( v = 0; v < jack_card->voices; ++v ) + { + void* jack_buf = jack_port_get_buffer( jack_card->ports[v], + nframes ); + memset( jack_buf, + 0, + total_size ); + } + } + + return 0; +} + +/** + * Prototype for the client supplied function that is called + * whenever jackd is shutdown. Note that after server shutdown, + * the client pointer is *not* deallocated by libjack, + * the application is responsible to properly use jack_client_close() + * to release client ressources. Warning: jack_client_close() cannot be + * safely used inside the shutdown callback and has to be called outside of + * the callback context. + * + * @param arg pointer to a client supplied structure + */ +void jack_shutdown( + void *arg ) +{ + ado_debug( DB_LVL_DRIVER, + "deva-ctrl-jack: jack_shutdown" ); + jack_card_t* card = (jack_card_t*)arg; + + /* + * TODO + * Find a way to trigger io-audio to call ctrl_destroy() to clean up + */ +} + +ado_ctrl_dll_init_t ctrl_init; +int ctrl_init( + HW_CONTEXT_T ** hw_context, + ado_card_t * card, + char *args ) +{ + int retval; + int i; + jack_card_t* jack_card; + + ado_debug( DB_LVL_DRIVER, + "deva-ctrl-jack: ctrl_init" ); + + jack_card = ado_calloc( 1, + sizeof(jack_card_t) ); + if( NULL == jack_card ) + { + ado_error( "Unable to allocate memory for jack_card (%s)", + strerror( errno ) ); + return -1; + } + + *hw_context = jack_card; + jack_card->card = card; + + pthread_mutex_init( &jack_card->process_lock, + NULL ); + + ado_card_set_shortname( card, + "jack_card" ); + ado_card_set_longname( card, + "jack_card", + 0x1000 ); + + retval = parse_commandline( jack_card, + args ); + if( EOK != retval ) + { + ado_error( + "Received error from parse_commandline(): %d; cannot continue", + retval ); + return -1; + } + + /* + * Create client per command line arguments + */ + int client_name_size = jack_client_name_size(); + if( strlen( jack_card->name ) > client_name_size ) + { + ado_error( + "Client name %s is too long (%d chars).\nPlease use a client name of %d characters or less.", + strlen( jack_card->name ), + client_name_size ); + return -1; + } + jack_status_t open_status; + if( NULL != jack_card->server ) + { + jack_card->client = jack_client_open( jack_card->name, + JackServerName, + &open_status, + jack_card->server ); + } + else + { + jack_card->client = jack_client_open( jack_card->name, + JackNullOption, + &open_status ); + } + if( NULL == jack_card->client ) + { + ado_error( "Error opening JACK client (%x)", + open_status ); + ado_free( jack_card ); + return -1; + } + + /* + * Set JACK and ADO processing callbacks + */ + jack_set_process_callback( jack_card->client, + jack_process, + jack_card ); + jack_on_shutdown( jack_card->client, + jack_shutdown, + 0 ); + + /* + * Allocate jack_port_t array + */ + int voices = jack_card->voices; + jack_card->ports = ado_calloc( voices, + sizeof(jack_port_t*) ); + + /* + * Register JACK ports and create their ringbuffers + */ + int port_name_size = jack_port_name_size(); + char* portname = calloc( jack_port_name_size(), + sizeof(char) ); + for( i = 0; i < voices; ++i ) + { + snprintf( portname, + port_name_size, + PORTNAME_FMT, + ( i + 1 ) ); + jack_card->ports[i] = jack_port_register( jack_card->client, + portname, + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, + 0 ); + } + free( portname ); + + /* + * Initialize Capabilities + */ + jack_card->caps.chn_flags = SND_PCM_CHNINFO_BLOCK + | SND_PCM_CHNINFO_NONINTERLEAVE; + jack_card->caps.formats = 0xFFFF; + jack_card->caps.formats = SND_PCM_FMT_FLOAT_LE | SND_PCM_FMT_S32_LE; + + jack_nframes_t rate = jack_get_sample_rate( jack_card->client ); + uint32_t rateflag = ado_pcm_rate2flag( rate ); + jack_card->caps.rates = rateflag; + + jack_card->caps.min_voices = 1; + jack_card->caps.max_voices = jack_card->voices; + + /* + * Get size of ringbuffer from JACK server + */ + jack_card->fragsize = jack_get_buffer_size( jack_card->client ) + * sizeof(sample_t); + jack_card->caps.min_fragsize = jack_card->fragsize; + jack_card->caps.max_fragsize = jack_card->fragsize; + jack_card->caps.max_dma_size = 0; + jack_card->caps.max_frags = 0; + + jack_card->funcs.capabilities2 = cb_capabilities; + jack_card->funcs.aquire = cb_aquire; + jack_card->funcs.release = cb_release; + jack_card->funcs.prepare = cb_prepare; + jack_card->funcs.trigger = cb_trigger; + jack_card->funcs.position = cb_position; + + /* + * Create Audio PCM Device + */ + retval = ado_pcm_create( card, + "JACK io-audio driver", + ( jack_card->channel_type + == SND_PCM_CHANNEL_PLAYBACK ) ? + SND_PCM_INFO_PLAYBACK : SND_PCM_INFO_CAPTURE, + "jack-audio", + ( jack_card->channel_type + == SND_PCM_CHANNEL_PLAYBACK ) ? 1 : 0, + ( jack_card->channel_type + == SND_PCM_CHANNEL_PLAYBACK ) ? + &jack_card->caps : NULL, + ( jack_card->channel_type + == SND_PCM_CHANNEL_PLAYBACK ) ? + &jack_card->funcs : NULL, + ( jack_card->channel_type + == SND_PCM_CHANNEL_CAPTURE ) ? 1 : 0, + ( jack_card->channel_type + == SND_PCM_CHANNEL_CAPTURE ) ? + &jack_card->caps : NULL, + ( jack_card->channel_type + == SND_PCM_CHANNEL_CAPTURE ) ? + &jack_card->funcs : NULL, + &jack_card->pcm ); + + if( -1 == retval ) + { + ado_error( "deva_ctrl_jack: ERROR: %s", + strerror( errno ) ); + ado_free( jack_card->ports ); + ado_free( jack_card ); + return -1; + } + + /* + * Activate JACK client + */ + return jack_activate( jack_card->client ); +} + +ado_ctrl_dll_destroy_t ctrl_destroy; +int ctrl_destroy( + HW_CONTEXT_T * context ) +{ + jack_card_t* jack_card = (jack_card_t*)context; + + ado_debug( DB_LVL_DRIVER, + "deva-ctrl-jack: ctrl_destroy()" ); + + jack_deactivate( jack_card->client ); + jack_client_close( jack_card->client ); + + ado_free( jack_card->ports ); + ado_free( jack_card->server ); + ado_free( jack_card->name ); + + pthread_mutex_destroy( &jack_card->process_lock ); + ado_free( jack_card ); + + return EOK; +} + +ado_dll_version_t ctrl_version; +void ctrl_version( + int *major, + int *minor, + char *date ) +{ + *major = ADO_MAJOR_VERSION; + *minor = 1; + date = __DATE__; +} + +int ctrl_devctl( + uint32_t cmd, + uint8_t *msg, + uint16_t *msg_size, + HW_CONTEXT_T *context ) +{ + +} + diff --git a/qnx/wscript b/qnx/wscript new file mode 100644 index 00000000..00f23e50 --- /dev/null +++ b/qnx/wscript @@ -0,0 +1,43 @@ +def configure(conf): + conf.check_cc(lib='asound', header_name='audio_driver.h', uselib_store='IOAUDIO', define_name="HAVE_IOAUDIO", mandatory=False) + if conf.is_defined('HAVE_IOAUDIO'): + conf.env['BUILD_DRIVER_IOAUDIO'] = True + +def create_jack_driver_obj(bld, target, sources, uselib=None): + driver = bld(features=['c', 'cxx', 'cxxshlib', 'cshlib']) + driver.env['cxxshlib_PATTERN'] = 'jack_%s.so' + + driver.defines = ['HAVE_CONFIG_H', 'SERVER_SIDE', 'HAVE_PPOLL', 'HAVE_TIMERFD'] + + driver.includes = ['.', '../qnx', '../posix', '../common', '../common/jack', '../dbus', '../'] + driver.target = target + driver.source = sources + driver.install_path = '${ADDON_DIR}/' + driver.use = [] + driver.cflags = [] + if uselib: + driver.use += uselib + return driver + +def build(bld): + if bld.env['BUILD_JACKD'] == True: + jackd = bld(features=['cxx', 'cxxprogram']) + jackd.includes = ['../qnx', '../posix', '../common/jack', '../common', '../dbus', '../'] + jackd.defines = ['HAVE_CONFIG_H', 'SERVER_SIDE'] + jackd.source = ['../common/Jackdmp.cpp'] + jackd.use = ['serverlib'] + if bld.env['IS_QNX'] and bld.env['BUILD_JACKDBUS']: + jackd.source += ['../dbus/reserve.c', '../dbus/audio_reserve.c'] + jackd.use += ['PTHREAD', 'DL', 'RT', 'M', 'STDC++', 'DBUS-1'] + else: + jackd.use += ['PTHREAD', 'DL', 'RT', 'M', 'STDC++'] + jackd.target = 'jackd' + + create_jack_driver_obj(bld, 'dummy', '../common/JackDummyDriver.cpp') + + create_jack_driver_obj(bld, 'loopback', '../common/JackLoopbackDriver.cpp') + + if bld.env['BUILD_DRIVER_IOAUDIO']: + deva = create_jack_driver_obj(bld, 'jack', 'ioaudio/ioaudio_driver.c', ['IOAUDIO', 'clientlib', 'PTHREAD']) + deva.env['cxxshlib_PATTERN'] = 'deva-ctrl-%s.so' + deva.install_path = '/lib/dll' diff --git a/tests/external_metro.cpp b/tests/external_metro.cpp index 6ca8bc90..2d5ff022 100644 --- a/tests/external_metro.cpp +++ b/tests/external_metro.cpp @@ -17,7 +17,7 @@ */ #include "external_metro.h" -#include +#include typedef jack_default_audio_sample_t sample_t; diff --git a/tests/external_metro.h b/tests/external_metro.h index 8c618410..317dcb60 100644 --- a/tests/external_metro.h +++ b/tests/external_metro.h @@ -21,18 +21,13 @@ #ifndef __external_metro__ #define __external_metro__ -#ifdef __cplusplus -extern "C" -{ -#endif - -#include -#include -#include +#include +#include +#include #include -#include +#include #include -#include +#include #include #include @@ -63,8 +58,4 @@ extern "C" static void shutdown (void* arg); }; -#ifdef __cplusplus -} -#endif - #endif diff --git a/tests/test.cpp b/tests/test.cpp index 8a8a8117..bedb91b9 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -479,7 +479,7 @@ int process4(jack_nframes_t nframes, void *arg) jack_nframes_t delta_time = cur_time - last_time; Log("calling process4 callback : jack_frame_time = %ld delta_time = %ld\n", cur_time, delta_time); - if (delta_time > 0 && (jack_nframes_t)abs(delta_time - cur_buffer_size) > tolerance) { + if (delta_time > 0 && (jack_nframes_t)abs((int64_t)delta_time - (int64_t)cur_buffer_size) > tolerance) { printf("!!! ERROR !!! jack_frame_time seems to return incorrect values cur_buffer_size = %d, delta_time = %d tolerance %d\n", cur_buffer_size, delta_time, tolerance); } diff --git a/tests/wscript b/tests/wscript index 70d48f0f..da7e7b7e 100644 --- a/tests/wscript +++ b/tests/wscript @@ -20,6 +20,8 @@ def build(bld): prog.includes = ['..','../macosx', '../posix', '../common/jack', '../common'] if bld.env['IS_LINUX']: prog.includes = ['..','../linux', '../posix', '../common/jack', '../common'] + if bld.env['IS_QNX']: + prog.includes = ['..','../qnx', '../posix', '../common/jack', '../common'] if bld.env['IS_SUN']: prog.includes = ['..','../solaris', '../posix', '../common/jack', '../common'] prog.source = test_program_sources diff --git a/wscript b/wscript index 561373df..0479bdcd 100644 --- a/wscript +++ b/wscript @@ -395,7 +395,7 @@ def options(opt): opt.add_option('--mandir', type='string', help="Manpage directory [Default: /share/man/man1]") # options affecting binaries - opt.add_option('--dist-target', type='string', default='auto', help='Specify the target for cross-compiling [auto,mingw]') + opt.add_option('--dist-target', type='string', default='auto', help='Specify the target for cross-compiling [auto,mingw,qnx]') opt.add_option('--mixed', action='store_true', default=False, help='Build with 32/64 bits mixed mode') opt.add_option('--debug', action='store_true', default=False, dest='debug', help='Build debuggable binaries') @@ -438,6 +438,8 @@ def options(opt): readline.add_library('readline') readline.set_check_hook(check_for_readline, check_for_readline_error) + ioaudio = add_auto_option(opt, 'ioaudio', help='Enable io-audio driver', conf_dest='BUILD_DRIVER_IOAUDIO') + # dbus options opt.recurse('dbus') @@ -457,6 +459,8 @@ def configure(conf): conf.env['IS_LINUX'] = True elif Options.options.dist_target == 'mingw': conf.env['IS_WINDOWS'] = True + elif Options.options.dist_target == 'qnx': + conf.env['IS_QNX'] = True if conf.env['IS_LINUX']: Logs.pprint('CYAN', "Linux detected") @@ -464,6 +468,9 @@ def configure(conf): if conf.env['IS_MACOSX']: Logs.pprint('CYAN', "MacOS X detected") + if conf.env['IS_QNX']: + Logs.pprint('CYAN', "Cross compile to QNX") + if conf.env['IS_SUN']: Logs.pprint('CYAN', "SunOS detected") @@ -483,6 +490,8 @@ def configure(conf): conf.recurse('common') if conf.env['IS_LINUX']: conf.recurse('linux') + if conf.env['IS_QNX']: + conf.recurse('qnx') if Options.options.dbus: conf.recurse('dbus') if conf.env['BUILD_JACKDBUS'] != True: @@ -495,6 +504,14 @@ def configure(conf): conf.env['LIB_RT'] = ['rt'] conf.env['LIB_M'] = ['m'] conf.env['LIB_STDC++'] = ['stdc++'] + + if conf.env['IS_QNX']: + conf.env['LIB_PTHREAD'] = ['c'] + conf.env['LIB_DL'] = ['c'] + conf.env['LIB_RT'] = [] + conf.check_cxx( lib=['rt'], uselib_store='RT', mandatory=False ) + conf.check_cxx( lib=['socket'], uselib_store='SOCKET' ) + conf.env['JACK_API_VERSION'] = JACK_API_VERSION conf.env['JACK_VERSION'] = VERSION @@ -508,6 +525,15 @@ def configure(conf): else: conf.env['BUILD_JACKD'] = True + path = os.path + if sys.platform == 'win32' and (conf.env['IS_QNX'] or conf.env['IS_LINUX']): + # If we are cross-compiling from Windows to a system with POSIX style filenames, + # we need to construct file paths using posixpath instead of os.path. Also, we + # need fix PREFIX, since Waf constructed it incorrectly + import posixpath + path = posixpath + conf.env['PREFIX'] = os.path.splitdrive(conf.env['PREFIX'])[1].replace('\\', '/') + conf.env['BINDIR'] = conf.env['PREFIX'] + '/bin' if Options.options.htmldir: @@ -564,9 +590,9 @@ def configure(conf): # don't define ADDON_DIR in config.h, use the default 'jack' defined in # windows/JackPlatformPlug_os.h else: - conf.env['ADDON_DIR'] = os.path.normpath(os.path.join(conf.env['LIBDIR'], 'jack')) + conf.env['ADDON_DIR'] = path.normpath(path.join(conf.env['LIBDIR'], 'jack')) conf.define('ADDON_DIR', conf.env['ADDON_DIR']) - conf.define('JACK_LOCATION', os.path.normpath(os.path.join(conf.env['PREFIX'], 'bin'))) + conf.define('JACK_LOCATION', path.normpath(path.join(conf.env['PREFIX'], 'bin'))) if not conf.env['IS_WINDOWS']: conf.define('USE_POSIX_SHM', 1) @@ -716,6 +742,14 @@ def build(bld): if bld.env['BUILD_JACKDBUS'] == True: bld.recurse('dbus') + if bld.env['IS_QNX']: + bld.recurse('qnx') + bld.recurse('example-clients') + bld.recurse('tests') + bld.recurse('man') + if bld.env['BUILD_JACKDBUS'] == True: + bld.recurse('dbus') + if bld.env['IS_SUN']: bld.recurse('solaris') bld.recurse('example-clients')