From 21c068f1916330e90f814bed461fe0821d1665ec Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Sun, 9 Oct 2011 16:36:37 +0000 Subject: checked in pd-0.43-0.src.tar.gz svn path=/trunk/; revision=15557 --- pd/portaudio/src/common/pa_allocation.h | 8 +- pd/portaudio/src/common/pa_converters.c | 4 +- pd/portaudio/src/common/pa_debugprint.c | 7 +- pd/portaudio/src/common/pa_dither.c | 29 +- pd/portaudio/src/common/pa_dither.h | 18 +- pd/portaudio/src/common/pa_endianness.h | 30 +- pd/portaudio/src/common/pa_front.c | 31 +- pd/portaudio/src/common/pa_hostapi.h | 16 +- pd/portaudio/src/common/pa_memorybarrier.h | 123 ++ pd/portaudio/src/common/pa_process.c | 10 +- pd/portaudio/src/common/pa_ringbuffer.c | 156 +- pd/portaudio/src/common/pa_ringbuffer.h | 90 +- pd/portaudio/src/common/pa_skeleton.c | 8 +- pd/portaudio/src/common/pa_stream.c | 10 +- pd/portaudio/src/common/pa_stream.h | 8 +- pd/portaudio/src/common/pa_trace.c | 4 +- pd/portaudio/src/common/pa_trace.h | 32 +- pd/portaudio/src/common/pa_util.h | 7 +- pd/portaudio/src/hostapi/alsa/pa_linux_alsa.c | 250 +++- pd/portaudio/src/hostapi/asio/pa_asio.cpp | 1538 ++++++++++++++++---- pd/portaudio/src/hostapi/coreaudio/notes.txt | 22 +- pd/portaudio/src/hostapi/coreaudio/pa_mac_core.c | 161 +- .../src/hostapi/coreaudio/pa_mac_core_blocking.c | 88 +- .../src/hostapi/coreaudio/pa_mac_core_blocking.h | 9 +- .../src/hostapi/coreaudio/pa_mac_core_internal.h | 7 +- .../src/hostapi/coreaudio/pa_mac_core_utilities.c | 141 +- .../src/hostapi/coreaudio/pa_mac_core_utilities.h | 31 +- pd/portaudio/src/hostapi/jack/pa_jack.c | 17 +- pd/portaudio/src/hostapi/oss/pa_unix_oss.c | 133 +- pd/portaudio/src/hostapi/wmme/pa_win_wmme.c | 565 +++++-- pd/portaudio/src/os/unix/pa_unix_hostapis.c | 29 +- pd/portaudio/src/os/unix/pa_unix_util.c | 45 +- pd/portaudio/src/os/win/pa_win_hostapis.c | 6 +- pd/portaudio/src/os/win/pa_win_util.c | 10 +- pd/portaudio/src/os/win/pa_win_waveformat.c | 154 ++ pd/portaudio/src/os/win/pa_win_wdmks_utils.c | 260 ++++ pd/portaudio/src/os/win/pa_win_wdmks_utils.h | 65 + pd/portaudio/src/os/win/pa_x86_plain_converters.c | 2 +- 38 files changed, 3322 insertions(+), 802 deletions(-) create mode 100644 pd/portaudio/src/common/pa_memorybarrier.h create mode 100644 pd/portaudio/src/os/win/pa_win_waveformat.c create mode 100644 pd/portaudio/src/os/win/pa_win_wdmks_utils.c create mode 100644 pd/portaudio/src/os/win/pa_win_wdmks_utils.h (limited to 'pd/portaudio/src') diff --git a/pd/portaudio/src/common/pa_allocation.h b/pd/portaudio/src/common/pa_allocation.h index b265b016..811dd72e 100644 --- a/pd/portaudio/src/common/pa_allocation.h +++ b/pd/portaudio/src/common/pa_allocation.h @@ -1,12 +1,12 @@ #ifndef PA_ALLOCATION_H #define PA_ALLOCATION_H /* - * $Id: pa_allocation.h 1097 2006-08-26 08:27:53Z rossb $ + * $Id: pa_allocation.h 1339 2008-02-15 07:50:33Z rossb $ * Portable Audio I/O Library allocation context header * memory allocation context for tracking allocation groups * * Based on the Open Source API proposed by Ross Bencina - * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * Copyright (c) 1999-2008 Ross Bencina, Phil Burk * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files @@ -43,12 +43,12 @@ @ingroup common_src @brief Allocation Group prototypes. An Allocation Group makes it easy to - allocate multiple blocks of memory and free them all simultanously. + allocate multiple blocks of memory and free them all at once. An allocation group is useful for keeping track of multiple blocks of memory which are allocated at the same time (such as during initialization) and need to be deallocated at the same time. The allocation group maintains - a list of allocated blocks, and can deallocate them all simultaneously which + a list of allocated blocks, and can free all allocations at once. This can be usefull for cleaning up after a partially initialized object fails. The allocation group implementation is built on top of the lower diff --git a/pd/portaudio/src/common/pa_converters.c b/pd/portaudio/src/common/pa_converters.c index 3b98c858..965c1460 100644 --- a/pd/portaudio/src/common/pa_converters.c +++ b/pd/portaudio/src/common/pa_converters.c @@ -1,5 +1,5 @@ /* - * $Id: pa_converters.c 1097 2006-08-26 08:27:53Z rossb $ + * $Id: pa_converters.c 1339 2008-02-15 07:50:33Z rossb $ * Portable Audio I/O Library sample conversion mechanism * * Based on the Open Source API proposed by Ross Bencina @@ -39,7 +39,7 @@ /** @file @ingroup common_src - @brief Conversion functions implementations. + @brief Conversion function implementations. If the C9x function lrintf() is available, define PA_USE_C99_LRINTF to use it diff --git a/pd/portaudio/src/common/pa_debugprint.c b/pd/portaudio/src/common/pa_debugprint.c index 33fcf32e..a878028c 100644 --- a/pd/portaudio/src/common/pa_debugprint.c +++ b/pd/portaudio/src/common/pa_debugprint.c @@ -74,8 +74,11 @@ void PaUtil_SetDebugPrintFunction(PaUtilLogCallback cb) VERY dangerous alternative, vsprintf (with no n) */ -#if (_MSC_VER) && (_MSC_VER < 1400) -#define VSNPRINTF _vsnprintf +#if _MSC_VER +/* Some Windows Mobile SDKs don't define vsnprintf but all define _vsnprintf (hopefully). + According to MSDN "vsnprintf is identical to _vsnprintf". So we use _vsnprintf with MSC. +*/ +#define VSNPRINTF _vsnprintf #else #define VSNPRINTF vsnprintf #endif diff --git a/pd/portaudio/src/common/pa_dither.c b/pd/portaudio/src/common/pa_dither.c index 6f6c9a1a..7a1b1312 100644 --- a/pd/portaudio/src/common/pa_dither.c +++ b/pd/portaudio/src/common/pa_dither.c @@ -1,5 +1,5 @@ /* - * $Id: pa_dither.c 1097 2006-08-26 08:27:53Z rossb $ + * $Id: pa_dither.c 1418 2009-10-12 21:00:53Z philburk $ * Portable Audio I/O Library triangular dither generator * * Based on the Open Source API proposed by Ross Bencina @@ -42,9 +42,14 @@ @brief Functions for generating dither noise */ - -#include "pa_dither.h" #include "pa_types.h" +#include "pa_dither.h" + + +/* Note that the linear congruential algorithm requires 32 bit integers + * because it uses arithmetic overflow. So use PaUint32 instead of + * unsigned long so it will work on 64 bit systems. + */ #define PA_DITHER_BITS_ (15) @@ -57,9 +62,9 @@ void PaUtil_InitializeTriangularDitherState( PaUtilTriangularDitherGenerator *st } -signed long PaUtil_Generate16BitTriangularDither( PaUtilTriangularDitherGenerator *state ) +PaInt32 PaUtil_Generate16BitTriangularDither( PaUtilTriangularDitherGenerator *state ) { - signed long current, highPass; + PaInt32 current, highPass; /* Generate two random numbers. */ state->randSeed1 = (state->randSeed1 * 196314165) + 907633515; @@ -69,9 +74,10 @@ signed long PaUtil_Generate16BitTriangularDither( PaUtilTriangularDitherGenerato * Shift before adding to prevent overflow which would skew the distribution. * Also shift an extra bit for the high pass filter. */ -#define DITHER_SHIFT_ ((SIZEOF_LONG*8 - PA_DITHER_BITS_) + 1) - current = (((signed long)state->randSeed1)>>DITHER_SHIFT_) + - (((signed long)state->randSeed2)>>DITHER_SHIFT_); +#define DITHER_SHIFT_ ((sizeof(PaInt32)*8 - PA_DITHER_BITS_) + 1) + + current = (((PaInt32)state->randSeed1)>>DITHER_SHIFT_) + + (((PaInt32)state->randSeed2)>>DITHER_SHIFT_); /* High pass filter to reduce audibility. */ highPass = current - state->previous; @@ -86,7 +92,7 @@ static const float const_float_dither_scale_ = PA_FLOAT_DITHER_SCALE_; float PaUtil_GenerateFloatTriangularDither( PaUtilTriangularDitherGenerator *state ) { - signed long current, highPass; + PaInt32 current, highPass; /* Generate two random numbers. */ state->randSeed1 = (state->randSeed1 * 196314165) + 907633515; @@ -96,9 +102,8 @@ float PaUtil_GenerateFloatTriangularDither( PaUtilTriangularDitherGenerator *sta * Shift before adding to prevent overflow which would skew the distribution. * Also shift an extra bit for the high pass filter. */ -#define DITHER_SHIFT_ ((SIZEOF_LONG*8 - PA_DITHER_BITS_) + 1) - current = (((signed long)state->randSeed1)>>DITHER_SHIFT_) + - (((signed long)state->randSeed2)>>DITHER_SHIFT_); + current = (((PaInt32)state->randSeed1)>>DITHER_SHIFT_) + + (((PaInt32)state->randSeed2)>>DITHER_SHIFT_); /* High pass filter to reduce audibility. */ highPass = current - state->previous; diff --git a/pd/portaudio/src/common/pa_dither.h b/pd/portaudio/src/common/pa_dither.h index e77ce470..a5131b27 100644 --- a/pd/portaudio/src/common/pa_dither.h +++ b/pd/portaudio/src/common/pa_dither.h @@ -1,7 +1,7 @@ #ifndef PA_DITHER_H #define PA_DITHER_H /* - * $Id: pa_dither.h 1097 2006-08-26 08:27:53Z rossb $ + * $Id: pa_dither.h 1418 2009-10-12 21:00:53Z philburk $ * Portable Audio I/O Library triangular dither generator * * Based on the Open Source API proposed by Ross Bencina @@ -44,18 +44,24 @@ @brief Functions for generating dither noise */ +#include "pa_types.h" + #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ +/* Note that the linear congruential algorithm requires 32 bit integers + * because it uses arithmetic overflow. So use PaUint32 instead of + * unsigned long so it will work on 64 bit systems. + */ /** @brief State needed to generate a dither signal */ typedef struct PaUtilTriangularDitherGenerator{ - unsigned long previous; - unsigned long randSeed1; - unsigned long randSeed2; + PaUint32 previous; + PaUint32 randSeed1; + PaUint32 randSeed2; } PaUtilTriangularDitherGenerator; @@ -73,9 +79,9 @@ void PaUtil_InitializeTriangularDitherState( PaUtilTriangularDitherGenerator *di signed short out = (signed short)(((in>>1) + dither) >> 15); @return - A signed long with a range of +32767 to -32768 + A signed 32-bit integer with a range of +32767 to -32768 */ -signed long PaUtil_Generate16BitTriangularDither( PaUtilTriangularDitherGenerator *ditherState ); +PaInt32 PaUtil_Generate16BitTriangularDither( PaUtilTriangularDitherGenerator *ditherState ); /** diff --git a/pd/portaudio/src/common/pa_endianness.h b/pd/portaudio/src/common/pa_endianness.h index bdcc74f7..84e904ca 100644 --- a/pd/portaudio/src/common/pa_endianness.h +++ b/pd/portaudio/src/common/pa_endianness.h @@ -1,7 +1,7 @@ #ifndef PA_ENDIANNESS_H #define PA_ENDIANNESS_H /* - * $Id: pa_endianness.h 1216 2007-06-10 09:26:00Z aknudsen $ + * $Id: pa_endianness.h 1324 2008-01-27 02:03:30Z bjornroche $ * Portable Audio I/O Library current platform endianness macros * * Based on the Open Source API proposed by Ross Bencina @@ -120,18 +120,22 @@ extern "C" and raises an assertion if they don't match. must be included in the context in which this macro is used. */ -#if defined(PA_LITTLE_ENDIAN) - #define PA_VALIDATE_ENDIANNESS \ - { \ - const long nativeOne = 1; \ - assert( "PortAudio: compile time and runtime endianness don't match" && (((char *)&nativeOne)[0]) == 1 ); \ - } -#elif defined(PA_BIG_ENDIAN) - #define PA_VALIDATE_ENDIANNESS \ - { \ - const long nativeOne = 1; \ - assert( "PortAudio: compile time and runtime endianness don't match" && (((char *)&nativeOne)[0]) == 0 ); \ - } +#if defined(NDEBUG) + #define PA_VALIDATE_ENDIANNESS +#else + #if defined(PA_LITTLE_ENDIAN) + #define PA_VALIDATE_ENDIANNESS \ + { \ + const long nativeOne = 1; \ + assert( "PortAudio: compile time and runtime endianness don't match" && (((char *)&nativeOne)[0]) == 1 ); \ + } + #elif defined(PA_BIG_ENDIAN) + #define PA_VALIDATE_ENDIANNESS \ + { \ + const long nativeOne = 1; \ + assert( "PortAudio: compile time and runtime endianness don't match" && (((char *)&nativeOne)[0]) == 0 ); \ + } + #endif #endif diff --git a/pd/portaudio/src/common/pa_front.c b/pd/portaudio/src/common/pa_front.c index 5af90d45..28f0c60e 100644 --- a/pd/portaudio/src/common/pa_front.c +++ b/pd/portaudio/src/common/pa_front.c @@ -1,10 +1,10 @@ /* - * $Id: pa_front.c 1229 2007-06-15 16:11:11Z rossb $ + * $Id: pa_front.c 1396 2008-11-03 19:31:30Z philburk $ * Portable Audio I/O Library Multi-Host API front end * Validate function parameters and manage multiple host APIs. * * Based on the Open Source API proposed by Ross Bencina - * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * Copyright (c) 1999-2008 Ross Bencina, Phil Burk * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files @@ -40,17 +40,20 @@ /** @file @ingroup common_src - @brief Implements public PortAudio API, checks some errors, forwards to - host API implementations. + @brief Implements PortAudio API functions defined in portaudio.h, checks + some errors, delegates platform-specific behavior to host API implementations. - Implements the functions defined in the PortAudio API, checks for - some parameter and state inconsistencies and forwards API requests to - specific Host API implementations (via the interface declared in - pa_hostapi.h), and Streams (via the interface declared in pa_stream.h). + Implements the functions defined in the PortAudio API (portaudio.h), + validates some parameters and checks for state inconsistencies before + forwarding API requests to specific Host API implementations (via the + interface declared in pa_hostapi.h), and Streams (via the interface + declared in pa_stream.h). - This file handles initialization and termination of Host API - implementations via initializers stored in the paHostApiInitializers - global variable. + This file manages initialization and termination of Host API + implementations via initializer functions stored in the paHostApiInitializers + global array (usually defined in an os-specific pa_[os]_hostapis.c file). + + This file maintains a list of all open streams and closes them at Pa_Terminate(). Some utility functions declared in pa_util.h are implemented in this file. @@ -87,7 +90,7 @@ #define PA_VERSION_ 1899 -#define PA_VERSION_TEXT_ "PortAudio V19-devel (built " __DATE__ ")" +#define PA_VERSION_TEXT_ "PortAudio V19-devel (built " __DATE__ " " __TIME__ ")" @@ -405,6 +408,8 @@ const char *Pa_GetErrorText( PaError errorCode ) case paCanNotWriteToACallbackStream: result = "Can't write to a callback stream"; break; case paCanNotReadFromAnOutputOnlyStream: result = "Can't read from an output only stream"; break; case paCanNotWriteToAnInputOnlyStream: result = "Can't write to an input only stream"; break; + case paIncompatibleStreamHostApi: result = "Incompatible stream host API"; break; + case paBadBufferPtr: result = "Bad buffer pointer"; break; default: if( errorCode > 0 ) result = "Invalid error code (value greater than zero)"; @@ -1501,7 +1506,7 @@ const PaStreamInfo* Pa_GetStreamInfo( PaStream *stream ) result = 0; PA_LOGAPI(("Pa_GetStreamInfo returned:\n" )); - PA_LOGAPI(("\tconst PaStreamInfo*: 0 [PaError error:%d ( %s )]\n", result, error, Pa_GetErrorText( error ) )); + PA_LOGAPI(("\tconst PaStreamInfo*: 0 [PaError error:%d ( %s )]\n", error, Pa_GetErrorText( error ) )); } else diff --git a/pd/portaudio/src/common/pa_hostapi.h b/pd/portaudio/src/common/pa_hostapi.h index 5a86d4e9..5462d443 100644 --- a/pd/portaudio/src/common/pa_hostapi.h +++ b/pd/portaudio/src/common/pa_hostapi.h @@ -1,12 +1,12 @@ #ifndef PA_HOSTAPI_H #define PA_HOSTAPI_H /* - * $Id: pa_hostapi.h 1097 2006-08-26 08:27:53Z rossb $ + * $Id: pa_hostapi.h 1339 2008-02-15 07:50:33Z rossb $ * Portable Audio I/O Library * host api representation * * Based on the Open Source API proposed by Ross Bencina - * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * Copyright (c) 1999-2008 Ross Bencina, Phil Burk * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files @@ -42,8 +42,8 @@ /** @file @ingroup common_src - @brief Interface used by pa_front to virtualize functions which operate on - host APIs. + @brief Interfaces and representation structures used by pa_front.c + to manage and communicate with host API implementations. */ @@ -224,13 +224,19 @@ typedef struct PaUtilHostApiRepresentation { /** Prototype for the initialization function which must be implemented by every host API. + This function should only return an error other than paNoError if it encounters + an unexpected and fatal error (memory allocation error for example). In general, + there may be conditions under which it returns a NULL interface pointer and also + returns paNoError. For example, if the ASIO implementation detects that ASIO is + not installed, it should return a NULL interface, and paNoError. + @see paHostApiInitializers */ typedef PaError PaUtilHostApiInitializer( PaUtilHostApiRepresentation**, PaHostApiIndex ); /** paHostApiInitializers is a NULL-terminated array of host API initialization - functions. These functions are called by pa_front to initialize the host APIs + functions. These functions are called by pa_front.c to initialize the host APIs when the client calls Pa_Initialize(). There is a platform specific file which defines paHostApiInitializers for that diff --git a/pd/portaudio/src/common/pa_memorybarrier.h b/pd/portaudio/src/common/pa_memorybarrier.h new file mode 100644 index 00000000..5efeed24 --- /dev/null +++ b/pd/portaudio/src/common/pa_memorybarrier.h @@ -0,0 +1,123 @@ +/* + * $Id: pa_memorybarrier.h 1240 2007-07-17 13:05:07Z bjornroche $ + * Portable Audio I/O Library + * Memory barrier utilities + * + * Author: Bjorn Roche, XO Audio, LLC + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** + @file pa_memorybarrier.h + @ingroup common_src +*/ + +/**************** + * Some memory barrier primitives based on the system. + * right now only OS X, FreeBSD, and Linux are supported. In addition to providing + * memory barriers, these functions should ensure that data cached in registers + * is written out to cache where it can be snooped by other CPUs. (ie, the volatile + * keyword should not be required) + * + * the primitives that must be defined are: + * + * PaUtil_FullMemoryBarrier() + * PaUtil_ReadMemoryBarrier() + * PaUtil_WriteMemoryBarrier() + * + ****************/ + +#if defined(__APPLE__) +# include + /* Here are the memory barrier functions. Mac OS X only provides + full memory barriers, so the three types of barriers are the same, + however, these barriers are superior to compiler-based ones. */ +# define PaUtil_FullMemoryBarrier() OSMemoryBarrier() +# define PaUtil_ReadMemoryBarrier() OSMemoryBarrier() +# define PaUtil_WriteMemoryBarrier() OSMemoryBarrier() +#elif defined(__GNUC__) + /* GCC >= 4.1 has built-in intrinsics. We'll use those */ +# if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) +# define PaUtil_FullMemoryBarrier() __sync_synchronize() +# define PaUtil_ReadMemoryBarrier() __sync_synchronize() +# define PaUtil_WriteMemoryBarrier() __sync_synchronize() + /* as a fallback, GCC understands volatile asm and "memory" to mean it + * should not reorder memory read/writes */ + /* Note that it is not clear that any compiler actually defines __PPC__, + * it can probably removed safely. */ +# elif defined( __ppc__ ) || defined( __powerpc__) || defined( __PPC__ ) +# define PaUtil_FullMemoryBarrier() asm volatile("sync":::"memory") +# define PaUtil_ReadMemoryBarrier() asm volatile("sync":::"memory") +# define PaUtil_WriteMemoryBarrier() asm volatile("sync":::"memory") +# elif defined( __i386__ ) || defined( __i486__ ) || defined( __i586__ ) || \ + defined( __i686__ ) || defined( __x86_64__ ) +# define PaUtil_FullMemoryBarrier() asm volatile("mfence":::"memory") +# define PaUtil_ReadMemoryBarrier() asm volatile("lfence":::"memory") +# define PaUtil_WriteMemoryBarrier() asm volatile("sfence":::"memory") +# else +# ifdef ALLOW_SMP_DANGERS +# warning Memory barriers not defined on this system or system unknown +# warning For SMP safety, you should fix this. +# define PaUtil_FullMemoryBarrier() +# define PaUtil_ReadMemoryBarrier() +# define PaUtil_WriteMemoryBarrier() +# else +# error Memory barriers are not defined on this system. You can still compile by defining ALLOW_SMP_DANGERS, but SMP safety will not be guaranteed. +# endif +# endif +#elif (_MSC_VER >= 1400) +# include +# pragma intrinsic(_ReadWriteBarrier) +# pragma intrinsic(_ReadBarrier) +# pragma intrinsic(_WriteBarrier) +# define PaUtil_FullMemoryBarrier() _ReadWriteBarrier() +# define PaUtil_ReadMemoryBarrier() _ReadBarrier() +# define PaUtil_WriteMemoryBarrier() _WriteBarrier() +#elif defined(_MSC_VER) || defined(__BORLANDC__) +# define PaUtil_FullMemoryBarrier() _asm { lock add [esp], 0 } +# define PaUtil_ReadMemoryBarrier() _asm { lock add [esp], 0 } +# define PaUtil_WriteMemoryBarrier() _asm { lock add [esp], 0 } +#else +# ifdef ALLOW_SMP_DANGERS +# warning Memory barriers not defined on this system or system unknown +# warning For SMP safety, you should fix this. +# define PaUtil_FullMemoryBarrier() +# define PaUtil_ReadMemoryBarrier() +# define PaUtil_WriteMemoryBarrier() +# else +# error Memory barriers are not defined on this system. You can still compile by defining ALLOW_SMP_DANGERS, but SMP safety will not be guaranteed. +# endif +#endif diff --git a/pd/portaudio/src/common/pa_process.c b/pd/portaudio/src/common/pa_process.c index fac474d5..4770610b 100644 --- a/pd/portaudio/src/common/pa_process.c +++ b/pd/portaudio/src/common/pa_process.c @@ -1,5 +1,5 @@ /* - * $Id: pa_process.c 1097 2006-08-26 08:27:53Z rossb $ + * $Id: pa_process.c 1408 2009-03-13 16:41:39Z rossb $ * Portable Audio I/O Library * streamCallback <-> host buffer processing adapter * @@ -56,8 +56,6 @@ @todo Consider cache tilings for intereave<->deinterleave. - @todo implement timeInfo->currentTime int PaUtil_BeginBufferProcessing() - @todo specify and implement some kind of logical policy for handling the underflow and overflow stream flags when the underflow/overflow overlaps multiple user buffers/callbacks. @@ -683,7 +681,9 @@ void PaUtil_BeginBufferProcessing( PaUtilBufferProcessor* bp, bp->timeInfo->inputBufferAdcTime -= bp->framesInTempInputBuffer * bp->samplePeriod; - bp->timeInfo->currentTime = 0; /** FIXME: @todo time info currentTime not implemented */ + /* We just pass through timeInfo->currentTime provided by the caller. This is + not strictly conformant to the word of the spec, since the buffer processor + might call the callback multiple times, and we never refresh currentTime. */ /* the first streamCallback will be called to generate samples which will be outputted after the frames currently in the output buffer have been @@ -997,7 +997,7 @@ static unsigned long AdaptingInputOnlyProcess( PaUtilBufferProcessor *bp, bp->framesPerUserBuffer, bp->timeInfo, bp->callbackStatusFlags, bp->userData ); - bp->timeInfo->inputBufferAdcTime += frameCount * bp->samplePeriod; + bp->timeInfo->inputBufferAdcTime += bp->framesPerUserBuffer * bp->samplePeriod; } bp->framesInTempInputBuffer = 0; diff --git a/pd/portaudio/src/common/pa_ringbuffer.c b/pd/portaudio/src/common/pa_ringbuffer.c index f4e1201a..ad9e2ae5 100644 --- a/pd/portaudio/src/common/pa_ringbuffer.c +++ b/pd/portaudio/src/common/pa_ringbuffer.c @@ -1,5 +1,5 @@ /* - * $Id: pa_ringbuffer.c 1240 2007-07-17 13:05:07Z bjornroche $ + * $Id: pa_ringbuffer.c 1346 2008-02-20 10:09:20Z rossb $ * Portable Audio I/O Library * Ring Buffer utility. * @@ -7,6 +7,8 @@ * modified for SMP safety on Mac OS X by Bjorn Roche * modified for SMP safety on Linux by Leland Lucius * also, allowed for const where possible + * modified for multiple-byte-sized data elements by Sven Fischer + * * Note that this is safe only for a single-thread reader and a * single-thread writer. * @@ -55,93 +57,33 @@ #include #include "pa_ringbuffer.h" #include - -/**************** - * First, we'll define some memory barrier primitives based on the system. - * right now only OS X, FreeBSD, and Linux are supported. In addition to providing - * memory barriers, these functions should ensure that data cached in registers - * is written out to cache where it can be snooped by other CPUs. (ie, the volatile - * keyword should not be required) - * - * the primitives that must be defined are: - * - * PaUtil_FullMemoryBarrier() - * PaUtil_ReadMemoryBarrier() - * PaUtil_WriteMemoryBarrier() - * - ****************/ - -#if defined(__APPLE__) -# include - /* Here are the memory barrier functions. Mac OS X only provides - full memory barriers, so the three types of barriers are the same, - however, these barriers are superior to compiler-based ones. */ -# define PaUtil_FullMemoryBarrier() OSMemoryBarrier() -# define PaUtil_ReadMemoryBarrier() OSMemoryBarrier() -# define PaUtil_WriteMemoryBarrier() OSMemoryBarrier() -#elif defined(__GNUC__) - /* GCC >= 4.1 has built-in intrinsics. We'll use those */ -# if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) -# define PaUtil_FullMemoryBarrier() __sync_synchronize() -# define PaUtil_ReadMemoryBarrier() __sync_synchronize() -# define PaUtil_WriteMemoryBarrier() __sync_synchronize() - /* as a fallback, GCC understands volatile asm and "memory" to mean it - * should not reorder memory read/writes */ -# elif defined( __PPC__ ) -# define PaUtil_FullMemoryBarrier() asm volatile("sync":::"memory") -# define PaUtil_ReadMemoryBarrier() asm volatile("sync":::"memory") -# define PaUtil_WriteMemoryBarrier() asm volatile("sync":::"memory") -# elif defined( __i386__ ) || defined( __i486__ ) || defined( __i586__ ) || defined( __i686__ ) || defined( __x86_64__ ) -# define PaUtil_FullMemoryBarrier() asm volatile("mfence":::"memory") -# define PaUtil_ReadMemoryBarrier() asm volatile("lfence":::"memory") -# define PaUtil_WriteMemoryBarrier() asm volatile("sfence":::"memory") -# else -# ifdef ALLOW_SMP_DANGERS -# warning Memory barriers not defined on this system or system unknown -# warning For SMP safety, you should fix this. -# define PaUtil_FullMemoryBarrier() -# define PaUtil_ReadMemoryBarrier() -# define PaUtil_WriteMemoryBarrier() -# else -# error Memory barriers are not defined on this system. You can still compile by defining ALLOW_SMP_DANGERS, but SMP safety will not be guaranteed. -# endif -# endif -#else -# ifdef ALLOW_SMP_DANGERS -# warning Memory barriers not defined on this system or system unknown -# warning For SMP safety, you should fix this. -# define PaUtil_FullMemoryBarrier() -# define PaUtil_ReadMemoryBarrier() -# define PaUtil_WriteMemoryBarrier() -# else -# error Memory barriers are not defined on this system. You can still compile by defining ALLOW_SMP_DANGERS, but SMP safety will not be guaranteed. -# endif -#endif +#include "pa_memorybarrier.h" /*************************************************************************** * Initialize FIFO. - * numBytes must be power of 2, returns -1 if not. + * elementCount must be power of 2, returns -1 if not. */ -long PaUtil_InitializeRingBuffer( PaUtilRingBuffer *rbuf, long numBytes, void *dataPtr ) +long PaUtil_InitializeRingBuffer( PaUtilRingBuffer *rbuf, long elementSizeBytes, long elementCount, void *dataPtr ) { - if( ((numBytes-1) & numBytes) != 0) return -1; /* Not Power of two. */ - rbuf->bufferSize = numBytes; + if( ((elementCount-1) & elementCount) != 0) return -1; /* Not Power of two. */ + rbuf->bufferSize = elementCount; rbuf->buffer = (char *)dataPtr; PaUtil_FlushRingBuffer( rbuf ); - rbuf->bigMask = (numBytes*2)-1; - rbuf->smallMask = (numBytes)-1; + rbuf->bigMask = (elementCount*2)-1; + rbuf->smallMask = (elementCount)-1; + rbuf->elementSizeBytes = elementSizeBytes; return 0; } /*************************************************************************** -** Return number of bytes available for reading. */ +** Return number of elements available for reading. */ long PaUtil_GetRingBufferReadAvailable( PaUtilRingBuffer *rbuf ) { PaUtil_ReadMemoryBarrier(); return ( (rbuf->writeIndex - rbuf->readIndex) & rbuf->bigMask ); } /*************************************************************************** -** Return number of bytes available for writing. */ +** Return number of elements available for writing. */ long PaUtil_GetRingBufferWriteAvailable( PaUtilRingBuffer *rbuf ) { /* Since we are calling PaUtil_GetRingBufferReadAvailable, we don't need an aditional MB */ @@ -159,126 +101,126 @@ void PaUtil_FlushRingBuffer( PaUtilRingBuffer *rbuf ) ** Get address of region(s) to which we can write data. ** If the region is contiguous, size2 will be zero. ** If non-contiguous, size2 will be the size of second region. -** Returns room available to be written or numBytes, whichever is smaller. +** Returns room available to be written or elementCount, whichever is smaller. */ -long PaUtil_GetRingBufferWriteRegions( PaUtilRingBuffer *rbuf, long numBytes, +long PaUtil_GetRingBufferWriteRegions( PaUtilRingBuffer *rbuf, long elementCount, void **dataPtr1, long *sizePtr1, void **dataPtr2, long *sizePtr2 ) { long index; long available = PaUtil_GetRingBufferWriteAvailable( rbuf ); - if( numBytes > available ) numBytes = available; + if( elementCount > available ) elementCount = available; /* Check to see if write is not contiguous. */ index = rbuf->writeIndex & rbuf->smallMask; - if( (index + numBytes) > rbuf->bufferSize ) + if( (index + elementCount) > rbuf->bufferSize ) { /* Write data in two blocks that wrap the buffer. */ long firstHalf = rbuf->bufferSize - index; - *dataPtr1 = &rbuf->buffer[index]; + *dataPtr1 = &rbuf->buffer[index*rbuf->elementSizeBytes]; *sizePtr1 = firstHalf; *dataPtr2 = &rbuf->buffer[0]; - *sizePtr2 = numBytes - firstHalf; + *sizePtr2 = elementCount - firstHalf; } else { - *dataPtr1 = &rbuf->buffer[index]; - *sizePtr1 = numBytes; + *dataPtr1 = &rbuf->buffer[index*rbuf->elementSizeBytes]; + *sizePtr1 = elementCount; *dataPtr2 = NULL; *sizePtr2 = 0; } - return numBytes; + return elementCount; } /*************************************************************************** */ -long PaUtil_AdvanceRingBufferWriteIndex( PaUtilRingBuffer *rbuf, long numBytes ) +long PaUtil_AdvanceRingBufferWriteIndex( PaUtilRingBuffer *rbuf, long elementCount ) { /* we need to ensure that previous writes are seen before we update the write index */ PaUtil_WriteMemoryBarrier(); - return rbuf->writeIndex = (rbuf->writeIndex + numBytes) & rbuf->bigMask; + return rbuf->writeIndex = (rbuf->writeIndex + elementCount) & rbuf->bigMask; } /*************************************************************************** ** Get address of region(s) from which we can read data. ** If the region is contiguous, size2 will be zero. ** If non-contiguous, size2 will be the size of second region. -** Returns room available to be written or numBytes, whichever is smaller. +** Returns room available to be written or elementCount, whichever is smaller. */ -long PaUtil_GetRingBufferReadRegions( PaUtilRingBuffer *rbuf, long numBytes, +long PaUtil_GetRingBufferReadRegions( PaUtilRingBuffer *rbuf, long elementCount, void **dataPtr1, long *sizePtr1, void **dataPtr2, long *sizePtr2 ) { long index; long available = PaUtil_GetRingBufferReadAvailable( rbuf ); - if( numBytes > available ) numBytes = available; + if( elementCount > available ) elementCount = available; /* Check to see if read is not contiguous. */ index = rbuf->readIndex & rbuf->smallMask; - if( (index + numBytes) > rbuf->bufferSize ) + if( (index + elementCount) > rbuf->bufferSize ) { /* Write data in two blocks that wrap the buffer. */ long firstHalf = rbuf->bufferSize - index; - *dataPtr1 = &rbuf->buffer[index]; + *dataPtr1 = &rbuf->buffer[index*rbuf->elementSizeBytes]; *sizePtr1 = firstHalf; *dataPtr2 = &rbuf->buffer[0]; - *sizePtr2 = numBytes - firstHalf; + *sizePtr2 = elementCount - firstHalf; } else { - *dataPtr1 = &rbuf->buffer[index]; - *sizePtr1 = numBytes; + *dataPtr1 = &rbuf->buffer[index*rbuf->elementSizeBytes]; + *sizePtr1 = elementCount; *dataPtr2 = NULL; *sizePtr2 = 0; } - return numBytes; + return elementCount; } /*************************************************************************** */ -long PaUtil_AdvanceRingBufferReadIndex( PaUtilRingBuffer *rbuf, long numBytes ) +long PaUtil_AdvanceRingBufferReadIndex( PaUtilRingBuffer *rbuf, long elementCount ) { /* we need to ensure that previous writes are always seen before updating the index. */ PaUtil_WriteMemoryBarrier(); - return rbuf->readIndex = (rbuf->readIndex + numBytes) & rbuf->bigMask; + return rbuf->readIndex = (rbuf->readIndex + elementCount) & rbuf->bigMask; } /*************************************************************************** -** Return bytes written. */ -long PaUtil_WriteRingBuffer( PaUtilRingBuffer *rbuf, const void *data, long numBytes ) +** Return elements written. */ +long PaUtil_WriteRingBuffer( PaUtilRingBuffer *rbuf, const void *data, long elementCount ) { long size1, size2, numWritten; void *data1, *data2; - numWritten = PaUtil_GetRingBufferWriteRegions( rbuf, numBytes, &data1, &size1, &data2, &size2 ); + numWritten = PaUtil_GetRingBufferWriteRegions( rbuf, elementCount, &data1, &size1, &data2, &size2 ); if( size2 > 0 ) { - memcpy( data1, data, size1 ); - data = ((char *)data) + size1; - memcpy( data2, data, size2 ); + memcpy( data1, data, size1*rbuf->elementSizeBytes ); + data = ((char *)data) + size1*rbuf->elementSizeBytes; + memcpy( data2, data, size2*rbuf->elementSizeBytes ); } else { - memcpy( data1, data, size1 ); + memcpy( data1, data, size1*rbuf->elementSizeBytes ); } PaUtil_AdvanceRingBufferWriteIndex( rbuf, numWritten ); return numWritten; } /*************************************************************************** -** Return bytes read. */ -long PaUtil_ReadRingBuffer( PaUtilRingBuffer *rbuf, void *data, long numBytes ) +** Return elements read. */ +long PaUtil_ReadRingBuffer( PaUtilRingBuffer *rbuf, void *data, long elementCount ) { long size1, size2, numRead; void *data1, *data2; - numRead = PaUtil_GetRingBufferReadRegions( rbuf, numBytes, &data1, &size1, &data2, &size2 ); + numRead = PaUtil_GetRingBufferReadRegions( rbuf, elementCount, &data1, &size1, &data2, &size2 ); if( size2 > 0 ) { - memcpy( data, data1, size1 ); - data = ((char *)data) + size1; - memcpy( data, data2, size2 ); + memcpy( data, data1, size1*rbuf->elementSizeBytes ); + data = ((char *)data) + size1*rbuf->elementSizeBytes; + memcpy( data, data2, size2*rbuf->elementSizeBytes ); } else { - memcpy( data, data1, size1 ); + memcpy( data, data1, size1*rbuf->elementSizeBytes ); } PaUtil_AdvanceRingBufferReadIndex( rbuf, numRead ); return numRead; diff --git a/pd/portaudio/src/common/pa_ringbuffer.h b/pd/portaudio/src/common/pa_ringbuffer.h index b3808898..fd92882a 100644 --- a/pd/portaudio/src/common/pa_ringbuffer.h +++ b/pd/portaudio/src/common/pa_ringbuffer.h @@ -1,13 +1,15 @@ #ifndef PA_RINGBUFFER_H #define PA_RINGBUFFER_H /* - * $Id: pa_ringbuffer.h 1151 2006-11-29 02:11:16Z leland_lucius $ + * $Id: pa_ringbuffer.h 1347 2008-02-21 04:54:36Z rossb $ * Portable Audio I/O Library * Ring Buffer utility. * * Author: Phil Burk, http://www.softsynth.com * modified for SMP safety on OS X by Bjorn Roche. * also allowed for const where possible. + * modified for multiple-byte-sized data elements by Sven Fischer + * * Note that this is safe only for a single-thread reader * and a single-thread writer. * @@ -48,6 +50,21 @@ /** @file @ingroup common_src + @brief Single-reader single-writer lock-free ring buffer + + PaUtilRingBuffer is a ring buffer used to transport samples between + different execution contexts (threads, OS callbacks, interrupt handlers) + without requiring the use of any locks. This only works when there is + a single reader and a single writer (ie. one thread or callback writes + to the ring buffer, another thread or callback reads from it). + + The PaUtilRingBuffer structure manages a ring buffer containing N + elements, where N must be a power of two. An element may be any size + (specified in bytes). + + The memory area used to store the buffer elements must be allocated by + the client prior to calling PaUtil_InitializeRingBuffer() and must outlive + the use of the ring buffer. */ #ifdef __cplusplus @@ -57,26 +74,29 @@ extern "C" typedef struct PaUtilRingBuffer { - long bufferSize; /* Number of bytes in FIFO. Power of 2. Set by PaUtil_InitRingBuffer. */ - long writeIndex; /* Index of next writable byte. Set by PaUtil_AdvanceRingBufferWriteIndex. */ - long readIndex; /* Index of next readable byte. Set by PaUtil_AdvanceRingBufferReadIndex. */ - long bigMask; /* Used for wrapping indices with extra bit to distinguish full/empty. */ - long smallMask; /* Used for fitting indices to buffer. */ - char *buffer; + long bufferSize; /**< Number of elements in FIFO. Power of 2. Set by PaUtil_InitRingBuffer. */ + long writeIndex; /**< Index of next writable element. Set by PaUtil_AdvanceRingBufferWriteIndex. */ + long readIndex; /**< Index of next readable element. Set by PaUtil_AdvanceRingBufferReadIndex. */ + long bigMask; /**< Used for wrapping indices with extra bit to distinguish full/empty. */ + long smallMask; /**< Used for fitting indices to buffer. */ + long elementSizeBytes; /**< Number of bytes per element. */ + char *buffer; /**< Pointer to the buffer containing the actual data. */ }PaUtilRingBuffer; /** Initialize Ring Buffer. @param rbuf The ring buffer. - @param numBytes The number of bytes in the buffer and must be power of 2. + @param elementSizeBytes The size of a single data element in bytes. + + @param elementCount The number of elements in the buffer (must be power of 2). @param dataPtr A pointer to a previously allocated area where the data - will be maintained. It must be numBytes long. + will be maintained. It must be elementCount*elementSizeBytes long. - @return -1 if numBytes is not a power of 2, otherwise 0. + @return -1 if elementCount is not a power of 2, otherwise 0. */ -long PaUtil_InitializeRingBuffer( PaUtilRingBuffer *rbuf, long numBytes, void *dataPtr ); +long PaUtil_InitializeRingBuffer( PaUtilRingBuffer *rbuf, long elementSizeBytes, long elementCount, void *dataPtr ); /** Clear buffer. Should only be called when buffer is NOT being read. @@ -84,19 +104,19 @@ long PaUtil_InitializeRingBuffer( PaUtilRingBuffer *rbuf, long numBytes, void *d */ void PaUtil_FlushRingBuffer( PaUtilRingBuffer *rbuf ); -/** Retrieve the number of bytes available in the ring buffer for writing. +/** Retrieve the number of elements available in the ring buffer for writing. @param rbuf The ring buffer. - @return The number of bytes available for writing. + @return The number of elements available for writing. */ long PaUtil_GetRingBufferWriteAvailable( PaUtilRingBuffer *rbuf ); -/** Retrieve the number of bytes available in the ring buffer for reading. +/** Retrieve the number of elements available in the ring buffer for reading. @param rbuf The ring buffer. - @return The number of bytes available for reading. + @return The number of elements available for reading. */ long PaUtil_GetRingBufferReadAvailable( PaUtilRingBuffer *rbuf ); @@ -106,11 +126,11 @@ long PaUtil_GetRingBufferReadAvailable( PaUtilRingBuffer *rbuf ); @param data The address of new data to write to the buffer. - @param numBytes The number of bytes to be written. + @param elementCount The number of elements to be written. - @return The number of bytes written. + @return The number of elements written. */ -long PaUtil_WriteRingBuffer( PaUtilRingBuffer *rbuf, const void *data, long numBytes ); +long PaUtil_WriteRingBuffer( PaUtilRingBuffer *rbuf, const void *data, long elementCount ); /** Read data from the ring buffer. @@ -118,17 +138,17 @@ long PaUtil_WriteRingBuffer( PaUtilRingBuffer *rbuf, const void *data, long numB @param data The address where the data should be stored. - @param numBytes The number of bytes to be read. + @param elementCount The number of elements to be read. - @return The number of bytes read. + @return The number of elements read. */ -long PaUtil_ReadRingBuffer( PaUtilRingBuffer *rbuf, void *data, long numBytes ); +long PaUtil_ReadRingBuffer( PaUtilRingBuffer *rbuf, void *data, long elementCount ); /** Get address of region(s) to which we can write data. @param rbuf The ring buffer. - @param numBytes The number of bytes desired. + @param elementCount The number of elements desired. @param dataPtr1 The address where the first (or only) region pointer will be stored. @@ -137,14 +157,14 @@ long PaUtil_ReadRingBuffer( PaUtilRingBuffer *rbuf, void *data, long numBytes ); stored. @param dataPtr2 The address where the second region pointer will be stored if - the first region is too small to satisfy numBytes. + the first region is too small to satisfy elementCount. @param sizePtr2 The address where the second region length will be stored if - the first region is too small to satisfy numBytes. + the first region is too small to satisfy elementCount. - @return The room available to be written or numBytes, whichever is smaller. + @return The room available to be written or elementCount, whichever is smaller. */ -long PaUtil_GetRingBufferWriteRegions( PaUtilRingBuffer *rbuf, long numBytes, +long PaUtil_GetRingBufferWriteRegions( PaUtilRingBuffer *rbuf, long elementCount, void **dataPtr1, long *sizePtr1, void **dataPtr2, long *sizePtr2 ); @@ -152,17 +172,17 @@ long PaUtil_GetRingBufferWriteRegions( PaUtilRingBuffer *rbuf, long numBytes, @param rbuf The ring buffer. - @param numBytes The number of bytes to advance. + @param elementCount The number of elements to advance. @return The new position. */ -long PaUtil_AdvanceRingBufferWriteIndex( PaUtilRingBuffer *rbuf, long numBytes ); +long PaUtil_AdvanceRingBufferWriteIndex( PaUtilRingBuffer *rbuf, long elementCount ); /** Get address of region(s) from which we can write data. @param rbuf The ring buffer. - @param numBytes The number of bytes desired. + @param elementCount The number of elements desired. @param dataPtr1 The address where the first (or only) region pointer will be stored. @@ -171,14 +191,14 @@ long PaUtil_AdvanceRingBufferWriteIndex( PaUtilRingBuffer *rbuf, long numBytes ) stored. @param dataPtr2 The address where the second region pointer will be stored if - the first region is too small to satisfy numBytes. + the first region is too small to satisfy elementCount. @param sizePtr2 The address where the second region length will be stored if - the first region is too small to satisfy numBytes. + the first region is too small to satisfy elementCount. - @return The number of bytes available for reading. + @return The number of elements available for reading. */ -long PaUtil_GetRingBufferReadRegions( PaUtilRingBuffer *rbuf, long numBytes, +long PaUtil_GetRingBufferReadRegions( PaUtilRingBuffer *rbuf, long elementCount, void **dataPtr1, long *sizePtr1, void **dataPtr2, long *sizePtr2 ); @@ -186,11 +206,11 @@ long PaUtil_GetRingBufferReadRegions( PaUtilRingBuffer *rbuf, long numBytes, @param rbuf The ring buffer. - @param numBytes The number of bytes to advance. + @param elementCount The number of elements to advance. @return The new position. */ -long PaUtil_AdvanceRingBufferReadIndex( PaUtilRingBuffer *rbuf, long numBytes ); +long PaUtil_AdvanceRingBufferReadIndex( PaUtilRingBuffer *rbuf, long elementCount ); #ifdef __cplusplus } diff --git a/pd/portaudio/src/common/pa_skeleton.c b/pd/portaudio/src/common/pa_skeleton.c index e229b07b..d5cb52d8 100644 --- a/pd/portaudio/src/common/pa_skeleton.c +++ b/pd/portaudio/src/common/pa_skeleton.c @@ -1,5 +1,5 @@ /* - * $Id: pa_skeleton.c 1097 2006-08-26 08:27:53Z rossb $ + * $Id: pa_skeleton.c 1339 2008-02-15 07:50:33Z rossb $ * Portable Audio I/O Library skeleton implementation * demonstrates how to use the common functions to implement support * for a host API @@ -43,8 +43,10 @@ @brief Skeleton implementation of support for a host API. - @note This file is provided as a starting point for implementing support for - a new host API. IMPLEMENT ME comments are used to indicate functionality + This file is provided as a starting point for implementing support for + a new host API. It provides examples of how the common code can be used. + + @note IMPLEMENT ME comments are used to indicate functionality which much be customised for each implementation. */ diff --git a/pd/portaudio/src/common/pa_stream.c b/pd/portaudio/src/common/pa_stream.c index 172e7d26..ea91821f 100644 --- a/pd/portaudio/src/common/pa_stream.c +++ b/pd/portaudio/src/common/pa_stream.c @@ -1,10 +1,10 @@ /* - * $Id: pa_stream.c 1097 2006-08-26 08:27:53Z rossb $ + * $Id: pa_stream.c 1339 2008-02-15 07:50:33Z rossb $ * Portable Audio I/O Library - * + * stream interface * * Based on the Open Source API proposed by Ross Bencina - * Copyright (c) 2002 Ross Bencina + * Copyright (c) 2008 Ross Bencina * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files @@ -40,8 +40,8 @@ /** @file @ingroup common_src - @brief Interface used by pa_front to virtualize functions which operate on - streams. + @brief Stream interfaces, representation structures and helper functions + used to interface between pa_front.c host API implementations. */ diff --git a/pd/portaudio/src/common/pa_stream.h b/pd/portaudio/src/common/pa_stream.h index f5363b3e..8d707b79 100644 --- a/pd/portaudio/src/common/pa_stream.h +++ b/pd/portaudio/src/common/pa_stream.h @@ -1,12 +1,12 @@ #ifndef PA_STREAM_H #define PA_STREAM_H /* - * $Id: pa_stream.h 1097 2006-08-26 08:27:53Z rossb $ + * $Id: pa_stream.h 1339 2008-02-15 07:50:33Z rossb $ * Portable Audio I/O Library * stream interface * * Based on the Open Source API proposed by Ross Bencina - * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * Copyright (c) 1999-2008 Ross Bencina, Phil Burk * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files @@ -42,8 +42,8 @@ /** @file @ingroup common_src - @brief Interface used by pa_front to virtualize functions which operate on - streams. + @brief Stream interfaces, representation structures and helper functions + used to interface between pa_front.c host API implementations. */ diff --git a/pd/portaudio/src/common/pa_trace.c b/pd/portaudio/src/common/pa_trace.c index 583d3ae9..24305003 100644 --- a/pd/portaudio/src/common/pa_trace.c +++ b/pd/portaudio/src/common/pa_trace.c @@ -1,5 +1,5 @@ /* - * $Id: pa_trace.c 1097 2006-08-26 08:27:53Z rossb $ + * $Id: pa_trace.c 1339 2008-02-15 07:50:33Z rossb $ * Portable Audio I/O Library Trace Facility * Store trace information in real-time for later printing. * @@ -40,7 +40,7 @@ /** @file @ingroup common_src - @brief Event trace mechanism for debugging. + @brief Real-time safe event trace logging facility for debugging. */ diff --git a/pd/portaudio/src/common/pa_trace.h b/pd/portaudio/src/common/pa_trace.h index a4d2a331..b11509e0 100644 --- a/pd/portaudio/src/common/pa_trace.h +++ b/pd/portaudio/src/common/pa_trace.h @@ -1,7 +1,7 @@ #ifndef PA_TRACE_H #define PA_TRACE_H /* - * $Id: pa_trace.h 1097 2006-08-26 08:27:53Z rossb $ + * $Id: pa_trace.h 1339 2008-02-15 07:50:33Z rossb $ * Portable Audio I/O Library Trace Facility * Store trace information in real-time for later printing. * @@ -42,14 +42,36 @@ /** @file @ingroup common_src - @brief Event trace mechanism for debugging. + @brief Real-time safe event trace logging facility for debugging. - Allows data to be written to the buffer at interrupt time and dumped later. + Allows data to be logged to a fixed size trace buffer in a real-time + execution context (such as at interrupt time). Each log entry consists + of a message comprising a string pointer and an int. The trace buffer + may be dumped to stdout later. + + This facility is only active if PA_TRACE_REALTIME_EVENTS is set to 1, + otherwise the trace functions expand to no-ops. + + @fn PaUtil_ResetTraceMessages + @brief Clear the trace buffer. + + @fn PaUtil_AddTraceMessage + @brief Add a message to the trace buffer. A message consists of string and an int. + @param msg The string pointer must remain valid until PaUtil_DumpTraceMessages + is called. As a result, usually only string literals should be passed as + the msg parameter. + + @fn PaUtil_DumpTraceMessages + @brief Print all messages in the trace buffer to stdout and clear the trace buffer. */ +#ifndef PA_TRACE_REALTIME_EVENTS +#define PA_TRACE_REALTIME_EVENTS (0) /**< Set to 1 to enable logging using the trace functions defined below */ +#endif -#define PA_TRACE_REALTIME_EVENTS (0) /* Keep log of various real-time events. */ -#define PA_MAX_TRACE_RECORDS (2048) +#ifndef PA_MAX_TRACE_RECORDS +#define PA_MAX_TRACE_RECORDS (2048) /**< Maximum number of records stored in trace buffer */ +#endif #ifdef __cplusplus extern "C" diff --git a/pd/portaudio/src/common/pa_util.h b/pd/portaudio/src/common/pa_util.h index 55eaa138..95ea6789 100644 --- a/pd/portaudio/src/common/pa_util.h +++ b/pd/portaudio/src/common/pa_util.h @@ -1,12 +1,12 @@ #ifndef PA_UTIL_H #define PA_UTIL_H /* - * $Id: pa_util.h 1229 2007-06-15 16:11:11Z rossb $ + * $Id: pa_util.h 1339 2008-02-15 07:50:33Z rossb $ * Portable Audio I/O Library implementation utilities header * common implementation utilities and interfaces * * Based on the Open Source API proposed by Ross Bencina - * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * Copyright (c) 1999-2008 Ross Bencina, Phil Burk * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files @@ -44,6 +44,9 @@ @brief Prototypes for utility functions used by PortAudio implementations. + Some functions declared here are defined in pa_front.c while others + are implemented separately for each platform. + @todo Document and adhere to the alignment guarantees provided by PaUtil_AllocateMemory(). */ diff --git a/pd/portaudio/src/hostapi/alsa/pa_linux_alsa.c b/pd/portaudio/src/hostapi/alsa/pa_linux_alsa.c index 06b17ac1..6b4a7b9f 100644 --- a/pd/portaudio/src/hostapi/alsa/pa_linux_alsa.c +++ b/pd/portaudio/src/hostapi/alsa/pa_linux_alsa.c @@ -1,11 +1,12 @@ /* - * $Id: pa_linux_alsa.c 1236 2007-06-24 20:39:26Z aknudsen $ + * $Id: pa_linux_alsa.c 1415 2009-06-03 18:57:56Z aknudsen $ * PortAudio Portable Real-Time Audio Library * Latest Version at: http://www.portaudio.com * ALSA implementation by Joshua Haberman and Arve Knudsen * * Copyright (c) 2002 Joshua Haberman - * Copyright (c) 2005-2006 Arve Knudsen + * Copyright (c) 2005-2009 Arve Knudsen + * Copyright (c) 2008 Kevin Kofler * * Based on the Open Source API proposed by Ross Bencina * Copyright (c) 1999-2002 Ross Bencina, Phil Burk @@ -62,6 +63,7 @@ #include #include /* For sig_atomic_t */ +#include "portaudio.h" #include "pa_util.h" #include "pa_unix_util.h" #include "pa_allocation.h" @@ -98,6 +100,7 @@ static int aErr_; /* Used with ENSURE_ */ static int numPeriods_ = 4; +static int busyRetries_ = 100; int PaAlsa_SetNumPeriods( int numPeriods ) { @@ -117,6 +120,8 @@ typedef struct unsigned long framesPerBuffer; int numUserChannels, numHostChannels; int userInterleaved, hostInterleaved; + int canMmap; + void *nonMmapBuffer; PaDeviceIndex device; /* Keep the device index */ snd_pcm_t *pcm; @@ -320,7 +325,7 @@ static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) * and a suitable result returned. The device is closed before returning. */ static PaError GropeDevice( snd_pcm_t* pcm, int isPlug, StreamDirection mode, int openBlocking, - PaAlsaDeviceInfo* devInfo, int* canMmap ) + PaAlsaDeviceInfo* devInfo ) { PaError result = paNoError; snd_pcm_hw_params_t *hwParams; @@ -353,9 +358,6 @@ static PaError GropeDevice( snd_pcm_t* pcm, int isPlug, StreamDirection mode, in snd_pcm_hw_params_alloca( &hwParams ); snd_pcm_hw_params_any( pcm, hwParams ); - *canMmap = snd_pcm_hw_params_test_access( pcm, hwParams, SND_PCM_ACCESS_MMAP_INTERLEAVED ) >= 0 || - snd_pcm_hw_params_test_access( pcm, hwParams, SND_PCM_ACCESS_MMAP_NONINTERLEAVED ) >= 0; - if( defaultSr >= 0 ) { /* Could be that the device opened in one mode supports samplerates that the other mode wont have, @@ -538,7 +540,7 @@ static int IgnorePlugin( const char *pluginId ) **/ static int OpenPcm( snd_pcm_t **pcmp, const char *name, snd_pcm_stream_t stream, int mode, int waitOnBusy ) { - int tries = 0, maxTries = waitOnBusy ? 100 : 0; + int tries = 0, maxTries = waitOnBusy ? busyRetries_ : 0; int ret = snd_pcm_open( pcmp, name, stream, mode ); for( tries = 0; tries < maxTries && -EBUSY == ret; ++tries ) { @@ -565,7 +567,6 @@ static PaError FillInDevInfo( PaAlsaHostApiRepresentation *alsaApi, HwDevInfo* d PaError result = 0; PaDeviceInfo *baseDeviceInfo = &devInfo->baseDeviceInfo; snd_pcm_t *pcm; - int canMmap = -1; PaUtilHostApiRepresentation *baseApi = &alsaApi->baseHostApiRep; /* Zero fields */ @@ -579,8 +580,7 @@ static PaError FillInDevInfo( PaAlsaHostApiRepresentation *alsaApi, HwDevInfo* d OpenPcm( &pcm, deviceName->alsaName, SND_PCM_STREAM_CAPTURE, blocking, 0 ) >= 0 ) { - if( GropeDevice( pcm, deviceName->isPlug, StreamDirection_In, blocking, devInfo, - &canMmap ) != paNoError ) + if( GropeDevice( pcm, deviceName->isPlug, StreamDirection_In, blocking, devInfo ) != paNoError ) { /* Error */ PA_DEBUG(("%s: Failed groping %s for capture\n", __FUNCTION__, deviceName->alsaName)); @@ -593,8 +593,7 @@ static PaError FillInDevInfo( PaAlsaHostApiRepresentation *alsaApi, HwDevInfo* d OpenPcm( &pcm, deviceName->alsaName, SND_PCM_STREAM_PLAYBACK, blocking, 0 ) >= 0 ) { - if( GropeDevice( pcm, deviceName->isPlug, StreamDirection_Out, blocking, devInfo, - &canMmap ) != paNoError ) + if( GropeDevice( pcm, deviceName->isPlug, StreamDirection_Out, blocking, devInfo ) != paNoError ) { /* Error */ PA_DEBUG(("%s: Failed groping %s for playback\n", __FUNCTION__, deviceName->alsaName)); @@ -602,12 +601,6 @@ static PaError FillInDevInfo( PaAlsaHostApiRepresentation *alsaApi, HwDevInfo* d } } - if( 0 == canMmap ) - { - PA_DEBUG(("%s: Device %s doesn't support mmap\n", __FUNCTION__, deviceName->alsaName)); - goto end; - } - baseDeviceInfo->structVersion = 2; baseDeviceInfo->hostApi = alsaApi->hostApiIndex; baseDeviceInfo->name = deviceName->name; @@ -620,8 +613,12 @@ static PaError FillInDevInfo( PaAlsaHostApiRepresentation *alsaApi, HwDevInfo* d if( baseDeviceInfo->maxInputChannels > 0 || baseDeviceInfo->maxOutputChannels > 0 ) { /* Make device default if there isn't already one or it is the ALSA "default" device */ - if( baseApi->info.defaultInputDevice == paNoDevice && baseDeviceInfo->maxInputChannels > 0 ) + if( (baseApi->info.defaultInputDevice == paNoDevice || !strcmp(deviceName->alsaName, + "default" )) && baseDeviceInfo->maxInputChannels > 0 ) + { baseApi->info.defaultInputDevice = *devIdx; + PA_DEBUG(("Default input device: %s\n", deviceName->name)); + } if( (baseApi->info.defaultOutputDevice == paNoDevice || !strcmp(deviceName->alsaName, "default" )) && baseDeviceInfo->maxOutputChannels > 0 ) { @@ -1192,6 +1189,8 @@ static PaError PaAlsaStreamComponent_Initialize( PaAlsaStreamComponent *self, Pa self->hostInterleaved = self->userInterleaved = !(userSampleFormat & paNonInterleaved); self->numUserChannels = params->channelCount; self->streamDir = streamDir; + self->canMmap = 0; + self->nonMmapBuffer = NULL; if( !callbackMode && !self->userInterleaved ) { @@ -1234,6 +1233,7 @@ static PaError PaAlsaStreamComponent_InitialConfigure( PaAlsaStreamComponent *se PaError result = paNoError; snd_pcm_access_t accessMode, alternateAccessMode; + snd_pcm_access_t rwAccessMode, alternateRwAccessMode; int dir = 0; snd_pcm_t *pcm = self->pcm; double sr = *sampleRate; @@ -1253,32 +1253,40 @@ static PaError PaAlsaStreamComponent_InitialConfigure( PaAlsaStreamComponent *se if( self->userInterleaved ) { accessMode = SND_PCM_ACCESS_MMAP_INTERLEAVED; + rwAccessMode = SND_PCM_ACCESS_RW_INTERLEAVED; alternateAccessMode = SND_PCM_ACCESS_MMAP_NONINTERLEAVED; + alternateRwAccessMode = SND_PCM_ACCESS_RW_NONINTERLEAVED; } else { accessMode = SND_PCM_ACCESS_MMAP_NONINTERLEAVED; + rwAccessMode = SND_PCM_ACCESS_RW_NONINTERLEAVED; alternateAccessMode = SND_PCM_ACCESS_MMAP_INTERLEAVED; + alternateRwAccessMode = SND_PCM_ACCESS_RW_INTERLEAVED; } /* If requested access mode fails, try alternate mode */ + self->canMmap = 1; if( snd_pcm_hw_params_set_access( pcm, hwParams, accessMode ) < 0 ) { - int err = 0; - if( (err = snd_pcm_hw_params_set_access( pcm, hwParams, alternateAccessMode )) < 0) + if( snd_pcm_hw_params_set_access( pcm, hwParams, rwAccessMode ) >= 0 ) + self->canMmap = 0; + else { - result = paUnanticipatedHostError; - if( -EINVAL == err ) - { - PaUtil_SetLastHostErrorInfo( paALSA, err, "PA ALSA requires that a device supports mmap access" ); - } - else + if( snd_pcm_hw_params_set_access( pcm, hwParams, alternateAccessMode ) < 0 ) { - PaUtil_SetLastHostErrorInfo( paALSA, err, snd_strerror( err ) ); + int err = 0; + if( (err = snd_pcm_hw_params_set_access( pcm, hwParams, alternateRwAccessMode )) >= 0) + self->canMmap = 0; + else + { + result = paUnanticipatedHostError; + PaUtil_SetLastHostErrorInfo( paALSA, err, snd_strerror( err ) ); + goto error; + } } - goto error; + /* Flip mode */ + self->hostInterleaved = !self->userInterleaved; } - /* Flip mode */ - self->hostInterleaved = !self->userInterleaved; } ENSURE_( snd_pcm_hw_params_set_format( pcm, hwParams, self->nativeFormat ), paUnanticipatedHostError ); @@ -1356,7 +1364,10 @@ static PaError PaAlsaStreamComponent_FinishConfigure( PaAlsaStreamComponent *sel ENSURE_( snd_pcm_sw_params_set_avail_min( self->pcm, swParams, self->framesPerBuffer ), paUnanticipatedHostError ); ENSURE_( snd_pcm_sw_params_set_xfer_align( self->pcm, swParams, 1 ), paUnanticipatedHostError ); - ENSURE_( snd_pcm_sw_params_set_tstamp_mode( self->pcm, swParams, SND_PCM_TSTAMP_MMAP ), paUnanticipatedHostError ); +#ifndef SND_PCM_TSTAMP_ENABLE /* old versions of ALSA called this something different */ +#define SND_PCM_TSTAMP_ENABLE SND_PCM_TSTAMP_MMAP +#endif + ENSURE_( snd_pcm_sw_params_set_tstamp_mode( self->pcm, swParams, SND_PCM_TSTAMP_ENABLE ), paUnanticipatedHostError ); /* Set the parameters! */ ENSURE_( snd_pcm_sw_params( self->pcm, swParams ), paUnanticipatedHostError ); @@ -1584,6 +1595,10 @@ static PaError PaAlsaStreamComponent_DetermineFramesPerBuffer( PaAlsaStreamCompo } } + /* non-mmap mode needs a reasonably-sized buffer or it'll stutter */ + if( !self->canMmap && framesPerHostBuffer < 2048 ) + framesPerHostBuffer = 2048; + assert( framesPerHostBuffer > 0 ); { snd_pcm_uframes_t min = 0, max = 0; @@ -1725,12 +1740,15 @@ static PaError PaAlsaStream_DetermineFramesPerBuffer( PaAlsaStream* self, double while( optimalPeriodSize >= periodSize ) { - if( snd_pcm_hw_params_test_period_size( self->capture.pcm, hwParamsCapture, optimalPeriodSize, 0 ) < 0 ) - continue; - if( snd_pcm_hw_params_test_period_size( self->playback.pcm, hwParamsPlayback, optimalPeriodSize, 0 ) >= 0 ) + if( snd_pcm_hw_params_test_period_size( self->capture.pcm, hwParamsCapture, optimalPeriodSize, 0 ) + >= 0 && snd_pcm_hw_params_test_period_size( self->playback.pcm, hwParamsPlayback, + optimalPeriodSize, 0 ) >= 0 ) + { break; + } optimalPeriodSize /= 2; } + if( optimalPeriodSize > periodSize ) periodSize = optimalPeriodSize; @@ -1823,12 +1841,13 @@ static PaError PaAlsaStream_DetermineFramesPerBuffer( PaAlsaStream* self, double PA_UNLESS( framesPerHostBuffer != 0, paInternalError ); self->maxFramesPerHostBuffer = framesPerHostBuffer; - if( !accurate ) + if( !self->playback.canMmap || !accurate ) { /* Don't know the exact size per host buffer */ *hostBufferSizeMode = paUtilBoundedHostBufferSize; /* Raise upper bound */ - ++self->maxFramesPerHostBuffer; + if( !accurate ) + ++self->maxFramesPerHostBuffer; } error: @@ -1987,11 +2006,11 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, /* Ok, buffer processor is initialized, now we can deduce it's latency */ if( numInputChannels > 0 ) - stream->streamRepresentation.streamInfo.inputLatency = inputLatency + PaUtil_GetBufferProcessorInputLatency( - &stream->bufferProcessor ); + stream->streamRepresentation.streamInfo.inputLatency = inputLatency + (PaTime)( + PaUtil_GetBufferProcessorInputLatency( &stream->bufferProcessor ) / sampleRate); if( numOutputChannels > 0 ) - stream->streamRepresentation.streamInfo.outputLatency = outputLatency + PaUtil_GetBufferProcessorOutputLatency( - &stream->bufferProcessor ); + stream->streamRepresentation.streamInfo.outputLatency = outputLatency + (PaTime)( + PaUtil_GetBufferProcessorOutputLatency( &stream->bufferProcessor ) / sampleRate); *s = (PaStream*)stream; @@ -2051,9 +2070,11 @@ static PaError AlsaStart( PaAlsaStream *stream, int priming ) { /* Buffer isn't primed, so prepare and silence */ ENSURE_( snd_pcm_prepare( stream->playback.pcm ), paUnanticipatedHostError ); - SilenceBuffer( stream ); + if( stream->playback.canMmap ) + SilenceBuffer( stream ); } - ENSURE_( snd_pcm_start( stream->playback.pcm ), paUnanticipatedHostError ); + if( stream->playback.canMmap ) + ENSURE_( snd_pcm_start( stream->playback.pcm ), paUnanticipatedHostError ); } else ENSURE_( snd_pcm_prepare( stream->playback.pcm ), paUnanticipatedHostError ); @@ -2151,7 +2172,9 @@ error: static PaError AlsaStop( PaAlsaStream *stream, int abort ) { PaError result = paNoError; - /* XXX: Seems that draining the dmix device may trigger a race condition in ALSA */ + /* XXX: snd_pcm_drain tends to lock up, avoid it until we find out more */ + abort = 1; + /* if( stream->capture.pcm && !strcmp( Pa_GetDeviceInfo( stream->capture.device )->name, "dmix" ) ) { @@ -2162,6 +2185,7 @@ static PaError AlsaStop( PaAlsaStream *stream, int abort ) { abort = 1; } + */ if( abort ) { @@ -2379,6 +2403,7 @@ static PaError PaAlsaStream_HandleXrun( PaAlsaStream *self ) snd_pcm_status_t *st; PaTime now = PaUtil_GetTime(); snd_timestamp_t t; + int errplayback = 0, errcapture = 0; snd_pcm_status_alloca( &st ); @@ -2389,6 +2414,7 @@ static PaError PaAlsaStream_HandleXrun( PaAlsaStream *self ) { snd_pcm_status_get_trigger_tstamp( st, &t ); self->underrun = now * 1000 - ((PaTime) t.tv_sec * 1000 + (PaTime) t.tv_usec / 1000); + errplayback = snd_pcm_recover( self->playback.pcm, -EPIPE, 0 ); } } if( self->capture.pcm ) @@ -2398,10 +2424,12 @@ static PaError PaAlsaStream_HandleXrun( PaAlsaStream *self ) { snd_pcm_status_get_trigger_tstamp( st, &t ); self->overrun = now * 1000 - ((PaTime) t.tv_sec * 1000 + (PaTime) t.tv_usec / 1000); + errcapture = snd_pcm_recover( self->capture.pcm, -EPIPE, 0 ); } } - PA_ENSURE( AlsaRestart( self ) ); + if( errplayback || errcapture ) + PA_ENSURE( AlsaRestart( self ) ); end: return result; @@ -2552,7 +2580,7 @@ static void CalculateTimeInfo( PaAlsaStream *stream, PaStreamCallbackTimeInfo *t static PaError PaAlsaStreamComponent_EndProcessing( PaAlsaStreamComponent *self, unsigned long numFrames, int *xrun ) { PaError result = paNoError; - int res; + int res = 0; /* @concern FullDuplex It is possible that only one direction is marked ready after polling, and processed * afterwards @@ -2560,7 +2588,34 @@ static PaError PaAlsaStreamComponent_EndProcessing( PaAlsaStreamComponent *self, if( !self->ready ) goto end; - res = snd_pcm_mmap_commit( self->pcm, self->offset, numFrames ); + if( !self->canMmap && StreamDirection_Out == self->streamDir ) + { + /* Play sound */ + if( self->hostInterleaved ) + res = snd_pcm_writei( self->pcm, self->nonMmapBuffer, numFrames ); + else + { + void *bufs[self->numHostChannels]; + int bufsize = snd_pcm_format_size( self->nativeFormat, self->framesPerBuffer + 1 ); + unsigned char *buffer = self->nonMmapBuffer; + int i; + for( i = 0; i < self->numHostChannels; ++i ) + { + bufs[i] = buffer; + buffer += bufsize; + } + res = snd_pcm_writen( self->pcm, bufs, numFrames ); + } + } + + if( self->canMmap ) + res = snd_pcm_mmap_commit( self->pcm, self->offset, numFrames ); + else + { + free( self->nonMmapBuffer ); + self->nonMmapBuffer = NULL; + } + if( res == -EPIPE || res == -ESTRPIPE ) { *xrun = 1; @@ -2600,7 +2655,7 @@ static PaError PaAlsaStreamComponent_DoChannelAdaption( PaAlsaStreamComponent *s if( self->hostInterleaved ) { int swidth = snd_pcm_format_size( self->nativeFormat, 1 ); - unsigned char *buffer = ExtractAddress( self->channelAreas, self->offset ); + unsigned char *buffer = self->canMmap ? ExtractAddress( self->channelAreas, self->offset ) : self->nonMmapBuffer; /* Start after the last user channel */ p = buffer + self->numUserChannels * swidth; @@ -2980,13 +3035,23 @@ static PaError PaAlsaStreamComponent_RegisterChannels( PaAlsaStreamComponent* se goto end; } - ENSURE_( snd_pcm_mmap_begin( self->pcm, &areas, &self->offset, numFrames ), paUnanticipatedHostError ); + if( self->canMmap ) + { + ENSURE_( snd_pcm_mmap_begin( self->pcm, &areas, &self->offset, numFrames ), paUnanticipatedHostError ); + /* @concern ChannelAdaption Buffer address is recorded so we can do some channel adaption later */ + self->channelAreas = (snd_pcm_channel_area_t *)areas; + } + else + { + free( self->nonMmapBuffer ); + self->nonMmapBuffer = calloc( self->numHostChannels, snd_pcm_format_size( self->nativeFormat, self->framesPerBuffer + 1 ) ); + } if( self->hostInterleaved ) { int swidth = snd_pcm_format_size( self->nativeFormat, 1 ); - p = buffer = ExtractAddress( areas, self->offset ); + p = buffer = self->canMmap ? ExtractAddress( areas, self->offset ) : self->nonMmapBuffer; for( i = 0; i < self->numUserChannels; ++i ) { /* We're setting the channels up to userChannels, but the stride will be hostChannels samples */ @@ -2996,16 +3061,52 @@ static PaError PaAlsaStreamComponent_RegisterChannels( PaAlsaStreamComponent* se } else { - for( i = 0; i < self->numUserChannels; ++i ) + if( self->canMmap ) + for( i = 0; i < self->numUserChannels; ++i ) + { + area = areas + i; + buffer = ExtractAddress( area, self->offset ); + setChannel( bp, i, buffer, 1 ); + } + else { - area = areas + i; - buffer = ExtractAddress( area, self->offset ); - setChannel( bp, i, buffer, 1 ); + int bufsize = snd_pcm_format_size( self->nativeFormat, self->framesPerBuffer + 1 ); + buffer = self->nonMmapBuffer; + for( i = 0; i < self->numUserChannels; ++i ) + { + setChannel( bp, i, buffer, 1 ); + buffer += bufsize; + } } } - /* @concern ChannelAdaption Buffer address is recorded so we can do some channel adaption later */ - self->channelAreas = (snd_pcm_channel_area_t *)areas; + if( !self->canMmap && StreamDirection_In == self->streamDir ) + { + /* Read sound */ + int res; + if( self->hostInterleaved ) + res = snd_pcm_readi( self->pcm, self->nonMmapBuffer, *numFrames ); + else + { + void *bufs[self->numHostChannels]; + int bufsize = snd_pcm_format_size( self->nativeFormat, self->framesPerBuffer + 1 ); + unsigned char *buffer = self->nonMmapBuffer; + int i; + for( i = 0; i < self->numHostChannels; ++i ) + { + bufs[i] = buffer; + buffer += bufsize; + } + res = snd_pcm_readn( self->pcm, bufs, *numFrames ); + } + if( res == -EPIPE || res == -ESTRPIPE ) + { + *xrun = 1; + *numFrames = 0; + free( self->nonMmapBuffer ); + self->nonMmapBuffer = NULL; + } + } end: error: @@ -3519,10 +3620,31 @@ void PaAlsa_EnableWatchdog( PaStream *s, int enable ) } #endif +static PaError GetAlsaStreamPointer( PaStream* s, PaAlsaStream** stream ) +{ + PaError result = paNoError; + PaUtilHostApiRepresentation* hostApi; + PaAlsaHostApiRepresentation* alsaHostApi; + + PA_ENSURE( PaUtil_ValidateStreamPointer( s ) ); + PA_ENSURE( PaUtil_GetHostApiRepresentation( &hostApi, paALSA ) ); + alsaHostApi = (PaAlsaHostApiRepresentation*)hostApi; + + PA_UNLESS( PA_STREAM_REP( s )->streamInterface == &alsaHostApi->callbackStreamInterface + || PA_STREAM_REP( s )->streamInterface == &alsaHostApi->blockingStreamInterface, + paIncompatibleStreamHostApi ); + + *stream = (PaAlsaStream*)s; +error: + return paNoError; +} + PaError PaAlsa_GetStreamInputCard(PaStream* s, int* card) { - PaAlsaStream *stream = (PaAlsaStream *) s; - snd_pcm_info_t* pcmInfo; + PaAlsaStream *stream; PaError result = paNoError; + snd_pcm_info_t* pcmInfo; + + PA_ENSURE( GetAlsaStreamPointer( s, &stream ) ); /* XXX: More descriptive error? */ PA_UNLESS( stream->capture.pcm, paDeviceUnavailable ); @@ -3536,9 +3658,11 @@ error: } PaError PaAlsa_GetStreamOutputCard(PaStream* s, int* card) { - PaAlsaStream *stream = (PaAlsaStream *) s; - snd_pcm_info_t* pcmInfo; + PaAlsaStream *stream; PaError result = paNoError; + snd_pcm_info_t* pcmInfo; + + PA_ENSURE( GetAlsaStreamPointer( s, &stream ) ); /* XXX: More descriptive error? */ PA_UNLESS( stream->playback.pcm, paDeviceUnavailable ); @@ -3550,3 +3674,9 @@ PaError PaAlsa_GetStreamOutputCard(PaStream* s, int* card) { error: return result; } + +PaError PaAlsa_SetRetriesBusy( int retries ) +{ + busyRetries_ = retries; + return paNoError; +} diff --git a/pd/portaudio/src/hostapi/asio/pa_asio.cpp b/pd/portaudio/src/hostapi/asio/pa_asio.cpp index 4b3fb68e..84d1c511 100644 --- a/pd/portaudio/src/hostapi/asio/pa_asio.cpp +++ b/pd/portaudio/src/hostapi/asio/pa_asio.cpp @@ -1,10 +1,12 @@ /* - * $Id: pa_asio.cpp 1230 2007-06-15 16:16:33Z rossb $ + * $Id: pa_asio.cpp 1416 2009-06-16 16:12:41Z rossb $ * Portable Audio I/O Library for ASIO Drivers * * Author: Stephane Letz * Based on the Open Source API proposed by Ross Bencina * Copyright (c) 2000-2002 Stephane Letz, Phil Burk, Ross Bencina + * Blocking i/o implementation by Sven Fischer, Institute of Hearing + * Technology and Audiology (www.hoertechnik-audiologie.de) * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files @@ -71,18 +73,18 @@ */ /** @file - @ingroup hostapi_src + @ingroup hostapi_src Note that specific support for paInputUnderflow, paOutputOverflow and paNeverDropInput is not necessary or possible with this driver due to the synchronous full duplex double-buffered architecture of ASIO. - @todo check that CoInitialize()/CoUninitialize() are always correctly - paired, even in error cases. - @todo implement host api specific extension to set i/o buffer sizes in frames - @todo implement ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable + @todo review ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable + + @todo review Blocking i/o latency computations in OpenStream(), changing ring + buffer to a non-power-of-two structure could reduce blocking i/o latency. @todo implement IsFormatSupported @@ -120,6 +122,7 @@ #include #include //#include +#include #include #include @@ -133,6 +136,7 @@ #include "pa_cpuload.h" #include "pa_process.h" #include "pa_debugprint.h" +#include "pa_ringbuffer.h" /* This version of pa_asio.cpp is currently only targetted at Win32, It would require a few tweaks to work with pre-OS X Macintosh. @@ -164,16 +168,24 @@ #endif */ -/* external references */ -extern AsioDrivers* asioDrivers ; -bool loadAsioDriver(char *name); +/* external reference to ASIO SDK's asioDrivers. -/* We are trying to be compatible with CARBON but this has not been thoroughly tested. */ -/* not tested at all since new code was introduced. */ -#define CARBON_COMPATIBLE (0) + This is a bit messy because we want to explicitly manage + allocation/deallocation of this structure, but some layers of the SDK + which we currently use (eg the implementation in asio.cpp) still + use this global version. + + For now we keep it in sync with our local instance in the host + API representation structure, but later we should be able to remove + all dependence on it. +*/ +extern AsioDrivers* asioDrivers; +/* We are trying to be compatible with CARBON but this has not been thoroughly tested. */ +/* not tested at all since new V19 code was introduced. */ +#define CARBON_COMPATIBLE (0) /* prototypes for functions declared in this file */ @@ -206,6 +218,14 @@ static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long static signed long GetStreamReadAvailable( PaStream* stream ); static signed long GetStreamWriteAvailable( PaStream* stream ); +/* Blocking i/o callback function. */ +static int BlockingIoPaCallback(const void *inputBuffer , + void *outputBuffer , + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo *timeInfo , + PaStreamCallbackFlags statusFlags , + void *userData ); + /* our ASIO callback functions */ static void bufferSwitch(long index, ASIOBool processNow); @@ -270,12 +290,12 @@ static const char* PaAsio_GetAsioErrorText( ASIOError asioError ) // Atomic increment and decrement operations #if MAC - /* need to be implemented on Mac */ - inline long PaAsio_AtomicIncrement(volatile long* v) {return ++(*const_cast(v));} - inline long PaAsio_AtomicDecrement(volatile long* v) {return --(*const_cast(v));} + /* need to be implemented on Mac */ + inline long PaAsio_AtomicIncrement(volatile long* v) {return ++(*const_cast(v));} + inline long PaAsio_AtomicDecrement(volatile long* v) {return --(*const_cast(v));} #elif WINDOWS - inline long PaAsio_AtomicIncrement(volatile long* v) {return InterlockedIncrement(const_cast(v));} - inline long PaAsio_AtomicDecrement(volatile long* v) {return InterlockedDecrement(const_cast(v));} + inline long PaAsio_AtomicIncrement(volatile long* v) {return InterlockedIncrement(const_cast(v));} + inline long PaAsio_AtomicDecrement(volatile long* v) {return InterlockedDecrement(const_cast(v));} #endif @@ -300,6 +320,7 @@ typedef struct PaUtilAllocationGroup *allocations; + AsioDrivers *asioDrivers; void *systemSpecific; /* the ASIO C API only allows one ASIO driver to be open at a time, @@ -323,7 +344,7 @@ PaAsioHostApiRepresentation; Retrieve driver names from ASIO, returned in a char** allocated in . */ -static char **GetAsioDriverNames( PaUtilAllocationGroup *group, long driverCount ) +static char **GetAsioDriverNames( PaAsioHostApiRepresentation *asioHostApi, PaUtilAllocationGroup *group, long driverCount ) { char **result = 0; int i; @@ -341,7 +362,7 @@ static char **GetAsioDriverNames( PaUtilAllocationGroup *group, long driverCount for( i=0; igetDriverNames( result, driverCount ); + asioHostApi->asioDrivers->getDriverNames( result, driverCount ); error: return result; @@ -917,7 +938,7 @@ PaAsioDeviceInfo; PaError PaAsio_GetAvailableLatencyValues( PaDeviceIndex device, - long *minLatency, long *maxLatency, long *preferredLatency, long *granularity ) + long *minLatency, long *maxLatency, long *preferredLatency, long *granularity ) { PaError result; PaUtilHostApiRepresentation *hostApi; @@ -944,23 +965,45 @@ PaError PaAsio_GetAvailableLatencyValues( PaDeviceIndex device, return result; } - +/* Unload whatever we loaded in LoadAsioDriver(). + Also balance the call to CoInitialize(0). +*/ +static void UnloadAsioDriver( void ) +{ + ASIOExit(); + CoUninitialize(); +} /* load the asio driver named by and return statistics about the driver in info. If no error occurred, the driver will remain open - and must be closed by the called by calling ASIOExit() - if an error - is returned the driver will already be closed. + and must be closed by the called by calling UnloadAsioDriver() - if an error + is returned the driver will already be unloaded. */ -static PaError LoadAsioDriver( const char *driverName, +static PaError LoadAsioDriver( PaAsioHostApiRepresentation *asioHostApi, const char *driverName, PaAsioDriverInfo *driverInfo, void *systemSpecific ) { PaError result = paNoError; ASIOError asioError; int asioIsInitialized = 0; - if( !loadAsioDriver( const_cast(driverName) ) ) + /* + ASIO uses CoCreateInstance() to load a driver. That requires that + CoInitialize(0) be called for every thread that loads a driver. + It is OK to call CoInitialize(0) multiple times form one thread as long + as it is balanced by a call to CoUninitialize(). See UnloadAsioDriver(). + + The V18 version called CoInitialize() starting on 2/19/02. + That was removed from PA V19 for unknown reasons. + Phil Burk added it back on 6/27/08 so that JSyn would work. + */ + CoInitialize( 0 ); + + if( !asioHostApi->asioDrivers->loadDriver( const_cast(driverName) ) ) { + /* If this returns an error then it might be because CoInitialize(0) was removed. + It should be called right before this. + */ result = paUnanticipatedHostError; PA_ASIO_SET_LAST_HOST_ERROR( 0, "Failed to load ASIO driver" ); goto error; @@ -1006,8 +1049,10 @@ static PaError LoadAsioDriver( const char *driverName, error: if( asioIsInitialized ) - ASIOExit(); - + { + ASIOExit(); + } + CoUninitialize(); return result; } @@ -1039,6 +1084,8 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex goto error; } + asioHostApi->asioDrivers = 0; /* avoid surprises in our error handler below */ + asioHostApi->allocations = PaUtil_CreateAllocationGroup(); if( !asioHostApi->allocations ) { @@ -1046,6 +1093,25 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex goto error; } + /* Allocate the AsioDrivers() driver list (class from ASIO SDK) */ + try + { + asioHostApi->asioDrivers = new AsioDrivers(); /* calls CoInitialize(0) */ + } + catch (std::bad_alloc) + { + asioHostApi->asioDrivers = 0; + } + /* some implementations of new (ie MSVC, see http://support.microsoft.com/?kbid=167733) + don't throw std::bad_alloc, so we also explicitly test for a null return. */ + if( asioHostApi->asioDrivers == 0 ) + { + result = paInsufficientMemory; + goto error; + } + + asioDrivers = asioHostApi->asioDrivers; /* keep SDK global in sync until we stop depending on it */ + asioHostApi->systemSpecific = 0; asioHostApi->openAsioDeviceIndex = paNoDevice; @@ -1059,23 +1125,19 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex #ifdef WINDOWS /* use desktop window as system specific ptr */ asioHostApi->systemSpecific = GetDesktopWindow(); - CoInitialize(NULL); #endif - /* MUST BE CHECKED : to force fragments loading on Mac */ - loadAsioDriver( "dummy" ); - /* driverCount is the number of installed drivers - not necessarily the number of installed physical devices. */ #if MAC - driverCount = asioDrivers->getNumFragments(); + driverCount = asioHostApi->asioDrivers->getNumFragments(); #elif WINDOWS - driverCount = asioDrivers->asioGetNumDev(); + driverCount = asioHostApi->asioDrivers->asioGetNumDev(); #endif if( driverCount > 0 ) { - names = GetAsioDriverNames( asioHostApi->allocations, driverCount ); + names = GetAsioDriverNames( asioHostApi, asioHostApi->allocations, driverCount ); if( !names ) { result = paInsufficientMemory; @@ -1102,7 +1164,7 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex goto error; } - IsDebuggerPresent_ = GetProcAddress( LoadLibrary( "Kernel32.dll" ), "IsDebuggerPresent" ); + IsDebuggerPresent_ = GetProcAddress( LoadLibrary( "Kernel32.dll" ), "IsDebuggerPresent" ); for( i=0; i < driverCount; ++i ) { @@ -1120,7 +1182,6 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex || strcmp (names[i],"ASIO Multimedia Driver") == 0 || strncmp(names[i],"Premiere",8) == 0 //"Premiere Elements Windows Sound 1.0" || strncmp(names[i],"Adobe",5) == 0 //"Adobe Default Windows Sound 1.5" - || strncmp(names[i],"ReaRoute ASIO",13) == 0 //Reaper www.reaper.fm <- fix your stuff man. ) { PA_DEBUG(("BLACKLISTED!!!\n")); @@ -1141,7 +1202,7 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex /* Attempt to load the asio driver... */ - if( LoadAsioDriver( names[i], &paAsioDriverInfo, asioHostApi->systemSpecific ) == paNoError ) + if( LoadAsioDriver( asioHostApi, names[i], &paAsioDriverInfo, asioHostApi->systemSpecific ) == paNoError ) { PaAsioDeviceInfo *asioDeviceInfo = &deviceInfoArray[ (*hostApi)->info.deviceCount ]; PaDeviceInfo *deviceInfo = &asioDeviceInfo->commonDeviceInfo; @@ -1233,7 +1294,7 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex if( !asioDeviceInfo->asioChannelInfos ) { result = paInsufficientMemory; - goto error; + goto error_unload; } int a; @@ -1246,7 +1307,7 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex { result = paUnanticipatedHostError; PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); - goto error; + goto error_unload; } } @@ -1259,13 +1320,13 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex { result = paUnanticipatedHostError; PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); - goto error; + goto error_unload; } } /* unload the driver */ - ASIOExit(); + UnloadAsioDriver(); (*hostApi)->deviceInfos[ (*hostApi)->info.deviceCount ] = deviceInfo; ++(*hostApi)->info.deviceCount; @@ -1302,6 +1363,9 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex return result; +error_unload: + UnloadAsioDriver(); + error: if( asioHostApi ) { @@ -1311,6 +1375,9 @@ error: PaUtil_DestroyAllocationGroup( asioHostApi->allocations ); } + delete asioHostApi->asioDrivers; + asioDrivers = 0; /* keep SDK global in sync until we stop depending on it */ + PaUtil_FreeMemory( asioHostApi ); } return result; @@ -1323,7 +1390,7 @@ static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) /* IMPLEMENT ME: - - clean up any resources not handled by the allocation group + - clean up any resources not handled by the allocation group (need to review if there are any) */ if( asioHostApi->allocations ) @@ -1332,6 +1399,9 @@ static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) PaUtil_DestroyAllocationGroup( asioHostApi->allocations ); } + delete asioHostApi->asioDrivers; /* calls CoUninitialize() */ + asioDrivers = 0; /* keep SDK global in sync until we stop depending on it */ + PaUtil_FreeMemory( asioHostApi ); } @@ -1418,7 +1488,7 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, /* if an ASIO device is open we can only get format information for the currently open device */ if( asioHostApi->openAsioDeviceIndex != paNoDevice - && asioHostApi->openAsioDeviceIndex != asioDeviceIndex ) + && asioHostApi->openAsioDeviceIndex != asioDeviceIndex ) { return paDeviceUnavailable; } @@ -1430,7 +1500,7 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, /* open the device if it's not already open */ if( asioHostApi->openAsioDeviceIndex == paNoDevice ) { - result = LoadAsioDriver( asioHostApi->inheritedHostApiRep.deviceInfos[ asioDeviceIndex ]->name, + result = LoadAsioDriver( asioHostApi, asioHostApi->inheritedHostApiRep.deviceInfos[ asioDeviceIndex ]->name, driverInfo, asioHostApi->systemSpecific ); if( result != paNoError ) return result; @@ -1468,7 +1538,7 @@ done: /* close the device if it wasn't already open */ if( asioHostApi->openAsioDeviceIndex == paNoDevice ) { - ASIOExit(); /* not sure if we should check for errors here */ + UnloadAsioDriver(); /* not sure if we should check for errors here */ } if( result == paNoError ) @@ -1479,6 +1549,40 @@ done: +/** A data structure specifically for storing blocking i/o related data. */ +typedef struct PaAsioStreamBlockingState +{ + int stopFlag; /**< Flag indicating that block processing is to be stopped. */ + + unsigned long writeBuffersRequested; /**< The number of available output buffers, requested by the #WriteStream() function. */ + unsigned long readFramesRequested; /**< The number of available input frames, requested by the #ReadStream() function. */ + + int writeBuffersRequestedFlag; /**< Flag to indicate that #WriteStream() has requested more output buffers to be available. */ + int readFramesRequestedFlag; /**< Flag to indicate that #ReadStream() requires more input frames to be available. */ + + HANDLE writeBuffersReadyEvent; /**< Event to signal that requested output buffers are available. */ + HANDLE readFramesReadyEvent; /**< Event to signal that requested input frames are available. */ + + void *writeRingBufferData; /**< The actual ring buffer memory, used by the output ring buffer. */ + void *readRingBufferData; /**< The actual ring buffer memory, used by the input ring buffer. */ + + PaUtilRingBuffer writeRingBuffer; /**< Frame-aligned blocking i/o ring buffer to store output data (interleaved user format). */ + PaUtilRingBuffer readRingBuffer; /**< Frame-aligned blocking i/o ring buffer to store input data (interleaved user format). */ + + long writeRingBufferInitialFrames; /**< The initial number of silent frames within the output ring buffer. */ + + const void **writeStreamBuffer; /**< Temp buffer, used by #WriteStream() for handling non-interleaved data. */ + void **readStreamBuffer; /**< Temp buffer, used by #ReadStream() for handling non-interleaved data. */ + + PaUtilBufferProcessor bufferProcessor; /**< Buffer processor, used to handle the blocking i/o ring buffers. */ + + int outputUnderflowFlag; /**< Flag to signal an output underflow from within the callback function. */ + int inputOverflowFlag; /**< Flag to signal an input overflow from within the callback function. */ +} +PaAsioStreamBlockingState; + + + /* PaAsioStream - a stream data structure specifically for this implementation */ typedef struct PaAsioStream @@ -1515,6 +1619,7 @@ typedef struct PaAsioStream HANDLE completedBuffersPlayedEvent; bool streamFinishedCallbackCalled; + int isStopped; volatile int isActive; volatile bool zeroOutput; /* all future calls to the callback will output silence */ @@ -1522,6 +1627,8 @@ typedef struct PaAsioStream volatile long reenterError; PaStreamCallbackFlags callbackFlags; + + PaAsioStreamBlockingState *blockingState; /**< Blocking i/o data struct, or NULL when using callback interface. */ } PaAsioStream; @@ -1621,15 +1728,15 @@ static PaError ValidateAsioSpecificStreamInfo( int deviceChannelCount, int **channelSelectors ) { - if( streamInfo ) - { - if( streamInfo->size != sizeof( PaAsioStreamInfo ) - || streamInfo->version != 1 ) - { - return paIncompatibleHostApiSpecificStreamInfo; - } + if( streamInfo ) + { + if( streamInfo->size != sizeof( PaAsioStreamInfo ) + || streamInfo->version != 1 ) + { + return paIncompatibleHostApiSpecificStreamInfo; + } - if( streamInfo->flags & paAsioUseChannelSelectors ) + if( streamInfo->flags & paAsioUseChannelSelectors ) *channelSelectors = streamInfo->channelSelectors; if( !(*channelSelectors) ) @@ -1641,9 +1748,101 @@ static PaError ValidateAsioSpecificStreamInfo( return paInvalidChannelCount; } } - } + } + + return paNoError; +} + + +static bool IsUsingExternalClockSource() +{ + bool result = false; + ASIOError asioError; + ASIOClockSource clocks[32]; + long numSources=32; - return paNoError; + /* davidv: listing ASIO Clock sources. there is an ongoing investigation by + me about whether or not to call ASIOSetSampleRate if an external Clock is + used. A few drivers expected different things here */ + + asioError = ASIOGetClockSources(clocks, &numSources); + if( asioError != ASE_OK ){ + PA_DEBUG(("ERROR: ASIOGetClockSources: %s\n", PaAsio_GetAsioErrorText(asioError) )); + }else{ + PA_DEBUG(("INFO ASIOGetClockSources listing %d clocks\n", numSources )); + for (int i=0;iopenAsioDeviceIndex != paNoDevice ){ + if( asioHostApi->openAsioDeviceIndex != paNoDevice ) + { PA_DEBUG(("OpenStream paDeviceUnavailable\n")); return paDeviceUnavailable; } + assert( theAsioStream == 0 ); + if( inputParameters && outputParameters ) { /* full duplex ASIO stream must use the same device for input and output */ - if( inputParameters->device != outputParameters->device ){ + if( inputParameters->device != outputParameters->device ) + { PA_DEBUG(("OpenStream paBadIODeviceCombination\n")); return paBadIODeviceCombination; - } + } } if( inputParameters ) @@ -1762,12 +1976,12 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, /* NOTE: we load the driver and use its current settings rather than the ones in our device info structure which may be stale */ - result = LoadAsioDriver( asioHostApi->inheritedHostApiRep.deviceInfos[ asioDeviceIndex ]->name, + result = LoadAsioDriver( asioHostApi, asioHostApi->inheritedHostApiRep.deviceInfos[ asioDeviceIndex ]->name, driverInfo, asioHostApi->systemSpecific ); if( result == paNoError ) asioIsInitialized = 1; else{ - PA_DEBUG(("OpenStream ERROR1\n")); + PA_DEBUG(("OpenStream ERROR1 - LoadAsioDriver returned %d\n", result)); goto error; } @@ -1793,76 +2007,9 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, } } - - /* davidv: listing ASIO Clock sources, there is an ongoing investigation by - me about whether or not call ASIOSetSampleRate if an external Clock is - used. A few drivers expected different things here */ - { - ASIOClockSource clocks[32]; - long numSources=32; - asioError = ASIOGetClockSources(clocks, &numSources); - if( asioError != ASE_OK ){ - PA_DEBUG(("ERROR: ASIOGetClockSources: %s\n", PaAsio_GetAsioErrorText(asioError) )); - }else{ - PA_DEBUG(("INFO ASIOGetClockSources listing %d clocks\n", numSources )); - for (int i=0;iblockingState = NULL; /* Blocking i/o not initialized, yet. */ + stream->completedBuffersPlayedEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); if( stream->completedBuffersPlayedEvent == NULL ) @@ -1900,15 +2049,19 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, stream->asioChannelInfos = 0; /* for deallocation in error */ stream->bufferPtrs = 0; /* for deallocation in error */ - if( streamCallback ) + /* Using blocking i/o interface... */ + if( usingBlockingIo ) { + /* Blocking i/o is implemented by running callback mode, using a special blocking i/o callback. */ + streamCallback = BlockingIoPaCallback; /* Setup PA to use the ASIO blocking i/o callback. */ + userData = &theAsioStream; /* The callback user data will be the PA ASIO stream. */ PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, - &asioHostApi->callbackStreamInterface, streamCallback, userData ); + &asioHostApi->blockingStreamInterface, streamCallback, userData ); } - else + else /* Using callback interface... */ { PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, - &asioHostApi->blockingStreamInterface, streamCallback, userData ); + &asioHostApi->callbackStreamInterface, streamCallback, userData ); } @@ -1959,13 +2112,24 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, } - framesPerHostBuffer = SelectHostBufferSize( - (( suggestedInputLatencyFrames > suggestedOutputLatencyFrames ) - ? suggestedInputLatencyFrames : suggestedOutputLatencyFrames), - driverInfo ); + /* Using blocking i/o interface... */ + if( usingBlockingIo ) + { +/** @todo REVIEW selection of host buffer size for blocking i/o */ + /* Use default host latency for blocking i/o. */ + framesPerHostBuffer = SelectHostBufferSize( 0, driverInfo ); + + } + else /* Using callback interface... */ + { + framesPerHostBuffer = SelectHostBufferSize( + (( suggestedInputLatencyFrames > suggestedOutputLatencyFrames ) + ? suggestedInputLatencyFrames : suggestedOutputLatencyFrames), + driverInfo ); + } - PA_DEBUG(("PaAsioOpenStream: framesPerHostBuffer :%d\n", framesPerHostBuffer)); + PA_DEBUG(("PaAsioOpenStream: framesPerHostBuffer :%d\n", framesPerHostBuffer)); asioError = ASIOCreateBuffers( stream->asioBufferInfos, inputChannelCount+outputChannelCount, @@ -2103,43 +2267,302 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, stream->outputBufferConverter = 0; } - result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, - inputChannelCount, inputSampleFormat, hostInputSampleFormat, - outputChannelCount, outputSampleFormat, hostOutputSampleFormat, - sampleRate, streamFlags, framesPerBuffer, - framesPerHostBuffer, paUtilFixedHostBufferSize, - streamCallback, userData ); - if( result != paNoError ){ - PA_DEBUG(("OpenStream ERROR13\n")); - goto error; - } - ASIOGetLatencies( &stream->inputLatency, &stream->outputLatency ); - stream->streamRepresentation.streamInfo.inputLatency = - (double)( PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor) - + stream->inputLatency) / sampleRate; // seconds - stream->streamRepresentation.streamInfo.outputLatency = - (double)( PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor) - + stream->outputLatency) / sampleRate; // seconds - stream->streamRepresentation.streamInfo.sampleRate = sampleRate; - - // the code below prints the ASIO latency which doesn't include the - // buffer processor latency. it reports the added latency separately - PA_DEBUG(("PaAsio : ASIO InputLatency = %ld (%ld ms), added buffProc:%ld (%ld ms)\n", - stream->inputLatency, - (long)((stream->inputLatency*1000)/ sampleRate), - PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor), - (long)((PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor)*1000)/ sampleRate) - )); - - PA_DEBUG(("PaAsio : ASIO OuputLatency = %ld (%ld ms), added buffProc:%ld (%ld ms)\n", - stream->outputLatency, - (long)((stream->outputLatency*1000)/ sampleRate), - PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor), - (long)((PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor)*1000)/ sampleRate) - )); + + /* Using blocking i/o interface... */ + if( usingBlockingIo ) + { + /* Allocate the blocking i/o input ring buffer memory. */ + stream->blockingState = (PaAsioStreamBlockingState*)PaUtil_AllocateMemory( sizeof(PaAsioStreamBlockingState) ); + if( !stream->blockingState ) + { + result = paInsufficientMemory; + PA_DEBUG(("ERROR! Blocking i/o interface struct allocation failed in OpenStream()\n")); + goto error; + } + + /* Initialize blocking i/o interface struct. */ + stream->blockingState->readFramesReadyEvent = NULL; /* Uninitialized, yet. */ + stream->blockingState->writeBuffersReadyEvent = NULL; /* Uninitialized, yet. */ + stream->blockingState->readRingBufferData = NULL; /* Uninitialized, yet. */ + stream->blockingState->writeRingBufferData = NULL; /* Uninitialized, yet. */ + stream->blockingState->readStreamBuffer = NULL; /* Uninitialized, yet. */ + stream->blockingState->writeStreamBuffer = NULL; /* Uninitialized, yet. */ + stream->blockingState->stopFlag = TRUE; /* Not started, yet. */ + + + /* If the user buffer is unspecified */ + if( framesPerBuffer == paFramesPerBufferUnspecified ) + { + /* Make the user buffer the same size as the host buffer. */ + framesPerBuffer = framesPerHostBuffer; + } + + + /* Initialize callback buffer processor. */ + result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor , + inputChannelCount , + inputSampleFormat & ~paNonInterleaved , /* Ring buffer. */ + hostInputSampleFormat , /* Host format. */ + outputChannelCount , + outputSampleFormat & ~paNonInterleaved, /* Ring buffer. */ + hostOutputSampleFormat , /* Host format. */ + sampleRate , + streamFlags , + framesPerBuffer , /* Frames per ring buffer block. */ + framesPerHostBuffer , /* Frames per asio buffer. */ + paUtilFixedHostBufferSize , + streamCallback , + userData ); + if( result != paNoError ){ + PA_DEBUG(("OpenStream ERROR13\n")); + goto error; + } + callbackBufferProcessorInited = TRUE; + + /* Initialize the blocking i/o buffer processor. */ + result = PaUtil_InitializeBufferProcessor(&stream->blockingState->bufferProcessor, + inputChannelCount , + inputSampleFormat , /* User format. */ + inputSampleFormat & ~paNonInterleaved , /* Ring buffer. */ + outputChannelCount , + outputSampleFormat , /* User format. */ + outputSampleFormat & ~paNonInterleaved, /* Ring buffer. */ + sampleRate , + paClipOff | paDitherOff , /* Don't use dither nor clipping. */ + framesPerBuffer , /* Frames per user buffer. */ + framesPerBuffer , /* Frames per ring buffer block. */ + paUtilBoundedHostBufferSize , + NULL, NULL );/* No callback! */ + if( result != paNoError ){ + PA_DEBUG(("ERROR! Blocking i/o buffer processor initialization failed in OpenStream()\n")); + goto error; + } + blockingBufferProcessorInited = TRUE; + + /* If input is requested. */ + if( inputChannelCount ) + { + /* Create the callback sync-event. */ + stream->blockingState->readFramesReadyEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + if( stream->blockingState->readFramesReadyEvent == NULL ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() ); + PA_DEBUG(("ERROR! Blocking i/o \"read frames ready\" event creation failed in OpenStream()\n")); + goto error; + } + blockingReadFramesReadyEventInitialized = 1; + + + /* Create pointer buffer to access non-interleaved data in ReadStream() */ + stream->blockingState->readStreamBuffer = (void**)PaUtil_AllocateMemory( sizeof(void*) * inputChannelCount ); + if( !stream->blockingState->readStreamBuffer ) + { + result = paInsufficientMemory; + PA_DEBUG(("ERROR! Blocking i/o read stream buffer allocation failed in OpenStream()\n")); + goto error; + } + + /* The ring buffer should store as many data blocks as needed + to achieve the requested latency. Whereas it must be large + enough to store at least two complete data blocks. + + 1) Determine the amount of latency to be added to the + prefered ASIO latency. + 2) Make sure we have at lest one additional latency frame. + 3) Divide the number of frames by the desired block size to + get the number (rounded up to pure integer) of blocks to + be stored in the buffer. + 4) Add one additional block for block processing and convert + to samples frames. + 5) Get the next larger (or equal) power-of-two buffer size. + */ + lBlockingBufferSize = suggestedInputLatencyFrames - stream->inputLatency; + lBlockingBufferSize = (lBlockingBufferSize > 0) ? lBlockingBufferSize : 1; + lBlockingBufferSize = (lBlockingBufferSize + framesPerBuffer - 1) / framesPerBuffer; + lBlockingBufferSize = (lBlockingBufferSize + 1) * framesPerBuffer; + + /* Get the next larger or equal power-of-two buffersize. */ + lBlockingBufferSizePow2 = 1; + while( lBlockingBufferSize > (lBlockingBufferSizePow2<<=1) ); + lBlockingBufferSize = lBlockingBufferSizePow2; + + /* Compute total intput latency in seconds */ + stream->streamRepresentation.streamInfo.inputLatency = + (double)( PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor ) + + PaUtil_GetBufferProcessorInputLatency(&stream->blockingState->bufferProcessor) + + (lBlockingBufferSize / framesPerBuffer - 1) * framesPerBuffer + + stream->inputLatency ) + / sampleRate; + + /* The code below prints the ASIO latency which doesn't include + the buffer processor latency nor the blocking i/o latency. It + reports the added latency separately. + */ + PA_DEBUG(("PaAsio : ASIO InputLatency = %ld (%ld ms),\n added buffProc:%ld (%ld ms),\n added blocking:%ld (%ld ms)\n", + stream->inputLatency, + (long)( stream->inputLatency * (1000.0 / sampleRate) ), + PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor), + (long)( PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor) * (1000.0 / sampleRate) ), + PaUtil_GetBufferProcessorInputLatency(&stream->blockingState->bufferProcessor) + (lBlockingBufferSize / framesPerBuffer - 1) * framesPerBuffer, + (long)( (PaUtil_GetBufferProcessorInputLatency(&stream->blockingState->bufferProcessor) + (lBlockingBufferSize / framesPerBuffer - 1) * framesPerBuffer) * (1000.0 / sampleRate) ) + )); + + /* Determine the size of ring buffer in bytes. */ + lBytesPerFrame = inputChannelCount * Pa_GetSampleSize(inputSampleFormat ); + + /* Allocate the blocking i/o input ring buffer memory. */ + stream->blockingState->readRingBufferData = (void*)PaUtil_AllocateMemory( lBlockingBufferSize * lBytesPerFrame ); + if( !stream->blockingState->readRingBufferData ) + { + result = paInsufficientMemory; + PA_DEBUG(("ERROR! Blocking i/o input ring buffer allocation failed in OpenStream()\n")); + goto error; + } + + /* Initialize the input ring buffer struct. */ + PaUtil_InitializeRingBuffer( &stream->blockingState->readRingBuffer , + lBytesPerFrame , + lBlockingBufferSize , + stream->blockingState->readRingBufferData ); + } + + /* If output is requested. */ + if( outputChannelCount ) + { + stream->blockingState->writeBuffersReadyEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + if( stream->blockingState->writeBuffersReadyEvent == NULL ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() ); + PA_DEBUG(("ERROR! Blocking i/o \"write buffers ready\" event creation failed in OpenStream()\n")); + goto error; + } + blockingWriteBuffersReadyEventInitialized = 1; + + /* Create pointer buffer to access non-interleaved data in WriteStream() */ + stream->blockingState->writeStreamBuffer = (const void**)PaUtil_AllocateMemory( sizeof(const void*) * outputChannelCount ); + if( !stream->blockingState->writeStreamBuffer ) + { + result = paInsufficientMemory; + PA_DEBUG(("ERROR! Blocking i/o write stream buffer allocation failed in OpenStream()\n")); + goto error; + } + + /* The ring buffer should store as many data blocks as needed + to achieve the requested latency. Whereas it must be large + enough to store at least two complete data blocks. + + 1) Determine the amount of latency to be added to the + prefered ASIO latency. + 2) Make sure we have at lest one additional latency frame. + 3) Divide the number of frames by the desired block size to + get the number (rounded up to pure integer) of blocks to + be stored in the buffer. + 4) Add one additional block for block processing and convert + to samples frames. + 5) Get the next larger (or equal) power-of-two buffer size. + */ + lBlockingBufferSize = suggestedOutputLatencyFrames - stream->outputLatency; + lBlockingBufferSize = (lBlockingBufferSize > 0) ? lBlockingBufferSize : 1; + lBlockingBufferSize = (lBlockingBufferSize + framesPerBuffer - 1) / framesPerBuffer; + lBlockingBufferSize = (lBlockingBufferSize + 1) * framesPerBuffer; + + /* The buffer size (without the additional block) corresponds + to the initial number of silent samples in the output ring + buffer. */ + stream->blockingState->writeRingBufferInitialFrames = lBlockingBufferSize - framesPerBuffer; + + /* Get the next larger or equal power-of-two buffersize. */ + lBlockingBufferSizePow2 = 1; + while( lBlockingBufferSize > (lBlockingBufferSizePow2<<=1) ); + lBlockingBufferSize = lBlockingBufferSizePow2; + + /* Compute total output latency in seconds */ + stream->streamRepresentation.streamInfo.outputLatency = + (double)( PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor ) + + PaUtil_GetBufferProcessorOutputLatency(&stream->blockingState->bufferProcessor) + + (lBlockingBufferSize / framesPerBuffer - 1) * framesPerBuffer + + stream->outputLatency ) + / sampleRate; + + /* The code below prints the ASIO latency which doesn't include + the buffer processor latency nor the blocking i/o latency. It + reports the added latency separately. + */ + PA_DEBUG(("PaAsio : ASIO OutputLatency = %ld (%ld ms),\n added buffProc:%ld (%ld ms),\n added blocking:%ld (%ld ms)\n", + stream->outputLatency, + (long)( stream->inputLatency * (1000.0 / sampleRate) ), + PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor), + (long)( PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor) * (1000.0 / sampleRate) ), + PaUtil_GetBufferProcessorOutputLatency(&stream->blockingState->bufferProcessor) + (lBlockingBufferSize / framesPerBuffer - 1) * framesPerBuffer, + (long)( (PaUtil_GetBufferProcessorOutputLatency(&stream->blockingState->bufferProcessor) + (lBlockingBufferSize / framesPerBuffer - 1) * framesPerBuffer) * (1000.0 / sampleRate) ) + )); + + /* Determine the size of ring buffer in bytes. */ + lBytesPerFrame = outputChannelCount * Pa_GetSampleSize(outputSampleFormat); + + /* Allocate the blocking i/o output ring buffer memory. */ + stream->blockingState->writeRingBufferData = (void*)PaUtil_AllocateMemory( lBlockingBufferSize * lBytesPerFrame ); + if( !stream->blockingState->writeRingBufferData ) + { + result = paInsufficientMemory; + PA_DEBUG(("ERROR! Blocking i/o output ring buffer allocation failed in OpenStream()\n")); + goto error; + } + + /* Initialize the output ring buffer struct. */ + PaUtil_InitializeRingBuffer( &stream->blockingState->writeRingBuffer , + lBytesPerFrame , + lBlockingBufferSize , + stream->blockingState->writeRingBufferData ); + } + + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + + + } + else /* Using callback interface... */ + { + result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, + inputChannelCount, inputSampleFormat, hostInputSampleFormat, + outputChannelCount, outputSampleFormat, hostOutputSampleFormat, + sampleRate, streamFlags, framesPerBuffer, + framesPerHostBuffer, paUtilFixedHostBufferSize, + streamCallback, userData ); + if( result != paNoError ){ + PA_DEBUG(("OpenStream ERROR13\n")); + goto error; + } + callbackBufferProcessorInited = TRUE; + + stream->streamRepresentation.streamInfo.inputLatency = + (double)( PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor) + + stream->inputLatency) / sampleRate; // seconds + stream->streamRepresentation.streamInfo.outputLatency = + (double)( PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor) + + stream->outputLatency) / sampleRate; // seconds + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + + // the code below prints the ASIO latency which doesn't include the + // buffer processor latency. it reports the added latency separately + PA_DEBUG(("PaAsio : ASIO InputLatency = %ld (%ld ms), added buffProc:%ld (%ld ms)\n", + stream->inputLatency, + (long)((stream->inputLatency*1000)/ sampleRate), + PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor), + (long)((PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor)*1000)/ sampleRate) + )); + + PA_DEBUG(("PaAsio : ASIO OuputLatency = %ld (%ld ms), added buffProc:%ld (%ld ms)\n", + stream->outputLatency, + (long)((stream->outputLatency*1000)/ sampleRate), + PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor), + (long)((PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor)*1000)/ sampleRate) + )); + } stream->asioHostApi = asioHostApi; stream->framesPerHostCallback = framesPerHostBuffer; @@ -2147,10 +2570,12 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, stream->inputChannelCount = inputChannelCount; stream->outputChannelCount = outputChannelCount; stream->postOutput = driverInfo->postOutput; + stream->isStopped = 1; stream->isActive = 0; - + asioHostApi->openAsioDeviceIndex = asioDeviceIndex; + theAsioStream = stream; *s = (PaStream*)stream; return result; @@ -2159,6 +2584,31 @@ error: PA_DEBUG(("goto errored\n")); if( stream ) { + if( stream->blockingState ) + { + if( blockingBufferProcessorInited ) + PaUtil_TerminateBufferProcessor( &stream->blockingState->bufferProcessor ); + + if( stream->blockingState->writeRingBufferData ) + PaUtil_FreeMemory( stream->blockingState->writeRingBufferData ); + if( stream->blockingState->writeStreamBuffer ) + PaUtil_FreeMemory( stream->blockingState->writeStreamBuffer ); + if( blockingWriteBuffersReadyEventInitialized ) + CloseHandle( stream->blockingState->writeBuffersReadyEvent ); + + if( stream->blockingState->readRingBufferData ) + PaUtil_FreeMemory( stream->blockingState->readRingBufferData ); + if( stream->blockingState->readStreamBuffer ) + PaUtil_FreeMemory( stream->blockingState->readStreamBuffer ); + if( blockingReadFramesReadyEventInitialized ) + CloseHandle( stream->blockingState->readFramesReadyEvent ); + + PaUtil_FreeMemory( stream->blockingState ); + } + + if( callbackBufferProcessorInited ) + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + if( completedBuffersPlayedEventInited ) CloseHandle( stream->completedBuffersPlayedEvent ); @@ -2178,8 +2628,9 @@ error: ASIODisposeBuffers(); if( asioIsInitialized ) - ASIOExit(); - + { + UnloadAsioDriver(); + } return result; } @@ -2205,13 +2656,34 @@ static PaError CloseStream( PaStream* s ) CloseHandle( stream->completedBuffersPlayedEvent ); + /* Using blocking i/o interface... */ + if( stream->blockingState ) + { + PaUtil_TerminateBufferProcessor( &stream->blockingState->bufferProcessor ); + + if( stream->inputChannelCount ) { + PaUtil_FreeMemory( stream->blockingState->readRingBufferData ); + PaUtil_FreeMemory( stream->blockingState->readStreamBuffer ); + CloseHandle( stream->blockingState->readFramesReadyEvent ); + } + if( stream->outputChannelCount ) { + PaUtil_FreeMemory( stream->blockingState->writeRingBufferData ); + PaUtil_FreeMemory( stream->blockingState->writeStreamBuffer ); + CloseHandle( stream->blockingState->writeBuffersReadyEvent ); + } + + PaUtil_FreeMemory( stream->blockingState ); + } + PaUtil_FreeMemory( stream->asioBufferInfos ); PaUtil_FreeMemory( stream->asioChannelInfos ); PaUtil_FreeMemory( stream->bufferPtrs ); PaUtil_FreeMemory( stream ); ASIODisposeBuffers(); - ASIOExit(); + UnloadAsioDriver(); + + theAsioStream = 0; return result; } @@ -2245,10 +2717,10 @@ static void bufferSwitch(long index, ASIOBool directProcess) // conversion from 64 bit ASIOSample/ASIOTimeStamp to double float #if NATIVE_INT64 - #define ASIO64toDouble(a) (a) + #define ASIO64toDouble(a) (a) #else - const double twoRaisedTo32 = 4294967296.; - #define ASIO64toDouble(a) ((a).lo + (a).hi * twoRaisedTo32) + const double twoRaisedTo32 = 4294967296.; + #define ASIO64toDouble(a) ((a).lo + (a).hi * twoRaisedTo32) #endif static ASIOTime *bufferSwitchTimeInfo( ASIOTime *timeInfo, long index, ASIOBool directProcess ) @@ -2430,7 +2902,9 @@ previousIndex = index; paTimeInfo.inputBufferAdcTime = paTimeInfo.currentTime - theAsioStream->streamRepresentation.streamInfo.inputLatency; paTimeInfo.outputBufferDacTime = paTimeInfo.currentTime + theAsioStream->streamRepresentation.streamInfo.outputLatency; */ -#if 1 + +/* Disabled! Stopping and re-starting the stream causes an input overflow / output undeflow. S.Fischer */ +#if 0 // detect underflows by checking inter-callback time > 2 buffer period static double previousTime = -1; if( previousTime > 0 ){ @@ -2634,6 +3108,7 @@ static PaError StartStream( PaStream *s ) { PaError result = paNoError; PaAsioStream *stream = (PaAsioStream*)s; + PaAsioStreamBlockingState *blockingState = stream->blockingState; ASIOError asioError; if( stream->outputChannelCount > 0 ) @@ -2658,18 +3133,72 @@ static PaError StartStream( PaStream *s ) PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() ); } - if( result == paNoError ) + + /* Using blocking i/o interface... */ + if( blockingState ) { - theAsioStream = stream; - asioError = ASIOStart(); - if( asioError == ASE_OK ) + /* Reset blocking i/o buffer processor. */ + PaUtil_ResetBufferProcessor( &blockingState->bufferProcessor ); + + /* If we're about to process some input data. */ + if( stream->inputChannelCount ) { - stream->isActive = 1; - stream->streamFinishedCallbackCalled = false; + /* Reset callback-ReadStream sync event. */ + if( ResetEvent( blockingState->readFramesReadyEvent ) == 0 ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() ); + } + + /* Flush blocking i/o ring buffer. */ + PaUtil_FlushRingBuffer( &blockingState->readRingBuffer ); + (*blockingState->bufferProcessor.inputZeroer)( blockingState->readRingBuffer.buffer, 1, blockingState->bufferProcessor.inputChannelCount * blockingState->readRingBuffer.bufferSize ); } - else + + /* If we're about to process some output data. */ + if( stream->outputChannelCount ) + { + /* Reset callback-WriteStream sync event. */ + if( ResetEvent( blockingState->writeBuffersReadyEvent ) == 0 ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() ); + } + + /* Flush blocking i/o ring buffer. */ + PaUtil_FlushRingBuffer( &blockingState->writeRingBuffer ); + (*blockingState->bufferProcessor.outputZeroer)( blockingState->writeRingBuffer.buffer, 1, blockingState->bufferProcessor.outputChannelCount * blockingState->writeRingBuffer.bufferSize ); + + /* Initialize the output ring buffer to "silence". */ + PaUtil_AdvanceRingBufferWriteIndex( &blockingState->writeRingBuffer, blockingState->writeRingBufferInitialFrames ); + } + + /* Clear requested frames / buffers count. */ + blockingState->writeBuffersRequested = 0; + blockingState->readFramesRequested = 0; + blockingState->writeBuffersRequestedFlag = FALSE; + blockingState->readFramesRequestedFlag = FALSE; + blockingState->outputUnderflowFlag = FALSE; + blockingState->inputOverflowFlag = FALSE; + blockingState->stopFlag = FALSE; + } + + + if( result == paNoError ) + { + assert( theAsioStream == stream ); /* theAsioStream should be set correctly in OpenStream */ + + /* initialize these variables before the callback has a chance to be invoked */ + stream->isStopped = 0; + stream->isActive = 1; + stream->streamFinishedCallbackCalled = false; + + asioError = ASIOStart(); + if( asioError != ASE_OK ) { - theAsioStream = 0; + stream->isStopped = 1; + stream->isActive = 0; + result = paUnanticipatedHostError; PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); } @@ -2678,15 +3207,59 @@ static PaError StartStream( PaStream *s ) return result; } +static void EnsureCallbackHasCompleted( PaAsioStream *stream ) +{ + // make sure that the callback is not still in-flight after ASIOStop() + // returns. This has been observed to happen on the Hoontech DSP24 for + // example. + int count = 2000; // only wait for 2 seconds, rather than hanging. + while( stream->reenterCount != -1 && count > 0 ) + { + Sleep(1); + --count; + } +} static PaError StopStream( PaStream *s ) { PaError result = paNoError; PaAsioStream *stream = (PaAsioStream*)s; + PaAsioStreamBlockingState *blockingState = stream->blockingState; ASIOError asioError; if( stream->isActive ) { + /* If blocking i/o output is in use */ + if( blockingState && stream->outputChannelCount ) + { + /* Request the whole output buffer to be available. */ + blockingState->writeBuffersRequested = blockingState->writeRingBuffer.bufferSize; + /* Signalize that additional buffers are need. */ + blockingState->writeBuffersRequestedFlag = TRUE; + /* Set flag to indicate the playback is to be stopped. */ + blockingState->stopFlag = TRUE; + + /* Wait until requested number of buffers has been freed. Time + out after twice the blocking i/o ouput buffer could have + been consumed. */ + DWORD timeout = (DWORD)( 2 * blockingState->writeRingBuffer.bufferSize * 1000 + / stream->streamRepresentation.streamInfo.sampleRate ); + DWORD waitResult = WaitForSingleObject( blockingState->writeBuffersReadyEvent, timeout ); + + /* If something seriously went wrong... */ + if( waitResult == WAIT_FAILED ) + { + PA_DEBUG(("WaitForSingleObject() failed in StopStream()\n")); + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() ); + } + else if( waitResult == WAIT_TIMEOUT ) + { + PA_DEBUG(("WaitForSingleObject() timed out in StopStream()\n")); + result = paTimedOut; + } + } + stream->stopProcessing = true; /* wait for the stream to finish playing out enqueued buffers. @@ -2696,22 +3269,26 @@ static PaError StopStream( PaStream *s ) length is longer than the asio buffer size then that should be taken into account. */ - if( WaitForSingleObject( theAsioStream->completedBuffersPlayedEvent, + if( WaitForSingleObject( stream->completedBuffersPlayedEvent, (DWORD)(stream->streamRepresentation.streamInfo.outputLatency * 1000. * 4.) ) - == WAIT_TIMEOUT ) + == WAIT_TIMEOUT ) { PA_DEBUG(("WaitForSingleObject() timed out in StopStream()\n" )); } } asioError = ASIOStop(); - if( asioError != ASE_OK ) + if( asioError == ASE_OK ) + { + EnsureCallbackHasCompleted( stream ); + } + else { result = paUnanticipatedHostError; PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); } - theAsioStream = 0; + stream->isStopped = 1; stream->isActive = 0; if( !stream->streamFinishedCallbackCalled ) @@ -2723,7 +3300,6 @@ static PaError StopStream( PaStream *s ) return result; } - static PaError AbortStream( PaStream *s ) { PaError result = paNoError; @@ -2733,31 +3309,17 @@ static PaError AbortStream( PaStream *s ) stream->zeroOutput = true; asioError = ASIOStop(); - if( asioError != ASE_OK ) + if( asioError == ASE_OK ) { - result = paUnanticipatedHostError; - PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + EnsureCallbackHasCompleted( stream ); } else { - // make sure that the callback is not still in-flight when ASIOStop() - // returns. This has been observed to happen on the Hoontech DSP24 for - // example. - int count = 2000; // only wait for 2 seconds, rather than hanging. - while( theAsioStream->reenterCount != -1 && count > 0 ) - { - Sleep(1); - --count; - } + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); } - /* it is questionable whether we should zero theAsioStream if ASIOStop() - returns an error, because the callback could still be active. We assume - not - this is based on the fact that ASIOStop is unlikely to fail - if the callback is running - it's more likely to fail because the - callback is not running. */ - - theAsioStream = 0; + stream->isStopped = 1; stream->isActive = 0; if( !stream->streamFinishedCallbackCalled ) @@ -2772,9 +3334,9 @@ static PaError AbortStream( PaStream *s ) static PaError IsStreamStopped( PaStream *s ) { - //PaAsioStream *stream = (PaAsioStream*)s; - (void) s; /* unused parameter */ - return theAsioStream == 0; + PaAsioStream *stream = (PaAsioStream*)s; + + return stream->isStopped; } @@ -2807,33 +3369,348 @@ static double GetStreamCpuLoad( PaStream* s ) for blocking streams. */ -static PaError ReadStream( PaStream* s, - void *buffer, - unsigned long frames ) +static PaError ReadStream( PaStream *s , + void *buffer, + unsigned long frames ) { - PaAsioStream *stream = (PaAsioStream*)s; + PaError result = paNoError; /* Initial return value. */ + PaAsioStream *stream = (PaAsioStream*)s; /* The PA ASIO stream. */ - /* IMPLEMENT ME, see portaudio.h for required behavior*/ - (void) stream; /* unused parameters */ - (void) buffer; - (void) frames; + /* Pointer to the blocking i/o data struct. */ + PaAsioStreamBlockingState *blockingState = stream->blockingState; - return paNoError; -} + /* Get blocking i/o buffer processor and ring buffer pointers. */ + PaUtilBufferProcessor *pBp = &blockingState->bufferProcessor; + PaUtilRingBuffer *pRb = &blockingState->readRingBuffer; + + /* Ring buffer segment(s) used for writing. */ + void *pRingBufferData1st = NULL; /* First segment. (Mandatory) */ + void *pRingBufferData2nd = NULL; /* Second segment. (Optional) */ + + /* Number of frames per ring buffer segment. */ + long lRingBufferSize1st = 0; /* First segment. (Mandatory) */ + long lRingBufferSize2nd = 0; /* Second segment. (Optional) */ + + /* Get number of frames to be processed per data block. */ + unsigned long lFramesPerBlock = stream->bufferProcessor.framesPerUserBuffer; + /* Actual number of frames that has been copied into the ring buffer. */ + unsigned long lFramesCopied = 0; + /* The number of remaining unprocessed dtat frames. */ + unsigned long lFramesRemaining = frames; + /* Copy the input argument to avoid pointer increment! */ + const void *userBuffer; + unsigned int i; /* Just a counter. */ + + /* About the time, needed to process 8 data blocks. */ + DWORD timeout = (DWORD)( 8 * lFramesPerBlock * 1000 / stream->streamRepresentation.streamInfo.sampleRate ); + DWORD waitResult = 0; + + + /* Check if the stream is still available ready to gather new data. */ + if( blockingState->stopFlag || !stream->isActive ) + { + PA_DEBUG(("Warning! Stream no longer available for reading in ReadStream()\n")); + result = paStreamIsStopped; + return result; + } + + /* If the stream is a input stream. */ + if( stream->inputChannelCount ) + { + /* Prepare buffer access. */ + if( !pBp->userOutputIsInterleaved ) + { + userBuffer = blockingState->readStreamBuffer; + for( i = 0; iinputChannelCount; ++i ) + { + ((void**)userBuffer)[i] = ((void**)buffer)[i]; + } + } /* Use the unchanged buffer. */ + else { userBuffer = buffer; } -static PaError WriteStream( PaStream* s, - const void *buffer, - unsigned long frames ) + do /* Internal block processing for too large user data buffers. */ + { + /* Get the size of the current data block to be processed. */ + lFramesPerBlock =(lFramesPerBlock < lFramesRemaining) + ? lFramesPerBlock : lFramesRemaining; + /* Use predefined block size for as long there are enough + buffers available, thereafter reduce the processing block + size to match the number of remaining buffers. So the final + data block is processed although it may be incomplete. */ + + /* If the available amount of data frames is insufficient. */ + if( PaUtil_GetRingBufferReadAvailable(pRb) < (long) lFramesPerBlock ) + { + /* Make sure, the event isn't already set! */ + /* ResetEvent( blockingState->readFramesReadyEvent ); */ + + /* Set the number of requested buffers. */ + blockingState->readFramesRequested = lFramesPerBlock; + + /* Signalize that additional buffers are need. */ + blockingState->readFramesRequestedFlag = TRUE; + + /* Wait until requested number of buffers has been freed. */ + waitResult = WaitForSingleObject( blockingState->readFramesReadyEvent, timeout ); + + /* If something seriously went wrong... */ + if( waitResult == WAIT_FAILED ) + { + PA_DEBUG(("WaitForSingleObject() failed in ReadStream()\n")); + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() ); + return result; + } + else if( waitResult == WAIT_TIMEOUT ) + { + PA_DEBUG(("WaitForSingleObject() timed out in ReadStream()\n")); + + /* If block processing has stopped, abort! */ + if( blockingState->stopFlag ) { return result = paStreamIsStopped; } + + /* If a timeout is encountered, give up eventually. */ + return result = paTimedOut; + } + } + /* Now, the ring buffer contains the required amount of data + frames. + (Therefor we don't need to check the return argument of + PaUtil_GetRingBufferReadRegions(). ;-) ) + */ + + /* Retrieve pointer(s) to the ring buffer's current write + position(s). If the first buffer segment is too small to + store the requested number of bytes, an additional second + segment is returned. Otherwise, i.e. if the first segment + is large enough, the second segment's pointer will be NULL. + */ + PaUtil_GetRingBufferReadRegions(pRb , + lFramesPerBlock , + &pRingBufferData1st, + &lRingBufferSize1st, + &pRingBufferData2nd, + &lRingBufferSize2nd); + + /* Set number of frames to be copied from the ring buffer. */ + PaUtil_SetInputFrameCount( pBp, lRingBufferSize1st ); + /* Setup ring buffer access. */ + PaUtil_SetInterleavedInputChannels(pBp , /* Buffer processor. */ + 0 , /* The first channel's index. */ + pRingBufferData1st, /* First ring buffer segment. */ + 0 ); /* Use all available channels. */ + + /* If a second ring buffer segment is required. */ + if( lRingBufferSize2nd ) { + /* Set number of frames to be copied from the ring buffer. */ + PaUtil_Set2ndInputFrameCount( pBp, lRingBufferSize2nd ); + /* Setup ring buffer access. */ + PaUtil_Set2ndInterleavedInputChannels(pBp , /* Buffer processor. */ + 0 , /* The first channel's index. */ + pRingBufferData2nd, /* Second ring buffer segment. */ + 0 ); /* Use all available channels. */ + } + + /* Let the buffer processor handle "copy and conversion" and + update the ring buffer indices manually. */ + lFramesCopied = PaUtil_CopyInput( pBp, &buffer, lFramesPerBlock ); + PaUtil_AdvanceRingBufferReadIndex( pRb, lFramesCopied ); + + /* Decrease number of unprocessed frames. */ + lFramesRemaining -= lFramesCopied; + + } /* Continue with the next data chunk. */ + while( lFramesRemaining ); + + + /* If there has been an input overflow within the callback */ + if( blockingState->inputOverflowFlag ) + { + blockingState->inputOverflowFlag = FALSE; + + /* Return the corresponding error code. */ + result = paInputOverflowed; + } + + } /* If this is not an input stream. */ + else { + result = paCanNotReadFromAnOutputOnlyStream; + } + + return result; +} + +static PaError WriteStream( PaStream *s , + const void *buffer, + unsigned long frames ) { - PaAsioStream *stream = (PaAsioStream*)s; + PaError result = paNoError; /* Initial return value. */ + PaAsioStream *stream = (PaAsioStream*)s; /* The PA ASIO stream. */ - /* IMPLEMENT ME, see portaudio.h for required behavior*/ - (void) stream; /* unused parameters */ - (void) buffer; - (void) frames; + /* Pointer to the blocking i/o data struct. */ + PaAsioStreamBlockingState *blockingState = stream->blockingState; - return paNoError; + /* Get blocking i/o buffer processor and ring buffer pointers. */ + PaUtilBufferProcessor *pBp = &blockingState->bufferProcessor; + PaUtilRingBuffer *pRb = &blockingState->writeRingBuffer; + + /* Ring buffer segment(s) used for writing. */ + void *pRingBufferData1st = NULL; /* First segment. (Mandatory) */ + void *pRingBufferData2nd = NULL; /* Second segment. (Optional) */ + + /* Number of frames per ring buffer segment. */ + long lRingBufferSize1st = 0; /* First segment. (Mandatory) */ + long lRingBufferSize2nd = 0; /* Second segment. (Optional) */ + + /* Get number of frames to be processed per data block. */ + unsigned long lFramesPerBlock = stream->bufferProcessor.framesPerUserBuffer; + /* Actual number of frames that has been copied into the ring buffer. */ + unsigned long lFramesCopied = 0; + /* The number of remaining unprocessed dtat frames. */ + unsigned long lFramesRemaining = frames; + + /* About the time, needed to process 8 data blocks. */ + DWORD timeout = (DWORD)( 8 * lFramesPerBlock * 1000 / stream->streamRepresentation.streamInfo.sampleRate ); + DWORD waitResult = 0; + + /* Copy the input argument to avoid pointer increment! */ + const void *userBuffer; + unsigned int i; /* Just a counter. */ + + + /* Check if the stream ist still available ready to recieve new data. */ + if( blockingState->stopFlag || !stream->isActive ) + { + PA_DEBUG(("Warning! Stream no longer available for writing in WriteStream()\n")); + result = paStreamIsStopped; + return result; + } + + /* If the stream is a output stream. */ + if( stream->outputChannelCount ) + { + /* Prepare buffer access. */ + if( !pBp->userOutputIsInterleaved ) + { + userBuffer = blockingState->writeStreamBuffer; + for( i = 0; ioutputChannelCount; ++i ) + { + ((const void**)userBuffer)[i] = ((const void**)buffer)[i]; + } + } /* Use the unchanged buffer. */ + else { userBuffer = buffer; } + + + do /* Internal block processing for too large user data buffers. */ + { + /* Get the size of the current data block to be processed. */ + lFramesPerBlock =(lFramesPerBlock < lFramesRemaining) + ? lFramesPerBlock : lFramesRemaining; + /* Use predefined block size for as long there are enough + frames available, thereafter reduce the processing block + size to match the number of remaining frames. So the final + data block is processed although it may be incomplete. */ + + /* If the available amount of buffers is insufficient. */ + if( PaUtil_GetRingBufferWriteAvailable(pRb) < (long) lFramesPerBlock ) + { + /* Make sure, the event isn't already set! */ + /* ResetEvent( blockingState->writeBuffersReadyEvent ); */ + + /* Set the number of requested buffers. */ + blockingState->writeBuffersRequested = lFramesPerBlock; + + /* Signalize that additional buffers are need. */ + blockingState->writeBuffersRequestedFlag = TRUE; + + /* Wait until requested number of buffers has been freed. */ + waitResult = WaitForSingleObject( blockingState->writeBuffersReadyEvent, timeout ); + + /* If something seriously went wrong... */ + if( waitResult == WAIT_FAILED ) + { + PA_DEBUG(("WaitForSingleObject() failed in WriteStream()\n")); + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() ); + return result; + } + else if( waitResult == WAIT_TIMEOUT ) + { + PA_DEBUG(("WaitForSingleObject() timed out in WriteStream()\n")); + + /* If block processing has stopped, abort! */ + if( blockingState->stopFlag ) { return result = paStreamIsStopped; } + + /* If a timeout is encountered, give up eventually. */ + return result = paTimedOut; + } + } + /* Now, the ring buffer contains the required amount of free + space to store the provided number of data frames. + (Therefor we don't need to check the return argument of + PaUtil_GetRingBufferWriteRegions(). ;-) ) + */ + + /* Retrieve pointer(s) to the ring buffer's current write + position(s). If the first buffer segment is too small to + store the requested number of bytes, an additional second + segment is returned. Otherwise, i.e. if the first segment + is large enough, the second segment's pointer will be NULL. + */ + PaUtil_GetRingBufferWriteRegions(pRb , + lFramesPerBlock , + &pRingBufferData1st, + &lRingBufferSize1st, + &pRingBufferData2nd, + &lRingBufferSize2nd); + + /* Set number of frames to be copied to the ring buffer. */ + PaUtil_SetOutputFrameCount( pBp, lRingBufferSize1st ); + /* Setup ring buffer access. */ + PaUtil_SetInterleavedOutputChannels(pBp , /* Buffer processor. */ + 0 , /* The first channel's index. */ + pRingBufferData1st, /* First ring buffer segment. */ + 0 ); /* Use all available channels. */ + + /* If a second ring buffer segment is required. */ + if( lRingBufferSize2nd ) { + /* Set number of frames to be copied to the ring buffer. */ + PaUtil_Set2ndOutputFrameCount( pBp, lRingBufferSize2nd ); + /* Setup ring buffer access. */ + PaUtil_Set2ndInterleavedOutputChannels(pBp , /* Buffer processor. */ + 0 , /* The first channel's index. */ + pRingBufferData2nd, /* Second ring buffer segment. */ + 0 ); /* Use all available channels. */ + } + + /* Let the buffer processor handle "copy and conversion" and + update the ring buffer indices manually. */ + lFramesCopied = PaUtil_CopyOutput( pBp, &userBuffer, lFramesPerBlock ); + PaUtil_AdvanceRingBufferWriteIndex( pRb, lFramesCopied ); + + /* Decrease number of unprocessed frames. */ + lFramesRemaining -= lFramesCopied; + + } /* Continue with the next data chunk. */ + while( lFramesRemaining ); + + + /* If there has been an output underflow within the callback */ + if( blockingState->outputUnderflowFlag ) + { + blockingState->outputUnderflowFlag = FALSE; + + /* Return the corresponding error code. */ + result = paOutputUnderflowed; + } + + } /* If this is not an output stream. */ + else + { + result = paCanNotWriteToAnInputOnlyStream; + } + + return result; } @@ -2841,10 +3718,8 @@ static signed long GetStreamReadAvailable( PaStream* s ) { PaAsioStream *stream = (PaAsioStream*)s; - /* IMPLEMENT ME, see portaudio.h for required behavior*/ - (void) stream; /* unused parameter */ - - return 0; + /* Call buffer utility routine to get the number of available frames. */ + return PaUtil_GetRingBufferReadAvailable( &stream->blockingState->readRingBuffer ); } @@ -2852,20 +3727,130 @@ static signed long GetStreamWriteAvailable( PaStream* s ) { PaAsioStream *stream = (PaAsioStream*)s; - /* IMPLEMENT ME, see portaudio.h for required behavior*/ - (void) stream; /* unused parameter */ + /* Call buffer utility routine to get the number of empty buffers. */ + return PaUtil_GetRingBufferWriteAvailable( &stream->blockingState->writeRingBuffer ); +} + + +/* This routine will be called by the PortAudio engine when audio is needed. +** It may called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int BlockingIoPaCallback(const void *inputBuffer , + void *outputBuffer , + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo *timeInfo , + PaStreamCallbackFlags statusFlags , + void *userData ) +{ + PaError result = paNoError; /* Initial return value. */ + PaAsioStream *stream = *(PaAsioStream**)userData; /* The PA ASIO stream. */ + PaAsioStreamBlockingState *blockingState = stream->blockingState; /* Persume blockingState is valid, otherwise the callback wouldn't be running. */ + + /* Get a pointer to the stream's blocking i/o buffer processor. */ + PaUtilBufferProcessor *pBp = &blockingState->bufferProcessor; + PaUtilRingBuffer *pRb = NULL; + + /* If output data has been requested. */ + if( stream->outputChannelCount ) + { + /* If the callback input argument signalizes a output underflow, + make sure the WriteStream() function knows about it, too! */ + if( statusFlags & paOutputUnderflowed ) { + blockingState->outputUnderflowFlag = TRUE; + } + + /* Access the corresponding ring buffer. */ + pRb = &blockingState->writeRingBuffer; + + /* If the blocking i/o buffer contains enough output data, */ + if( PaUtil_GetRingBufferReadAvailable(pRb) >= (long) framesPerBuffer ) + { + /* Extract the requested data from the ring buffer. */ + PaUtil_ReadRingBuffer( pRb, outputBuffer, framesPerBuffer ); + } + else /* If no output data is available :-( */ + { + /* Signalize a write-buffer underflow. */ + blockingState->outputUnderflowFlag = TRUE; + + /* Fill the output buffer with silence. */ + (*pBp->outputZeroer)( outputBuffer, 1, pBp->outputChannelCount * framesPerBuffer ); - return 0; + /* If playback is to be stopped */ + if( blockingState->stopFlag && PaUtil_GetRingBufferReadAvailable(pRb) < (long) framesPerBuffer ) + { + /* Extract all the remaining data from the ring buffer, + whether it is a complete data block or not. */ + PaUtil_ReadRingBuffer( pRb, outputBuffer, PaUtil_GetRingBufferReadAvailable(pRb) ); + } + } + + /* Set blocking i/o event? */ + if( blockingState->writeBuffersRequestedFlag && PaUtil_GetRingBufferWriteAvailable(pRb) >= (long) blockingState->writeBuffersRequested ) + { + /* Reset buffer request. */ + blockingState->writeBuffersRequestedFlag = FALSE; + blockingState->writeBuffersRequested = 0; + /* Signalize that requested buffers are ready. */ + SetEvent( blockingState->writeBuffersReadyEvent ); + /* What do we do if SetEvent() returns zero, i.e. the event + could not be set? How to return errors from within the + callback? - S.Fischer */ + } + } + + /* If input data has been supplied. */ + if( stream->inputChannelCount ) + { + /* If the callback input argument signalizes a input overflow, + make sure the ReadStream() function knows about it, too! */ + if( statusFlags & paInputOverflowed ) { + blockingState->inputOverflowFlag = TRUE; + } + + /* Access the corresponding ring buffer. */ + pRb = &blockingState->readRingBuffer; + + /* If the blocking i/o buffer contains not enough input buffers */ + if( PaUtil_GetRingBufferWriteAvailable(pRb) < (long) framesPerBuffer ) + { + /* Signalize a read-buffer overflow. */ + blockingState->inputOverflowFlag = TRUE; + + /* Remove some old data frames from the buffer. */ + PaUtil_AdvanceRingBufferReadIndex( pRb, framesPerBuffer ); + } + + /* Insert the current input data into the ring buffer. */ + PaUtil_WriteRingBuffer( pRb, inputBuffer, framesPerBuffer ); + + /* Set blocking i/o event? */ + if( blockingState->readFramesRequestedFlag && PaUtil_GetRingBufferReadAvailable(pRb) >= (long) blockingState->readFramesRequested ) + { + /* Reset buffer request. */ + blockingState->readFramesRequestedFlag = FALSE; + blockingState->readFramesRequested = 0; + /* Signalize that requested buffers are ready. */ + SetEvent( blockingState->readFramesReadyEvent ); + /* What do we do if SetEvent() returns zero, i.e. the event + could not be set? How to return errors from within the + callback? - S.Fischer */ + /** @todo report an error with PA_DEBUG */ + } + } + + return paContinue; } PaError PaAsio_ShowControlPanel( PaDeviceIndex device, void* systemSpecific ) { - PaError result = paNoError; + PaError result = paNoError; PaUtilHostApiRepresentation *hostApi; PaDeviceIndex hostApiDevice; ASIODriverInfo asioDriverInfo; - ASIOError asioError; + ASIOError asioError; int asioIsInitialized = 0; PaAsioHostApiRepresentation *asioHostApi; PaAsioDeviceInfo *asioDeviceInfo; @@ -2896,7 +3881,10 @@ PaError PaAsio_ShowControlPanel( PaDeviceIndex device, void* systemSpecific ) asioDeviceInfo = (PaAsioDeviceInfo*)hostApi->deviceInfos[hostApiDevice]; - if( !loadAsioDriver( const_cast(asioDeviceInfo->commonDeviceInfo.name) ) ) + /* See notes about CoInitialize(0) in LoadAsioDriver(). */ + CoInitialize(0); + + if( !asioHostApi->asioDrivers->loadDriver( const_cast(asioDeviceInfo->commonDeviceInfo.name) ) ) { result = paUnanticipatedHostError; goto error; @@ -2944,15 +3932,19 @@ PA_DEBUG(("PaAsio_ShowControlPanel: ASIOControlPanel(): %s\n", PaAsio_GetAsioErr goto error; } + CoUninitialize(); PA_DEBUG(("PaAsio_ShowControlPanel: ASIOExit(): %s\n", PaAsio_GetAsioErrorText(asioError) )); - return result; + return result; error: if( asioIsInitialized ) - ASIOExit(); + { + ASIOExit(); + } + CoUninitialize(); - return result; + return result; } @@ -3021,3 +4013,53 @@ PaError PaAsio_GetOutputChannelName( PaDeviceIndex device, int channelIndex, error: return result; } + + +/* NOTE: the following functions are ASIO-stream specific, and are called directly + by client code. We need to check for many more error conditions here because + we don't have the benefit of pa_front.c's parameter checking. +*/ + +static PaError GetAsioStreamPointer( PaAsioStream **stream, PaStream *s ) +{ + PaError result; + PaUtilHostApiRepresentation *hostApi; + PaAsioHostApiRepresentation *asioHostApi; + + result = PaUtil_ValidateStreamPointer( s ); + if( result != paNoError ) + return result; + + result = PaUtil_GetHostApiRepresentation( &hostApi, paASIO ); + if( result != paNoError ) + return result; + + asioHostApi = (PaAsioHostApiRepresentation*)hostApi; + + if( PA_STREAM_REP( s )->streamInterface == &asioHostApi->callbackStreamInterface + || PA_STREAM_REP( s )->streamInterface == &asioHostApi->blockingStreamInterface ) + { + /* s is an ASIO stream */ + *stream = (PaAsioStream *)s; + return paNoError; + } + else + { + return paIncompatibleStreamHostApi; + } +} + + +PaError PaAsio_SetStreamSampleRate( PaStream* s, double sampleRate ) +{ + PaAsioStream *stream; + PaError result = GetAsioStreamPointer( &stream, s ); + if( result != paNoError ) + return result; + + if( stream != theAsioStream ) + return paBadStreamPtr; + + return ValidateAndSetSampleRate( sampleRate ); +} + diff --git a/pd/portaudio/src/hostapi/coreaudio/notes.txt b/pd/portaudio/src/hostapi/coreaudio/notes.txt index ffe96962..145afe15 100644 --- a/pd/portaudio/src/hostapi/coreaudio/notes.txt +++ b/pd/portaudio/src/hostapi/coreaudio/notes.txt @@ -73,17 +73,24 @@ the stream with it. See below for creating a channel map. Known issues: -- Latency: Latency settings are ignored in most cases. Exceptions are when -doing I/O between different devices and as a hint for selecting a realtively -low or relatively high latency in conjunction with -paHostFramesPerBufferUnspecified. Latency settings are always automatically -bound to "safe" values, however, so setting extreme values here should not be +- Buffering: No buffering beyond that provided by core audio is provided +except where absolutely needed for the implementation to work. This may cause +issues with large framesPerBuffer settings and it also means that no additional +latency will be provided even if a large latency setting is selected. + +- Latency: Latency settings are generally ignored. They may be used as a +hint for buffer size in paHostFramesPerBufferUnspecified, or the value may +be used in cases where additional buffering is needed, such as doing input and +output on seperate devices. Latency settings are always automatically bound +to "safe" values, however, so setting extreme values here should not be an issue. - Buffer Size: paHostFramesPerBufferUnspecified and specific host buffer sizes are supported. paHostFramesPerBufferUnspecified works best in "pro" mode, where the buffer size and sample rate of the audio device is most likely -to match the expected values. +to match the expected values. In the case of paHostFramesPerBuffer, an +appropriate framesPerBuffer value will be used that guarantees minimum +requested latency if that's possible. - Timing info. It reports on stream time, but I'm probably doing something wrong since patest_sine_time often reports negative latency numbers. Also, @@ -111,8 +118,7 @@ render quyality property is used to set the sample rate conversion quality as "documented" here: http://lists.apple.com/archives/coreaudio-api/2004/Jan/msg00141.html -- x86/Universal Binary: to build a universal binary, be sure to use -the darwin makefile and not the usual configure && make combo. +- x86/Universal Binary: Universal binaries can be build. diff --git a/pd/portaudio/src/hostapi/coreaudio/pa_mac_core.c b/pd/portaudio/src/hostapi/coreaudio/pa_mac_core.c index 7c887bd6..98cfbb51 100644 --- a/pd/portaudio/src/hostapi/coreaudio/pa_mac_core.c +++ b/pd/portaudio/src/hostapi/coreaudio/pa_mac_core.c @@ -65,6 +65,7 @@ #include "pa_mac_core_internal.h" #include /* strlen(), memcmp() etc. */ +#include #include "pa_mac_core.h" #include "pa_mac_core_utilities.h" @@ -127,6 +128,8 @@ const char *PaMacCore_GetChannelName( int device, int channelIndex, bool input ) OSStatus error; err = PaUtil_GetHostApiRepresentation( &hostApi, paCoreAudio ); assert(err == paNoError); + if( err != paNoError ) + return NULL; PaMacAUHAL *macCoreHostApi = (PaMacAUHAL*)hostApi; AudioDeviceID hostApiDevice = macCoreHostApi->devIds[device]; @@ -261,11 +264,12 @@ static PaError GetChannelInfo( PaMacAUHAL *auhalHostApi, int isInput); static PaError OpenAndSetupOneAudioUnit( + const PaMacCoreStream *stream, const PaStreamParameters *inStreamParams, const PaStreamParameters *outStreamParams, - const unsigned long requestedFramesPerBuffer, - unsigned long *actualInputFramesPerBuffer, - unsigned long *actualOutputFramesPerBuffer, + const UInt32 requestedFramesPerBuffer, + UInt32 *actualInputFramesPerBuffer, + UInt32 *actualOutputFramesPerBuffer, const PaMacAUHAL *auhalHostApi, AudioUnit *audioUnit, AudioConverterRef *srConverter, @@ -277,6 +281,37 @@ static PaError OpenAndSetupOneAudioUnit( #define PA_AUHAL_SET_LAST_HOST_ERROR( errorCode, errorText ) \ PaUtil_SetLastHostErrorInfo( paInDevelopment, errorCode, errorText ) +/* + * Callback called when starting or stopping a stream. + */ +static void startStopCallback( + void * inRefCon, + AudioUnit ci, + AudioUnitPropertyID inID, + AudioUnitScope inScope, + AudioUnitElement inElement ) +{ + PaMacCoreStream *stream = (PaMacCoreStream *) inRefCon; + UInt32 isRunning; + UInt32 size = sizeof( isRunning ); + OSStatus err; + err = AudioUnitGetProperty( ci, kAudioOutputUnitProperty_IsRunning, inScope, inElement, &isRunning, &size ); + assert( !err ); + if( err ) + isRunning = false; //it's very unclear what to do in case of error here. There's no real way to notify the user, and crashing seems unreasonable. + if( isRunning ) + return; //We are only interested in when we are stopping + // -- if we are using 2 I/O units, we only need one notification! + if( stream->inputUnit && stream->outputUnit && stream->inputUnit != stream->outputUnit && ci == stream->inputUnit ) + return; + PaStreamFinishedCallback *sfc = stream->streamRepresentation.streamFinishedCallback; + if( stream->state == STOPPING ) + stream->state = STOPPED ; + if( sfc ) + sfc( stream->streamRepresentation.userData ); +} + + /*currently, this is only used in initialization, but it might be modified to be used when the list of devices changes.*/ static PaError gatherDeviceInfo(PaMacAUHAL *auhalHostApi) @@ -497,9 +532,15 @@ PaError PaMacCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIn int i; PaMacAUHAL *auhalHostApi; PaDeviceInfo *deviceInfoArray; + int unixErr; VVDBUG(("PaMacCore_Initialize(): hostApiIndex=%d\n", hostApiIndex)); + unixErr = initializeXRunListenerList(); + if( 0 != unixErr ) { + return UNIX_ERR(unixErr); + } + auhalHostApi = (PaMacAUHAL*)PaUtil_AllocateMemory( sizeof(PaMacAUHAL) ); if( !auhalHostApi ) { @@ -618,10 +659,16 @@ error: static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) { + int unixErr; + PaMacAUHAL *auhalHostApi = (PaMacAUHAL*)hostApi; VVDBUG(("Terminate()\n")); + unixErr = destroyXRunListenerList(); + if( 0 != unixErr ) + UNIX_ERR(unixErr); + /* IMPLEMENT ME: - clean up any resources not handled by the allocation group @@ -737,11 +784,12 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, } static PaError OpenAndSetupOneAudioUnit( + const PaMacCoreStream *stream, const PaStreamParameters *inStreamParams, const PaStreamParameters *outStreamParams, - const unsigned long requestedFramesPerBuffer, - unsigned long *actualInputFramesPerBuffer, - unsigned long *actualOutputFramesPerBuffer, + const UInt32 requestedFramesPerBuffer, + UInt32 *actualInputFramesPerBuffer, + UInt32 *actualOutputFramesPerBuffer, const PaMacAUHAL *auhalHostApi, AudioUnit *audioUnit, AudioConverterRef *srConverter, @@ -753,7 +801,7 @@ static PaError OpenAndSetupOneAudioUnit( Component comp; /*An Apple TN suggests using CAStreamBasicDescription, but that is C++*/ AudioStreamBasicDescription desiredFormat; - OSErr result = noErr; + OSStatus result = noErr; PaError paResult = paNoError; int line = 0; UInt32 callbackKey; @@ -881,7 +929,7 @@ static PaError OpenAndSetupOneAudioUnit( audioDevice, sizeof(AudioDeviceID) ) ); } - if( outStreamParams ) + if( outStreamParams && outStreamParams != inStreamParams ) { *audioDevice = auhalHostApi->devIds[outStreamParams->device] ; ERR_WRAP( AudioUnitSetProperty( *audioUnit, @@ -891,6 +939,24 @@ static PaError OpenAndSetupOneAudioUnit( audioDevice, sizeof(AudioDeviceID) ) ); } + /* -- add listener for dropouts -- */ + result = AudioDeviceAddPropertyListener( *audioDevice, + 0, + outStreamParams ? false : true, + kAudioDeviceProcessorOverload, + xrunCallback, + addToXRunListenerList( (void *)stream ) ) ; + if( result == kAudioHardwareIllegalOperationError ) { + // -- already registered, we're good + } else { + // -- not already registered, just check for errors + ERR_WRAP( result ); + } + /* -- listen for stream start and stop -- */ + ERR_WRAP( AudioUnitAddPropertyListener( *audioUnit, + kAudioOutputUnitProperty_IsRunning, + startStopCallback, + (void *)stream ) ); /* -- set format -- */ bzero( &desiredFormat, sizeof(desiredFormat) ); @@ -1010,7 +1076,7 @@ static PaError OpenAndSetupOneAudioUnit( kAudioUnitScope_Input, OUTPUT_ELEMENT, actualOutputFramesPerBuffer, - sizeof(unsigned long) ) ); + sizeof(*actualOutputFramesPerBuffer) ) ); ERR_WRAP( AudioUnitGetProperty( *audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, @@ -1025,7 +1091,7 @@ static PaError OpenAndSetupOneAudioUnit( kAudioUnitScope_Output, INPUT_ELEMENT, actualInputFramesPerBuffer, - sizeof(unsigned long) ) ); + sizeof(*actualInputFramesPerBuffer) ) ); /* Don't know why this causes problems ERR_WRAP( AudioUnitGetProperty( *audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, @@ -1305,7 +1371,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, /*requested a realtively low latency. make sure this is in range of devices */ /*try to get the device's min natural buffer size and use that (but no smaller than 64).*/ AudioValueRange audioRange; - size_t size = sizeof( audioRange ); + UInt32 size = sizeof( audioRange ); if( inputParameters ) { WARNING( result = AudioDeviceGetProperty( auhalHostApi->devIds[inputParameters->device], 0, @@ -1315,6 +1381,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, if( result ) requested = MAX( requested, audioRange.mMinimum ); } + size = sizeof( audioRange ); if( outputParameters ) { WARNING( result = AudioDeviceGetProperty( auhalHostApi->devIds[outputParameters->device], 0, @@ -1328,7 +1395,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, /* requested a realtively high latency. make sure this is in range of devices */ /*try to get the device's max natural buffer size and use that (but no larger than 1024).*/ AudioValueRange audioRange; - size_t size = sizeof( audioRange ); + UInt32 size = sizeof( audioRange ); requested = MIN( requested, 1024 ); if( inputParameters ) { WARNING( result = AudioDeviceGetProperty( auhalHostApi->devIds[inputParameters->device], @@ -1339,6 +1406,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, if( result ) requested = MIN( requested, audioRange.mMaximum ); } + size = sizeof( audioRange ); if( outputParameters ) { WARNING( result = AudioDeviceGetProperty( auhalHostApi->devIds[outputParameters->device], 0, @@ -1359,17 +1427,22 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, /* -- Now we actually open and setup streams. -- */ if( inputParameters && outputParameters && outputParameters->device == inputParameters->device ) { /* full duplex. One device. */ - result = OpenAndSetupOneAudioUnit( inputParameters, + UInt32 inputFramesPerBuffer = (UInt32) stream->inputFramesPerBuffer; + UInt32 outputFramesPerBuffer = (UInt32) stream->outputFramesPerBuffer; + result = OpenAndSetupOneAudioUnit( stream, + inputParameters, outputParameters, framesPerBuffer, - &(stream->inputFramesPerBuffer), - &(stream->outputFramesPerBuffer), + &inputFramesPerBuffer, + &outputFramesPerBuffer, auhalHostApi, &(stream->inputUnit), &(stream->inputSRConverter), &(stream->inputDevice), sampleRate, stream ); + stream->inputFramesPerBuffer = inputFramesPerBuffer; + stream->outputFramesPerBuffer = outputFramesPerBuffer; stream->outputUnit = stream->inputUnit; stream->outputDevice = stream->inputDevice; if( result != paNoError ) @@ -1377,11 +1450,14 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, } else { /* full duplex, different devices OR simplex */ - result = OpenAndSetupOneAudioUnit( NULL, + UInt32 outputFramesPerBuffer = (UInt32) stream->outputFramesPerBuffer; + UInt32 inputFramesPerBuffer = (UInt32) stream->inputFramesPerBuffer; + result = OpenAndSetupOneAudioUnit( stream, + NULL, outputParameters, framesPerBuffer, NULL, - &(stream->outputFramesPerBuffer), + &outputFramesPerBuffer, auhalHostApi, &(stream->outputUnit), NULL, @@ -1390,10 +1466,11 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, stream ); if( result != paNoError ) goto error; - result = OpenAndSetupOneAudioUnit( inputParameters, + result = OpenAndSetupOneAudioUnit( stream, + inputParameters, NULL, framesPerBuffer, - &(stream->inputFramesPerBuffer), + &inputFramesPerBuffer, NULL, auhalHostApi, &(stream->inputUnit), @@ -1403,6 +1480,8 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, stream ); if( result != paNoError ) goto error; + stream->inputFramesPerBuffer = inputFramesPerBuffer; + stream->outputFramesPerBuffer = outputFramesPerBuffer; } if( stream->inputUnit ) { @@ -1456,8 +1535,16 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, } /* now we can initialize the ring buffer */ + //FIXME: element size whould probably be szfl*inputchan + // but that will require some work all over the + // place to patch up. szfl may be sufficient and would + // be way easier to handle, but it seems clear from the + // discussion that buffer processor compatibility + // requires szfl*inputchan. + // See revision 1346 and discussion: + // http://techweb.rfa.org/pipermail/portaudio/2008-February/008295.html PaUtil_InitializeRingBuffer( &stream->inputRingBuffer, - ringSize*szfl, data ) ; + 1, ringSize*szfl, data ) ; /* advance the read point a little, so we are reading from the middle of the buffer */ if( stream->outputUnit ) @@ -1727,14 +1814,14 @@ static OSStatus AudioIOProc( void *inRefCon, * we do not use the input SR converter or the input ring buffer. * */ - OSErr err = 0; + OSStatus err = 0; unsigned long frames; /* -- start processing -- */ PaUtil_BeginBufferProcessing( &(stream->bufferProcessor), &timeInfo, stream->xrunFlags ); - stream->xrunFlags = 0; + stream->xrunFlags = 0; //FIXME: this flag also gets set outside by a callback, which calls the xrunCallback function. It should be in the same thread as the main audio callback, but the apple docs just use the word "usually" so it may be possible to loose an xrun notification, if that callback happens here. /* -- compute frames. do some checks -- */ assert( ioData->mNumberBuffers == 1 ); @@ -1748,7 +1835,8 @@ static OSStatus AudioIOProc( void *inRefCon, INPUT_ELEMENT, inNumberFrames, &stream->inputAudioBufferList ); - /* FEEDBACK: I'm not sure what to do when this call fails */ + /* FEEDBACK: I'm not sure what to do when this call fails. There's nothing in the PA API to + * do about failures in the callback system. */ assert( !err ); PaUtil_SetInputFrameCount( &(stream->bufferProcessor), frames ); @@ -1868,7 +1956,7 @@ static OSStatus AudioIOProc( void *inRefCon, PaUtil_AdvanceRingBufferReadIndex(&stream->inputRingBuffer, size1 ); } else if( ( size1 + size2 ) / ( flsz * inChan ) < frames ) { /*we underflowed. take what data we can, zero the rest.*/ - float data[frames*inChan]; + unsigned char data[frames*inChan*flsz]; if( size1 ) memcpy( data, data1, size1 ); if( size2 ) @@ -1922,7 +2010,7 @@ static OSStatus AudioIOProc( void *inRefCon, * if this is an input-only stream, we need to process it more, * otherwise, we let the output case deal with it. */ - OSErr err = 0; + OSStatus err = 0; int chan = stream->inputAudioBufferList.mBuffers[0].mNumberChannels ; /* FIXME: looping here may not actually be necessary, but it was something I tried in testing. */ do { @@ -2054,6 +2142,24 @@ static PaError CloseStream( PaStream* s ) VDBUG( ( "Closing stream.\n" ) ); if( stream ) { + if( stream->outputUnit ) { + int count = removeFromXRunListenerList( stream ); + if( count == 0 ) + AudioDeviceRemovePropertyListener( stream->outputDevice, + 0, + false, + kAudioDeviceProcessorOverload, + xrunCallback ); + } + if( stream->inputUnit && stream->outputUnit != stream->inputUnit ) { + int count = removeFromXRunListenerList( stream ); + if( count == 0 ) + AudioDeviceRemovePropertyListener( stream->inputDevice, + 0, + true, + kAudioDeviceProcessorOverload, + xrunCallback ); + } if( stream->outputUnit && stream->outputUnit != stream->inputUnit ) { AudioUnitUninitialize( stream->outputUnit ); CloseComponent( stream->outputUnit ); @@ -2089,11 +2195,10 @@ static PaError CloseStream( PaStream* s ) return result; } - static PaError StartStream( PaStream *s ) { PaMacCoreStream *stream = (PaMacCoreStream*)s; - OSErr result = noErr; + OSStatus result = noErr; VVDBUG(("StartStream()\n")); VDBUG( ( "Starting stream.\n" ) ); @@ -2138,7 +2243,7 @@ static ComponentResult BlockWhileAudioUnitIsRunning( AudioUnit audioUnit, AudioU static PaError StopStream( PaStream *s ) { PaMacCoreStream *stream = (PaMacCoreStream*)s; - OSErr result = noErr; + OSStatus result = noErr; PaError paErr; VVDBUG(("StopStream()\n")); diff --git a/pd/portaudio/src/hostapi/coreaudio/pa_mac_core_blocking.c b/pd/portaudio/src/hostapi/coreaudio/pa_mac_core_blocking.c index 3b81389d..6d31a713 100644 --- a/pd/portaudio/src/hostapi/coreaudio/pa_mac_core_blocking.c +++ b/pd/portaudio/src/hostapi/coreaudio/pa_mac_core_blocking.c @@ -53,7 +53,7 @@ /** @file - @ingroup hostaip_src + @ingroup hostapi_src This file contains the implementation required for blocking I/O. It is separated from pa_mac_core.c simply to ease @@ -85,6 +85,22 @@ static size_t computeSampleSizeFromFormat( PaSampleFormat format ) default: return 0; } } +/* + * Same as computeSampleSizeFromFormat, except that if + * the size is not a power of two, it returns the next power of two up + */ +static size_t computeSampleSizeFromFormatPow2( PaSampleFormat format ) +{ + switch( format ) { + case paFloat32: return 4; + case paInt32: return 4; + case paInt24: return 4; + case paInt16: return 2; + case paInt8: case paUInt8: return 1; + default: return 0; + } +} + /* @@ -105,6 +121,7 @@ PaError initializeBlioRingBuffers( { void *data; int result; + OSStatus err; /* zeroify things */ bzero( blio, sizeof( PaMacBlio ) ); @@ -114,10 +131,14 @@ PaError initializeBlioRingBuffers( blio->outputRingBuffer.buffer = NULL; /* initialize simple data */ + blio->ringBufferFrames = ringBufferSize; blio->inputSampleFormat = inputSampleFormat; - blio->inputSampleSize = computeSampleSizeFromFormat(inputSampleFormat); + blio->inputSampleSizeActual = computeSampleSizeFromFormat(inputSampleFormat); + blio->inputSampleSizePow2 = computeSampleSizeFromFormatPow2(inputSampleFormat); blio->outputSampleFormat = outputSampleFormat; - blio->outputSampleSize = computeSampleSizeFromFormat(outputSampleFormat); + blio->outputSampleSizeActual = computeSampleSizeFromFormat(outputSampleFormat); + blio->outputSampleSizePow2 = computeSampleSizeFromFormatPow2(outputSampleFormat); + blio->framesPerBuffer = framesPerBuffer; blio->inChan = inChan; blio->outChan = outChan; @@ -142,30 +163,32 @@ PaError initializeBlioRingBuffers( result = UNIX_ERR( pthread_cond_init( &(blio->outputCond), NULL ) ); #endif if( inChan ) { - data = calloc( ringBufferSize, blio->inputSampleSize ); + data = calloc( ringBufferSize, blio->inputSampleSizePow2*inChan ); if( !data ) { result = paInsufficientMemory; goto error; } - assert( 0 == PaUtil_InitializeRingBuffer( + err = PaUtil_InitializeRingBuffer( &blio->inputRingBuffer, - ringBufferSize*blio->inputSampleSize, - data ) ); + 1, ringBufferSize*blio->inputSampleSizePow2*inChan, + data ); + assert( !err ); } if( outChan ) { - data = calloc( ringBufferSize, blio->outputSampleSize ); + data = calloc( ringBufferSize, blio->outputSampleSizePow2*outChan ); if( !data ) { result = paInsufficientMemory; goto error; } - assert( 0 == PaUtil_InitializeRingBuffer( + err = PaUtil_InitializeRingBuffer( &blio->outputRingBuffer, - ringBufferSize*blio->outputSampleSize, - data ) ); + 1, ringBufferSize*blio->outputSampleSizePow2*outChan, + data ); + assert( !err ); } result = resetBlioRingBuffers( blio ); @@ -247,7 +270,8 @@ PaError resetBlioRingBuffers( PaMacBlio *blio ) bzero( blio->outputRingBuffer.buffer, blio->outputRingBuffer.bufferSize ); /* Advance buffer */ - PaUtil_AdvanceRingBufferWriteIndex( &blio->outputRingBuffer, blio->outputRingBuffer.bufferSize ); + PaUtil_AdvanceRingBufferWriteIndex( &blio->outputRingBuffer, blio->ringBufferFrames*blio->outputSampleSizeActual*blio->outChan ); + //PaUtil_AdvanceRingBufferWriteIndex( &blio->outputRingBuffer, blio->outputRingBuffer.bufferSize ); /* Update isOutputFull. */ #ifdef PA_MAC__BLIO_MUTEX @@ -323,6 +347,8 @@ int BlioCallback( const void *input, void *output, unsigned long frameCount, long avail; long toRead; long toWrite; + long read; + long written; /* set flags returned by OS: */ OSAtomicOr32( statusFlags, &blio->statusFlags ) ; @@ -332,14 +358,15 @@ int BlioCallback( const void *input, void *output, unsigned long frameCount, avail = PaUtil_GetRingBufferWriteAvailable( &blio->inputRingBuffer ); /* check for underflow */ - if( avail < frameCount * blio->inputSampleSize * blio->inChan ) + if( avail < frameCount * blio->inputSampleSizeActual * blio->inChan ) OSAtomicOr32( paInputOverflow, &blio->statusFlags ); - toRead = MIN( avail, frameCount * blio->inputSampleSize * blio->inChan ); + toRead = MIN( avail, frameCount * blio->inputSampleSizeActual * blio->inChan ); /* copy the data */ /*printf( "reading %d\n", toRead );*/ - assert( toRead == PaUtil_WriteRingBuffer( &blio->inputRingBuffer, input, toRead ) ); + read = PaUtil_WriteRingBuffer( &blio->inputRingBuffer, input, toRead ); + assert( toRead == read ); #ifdef PA_MAC__BLIO_MUTEX /* Priority inversion. See notes below. */ blioSetIsInputEmpty( blio, false ); @@ -352,17 +379,18 @@ int BlioCallback( const void *input, void *output, unsigned long frameCount, avail = PaUtil_GetRingBufferReadAvailable( &blio->outputRingBuffer ); /* check for underflow */ - if( avail < frameCount * blio->outputSampleSize * blio->outChan ) + if( avail < frameCount * blio->outputSampleSizeActual * blio->outChan ) OSAtomicOr32( paOutputUnderflow, &blio->statusFlags ); - toWrite = MIN( avail, frameCount * blio->outputSampleSize * blio->outChan ); + toWrite = MIN( avail, frameCount * blio->outputSampleSizeActual * blio->outChan ); - if( toWrite != frameCount * blio->outputSampleSize * blio->outChan ) + if( toWrite != frameCount * blio->outputSampleSizeActual * blio->outChan ) bzero( ((char *)output)+toWrite, - frameCount * blio->outputSampleSize * blio->outChan - toWrite ); + frameCount * blio->outputSampleSizeActual * blio->outChan - toWrite ); /* copy the data */ /*printf( "writing %d\n", toWrite );*/ - assert( toWrite == PaUtil_ReadRingBuffer( &blio->outputRingBuffer, output, toWrite ) ); + written = PaUtil_ReadRingBuffer( &blio->outputRingBuffer, output, toWrite ); + assert( toWrite == written ); #ifdef PA_MAC__BLIO_MUTEX /* We have a priority inversion here. However, we will only have to wait if this was true and is now false, which means we've got @@ -413,11 +441,11 @@ PaError ReadStream( PaStream* stream, #endif } } while( avail == 0 ); - toRead = MIN( avail, frames * blio->inputSampleSize * blio->inChan ); - toRead -= toRead % blio->inputSampleSize * blio->inChan ; + toRead = MIN( avail, frames * blio->inputSampleSizeActual * blio->inChan ); + toRead -= toRead % blio->inputSampleSizeActual * blio->inChan ; PaUtil_ReadRingBuffer( &blio->inputRingBuffer, (void *)cbuf, toRead ); cbuf += toRead; - frames -= toRead / ( blio->inputSampleSize * blio->inChan ); + frames -= toRead / ( blio->inputSampleSizeActual * blio->inChan ); if( toRead == avail ) { #ifdef PA_MAC_BLIO_MUTEX @@ -443,7 +471,7 @@ PaError ReadStream( PaStream* stream, /* report underflow only once: */ if( ret ) { - OSAtomicAnd32( ~paInputOverflow, &blio->statusFlags ); + OSAtomicAnd32( (uint32_t)(~paInputOverflow), &blio->statusFlags ); ret = paInputOverflowed; } @@ -491,11 +519,11 @@ PaError WriteStream( PaStream* stream, } } while( avail == 0 ); - toWrite = MIN( avail, frames * blio->outputSampleSize * blio->outChan ); - toWrite -= toWrite % blio->outputSampleSize * blio->outChan ; + toWrite = MIN( avail, frames * blio->outputSampleSizeActual * blio->outChan ); + toWrite -= toWrite % blio->outputSampleSizeActual * blio->outChan ; PaUtil_WriteRingBuffer( &blio->outputRingBuffer, (void *)cbuf, toWrite ); cbuf += toWrite; - frames -= toWrite / ( blio->outputSampleSize * blio->outChan ); + frames -= toWrite / ( blio->outputSampleSizeActual * blio->outChan ); #ifdef PA_MAC_BLIO_MUTEX if( toWrite == avail ) { @@ -520,7 +548,7 @@ PaError WriteStream( PaStream* stream, /* report underflow only once: */ if( ret ) { - OSAtomicAnd32( ~paOutputUnderflow, &blio->statusFlags ); + OSAtomicAnd32( (uint32_t)(~paOutputUnderflow), &blio->statusFlags ); ret = paOutputUnderflowed; } @@ -549,7 +577,7 @@ signed long GetStreamReadAvailable( PaStream* stream ) VVDBUG(("GetStreamReadAvailable()\n")); return PaUtil_GetRingBufferReadAvailable( &blio->inputRingBuffer ) - / ( blio->outputSampleSize * blio->outChan ); + / ( blio->inputSampleSizeActual * blio->inChan ); } @@ -559,6 +587,6 @@ signed long GetStreamWriteAvailable( PaStream* stream ) VVDBUG(("GetStreamWriteAvailable()\n")); return PaUtil_GetRingBufferWriteAvailable( &blio->outputRingBuffer ) - / ( blio->outputSampleSize * blio->outChan ); + / ( blio->outputSampleSizeActual * blio->outChan ); } diff --git a/pd/portaudio/src/hostapi/coreaudio/pa_mac_core_blocking.h b/pd/portaudio/src/hostapi/coreaudio/pa_mac_core_blocking.h index 8ad79eaa..971223b3 100644 --- a/pd/portaudio/src/hostapi/coreaudio/pa_mac_core_blocking.h +++ b/pd/portaudio/src/hostapi/coreaudio/pa_mac_core_blocking.h @@ -53,7 +53,7 @@ /** @file - @ingroup hostaip_src + @ingroup hostapi_src */ #ifndef PA_MAC_CORE_BLOCKING_H_ @@ -79,10 +79,13 @@ typedef struct { PaUtilRingBuffer inputRingBuffer; PaUtilRingBuffer outputRingBuffer; + size_t ringBufferFrames; PaSampleFormat inputSampleFormat; - size_t inputSampleSize; + size_t inputSampleSizeActual; + size_t inputSampleSizePow2; PaSampleFormat outputSampleFormat; - size_t outputSampleSize; + size_t outputSampleSizeActual; + size_t outputSampleSizePow2; size_t framesPerBuffer; diff --git a/pd/portaudio/src/hostapi/coreaudio/pa_mac_core_internal.h b/pd/portaudio/src/hostapi/coreaudio/pa_mac_core_internal.h index 998b819c..1797cbaf 100644 --- a/pd/portaudio/src/hostapi/coreaudio/pa_mac_core_internal.h +++ b/pd/portaudio/src/hostapi/coreaudio/pa_mac_core_internal.h @@ -61,10 +61,11 @@ #ifndef PA_MAC_CORE_INTERNAL_H__ #define PA_MAC_CORE_INTERNAL_H__ +#include +#include #include #include - #include "portaudio.h" #include "pa_util.h" #include "pa_hostapi.h" @@ -139,6 +140,7 @@ typedef struct PaMacCoreStream /* We need to preallocate an inputBuffer for reading data. */ AudioBufferList inputAudioBufferList; AudioTimeStamp startTime; + /* FIXME: instead of volatile, these should be properly memory barriered */ volatile PaStreamCallbackFlags xrunFlags; volatile bool isTimeSet; volatile enum { @@ -146,7 +148,8 @@ typedef struct PaMacCoreStream and the user has called StopStream(). */ CALLBACK_STOPPED = 1, /* callback has requested stop, but user has not yet called StopStream(). */ - STOPPING = 2, /* The stream is in the process of closing. + STOPPING = 2, /* The stream is in the process of closing + because the user has called StopStream. This state is just used internally; externally it is indistinguishable from ACTIVE.*/ diff --git a/pd/portaudio/src/hostapi/coreaudio/pa_mac_core_utilities.c b/pd/portaudio/src/hostapi/coreaudio/pa_mac_core_utilities.c index 37403251..5bc592e8 100644 --- a/pd/portaudio/src/hostapi/coreaudio/pa_mac_core_utilities.c +++ b/pd/portaudio/src/hostapi/coreaudio/pa_mac_core_utilities.c @@ -57,6 +57,10 @@ */ #include "pa_mac_core_utilities.h" +#include "pa_mac_core_internal.h" +#include +#include +#include PaError PaMacCore_SetUnixError( int err, int line ) { @@ -199,10 +203,19 @@ PaError PaMacCore_SetError(OSStatus error, int line, int isError) else errorType = "Warning"; - if ((int)error < -99999 || (int)error > 99999) - DBUG(("%s on line %d: err='%4s', msg='%s'\n", errorType, line, (const char *)&error, errorText)); - else - DBUG(("%s on line %d: err=%d, 0x%x, msg='%s'\n", errorType, line, (int)error, (unsigned)error, errorText)); + char str[20]; + // see if it appears to be a 4-char-code + *(UInt32 *)(str + 1) = CFSwapInt32HostToBig(error); + if (isprint(str[1]) && isprint(str[2]) && isprint(str[3]) && isprint(str[4])) + { + str[0] = str[5] = '\''; + str[6] = '\0'; + } else { + // no, format it as an integer + sprintf(str, "%d", (int)error); + } + + DBUG(("%s on line %d: err='%s', msg=%s\n", errorType, line, str, errorText)); PaUtil_SetLastHostErrorInfo( paCoreAudio, error, errorText ); @@ -520,9 +533,9 @@ PaError setBestSampleRateForDevice( const AudioDeviceID device, not usually catastrophic. */ PaError setBestFramesPerBuffer( const AudioDeviceID device, - const bool isOutput, - unsigned long requestedFramesPerBuffer, - unsigned long *actualFramesPerBuffer ) + const bool isOutput, + UInt32 requestedFramesPerBuffer, + UInt32 *actualFramesPerBuffer ) { UInt32 afpb; const bool isInput = !isOutput; @@ -609,3 +622,117 @@ PaError setBestFramesPerBuffer( const AudioDeviceID device, return paNoError; } + +/********************** + * + * XRun stuff + * + **********************/ + +struct PaMacXRunListNode_s { + PaMacCoreStream *stream; + struct PaMacXRunListNode_s *next; +} ; + +typedef struct PaMacXRunListNode_s PaMacXRunListNode; + +/** Always empty, so that it can always be the one returned by + addToXRunListenerList. note that it's not a pointer. */ +static PaMacXRunListNode firstXRunListNode; +static int xRunListSize; +static pthread_mutex_t xrunMutex; + +OSStatus xrunCallback( + AudioDeviceID inDevice, + UInt32 inChannel, + Boolean isInput, + AudioDevicePropertyID inPropertyID, + void* inClientData) +{ + PaMacXRunListNode *node = (PaMacXRunListNode *) inClientData; + + int ret = pthread_mutex_trylock( &xrunMutex ) ; + + if( ret == 0 ) { + + node = node->next ; //skip the first node + + for( ; node; node=node->next ) { + PaMacCoreStream *stream = node->stream; + + if( stream->state != ACTIVE ) + continue; //if the stream isn't active, we don't care if the device is dropping + + if( isInput ) { + if( stream->inputDevice == inDevice ) + OSAtomicOr32( paInputOverflow, (uint32_t *)&(stream->xrunFlags) ); + } else { + if( stream->outputDevice == inDevice ) + OSAtomicOr32( paOutputUnderflow, (uint32_t *)&(stream->xrunFlags) ); + } + } + + pthread_mutex_unlock( &xrunMutex ); + } + + return 0; +} + +int initializeXRunListenerList() +{ + xRunListSize = 0; + bzero( (void *) &firstXRunListNode, sizeof(firstXRunListNode) ); + return pthread_mutex_init( &xrunMutex, NULL ); +} +int destroyXRunListenerList() +{ + PaMacXRunListNode *node; + node = firstXRunListNode.next; + while( node ) { + PaMacXRunListNode *tmp = node; + node = node->next; + free( tmp ); + } + xRunListSize = 0; + return pthread_mutex_destroy( &xrunMutex ); +} + +void *addToXRunListenerList( void *stream ) +{ + pthread_mutex_lock( &xrunMutex ); + PaMacXRunListNode *newNode; + // setup new node: + newNode = (PaMacXRunListNode *) malloc( sizeof( PaMacXRunListNode ) ); + newNode->stream = (PaMacCoreStream *) stream; + newNode->next = firstXRunListNode.next; + // insert: + firstXRunListNode.next = newNode; + pthread_mutex_unlock( &xrunMutex ); + + return &firstXRunListNode; +} + +int removeFromXRunListenerList( void *stream ) +{ + pthread_mutex_lock( &xrunMutex ); + PaMacXRunListNode *node, *prev; + prev = &firstXRunListNode; + node = firstXRunListNode.next; + while( node ) { + if( node->stream == stream ) { + //found it: + --xRunListSize; + prev->next = node->next; + free( node ); + pthread_mutex_unlock( &xrunMutex ); + return xRunListSize; + } + prev = prev->next; + node = node->next; + } + + pthread_mutex_unlock( &xrunMutex ); + // failure + return xRunListSize; +} + diff --git a/pd/portaudio/src/hostapi/coreaudio/pa_mac_core_utilities.h b/pd/portaudio/src/hostapi/coreaudio/pa_mac_core_utilities.h index 8a69c25a..899826d5 100644 --- a/pd/portaudio/src/hostapi/coreaudio/pa_mac_core_utilities.h +++ b/pd/portaudio/src/hostapi/coreaudio/pa_mac_core_utilities.h @@ -199,7 +199,32 @@ PaError setBestSampleRateForDevice( const AudioDeviceID device, not usually catastrophic. */ PaError setBestFramesPerBuffer( const AudioDeviceID device, - const bool isOutput, - unsigned long requestedFramesPerBuffer, - unsigned long *actualFramesPerBuffer ); + const bool isOutput, + UInt32 requestedFramesPerBuffer, + UInt32 *actualFramesPerBuffer ); + + +/********************* + * + * xrun handling + * + *********************/ + +OSStatus xrunCallback( + AudioDeviceID inDevice, + UInt32 inChannel, + Boolean isInput, + AudioDevicePropertyID inPropertyID, + void* inClientData ) ; + +/** returns zero on success or a unix style error code. */ +int initializeXRunListenerList(); +/** returns zero on success or a unix style error code. */ +int destroyXRunListenerList(); + +/**Returns the list, so that it can be passed to CorAudio.*/ +void *addToXRunListenerList( void *stream ); +/**Returns the number of Listeners in the list remaining.*/ +int removeFromXRunListenerList( void *stream ); + #endif /* PA_MAC_CORE_UTILITIES_H__*/ diff --git a/pd/portaudio/src/hostapi/jack/pa_jack.c b/pd/portaudio/src/hostapi/jack/pa_jack.c index 6b6c2120..6732c9b1 100644 --- a/pd/portaudio/src/hostapi/jack/pa_jack.c +++ b/pd/portaudio/src/hostapi/jack/pa_jack.c @@ -1,5 +1,5 @@ /* - * $Id: pa_jack.c 1238 2007-07-15 16:58:50Z aknudsen $ + * $Id: pa_jack.c 1346 2008-02-20 10:09:20Z rossb $ * PortAudio Portable Real-Time Audio Library * Latest Version at: http://www.portaudio.com * JACK Implementation by Joshua Haberman @@ -254,7 +254,7 @@ static PaError BlockingInitFIFO( PaUtilRingBuffer *rbuf, long numFrames, long by char *buffer = (char *) malloc( numBytes ); if( buffer == NULL ) return paInsufficientMemory; memset( buffer, 0, numBytes ); - return (PaError) PaUtil_InitializeRingBuffer( rbuf, numBytes, buffer ); + return (PaError) PaUtil_InitializeRingBuffer( rbuf, 1, numBytes, buffer ); } /* Free buffer. */ @@ -717,14 +717,19 @@ PaError PaJack_Initialize( PaUtilHostApiRepresentation **hostApi, ASSERT_CALL( pthread_cond_init( &jackHostApi->cond, NULL ), 0 ); /* Try to become a client of the JACK server. If we cannot do - * this, then this API cannot be used. */ + * this, then this API cannot be used. + * + * Without the JackNoStartServer option, the jackd server is started + * automatically which we do not want. + */ - jackHostApi->jack_client = jack_client_open( clientName_, 0, &jackStatus ); + jackHostApi->jack_client = jack_client_open( clientName_, JackNoStartServer, &jackStatus ); if( !jackHostApi->jack_client ) { /* the V19 development docs say that if an implementation * detects that it cannot be used, it should return a NULL * interface and paNoError */ + PA_DEBUG(( "%s: Couldn't connect to JACK, status: %d\n", __FUNCTION__, jackStatus )); result = paNoError; goto error; } @@ -737,7 +742,6 @@ PaError PaJack_Initialize( PaUtilHostApiRepresentation **hostApi, (*hostApi)->info.name = "JACK Audio Connection Kit"; /* Build a device list by querying the JACK server */ - ENSURE_PA( BuildDeviceList( jackHostApi ) ); /* Register functions */ @@ -1748,7 +1752,8 @@ PaError PaJack_GetClientName(const char** clientName) { PaError result = paNoError; PaJackHostApiRepresentation* jackHostApi = NULL; - ENSURE_PA( PaUtil_GetHostApiRepresentation( (PaUtilHostApiRepresentation**)&jackHostApi, paJACK ) ); + PaJackHostApiRepresentation** ref = &jackHostApi; + ENSURE_PA( PaUtil_GetHostApiRepresentation( (PaUtilHostApiRepresentation**)ref, paJACK ) ); *clientName = jack_get_client_name( jackHostApi->jack_client ); error: diff --git a/pd/portaudio/src/hostapi/oss/pa_unix_oss.c b/pd/portaudio/src/hostapi/oss/pa_unix_oss.c index 516f5a45..8d8424fc 100644 --- a/pd/portaudio/src/hostapi/oss/pa_unix_oss.c +++ b/pd/portaudio/src/hostapi/oss/pa_unix_oss.c @@ -1,5 +1,5 @@ /* - * $Id: pa_unix_oss.c 1238 2007-07-15 16:58:50Z aknudsen $ + * $Id: pa_unix_oss.c 1385 2008-06-05 21:13:54Z aknudsen $ * PortAudio Portable Real-Time Audio Library * Latest Version at: http://www.portaudio.com * OSS implementation by: @@ -65,7 +65,11 @@ #ifdef HAVE_SYS_SOUNDCARD_H # include -# define DEVICE_NAME_BASE "/dev/dsp" +# ifdef __NetBSD__ +# define DEVICE_NAME_BASE "/dev/audio" +# else +# define DEVICE_NAME_BASE "/dev/dsp" +# endif #elif defined(HAVE_LINUX_SOUNDCARD_H) # include # define DEVICE_NAME_BASE "/dev/dsp" @@ -97,7 +101,7 @@ static pthread_t mainThread_; /* PaUtil_SetLastHostErrorInfo should only be used in the main thread */ \ if( (code) == paUnanticipatedHostError && pthread_self() == mainThread_ ) \ { \ - PaUtil_SetLastHostErrorInfo( paALSA, sysErr_, strerror( errno ) ); \ + PaUtil_SetLastHostErrorInfo( paOSS, sysErr_, strerror( errno ) ); \ } \ \ PaUtil_DebugPrint(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \ @@ -332,7 +336,11 @@ static PaError QueryDirection( const char *deviceName, StreamMode mode, double * } else { - PA_DEBUG(( "%s: Can't access device: %s\n", __FUNCTION__, strerror( errno ) )); + /* Ignore ENOENT, which means we've tried a non-existent device */ + if( errno != ENOENT ) + { + PA_DEBUG(( "%s: Can't access device %s: %s\n", __FUNCTION__, deviceName, strerror( errno ) )); + } } return paDeviceUnavailable; @@ -401,11 +409,7 @@ static PaError QueryDirection( const char *deviceName, StreamMode mode, double * if( *defaultSampleRate < 0 ) { sr = 44100; - if( ioctl( devHandle, SNDCTL_DSP_SPEED, &sr ) < 0 ) - { - result = paUnanticipatedHostError; - goto error; - } + ENSURE_( ioctl( devHandle, SNDCTL_DSP_SPEED, &sr ), paUnanticipatedHostError ); *defaultSampleRate = sr; } @@ -508,27 +512,20 @@ static PaError BuildDeviceList( PaOSSHostApiRepresentation *ossApi ) /* Find devices by calling QueryDevice on each * potential device names. When we find a valid one, * add it to a linked list. - * A: Can there only be 10 devices? */ + * A: Set an arbitrary of 100 devices, should probably be a smarter way. */ - for( i = 0; i < 10; i++ ) + for( i = 0; i < 100; i++ ) { char deviceName[32]; PaDeviceInfo *deviceInfo; int testResult; - struct stat stbuf; if( i == 0 ) snprintf(deviceName, sizeof (deviceName), "%s", DEVICE_NAME_BASE); else snprintf(deviceName, sizeof (deviceName), "%s%d", DEVICE_NAME_BASE, i); - /* PA_DEBUG(("PaOSS BuildDeviceList: trying device %s\n", deviceName )); */ - if( stat( deviceName, &stbuf ) < 0 ) - { - if( ENOENT != errno ) - PA_DEBUG(( "%s: Error stat'ing %s: %s\n", __FUNCTION__, deviceName, strerror( errno ) )); - continue; - } + /* PA_DEBUG(("%s: trying device %s\n", __FUNCTION__, deviceName )); */ if( (testResult = QueryDevice( deviceName, ossApi, &deviceInfo )) != paNoError ) { if( testResult != paDeviceUnavailable ) @@ -785,11 +782,15 @@ error: return result; } +/** Open input and output devices. + * + * @param idev: Returned input device file descriptor. + * @param odev: Returned output device file descriptor. + */ static PaError OpenDevices( const char *idevName, const char *odevName, int *idev, int *odev ) { PaError result = paNoError; int flags = O_NONBLOCK, duplex = 0; - int enableBits = 0; *idev = *odev = -1; if( idevName && odevName ) @@ -809,10 +810,6 @@ static PaError OpenDevices( const char *idevName, const char *odevName, int *ide { ENSURE_( *idev = open( idevName, flags ), paDeviceUnavailable ); PA_ENSURE( ModifyBlocking( *idev, 1 ) ); /* Blocking */ - - /* Initially disable */ - enableBits = ~PCM_ENABLE_INPUT; - ENSURE_( ioctl( *idev, SNDCTL_DSP_SETTRIGGER, &enableBits ), paUnanticipatedHostError ); } if( odevName ) { @@ -820,10 +817,6 @@ static PaError OpenDevices( const char *idevName, const char *odevName, int *ide { ENSURE_( *odev = open( odevName, flags ), paDeviceUnavailable ); PA_ENSURE( ModifyBlocking( *odev, 1 ) ); /* Blocking */ - - /* Initially disable */ - enableBits = ~PCM_ENABLE_OUTPUT; - ENSURE_( ioctl( *odev, SNDCTL_DSP_SETTRIGGER, &enableBits ), paUnanticipatedHostError ); } else { @@ -976,8 +969,10 @@ static int CalcHigherLogTwo( int n ) return log2; } -static PaError PaOssStreamComponent_Configure( PaOssStreamComponent *component, double sampleRate, unsigned long framesPerBuffer, - StreamMode streamMode, PaOssStreamComponent *master ) +/** Configure stream component device parameters. + */ +static PaError PaOssStreamComponent_Configure( PaOssStreamComponent *component, double sampleRate, unsigned long + framesPerBuffer, StreamMode streamMode, PaOssStreamComponent *master ) { PaError result = paNoError; int temp, nativeFormat; @@ -1189,6 +1184,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, const PaDeviceInfo *inputDeviceInfo = 0, *outputDeviceInfo = 0; int bpInitialized = 0; double inLatency = 0., outLatency = 0.; + int i = 0; /* validate platform specific flags */ if( (streamFlags & paPlatformSpecificFlags) != 0 ) @@ -1225,6 +1221,14 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, } } + /* Round framesPerBuffer to the next power-of-two to make OSS happy. */ + if( framesPerBuffer != paFramesPerBufferUnspecified ) + { + framesPerBuffer &= INT_MAX; + for (i = 1; framesPerBuffer > i; i <<= 1) ; + framesPerBuffer = i; + } + /* allocate and do basic initialization of the stream structure */ PA_UNLESS( stream = (PaOssStream*)PaUtil_AllocateMemory( sizeof(PaOssStream) ), paInsufficientMemory ); PA_ENSURE( PaOssStream_Initialize( stream, inputParameters, outputParameters, streamCallback, userData, streamFlags, ossHostApi ) ); @@ -1428,6 +1432,12 @@ static PaError PaOssStream_Prepare( PaOssStream *stream ) if( stream->triggered ) return result; + /* The OSS reference instructs us to clear direction bits before setting them.*/ + if( stream->playback ) + ENSURE_( ioctl( stream->playback->fd, SNDCTL_DSP_SETTRIGGER, &enableBits ), paUnanticipatedHostError ); + if( stream->capture ) + ENSURE_( ioctl( stream->capture->fd, SNDCTL_DSP_SETTRIGGER, &enableBits ), paUnanticipatedHostError ); + if( stream->playback ) { size_t bufSz = PaOssStreamComponent_BufferSize( stream->playback ); @@ -1478,17 +1488,29 @@ static PaError PaOssStream_Stop( PaOssStream *stream, int abort ) PaError result = paNoError; /* Looks like the only safe way to stop audio without reopening the device is SNDCTL_DSP_POST. - * Also disable capture/playback till the stream is started again */ + * Also disable capture/playback till the stream is started again. + */ + int captureErr = 0, playbackErr = 0; if( stream->capture ) { - ENSURE_( ioctl( stream->capture->fd, SNDCTL_DSP_POST, 0 ), paUnanticipatedHostError ); + if( (captureErr = ioctl( stream->capture->fd, SNDCTL_DSP_POST, 0 )) < 0 ) + { + PA_DEBUG(( "%s: Failed to stop capture device, error: %d\n", __FUNCTION__, captureErr )); + } } if( stream->playback && !stream->sharedDevice ) { - ENSURE_( ioctl( stream->playback->fd, SNDCTL_DSP_POST, 0 ), paUnanticipatedHostError ); + if( (playbackErr = ioctl( stream->playback->fd, SNDCTL_DSP_POST, 0 )) < 0 ) + { + PA_DEBUG(( "%s: Failed to stop playback device, error: %d\n", __FUNCTION__, playbackErr )); + } + } + + if( captureErr || playbackErr ) + { + result = paUnanticipatedHostError; } -error: return result; } @@ -1852,6 +1874,7 @@ static PaError ReadStream( PaStream* s, void *buffer, unsigned long frames ) { + PaError result = paNoError; PaOssStream *stream = (PaOssStream*)s; int bytesRequested, bytesRead; unsigned long framesRequested; @@ -1872,21 +1895,28 @@ static PaError ReadStream( PaStream* s, framesRequested = PA_MIN( frames, stream->capture->hostFrames ); bytesRequested = framesRequested * PaOssStreamComponent_FrameSize( stream->capture ); - bytesRead = read( stream->capture->fd, stream->capture->buffer, bytesRequested ); + ENSURE_( (bytesRead = read( stream->capture->fd, stream->capture->buffer, bytesRequested )), + paUnanticipatedHostError ); if ( bytesRequested != bytesRead ) + { + PA_DEBUG(( "Requested %d bytes, read %d\n", bytesRequested, bytesRead )); return paUnanticipatedHostError; + } PaUtil_SetInputFrameCount( &stream->bufferProcessor, stream->capture->hostFrames ); PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, 0, stream->capture->buffer, stream->capture->hostChannelCount ); PaUtil_CopyInput( &stream->bufferProcessor, &userBuffer, framesRequested ); frames -= framesRequested; } - return paNoError; + +error: + return result; } static PaError WriteStream( PaStream *s, const void *buffer, unsigned long frames ) { + PaError result = paNoError; PaOssStream *stream = (PaOssStream*)s; int bytesRequested, bytesWritten; unsigned long framesConverted; @@ -1912,35 +1942,50 @@ static PaError WriteStream( PaStream *s, const void *buffer, unsigned long frame frames -= framesConverted; bytesRequested = framesConverted * PaOssStreamComponent_FrameSize( stream->playback ); - bytesWritten = write( stream->playback->fd, stream->playback->buffer, bytesRequested ); + ENSURE_( (bytesWritten = write( stream->playback->fd, stream->playback->buffer, bytesRequested )), + paUnanticipatedHostError ); if ( bytesRequested != bytesWritten ) + { + PA_DEBUG(( "Requested %d bytes, wrote %d\n", bytesRequested, bytesWritten )); return paUnanticipatedHostError; + } } - return paNoError; + +error: + return result; } static signed long GetStreamReadAvailable( PaStream* s ) { + PaError result = paNoError; PaOssStream *stream = (PaOssStream*)s; audio_buf_info info; - if( ioctl( stream->capture->fd, SNDCTL_DSP_GETISPACE, &info ) < 0 ) - return paUnanticipatedHostError; + ENSURE_( ioctl( stream->capture->fd, SNDCTL_DSP_GETISPACE, &info ), paUnanticipatedHostError ); return info.fragments * stream->capture->hostFrames; + +error: + return result; } /* TODO: Compute number of allocated bytes somewhere else, can we use ODELAY with capture */ static signed long GetStreamWriteAvailable( PaStream* s ) { + PaError result = paNoError; PaOssStream *stream = (PaOssStream*)s; int delay = 0; - - if( ioctl( stream->playback->fd, SNDCTL_DSP_GETODELAY, &delay ) < 0 ) - return paUnanticipatedHostError; - +#ifdef SNDCTL_DSP_GETODELAY + ENSURE_( ioctl( stream->playback->fd, SNDCTL_DSP_GETODELAY, &delay ), paUnanticipatedHostError ); +#endif return (PaOssStreamComponent_BufferSize( stream->playback ) - delay) / PaOssStreamComponent_FrameSize( stream->playback ); + +/* Conditionally compile this to avoid warning about unused label */ +#ifdef SNDCTL_DSP_GETODELAY +error: + return result; +#endif } diff --git a/pd/portaudio/src/hostapi/wmme/pa_win_wmme.c b/pd/portaudio/src/hostapi/wmme/pa_win_wmme.c index 3264ebf8..d2c13f99 100644 --- a/pd/portaudio/src/hostapi/wmme/pa_win_wmme.c +++ b/pd/portaudio/src/hostapi/wmme/pa_win_wmme.c @@ -1,5 +1,5 @@ /* - * $Id: pa_win_wmme.c 1229 2007-06-15 16:11:11Z rossb $ + * $Id: pa_win_wmme.c 1405 2009-03-08 08:10:55Z rossb $ * pa_win_wmme.c * Implementation of PortAudio for Windows MultiMedia Extensions (WMME) * @@ -62,7 +62,9 @@ */ /** @file - @ingroup hostaip_src + @ingroup hostapi_src + + @brief Win32 host API implementation for the Windows MultiMedia Extensions (WMME) audio API. @todo Fix buffer catch up code, can sometimes get stuck (perhaps fixed now, needs to be reviewed and tested.) @@ -88,6 +90,10 @@ Non-critical stuff for the future: @todo define UNICODE and _UNICODE in the project settings and see what breaks + @todo refactor conversion of MMSYSTEM errors into PA arrors into a single function. + + @todo cleanup WAVEFORMATEXTENSIBLE retry in InitializeWaveHandles to not use a for loop + */ /* @@ -133,6 +139,24 @@ Non-critical stuff for the future: #include "pa_debugprint.h" #include "pa_win_wmme.h" +#include "pa_win_waveformat.h" + +#ifdef PAWIN_USE_WDMKS_DEVICE_INFO +#include "pa_win_wdmks_utils.h" +#ifndef DRV_QUERYDEVICEINTERFACE +#define DRV_QUERYDEVICEINTERFACE (DRV_RESERVED + 12) +#endif +#ifndef DRV_QUERYDEVICEINTERFACESIZE +#define DRV_QUERYDEVICEINTERFACESIZE (DRV_RESERVED + 13) +#endif +#endif /* PAWIN_USE_WDMKS_DEVICE_INFO */ + +/* use CreateThread for CYGWIN, _beginthreadex for all others */ +#ifndef __CYGWIN__ +#define CREATE_THREAD (HANDLE)_beginthreadex( 0, 0, ProcessingThreadProc, stream, 0, &stream->processingThreadId ) +#else +#define CREATE_THREAD CreateThread( 0, 0, ProcessingThreadProc, stream, 0, &stream->processingThreadId ) +#endif #if (defined(UNDER_CE)) #pragma comment(lib, "Coredll.lib") @@ -374,6 +398,8 @@ typedef struct { PaDeviceInfo inheritedDeviceInfo; DWORD dwFormats; /**<< standard formats bitmask from the WAVEINCAPS and WAVEOUTCAPS structures */ + char deviceInputChannelCountIsKnown; /**<< if the system returns 0xFFFF then we don't really know the number of supported channels (1=>known, 0=>unknown)*/ + char deviceOutputChannelCountIsKnown; /**<< if the system returns 0xFFFF then we don't really know the number of supported channels (1=>known, 0=>unknown)*/ } PaWinMmeDeviceInfo; @@ -445,6 +471,21 @@ static UINT LocalDeviceIndexToWinMmeDeviceId( PaWinMmeHostApiRepresentation *hos } +static int SampleFormatAndWinWmmeSpecificFlagsToLinearWaveFormatTag( PaSampleFormat sampleFormat, unsigned long winMmeSpecificFlags ) +{ + int waveFormatTag = 0; + + if( winMmeSpecificFlags & paWinMmeWaveFormatDolbyAc3Spdif ) + waveFormatTag = PAWIN_WAVE_FORMAT_DOLBY_AC3_SPDIF; + else if( winMmeSpecificFlags & paWinMmeWaveFormatWmaSpdif ) + waveFormatTag = PAWIN_WAVE_FORMAT_WMA_SPDIF; + else + waveFormatTag = PaWin_SampleFormatToLinearWaveFormatTag( sampleFormat ); + + return waveFormatTag; +} + + static PaError QueryInputWaveFormatEx( int deviceId, WAVEFORMATEX *waveFormatEx ) { MMRESULT mmresult; @@ -499,41 +540,57 @@ static PaError QueryOutputWaveFormatEx( int deviceId, WAVEFORMATEX *waveFormatEx static PaError QueryFormatSupported( PaDeviceInfo *deviceInfo, PaError (*waveFormatExQueryFunction)(int, WAVEFORMATEX*), - int winMmeDeviceId, int channels, double sampleRate ) + int winMmeDeviceId, int channels, double sampleRate, unsigned long winMmeSpecificFlags ) { PaWinMmeDeviceInfo *winMmeDeviceInfo = (PaWinMmeDeviceInfo*)deviceInfo; - WAVEFORMATEX waveFormatEx; + PaWinWaveFormat waveFormat; + PaSampleFormat sampleFormat; + int waveFormatTag; - if( sampleRate == 11025.0 - && ( (channels == 1 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_1M16)) - || (channels == 2 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_1S16)) ) ){ + /* @todo at the moment we only query with 16 bit sample format and directout speaker config*/ - return paNoError; - } + sampleFormat = paInt16; + waveFormatTag = SampleFormatAndWinWmmeSpecificFlagsToLinearWaveFormatTag( sampleFormat, winMmeSpecificFlags ); - if( sampleRate == 22050.0 - && ( (channels == 1 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_2M16)) - || (channels == 2 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_2S16)) ) ){ + if( waveFormatTag == PaWin_SampleFormatToLinearWaveFormatTag( paInt16 ) ){ + + /* attempt bypass querying the device for linear formats */ - return paNoError; + if( sampleRate == 11025.0 + && ( (channels == 1 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_1M16)) + || (channels == 2 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_1S16)) ) ){ + + return paNoError; + } + + if( sampleRate == 22050.0 + && ( (channels == 1 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_2M16)) + || (channels == 2 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_2S16)) ) ){ + + return paNoError; + } + + if( sampleRate == 44100.0 + && ( (channels == 1 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_4M16)) + || (channels == 2 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_4S16)) ) ){ + + return paNoError; + } } - if( sampleRate == 44100.0 - && ( (channels == 1 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_4M16)) - || (channels == 2 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_4S16)) ) ){ + /* first, attempt to query the device using WAVEFORMATEXTENSIBLE, + if this fails we fall back to WAVEFORMATEX */ + + PaWin_InitializeWaveFormatExtensible( &waveFormat, channels, sampleFormat, waveFormatTag, + sampleRate, PAWIN_SPEAKER_DIRECTOUT ); + + if( waveFormatExQueryFunction( winMmeDeviceId, (WAVEFORMATEX*)&waveFormat ) == paNoError ) return paNoError; - } - waveFormatEx.wFormatTag = WAVE_FORMAT_PCM; - waveFormatEx.nChannels = (WORD)channels; - waveFormatEx.nSamplesPerSec = (DWORD)sampleRate; - waveFormatEx.nAvgBytesPerSec = waveFormatEx.nSamplesPerSec * channels * sizeof(short); - waveFormatEx.nBlockAlign = (WORD)(channels * sizeof(short)); - waveFormatEx.wBitsPerSample = 16; - waveFormatEx.cbSize = 0; + PaWin_InitializeWaveFormatEx( &waveFormat, channels, sampleFormat, waveFormatTag, sampleRate ); - return waveFormatExQueryFunction( winMmeDeviceId, &waveFormatEx ); + return waveFormatExQueryFunction( winMmeDeviceId, (WAVEFORMATEX*)&waveFormat ); } @@ -553,7 +610,7 @@ static void DetectDefaultSampleRate( PaWinMmeDeviceInfo *winMmeDeviceInfo, int w for( i=0; i < PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_; ++i ) { double sampleRate = defaultSampleRateSearchOrder_[ i ]; - PaError paerror = QueryFormatSupported( deviceInfo, waveFormatExQueryFunction, winMmeDeviceId, maxChannels, sampleRate ); + PaError paerror = QueryFormatSupported( deviceInfo, waveFormatExQueryFunction, winMmeDeviceId, maxChannels, sampleRate, 0 ); if( paerror == paNoError ) { deviceInfo->defaultSampleRate = sampleRate; @@ -563,6 +620,40 @@ static void DetectDefaultSampleRate( PaWinMmeDeviceInfo *winMmeDeviceInfo, int w } +#ifdef PAWIN_USE_WDMKS_DEVICE_INFO +static int QueryWaveInKSFilterMaxChannels( int waveInDeviceId, int *maxChannels ) +{ + void *devicePath; + DWORD devicePathSize; + int result = 0; + + if( waveInMessage((HWAVEIN)waveInDeviceId, DRV_QUERYDEVICEINTERFACESIZE, + (DWORD_PTR)&devicePathSize, 0 ) != MMSYSERR_NOERROR ) + return 0; + + devicePath = PaUtil_AllocateMemory( devicePathSize ); + if( !devicePath ) + return 0; + + /* apparently DRV_QUERYDEVICEINTERFACE returns a unicode interface path, although this is undocumented */ + if( waveInMessage((HWAVEIN)waveInDeviceId, DRV_QUERYDEVICEINTERFACE, + (DWORD_PTR)devicePath, devicePathSize ) == MMSYSERR_NOERROR ) + { + int count = PaWin_WDMKS_QueryFilterMaximumChannelCount( devicePath, /* isInput= */ 1 ); + if( count > 0 ) + { + *maxChannels = count; + result = 1; + } + } + + PaUtil_FreeMemory( devicePath ); + + return result; +} +#endif /* PAWIN_USE_WDMKS_DEVICE_INFO */ + + static PaError InitializeInputDeviceInfo( PaWinMmeHostApiRepresentation *winMmeHostApi, PaWinMmeDeviceInfo *winMmeDeviceInfo, UINT winMmeInputDeviceId, int *success ) { @@ -616,17 +707,30 @@ static PaError InitializeInputDeviceInfo( PaWinMmeHostApiRepresentation *winMmeH } deviceInfo->name = deviceName; - deviceInfo->maxInputChannels = wic.wChannels; - /* Sometimes a device can return a rediculously large number of channels. - * This happened with an SBLive card on a Windows ME box. - * If that happens, then force it to 2 channels. PLB20010413 + if( wic.wChannels == 0xFFFF || wic.wChannels < 1 || wic.wChannels > 255 ){ + /* For Windows versions using WDM (possibly Windows 98 ME and later) + * the kernel mixer sits between the application and the driver. As a result, + * wave*GetDevCaps often kernel mixer channel counts, which are unlimited. + * When this happens we assume the device is stereo and set a flag + * so that other channel counts can be tried with OpenStream -- i.e. when + * device*ChannelCountIsKnown is false, OpenStream will try whatever + * channel count you supply. + * see also InitializeOutputDeviceInfo() below. */ - if( (deviceInfo->maxInputChannels < 1) || (deviceInfo->maxInputChannels > 256) ) - { - PA_DEBUG(("Pa_GetDeviceInfo: Num input channels reported as %d! Changed to 2.\n", deviceInfo->maxInputChannels )); + + PA_DEBUG(("Pa_GetDeviceInfo: Num input channels reported as %d! Changed to 2.\n", wic.wChannels )); deviceInfo->maxInputChannels = 2; + winMmeDeviceInfo->deviceInputChannelCountIsKnown = 0; + }else{ + deviceInfo->maxInputChannels = wic.wChannels; + winMmeDeviceInfo->deviceInputChannelCountIsKnown = 1; } +#ifdef PAWIN_USE_WDMKS_DEVICE_INFO + winMmeDeviceInfo->deviceInputChannelCountIsKnown = + QueryWaveInKSFilterMaxChannels( winMmeInputDeviceId, &deviceInfo->maxInputChannels ); +#endif /* PAWIN_USE_WDMKS_DEVICE_INFO */ + winMmeDeviceInfo->dwFormats = wic.dwFormats; DetectDefaultSampleRate( winMmeDeviceInfo, winMmeInputDeviceId, @@ -639,6 +743,40 @@ error: } +#ifdef PAWIN_USE_WDMKS_DEVICE_INFO +static int QueryWaveOutKSFilterMaxChannels( int waveOutDeviceId, int *maxChannels ) +{ + void *devicePath; + DWORD devicePathSize; + int result = 0; + + if( waveOutMessage((HWAVEOUT)waveOutDeviceId, DRV_QUERYDEVICEINTERFACESIZE, + (DWORD_PTR)&devicePathSize, 0 ) != MMSYSERR_NOERROR ) + return 0; + + devicePath = PaUtil_AllocateMemory( devicePathSize ); + if( !devicePath ) + return 0; + + /* apparently DRV_QUERYDEVICEINTERFACE returns a unicode interface path, although this is undocumented */ + if( waveOutMessage((HWAVEOUT)waveOutDeviceId, DRV_QUERYDEVICEINTERFACE, + (DWORD_PTR)devicePath, devicePathSize ) == MMSYSERR_NOERROR ) + { + int count = PaWin_WDMKS_QueryFilterMaximumChannelCount( devicePath, /* isInput= */ 0 ); + if( count > 0 ) + { + *maxChannels = count; + result = 1; + } + } + + PaUtil_FreeMemory( devicePath ); + + return result; +} +#endif /* PAWIN_USE_WDMKS_DEVICE_INFO */ + + static PaError InitializeOutputDeviceInfo( PaWinMmeHostApiRepresentation *winMmeHostApi, PaWinMmeDeviceInfo *winMmeDeviceInfo, UINT winMmeOutputDeviceId, int *success ) { @@ -647,7 +785,7 @@ static PaError InitializeOutputDeviceInfo( PaWinMmeHostApiRepresentation *winMme MMRESULT mmresult; WAVEOUTCAPS woc; PaDeviceInfo *deviceInfo = &winMmeDeviceInfo->inheritedDeviceInfo; - + *success = 0; mmresult = waveOutGetDevCaps( winMmeOutputDeviceId, &woc, sizeof( WAVEOUTCAPS ) ); @@ -692,17 +830,30 @@ static PaError InitializeOutputDeviceInfo( PaWinMmeHostApiRepresentation *winMme } deviceInfo->name = deviceName; - deviceInfo->maxOutputChannels = woc.wChannels; - /* Sometimes a device can return a rediculously large number of channels. - * This happened with an SBLive card on a Windows ME box. - * It also happens on Win XP! + if( woc.wChannels == 0xFFFF || woc.wChannels < 1 || woc.wChannels > 255 ){ + /* For Windows versions using WDM (possibly Windows 98 ME and later) + * the kernel mixer sits between the application and the driver. As a result, + * wave*GetDevCaps often kernel mixer channel counts, which are unlimited. + * When this happens we assume the device is stereo and set a flag + * so that other channel counts can be tried with OpenStream -- i.e. when + * device*ChannelCountIsKnown is false, OpenStream will try whatever + * channel count you supply. + * see also InitializeInputDeviceInfo() above. */ - if( (deviceInfo->maxOutputChannels < 1) || (deviceInfo->maxOutputChannels > 256) ) - { - PA_DEBUG(("Pa_GetDeviceInfo: Num output channels reported as %d! Changed to 2.\n", deviceInfo->maxOutputChannels )); + + PA_DEBUG(("Pa_GetDeviceInfo: Num output channels reported as %d! Changed to 2.\n", woc.wChannels )); deviceInfo->maxOutputChannels = 2; + winMmeDeviceInfo->deviceOutputChannelCountIsKnown = 0; + }else{ + deviceInfo->maxOutputChannels = woc.wChannels; + winMmeDeviceInfo->deviceOutputChannelCountIsKnown = 1; } +#ifdef PAWIN_USE_WDMKS_DEVICE_INFO + winMmeDeviceInfo->deviceOutputChannelCountIsKnown = + QueryWaveOutKSFilterMaxChannels( winMmeOutputDeviceId, &deviceInfo->maxOutputChannels ); +#endif /* PAWIN_USE_WDMKS_DEVICE_INFO */ + winMmeDeviceInfo->dwFormats = woc.dwFormats; DetectDefaultSampleRate( winMmeDeviceInfo, winMmeOutputDeviceId, @@ -748,6 +899,8 @@ PaError PaWinMme_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd PaWinMmeDeviceInfo *deviceInfoArray; int deviceInfoInitializationSucceeded; PaTime defaultLowLatency, defaultHighLatency; + DWORD waveInPreferredDevice, waveOutPreferredDevice; + DWORD preferredDeviceStatusFlags; winMmeHostApi = (PaWinMmeHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaWinMmeHostApiRepresentation) ); if( !winMmeHostApi ) @@ -779,6 +932,19 @@ PaError PaWinMme_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd winMmeHostApi->inputDeviceCount = 0; winMmeHostApi->outputDeviceCount = 0; +#if !defined(DRVM_MAPPER_PREFERRED_GET) +/* DRVM_MAPPER_PREFERRED_GET is defined in mmddk.h but we avoid a dependency on the DDK by defining it here */ +#define DRVM_MAPPER_PREFERRED_GET (0x2000+21) +#endif + + /* the following calls assume that if wave*Message fails the preferred device parameter won't be modified */ + preferredDeviceStatusFlags = 0; + waveInPreferredDevice = -1; + waveInMessage( (HWAVEIN)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, (DWORD)&waveInPreferredDevice, (DWORD)&preferredDeviceStatusFlags ); + + preferredDeviceStatusFlags = 0; + waveOutPreferredDevice = -1; + waveOutMessage( (HWAVEOUT)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, (DWORD)&waveOutPreferredDevice, (DWORD)&preferredDeviceStatusFlags ); maximumPossibleDeviceCount = 0; @@ -830,7 +996,9 @@ PaError PaWinMme_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd deviceInfo->hostApi = hostApiIndex; deviceInfo->maxInputChannels = 0; + wmmeDeviceInfo->deviceInputChannelCountIsKnown = 1; deviceInfo->maxOutputChannels = 0; + wmmeDeviceInfo->deviceOutputChannelCountIsKnown = 1; deviceInfo->defaultLowInputLatency = defaultLowLatency; deviceInfo->defaultLowOutputLatency = defaultLowLatency; @@ -843,8 +1011,14 @@ PaError PaWinMme_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd goto error; if( deviceInfoInitializationSucceeded ){ - if( (*hostApi)->info.defaultInputDevice == paNoDevice ) + if( (*hostApi)->info.defaultInputDevice == paNoDevice ){ + /* if there is currently no default device, use the first one available */ + (*hostApi)->info.defaultInputDevice = (*hostApi)->info.deviceCount; + + }else if( winMmeDeviceId == waveInPreferredDevice ){ + /* set the default device to the system preferred device */ (*hostApi)->info.defaultInputDevice = (*hostApi)->info.deviceCount; + } winMmeHostApi->winMmeDeviceIds[ (*hostApi)->info.deviceCount ] = winMmeDeviceId; (*hostApi)->deviceInfos[ (*hostApi)->info.deviceCount ] = deviceInfo; @@ -865,7 +1039,9 @@ PaError PaWinMme_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd deviceInfo->hostApi = hostApiIndex; deviceInfo->maxInputChannels = 0; + wmmeDeviceInfo->deviceInputChannelCountIsKnown = 1; deviceInfo->maxOutputChannels = 0; + wmmeDeviceInfo->deviceOutputChannelCountIsKnown = 1; deviceInfo->defaultLowInputLatency = defaultLowLatency; deviceInfo->defaultLowOutputLatency = defaultLowLatency; @@ -878,8 +1054,14 @@ PaError PaWinMme_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd goto error; if( deviceInfoInitializationSucceeded ){ - if( (*hostApi)->info.defaultOutputDevice == paNoDevice ) + if( (*hostApi)->info.defaultOutputDevice == paNoDevice ){ + /* if there is currently no default device, use the first one available */ + (*hostApi)->info.defaultOutputDevice = (*hostApi)->info.deviceCount; + + }else if( winMmeDeviceId == waveOutPreferredDevice ){ + /* set the default device to the system preferred device */ (*hostApi)->info.defaultOutputDevice = (*hostApi)->info.deviceCount; + } winMmeHostApi->winMmeDeviceIds[ (*hostApi)->info.deviceCount ] = winMmeDeviceId; (*hostApi)->deviceInfos[ (*hostApi)->info.deviceCount ] = deviceInfo; @@ -891,7 +1073,6 @@ PaError PaWinMme_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd } } - InitializeDefaultDeviceIdsFromEnv( winMmeHostApi ); (*hostApi)->Terminate = Terminate; @@ -941,6 +1122,34 @@ static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) } +static PaError IsInputChannelCountSupported( PaWinMmeDeviceInfo* deviceInfo, int channelCount ) +{ + PaError result = paNoError; + + if( channelCount > 0 + && deviceInfo->deviceInputChannelCountIsKnown + && channelCount > deviceInfo->inheritedDeviceInfo.maxInputChannels ){ + + result = paInvalidChannelCount; + } + + return result; +} + +static PaError IsOutputChannelCountSupported( PaWinMmeDeviceInfo* deviceInfo, int channelCount ) +{ + PaError result = paNoError; + + if( channelCount > 0 + && deviceInfo->deviceOutputChannelCountIsKnown + && channelCount > deviceInfo->inheritedDeviceInfo.maxOutputChannels ){ + + result = paInvalidChannelCount; + } + + return result; +} + static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, const PaStreamParameters *inputParameters, const PaStreamParameters *outputParameters, @@ -985,13 +1194,19 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, inputDeviceInfo = hostApi->deviceInfos[ inputStreamInfo->devices[i].device ]; /* check that input device can support inputChannelCount */ - if( inputStreamInfo->devices[i].channelCount <= 0 - || inputStreamInfo->devices[i].channelCount > inputDeviceInfo->maxInputChannels ) + if( inputStreamInfo->devices[i].channelCount < 1 ) return paInvalidChannelCount; + paerror = IsInputChannelCountSupported( (PaWinMmeDeviceInfo*)inputDeviceInfo, + inputStreamInfo->devices[i].channelCount ); + if( paerror != paNoError ) + return paerror; + /* test for valid sample rate, see comment above */ winMmeInputDeviceId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, inputStreamInfo->devices[i].device ); - paerror = QueryFormatSupported( inputDeviceInfo, QueryInputWaveFormatEx, winMmeInputDeviceId, inputStreamInfo->devices[i].channelCount, sampleRate ); + paerror = QueryFormatSupported( inputDeviceInfo, QueryInputWaveFormatEx, + winMmeInputDeviceId, inputStreamInfo->devices[i].channelCount, sampleRate, + ((inputStreamInfo) ? inputStreamInfo->flags : 0) ); if( paerror != paNoError ) return paInvalidSampleRate; } @@ -1007,12 +1222,15 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, inputDeviceInfo = hostApi->deviceInfos[ inputParameters->device ]; /* check that input device can support inputChannelCount */ - if( inputChannelCount > inputDeviceInfo->maxInputChannels ) - return paInvalidChannelCount; + paerror = IsInputChannelCountSupported( (PaWinMmeDeviceInfo*)inputDeviceInfo, inputChannelCount ); + if( paerror != paNoError ) + return paerror; /* test for valid sample rate, see comment above */ winMmeInputDeviceId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, inputParameters->device ); - paerror = QueryFormatSupported( inputDeviceInfo, QueryInputWaveFormatEx, winMmeInputDeviceId, inputChannelCount, sampleRate ); + paerror = QueryFormatSupported( inputDeviceInfo, QueryInputWaveFormatEx, + winMmeInputDeviceId, inputChannelCount, sampleRate, + ((inputStreamInfo) ? inputStreamInfo->flags : 0) ); if( paerror != paNoError ) return paInvalidSampleRate; } @@ -1040,13 +1258,19 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, outputDeviceInfo = hostApi->deviceInfos[ outputStreamInfo->devices[i].device ]; /* check that output device can support outputChannelCount */ - if( outputStreamInfo->devices[i].channelCount <= 0 - || outputStreamInfo->devices[i].channelCount > outputDeviceInfo->maxOutputChannels ) + if( outputStreamInfo->devices[i].channelCount < 1 ) return paInvalidChannelCount; + paerror = IsOutputChannelCountSupported( (PaWinMmeDeviceInfo*)outputDeviceInfo, + outputStreamInfo->devices[i].channelCount ); + if( paerror != paNoError ) + return paerror; + /* test for valid sample rate, see comment above */ winMmeOutputDeviceId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, outputStreamInfo->devices[i].device ); - paerror = QueryFormatSupported( outputDeviceInfo, QueryOutputWaveFormatEx, winMmeOutputDeviceId, outputStreamInfo->devices[i].channelCount, sampleRate ); + paerror = QueryFormatSupported( outputDeviceInfo, QueryOutputWaveFormatEx, + winMmeOutputDeviceId, outputStreamInfo->devices[i].channelCount, sampleRate, + ((outputStreamInfo) ? outputStreamInfo->flags : 0) ); if( paerror != paNoError ) return paInvalidSampleRate; } @@ -1062,12 +1286,15 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, outputDeviceInfo = hostApi->deviceInfos[ outputParameters->device ]; /* check that output device can support outputChannelCount */ - if( outputChannelCount > outputDeviceInfo->maxOutputChannels ) - return paInvalidChannelCount; + paerror = IsOutputChannelCountSupported( (PaWinMmeDeviceInfo*)outputDeviceInfo, outputChannelCount ); + if( paerror != paNoError ) + return paerror; /* test for valid sample rate, see comment above */ winMmeOutputDeviceId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, outputParameters->device ); - paerror = QueryFormatSupported( outputDeviceInfo, QueryOutputWaveFormatEx, winMmeOutputDeviceId, outputChannelCount, sampleRate ); + paerror = QueryFormatSupported( outputDeviceInfo, QueryOutputWaveFormatEx, + winMmeOutputDeviceId, outputChannelCount, sampleRate, + ((outputStreamInfo) ? outputStreamInfo->flags : 0) ); if( paerror != paNoError ) return paInvalidSampleRate; } @@ -1491,9 +1718,10 @@ typedef struct static void InitializeSingleDirectionHandlesAndBuffers( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers ); static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostApi, PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, + unsigned long winMmeSpecificFlags, unsigned long bytesPerHostSample, double sampleRate, PaWinMmeDeviceAndChannelCount *devices, - unsigned int deviceCount, int isInput ); + unsigned int deviceCount, PaWinWaveFormatChannelMask channelMask, int isInput ); static PaError TerminateWaveHandles( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, int isInput, int currentlyProcessingAnError ); static PaError InitializeWaveHeaders( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, unsigned long hostBufferCount, @@ -1515,15 +1743,16 @@ static void InitializeSingleDirectionHandlesAndBuffers( PaWinMmeSingleDirectionH static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostApi, PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, + unsigned long winMmeSpecificFlags, unsigned long bytesPerHostSample, double sampleRate, PaWinMmeDeviceAndChannelCount *devices, - unsigned int deviceCount, int isInput ) + unsigned int deviceCount, PaWinWaveFormatChannelMask channelMask, int isInput ) { PaError result; MMRESULT mmresult; - unsigned long bytesPerFrame; - WAVEFORMATEX wfx; - signed int i; + signed int i, j; + PaSampleFormat sampleFormat; + int waveFormatTag; /* for error cleanup we expect that InitializeSingleDirectionHandlesAndBuffers() has already been called to zero some fields */ @@ -1551,64 +1780,98 @@ static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostA ((HWAVEOUT*)handlesAndBuffers->waveHandles)[i] = 0; } - wfx.wFormatTag = WAVE_FORMAT_PCM; - wfx.nSamplesPerSec = (DWORD) sampleRate; - wfx.cbSize = 0; - + /* @todo at the moment we only use 16 bit sample format */ + sampleFormat = paInt16; + waveFormatTag = SampleFormatAndWinWmmeSpecificFlagsToLinearWaveFormatTag( sampleFormat, winMmeSpecificFlags ); + for( i = 0; i < (signed int)deviceCount; ++i ) { - UINT winMmeDeviceId; + PaWinWaveFormat waveFormat; + UINT winMmeDeviceId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, devices[i].device ); + + /* @todo: consider providing a flag or #define to not try waveformat extensible + this could just initialize j to 1 the first time round. */ - winMmeDeviceId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, devices[i].device ); - wfx.nChannels = (WORD)devices[i].channelCount; + for( j = 0; j < 2; ++j ) + { + if( j == 0 ) + { + /* first, attempt to open the device using WAVEFORMATEXTENSIBLE, + if this fails we fall back to WAVEFORMATEX */ - bytesPerFrame = wfx.nChannels * bytesPerHostSample; + PaWin_InitializeWaveFormatExtensible( &waveFormat, devices[i].channelCount, + sampleFormat, waveFormatTag, sampleRate, channelMask ); - wfx.nAvgBytesPerSec = (DWORD)(bytesPerFrame * sampleRate); - wfx.nBlockAlign = (WORD)bytesPerFrame; - wfx.wBitsPerSample = (WORD)((bytesPerFrame/wfx.nChannels) * 8); + } + else + { + /* retry with WAVEFORMATEX */ - /* REVIEW: consider not firing an event for input when a full duplex - stream is being used. this would probably depend on the - neverDropInput flag. */ + PaWin_InitializeWaveFormatEx( &waveFormat, devices[i].channelCount, + sampleFormat, waveFormatTag, sampleRate ); + } - if( isInput ) - mmresult = waveInOpen( &((HWAVEIN*)handlesAndBuffers->waveHandles)[i], winMmeDeviceId, &wfx, + /* REVIEW: consider not firing an event for input when a full duplex + stream is being used. this would probably depend on the + neverDropInput flag. */ + + if( isInput ) + { + mmresult = waveInOpen( &((HWAVEIN*)handlesAndBuffers->waveHandles)[i], winMmeDeviceId, + (WAVEFORMATEX*)&waveFormat, (DWORD_PTR)handlesAndBuffers->bufferEvent, (DWORD_PTR)0, CALLBACK_EVENT ); - else - mmresult = waveOutOpen( &((HWAVEOUT*)handlesAndBuffers->waveHandles)[i], winMmeDeviceId, &wfx, + } + else + { + mmresult = waveOutOpen( &((HWAVEOUT*)handlesAndBuffers->waveHandles)[i], winMmeDeviceId, + (WAVEFORMATEX*)&waveFormat, (DWORD_PTR)handlesAndBuffers->bufferEvent, (DWORD_PTR)0, CALLBACK_EVENT ); + } - if( mmresult != MMSYSERR_NOERROR ) - { - switch( mmresult ) + if( mmresult == MMSYSERR_NOERROR ) { - case MMSYSERR_ALLOCATED: /* Specified resource is already allocated. */ - result = paDeviceUnavailable; - break; - case MMSYSERR_NODRIVER: /* No device driver is present. */ - result = paDeviceUnavailable; - break; - case MMSYSERR_NOMEM: /* Unable to allocate or lock memory. */ - result = paInsufficientMemory; - break; + break; /* success */ + } + else if( j == 0 ) + { + continue; /* try again with WAVEFORMATEX */ + } + else + { + switch( mmresult ) + { + case MMSYSERR_ALLOCATED: /* Specified resource is already allocated. */ + result = paDeviceUnavailable; + break; + case MMSYSERR_NODRIVER: /* No device driver is present. */ + result = paDeviceUnavailable; + break; + case MMSYSERR_NOMEM: /* Unable to allocate or lock memory. */ + result = paInsufficientMemory; + break; - case MMSYSERR_BADDEVICEID: /* Specified device identifier is out of range. */ - /* falls through */ - case WAVERR_BADFORMAT: /* Attempted to open with an unsupported waveform-audio format. */ - /* falls through */ - default: - result = paUnanticipatedHostError; - if( isInput ) - { - PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); - } - else - { - PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ); - } + case MMSYSERR_BADDEVICEID: /* Specified device identifier is out of range. */ + /* falls through */ + + case WAVERR_BADFORMAT: /* Attempted to open with an unsupported waveform-audio format. */ + /* This can also occur if we try to open the device with an unsupported + * number of channels. This is attempted when device*ChannelCountIsKnown is + * set to 0. + */ + /* falls through */ + default: + result = paUnanticipatedHostError; + if( isInput ) + { + PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); + } + else + { + PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ); + } + } + goto error; } - goto error; } } @@ -1850,6 +2113,7 @@ struct PaWinMmeStream static PaError ValidateWinMmeSpecificStreamInfo( const PaStreamParameters *streamParameters, const PaWinMmeStreamInfo *streamInfo, + unsigned long *winMmeSpecificFlags, char *throttleProcessingThreadOnOverload, unsigned long *deviceCount ) { @@ -1861,6 +2125,8 @@ static PaError ValidateWinMmeSpecificStreamInfo( return paIncompatibleHostApiSpecificStreamInfo; } + *winMmeSpecificFlags = streamInfo->flags; + if( streamInfo->flags & paWinMmeDontThrottleOverloadedProcessingThread ) *throttleProcessingThreadOnOverload = 0; @@ -1926,12 +2192,20 @@ static PaError ValidateInputChannelCounts( unsigned long deviceCount ) { unsigned int i; + PaWinMmeDeviceInfo *inputDeviceInfo; + PaError paerror; for( i=0; i < deviceCount; ++i ) { - if( devices[i].channelCount < 1 || devices[i].channelCount - > hostApi->deviceInfos[ devices[i].device ]->maxInputChannels ) + if( devices[i].channelCount < 1 ) return paInvalidChannelCount; + + inputDeviceInfo = + (PaWinMmeDeviceInfo*)hostApi->deviceInfos[ devices[i].device ]; + + paerror = IsInputChannelCountSupported( inputDeviceInfo, devices[i].channelCount ); + if( paerror != paNoError ) + return paerror; } return paNoError; @@ -1943,12 +2217,20 @@ static PaError ValidateOutputChannelCounts( unsigned long deviceCount ) { unsigned int i; + PaWinMmeDeviceInfo *outputDeviceInfo; + PaError paerror; for( i=0; i < deviceCount; ++i ) { - if( devices[i].channelCount < 1 || devices[i].channelCount - > hostApi->deviceInfos[ devices[i].device ]->maxOutputChannels ) + if( devices[i].channelCount < 1 ) return paInvalidChannelCount; + + outputDeviceInfo = + (PaWinMmeDeviceInfo*)hostApi->deviceInfos[ devices[i].device ]; + + paerror = IsOutputChannelCountSupported( outputDeviceInfo, devices[i].channelCount ); + if( paerror != paNoError ) + return paerror; } return paNoError; @@ -1981,14 +2263,17 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, PaSampleFormat inputSampleFormat, outputSampleFormat; double suggestedInputLatency, suggestedOutputLatency; PaWinMmeStreamInfo *inputStreamInfo, *outputStreamInfo; + PaWinWaveFormatChannelMask inputChannelMask, outputChannelMask; unsigned long framesPerHostInputBuffer; unsigned long hostInputBufferCount; unsigned long framesPerHostOutputBuffer; unsigned long hostOutputBufferCount; unsigned long framesPerBufferProcessorCall; PaWinMmeDeviceAndChannelCount *inputDevices = 0; /* contains all devices and channel counts as local host api ids, even when PaWinMmeUseMultipleDevices is not used */ + unsigned long winMmeSpecificInputFlags = 0; unsigned long inputDeviceCount = 0; PaWinMmeDeviceAndChannelCount *outputDevices = 0; + unsigned long winMmeSpecificOutputFlags = 0; unsigned long outputDeviceCount = 0; /* contains all devices and channel counts as local host api ids, even when PaWinMmeUseMultipleDevices is not used */ char throttleProcessingThreadOnOverload = 1; @@ -2004,6 +2289,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, /* validate input hostApiSpecificStreamInfo */ inputStreamInfo = (PaWinMmeStreamInfo*)inputParameters->hostApiSpecificStreamInfo; result = ValidateWinMmeSpecificStreamInfo( inputParameters, inputStreamInfo, + &winMmeSpecificInputFlags, &throttleProcessingThreadOnOverload, &inputDeviceCount ); if( result != paNoError ) return result; @@ -2019,6 +2305,18 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, hostInputSampleFormat = PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, inputSampleFormat ); + + if( inputDeviceCount != 1 ){ + /* always use direct speakers when using multi-device multichannel mode */ + inputChannelMask = PAWIN_SPEAKER_DIRECTOUT; + } + else + { + if( inputStreamInfo && inputStreamInfo->flags & paWinMmeUseChannelMask ) + inputChannelMask = inputStreamInfo->channelMask; + else + inputChannelMask = PaWin_DefaultChannelMask( inputDevices[0].channelCount ); + } } else { @@ -2041,6 +2339,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, /* validate output hostApiSpecificStreamInfo */ outputStreamInfo = (PaWinMmeStreamInfo*)outputParameters->hostApiSpecificStreamInfo; result = ValidateWinMmeSpecificStreamInfo( outputParameters, outputStreamInfo, + &winMmeSpecificOutputFlags, &throttleProcessingThreadOnOverload, &outputDeviceCount ); if( result != paNoError ) return result; @@ -2056,6 +2355,18 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, hostOutputSampleFormat = PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, outputSampleFormat ); + + if( outputDeviceCount != 1 ){ + /* always use direct speakers when using multi-device multichannel mode */ + outputChannelMask = PAWIN_SPEAKER_DIRECTOUT; + } + else + { + if( outputStreamInfo && outputStreamInfo->flags & paWinMmeUseChannelMask ) + outputChannelMask = outputStreamInfo->channelMask; + else + outputChannelMask = PaWin_DefaultChannelMask( outputDevices[0].channelCount ); + } } else { @@ -2078,6 +2389,14 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, return paInvalidFlag; /* unexpected platform specific flag */ + /* always disable clipping and dithering if we are outputting a raw spdif stream */ + if( (winMmeSpecificOutputFlags & paWinMmeWaveFormatDolbyAc3Spdif) + || (winMmeSpecificOutputFlags & paWinMmeWaveFormatWmaSpdif) ){ + + streamFlags = streamFlags | paClipOff | paDitherOff; + } + + result = CalculateBufferSettings( &framesPerHostInputBuffer, &hostInputBufferCount, &framesPerHostOutputBuffer, &hostOutputBufferCount, inputChannelCount, hostInputSampleFormat, suggestedInputLatency, inputStreamInfo, @@ -2176,16 +2495,18 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, if( inputParameters ) { result = InitializeWaveHandles( winMmeHostApi, &stream->input, + winMmeSpecificInputFlags, stream->bufferProcessor.bytesPerHostInputSample, sampleRate, - inputDevices, inputDeviceCount, 1 /* isInput */ ); + inputDevices, inputDeviceCount, inputChannelMask, 1 /* isInput */ ); if( result != paNoError ) goto error; } if( outputParameters ) { result = InitializeWaveHandles( winMmeHostApi, &stream->output, + winMmeSpecificOutputFlags, stream->bufferProcessor.bytesPerHostOutputSample, sampleRate, - outputDevices, outputDeviceCount, 0 /* isInput */ ); + outputDevices, outputDeviceCount, outputChannelMask, 0 /* isInput */ ); if( result != paNoError ) goto error; } @@ -2340,6 +2661,7 @@ static PaError AdvanceToNextInputBuffer( PaWinMmeStream *stream ) for( i=0; i < stream->input.deviceCount; ++i ) { + stream->input.waveHeaders[i][ stream->input.currentBufferIndex ].dwFlags &= ~WHDR_DONE; mmresult = waveInAddBuffer( ((HWAVEIN*)stream->input.waveHandles)[i], &stream->input.waveHeaders[i][ stream->input.currentBufferIndex ], sizeof(WAVEHDR) ); @@ -2642,7 +2964,7 @@ static DWORD WINAPI ProcessingThreadProc( void *pArg ) for( i=0; iinput.deviceCount; ++i ) { /* we have stored the number of channels in the buffer in dwUser */ - int channelCount = stream->input.waveHeaders[i][ hostInputBufferIndex ].dwUser; + int channelCount = (int)stream->input.waveHeaders[i][ hostInputBufferIndex ].dwUser; PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, channel, stream->input.waveHeaders[i][ hostInputBufferIndex ].lpData + @@ -2663,7 +2985,7 @@ static DWORD WINAPI ProcessingThreadProc( void *pArg ) for( i=0; ioutput.deviceCount; ++i ) { /* we have stored the number of channels in the buffer in dwUser */ - int channelCount = stream->output.waveHeaders[i][ hostOutputBufferIndex ].dwUser; + int channelCount = (int)stream->output.waveHeaders[i][ hostOutputBufferIndex ].dwUser; PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, channel, stream->output.waveHeaders[i][ hostOutputBufferIndex ].lpData + @@ -2866,6 +3188,7 @@ static PaError StartStream( PaStream *s ) { for( j=0; jinput.deviceCount; ++j ) { + stream->input.waveHeaders[j][i].dwFlags &= ~WHDR_DONE; mmresult = waveInAddBuffer( ((HWAVEIN*)stream->input.waveHandles)[j], &stream->input.waveHeaders[j][i], sizeof(WAVEHDR) ); if( mmresult != MMSYSERR_NOERROR ) { @@ -2912,7 +3235,7 @@ static PaError StartStream( PaStream *s ) for( j=0; joutput.deviceCount; ++j ) { /* we have stored the number of channels in the buffer in dwUser */ - int channelCount = stream->output.waveHeaders[j][i].dwUser; + int channelCount = (int)stream->output.waveHeaders[j][i].dwUser; PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, channel, stream->output.waveHeaders[j][i].lpData + @@ -2988,7 +3311,7 @@ static PaError StartStream( PaStream *s ) if( result != paNoError ) goto error; /* Create thread that waits for audio buffers to be ready for processing. */ - stream->processingThread = CreateThread( 0, 0, ProcessingThreadProc, stream, 0, &stream->processingThreadId ); + stream->processingThread = CREATE_THREAD; if( !stream->processingThread ) { result = paUnanticipatedHostError; @@ -3120,7 +3443,7 @@ static PaError StopStream( PaStream *s ) for( i=0; ioutput.deviceCount; ++i ) { /* we have stored the number of channels in the buffer in dwUser */ - int channelCount = stream->output.waveHeaders[i][ hostOutputBufferIndex ].dwUser; + int channelCount = (int)stream->output.waveHeaders[i][ hostOutputBufferIndex ].dwUser; PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, channel, stream->output.waveHeaders[i][ hostOutputBufferIndex ].lpData + @@ -3344,7 +3667,7 @@ static PaError ReadStream( PaStream* s, } else { - userBuffer = alloca( sizeof(void*) * stream->bufferProcessor.inputChannelCount ); + userBuffer = (void*)alloca( sizeof(void*) * stream->bufferProcessor.inputChannelCount ); if( !userBuffer ) return paInsufficientMemory; for( i = 0; ibufferProcessor.inputChannelCount; ++i ) @@ -3372,7 +3695,7 @@ static PaError ReadStream( PaStream* s, for( i=0; iinput.deviceCount; ++i ) { /* we have stored the number of channels in the buffer in dwUser */ - int channelCount = stream->input.waveHeaders[i][ hostInputBufferIndex ].dwUser; + int channelCount = (int)stream->input.waveHeaders[i][ hostInputBufferIndex ].dwUser; PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, channel, stream->input.waveHeaders[i][ hostInputBufferIndex ].lpData + @@ -3448,7 +3771,7 @@ static PaError WriteStream( PaStream* s, } else { - userBuffer = alloca( sizeof(void*) * stream->bufferProcessor.outputChannelCount ); + userBuffer = (const void*)alloca( sizeof(void*) * stream->bufferProcessor.outputChannelCount ); if( !userBuffer ) return paInsufficientMemory; for( i = 0; ibufferProcessor.outputChannelCount; ++i ) @@ -3477,7 +3800,7 @@ static PaError WriteStream( PaStream* s, for( i=0; ioutput.deviceCount; ++i ) { /* we have stored the number of channels in the buffer in dwUser */ - int channelCount = stream->output.waveHeaders[i][ hostOutputBufferIndex ].dwUser; + int channelCount = (int)stream->output.waveHeaders[i][ hostOutputBufferIndex ].dwUser; PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, channel, stream->output.waveHeaders[i][ hostOutputBufferIndex ].lpData + diff --git a/pd/portaudio/src/os/unix/pa_unix_hostapis.c b/pd/portaudio/src/os/unix/pa_unix_hostapis.c index d695e1b1..339e1b14 100644 --- a/pd/portaudio/src/os/unix/pa_unix_hostapis.c +++ b/pd/portaudio/src/os/unix/pa_unix_hostapis.c @@ -1,5 +1,5 @@ /* - * $Id: pa_unix_hostapis.c 1097 2006-08-26 08:27:53Z rossb $ + * $Id: pa_unix_hostapis.c 1413 2009-05-24 17:00:36Z aknudsen $ * Portable Audio I/O Library UNIX initialization table * * Based on the Open Source API proposed by Ross Bencina @@ -49,10 +49,26 @@ PaError PaOSS_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex PaError PaSGI_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); /* Linux AudioScience HPI */ PaError PaAsiHpi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); +PaError PaMacCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); +PaError PaSkeleton_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); +/** Note that on Linux, ALSA is placed before OSS so that the former is preferred over the latter. + */ PaUtilHostApiInitializer *paHostApiInitializers[] = { +#ifdef __linux__ + +#ifdef PA_USE_ALSA + PaAlsa_Initialize, +#endif + +#ifdef PA_USE_OSS + PaOSS_Initialize, +#endif + +#else /* __linux__ */ + #ifdef PA_USE_OSS PaOSS_Initialize, #endif @@ -61,6 +77,8 @@ PaUtilHostApiInitializer *paHostApiInitializers[] = PaAlsa_Initialize, #endif +#endif /* __linux__ */ + #ifdef PA_USE_JACK PaJack_Initialize, #endif @@ -72,6 +90,15 @@ PaUtilHostApiInitializer *paHostApiInitializers[] = #ifdef PA_USE_ASIHPI PaAsiHpi_Initialize, #endif + +#ifdef PA_USE_COREAUDIO + PaMacCore_Initialize, +#endif + +#ifdef PA_USE_SKELETON + PaSkeleton_Initialize, +#endif + 0 /* NULL terminated array */ }; diff --git a/pd/portaudio/src/os/unix/pa_unix_util.c b/pd/portaudio/src/os/unix/pa_unix_util.c index 1cb83875..de54e51d 100644 --- a/pd/portaudio/src/os/unix/pa_unix_util.c +++ b/pd/portaudio/src/os/unix/pa_unix_util.c @@ -1,5 +1,5 @@ /* - * $Id: pa_unix_util.c 1232 2007-06-16 14:49:43Z rossb $ + * $Id: pa_unix_util.c 1419 2009-10-22 17:28:35Z bjornroche $ * Portable Audio I/O Library * UNIX platform-specific support functions * @@ -51,6 +51,13 @@ #include #include +#if defined(__APPLE__) && !defined(HAVE_MACH_ABSOLUTE_TIME) +#define HAVE_MACH_ABSOLUTE_TIME +#endif +#ifdef HAVE_MACH_ABSOLUTE_TIME +#include +#endif + #include "pa_util.h" #include "pa_unix_util.h" #include "pa_debugprint.h" @@ -118,27 +125,47 @@ void Pa_Sleep( long msec ) #endif } -/* *** NOT USED YET: *** -static int usePerformanceCounter_; -static double microsecondsPerTick_; +#ifdef HAVE_MACH_ABSOLUTE_TIME +/* + Discussion on the CoreAudio mailing list suggests that calling + gettimeofday (or anything else in the BSD layer) is not real-time + safe, so we use mach_absolute_time on OSX. This implementation is + based on these two links: + + Technical Q&A QA1398 - Mach Absolute Time Units + http://developer.apple.com/mac/library/qa/qa2004/qa1398.html + + Tutorial: Performance and Time. + http://www.macresearch.org/tutorial_performance_and_time */ +/* Scaler to convert the result of mach_absolute_time to seconds */ +static double machSecondsConversionScaler_ = 0.0; +#endif + void PaUtil_InitializeClock( void ) { - /* TODO */ +#ifdef HAVE_MACH_ABSOLUTE_TIME + mach_timebase_info_data_t info; + kern_return_t err = mach_timebase_info( &info ); + if( err == 0 ) + machSecondsConversionScaler_ = 1e-9 * (double) info.numer / (double) info.denom; +#endif } PaTime PaUtil_GetTime( void ) { -#ifdef HAVE_CLOCK_GETTIME +#ifdef HAVE_MACH_ABSOLUTE_TIME + return mach_absolute_time() * machSecondsConversionScaler_; +#elif defined(HAVE_CLOCK_GETTIME) struct timespec tp; clock_gettime(CLOCK_REALTIME, &tp); - return (PaTime)(tp.tv_sec + tp.tv_nsec / 1.e9); + return (PaTime)(tp.tv_sec + tp.tv_nsec * 1e-9); #else struct timeval tv; gettimeofday( &tv, NULL ); - return (PaTime) tv.tv_usec / 1000000. + tv.tv_sec; + return (PaTime) tv.tv_usec * 1e-6 + tv.tv_sec; #endif } @@ -190,7 +217,7 @@ PaError PaUtil_CancelThreading( PaUtilThreading *threading, int wait, PaError *e /* paUnixMainThread * We have to be a bit careful with defining this global variable, * as explained below. */ -#ifdef __apple__ +#ifdef __APPLE__ /* apple/gcc has a "problem" with global vars and dynamic libs. Initializing it seems to fix the problem. Described a bit in this thread: diff --git a/pd/portaudio/src/os/win/pa_win_hostapis.c b/pd/portaudio/src/os/win/pa_win_hostapis.c index 83f1e914..1cca3d9f 100644 --- a/pd/portaudio/src/os/win/pa_win_hostapis.c +++ b/pd/portaudio/src/os/win/pa_win_hostapis.c @@ -1,9 +1,9 @@ /* - * $Id: pa_win_hostapis.c 1097 2006-08-26 08:27:53Z rossb $ + * $Id: pa_win_hostapis.c 1339 2008-02-15 07:50:33Z rossb $ * Portable Audio I/O Library Windows initialization table * * Based on the Open Source API proposed by Ross Bencina - * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * Copyright (c) 1999-2008 Ross Bencina, Phil Burk * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files @@ -39,7 +39,7 @@ /** @file @ingroup win_src - Win32 host API initialization function table. + @brief Win32 host API initialization function table. @todo Consider using PA_USE_WMME etc instead of PA_NO_WMME. This is what the Unix version does, we should consider being consistent. diff --git a/pd/portaudio/src/os/win/pa_win_util.c b/pd/portaudio/src/os/win/pa_win_util.c index 2f0cdf34..1354b9ab 100644 --- a/pd/portaudio/src/os/win/pa_win_util.c +++ b/pd/portaudio/src/os/win/pa_win_util.c @@ -1,10 +1,10 @@ /* - * $Id: pa_win_util.c 1197 2007-05-04 13:07:10Z gordon_gidluck $ + * $Id: pa_win_util.c 1410 2009-04-07 10:08:48Z rossb $ * Portable Audio I/O Library * Win32 platform-specific support functions * * Based on the Open Source API proposed by Ross Bencina - * Copyright (c) 1999-2000 Ross Bencina + * Copyright (c) 1999-2008 Ross Bencina * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files @@ -40,7 +40,7 @@ /** @file @ingroup win_src - Win32 platform-specific support functions. + @brief Win32 implementation of platform-specific PaUtil support functions. @todo Implement workaround for QueryPerformanceCounter() skipping forward bug. (see msdn kb Q274323). @@ -51,6 +51,10 @@ #include "pa_util.h" +#if (defined(WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1200))) /* MSC version 6 and above */ +#pragma comment( lib, "winmm.lib" ) +#endif + /* Track memory allocations to avoid leaks. diff --git a/pd/portaudio/src/os/win/pa_win_waveformat.c b/pd/portaudio/src/os/win/pa_win_waveformat.c new file mode 100644 index 00000000..bbf616c3 --- /dev/null +++ b/pd/portaudio/src/os/win/pa_win_waveformat.c @@ -0,0 +1,154 @@ +/* + * PortAudio Portable Real-Time Audio Library + * Windows WAVEFORMAT* data structure utilities + * portaudio.h should be included before this file. + * + * Copyright (c) 2007 Ross Bencina + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +#include +#include + +#include "portaudio.h" +#include "pa_win_waveformat.h" + + +#if !defined(WAVE_FORMAT_EXTENSIBLE) +#define WAVE_FORMAT_EXTENSIBLE 0xFFFE +#endif + +static GUID pawin_ksDataFormatSubtypeGuidBase = + { (USHORT)(WAVE_FORMAT_PCM), 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }; + + +int PaWin_SampleFormatToLinearWaveFormatTag( PaSampleFormat sampleFormat ) +{ + if( sampleFormat == paFloat32 ) + return PAWIN_WAVE_FORMAT_IEEE_FLOAT; + + return PAWIN_WAVE_FORMAT_PCM; +} + + +void PaWin_InitializeWaveFormatEx( PaWinWaveFormat *waveFormat, + int numChannels, PaSampleFormat sampleFormat, int waveFormatTag, double sampleRate ) +{ + WAVEFORMATEX *waveFormatEx = (WAVEFORMATEX*)waveFormat; + int bytesPerSample = Pa_GetSampleSize(sampleFormat); + unsigned long bytesPerFrame = numChannels * bytesPerSample; + + waveFormatEx->wFormatTag = waveFormatTag; + waveFormatEx->nChannels = (WORD)numChannels; + waveFormatEx->nSamplesPerSec = (DWORD)sampleRate; + waveFormatEx->nAvgBytesPerSec = waveFormatEx->nSamplesPerSec * bytesPerFrame; + waveFormatEx->nBlockAlign = (WORD)bytesPerFrame; + waveFormatEx->wBitsPerSample = bytesPerSample * 8; + waveFormatEx->cbSize = 0; +} + + +void PaWin_InitializeWaveFormatExtensible( PaWinWaveFormat *waveFormat, + int numChannels, PaSampleFormat sampleFormat, int waveFormatTag, double sampleRate, + PaWinWaveFormatChannelMask channelMask ) +{ + WAVEFORMATEX *waveFormatEx = (WAVEFORMATEX*)waveFormat; + int bytesPerSample = Pa_GetSampleSize(sampleFormat); + unsigned long bytesPerFrame = numChannels * bytesPerSample; + GUID guid; + + waveFormatEx->wFormatTag = WAVE_FORMAT_EXTENSIBLE; + waveFormatEx->nChannels = (WORD)numChannels; + waveFormatEx->nSamplesPerSec = (DWORD)sampleRate; + waveFormatEx->nAvgBytesPerSec = waveFormatEx->nSamplesPerSec * bytesPerFrame; + waveFormatEx->nBlockAlign = (WORD)bytesPerFrame; + waveFormatEx->wBitsPerSample = bytesPerSample * 8; + waveFormatEx->cbSize = 22; + + *((WORD*)&waveFormat->fields[PAWIN_INDEXOF_WVALIDBITSPERSAMPLE]) = + waveFormatEx->wBitsPerSample; + + *((DWORD*)&waveFormat->fields[PAWIN_INDEXOF_DWCHANNELMASK]) = channelMask; + + guid = pawin_ksDataFormatSubtypeGuidBase; + guid.Data1 = (USHORT)waveFormatTag; + *((GUID*)&waveFormat->fields[PAWIN_INDEXOF_SUBFORMAT]) = guid; +} + + +PaWinWaveFormatChannelMask PaWin_DefaultChannelMask( int numChannels ) +{ + switch( numChannels ){ + case 1: + return PAWIN_SPEAKER_MONO; + case 2: + return PAWIN_SPEAKER_STEREO; + case 3: + return PAWIN_SPEAKER_FRONT_LEFT | PAWIN_SPEAKER_FRONT_CENTER | PAWIN_SPEAKER_FRONT_RIGHT; + case 4: + return PAWIN_SPEAKER_QUAD; + case 5: + return PAWIN_SPEAKER_QUAD | PAWIN_SPEAKER_FRONT_CENTER; + case 6: + /* The meaning of the PAWIN_SPEAKER_5POINT1 flag has changed over time: + http://msdn2.microsoft.com/en-us/library/aa474707.aspx + We use PAWIN_SPEAKER_5POINT1 (not PAWIN_SPEAKER_5POINT1_SURROUND) + because on some cards (eg Audigy) PAWIN_SPEAKER_5POINT1_SURROUND + results in a virtual mixdown placing the rear output in the + front _and_ rear speakers. + */ + return PAWIN_SPEAKER_5POINT1; + /* case 7: */ + case 8: + return PAWIN_SPEAKER_7POINT1; + } + + /* Apparently some Audigy drivers will output silence + if the direct-out constant (0) is used. So this is not ideal. + */ + return PAWIN_SPEAKER_DIRECTOUT; + + /* Note that Alec Rogers proposed the following as an alternate method to + generate the default channel mask, however it doesn't seem to be an improvement + over the above, since some drivers will matrix outputs mapping to non-present + speakers accross multiple physical speakers. + + if(nChannels==1) { + pwfFormat->dwChannelMask = SPEAKER_FRONT_CENTER; + } + else { + pwfFormat->dwChannelMask = 0; + for(i=0; idwChannelMask = (pwfFormat->dwChannelMask << 1) | 0x1; + } + */ +} diff --git a/pd/portaudio/src/os/win/pa_win_wdmks_utils.c b/pd/portaudio/src/os/win/pa_win_wdmks_utils.c new file mode 100644 index 00000000..7119b43a --- /dev/null +++ b/pd/portaudio/src/os/win/pa_win_wdmks_utils.c @@ -0,0 +1,260 @@ +/* + * PortAudio Portable Real-Time Audio Library + * Windows WDM KS utilities + * + * Copyright (c) 1999 - 2007 Andrew Baldwin, Ross Bencina + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +#include +#include +#include +#include +#include // just for some development printfs + +#include "portaudio.h" +#include "pa_util.h" +#include "pa_win_wdmks_utils.h" + + +static PaError WdmGetPinPropertySimple( + HANDLE handle, + unsigned long pinId, + unsigned long property, + void* value, + unsigned long valueSize ) +{ + DWORD bytesReturned; + KSP_PIN ksPProp; + ksPProp.Property.Set = KSPROPSETID_Pin; + ksPProp.Property.Id = property; + ksPProp.Property.Flags = KSPROPERTY_TYPE_GET; + ksPProp.PinId = pinId; + ksPProp.Reserved = 0; + + if( DeviceIoControl( handle, IOCTL_KS_PROPERTY, &ksPProp, sizeof(KSP_PIN), + value, valueSize, &bytesReturned, NULL ) == 0 || bytesReturned != valueSize ) + { + return paUnanticipatedHostError; + } + else + { + return paNoError; + } +} + + +static PaError WdmGetPinPropertyMulti( + HANDLE handle, + unsigned long pinId, + unsigned long property, + KSMULTIPLE_ITEM** ksMultipleItem) +{ + unsigned long multipleItemSize = 0; + KSP_PIN ksPProp; + DWORD bytesReturned; + + *ksMultipleItem = 0; + + ksPProp.Property.Set = KSPROPSETID_Pin; + ksPProp.Property.Id = property; + ksPProp.Property.Flags = KSPROPERTY_TYPE_GET; + ksPProp.PinId = pinId; + ksPProp.Reserved = 0; + + if( DeviceIoControl( handle, IOCTL_KS_PROPERTY, &ksPProp.Property, + sizeof(KSP_PIN), NULL, 0, &multipleItemSize, NULL ) == 0 && GetLastError() != ERROR_MORE_DATA ) + { + return paUnanticipatedHostError; + } + + *ksMultipleItem = (KSMULTIPLE_ITEM*)PaUtil_AllocateMemory( multipleItemSize ); + if( !*ksMultipleItem ) + { + return paInsufficientMemory; + } + + if( DeviceIoControl( handle, IOCTL_KS_PROPERTY, &ksPProp, sizeof(KSP_PIN), + (void*)*ksMultipleItem, multipleItemSize, &bytesReturned, NULL ) == 0 || bytesReturned != multipleItemSize ) + { + PaUtil_FreeMemory( ksMultipleItem ); + return paUnanticipatedHostError; + } + + return paNoError; +} + + +static int GetKSFilterPinCount( HANDLE deviceHandle ) +{ + DWORD result; + + if( WdmGetPinPropertySimple( deviceHandle, 0, KSPROPERTY_PIN_CTYPES, &result, sizeof(result) ) == paNoError ){ + return result; + }else{ + return 0; + } +} + + +static KSPIN_COMMUNICATION GetKSFilterPinPropertyCommunication( HANDLE deviceHandle, int pinId ) +{ + KSPIN_COMMUNICATION result; + + if( WdmGetPinPropertySimple( deviceHandle, pinId, KSPROPERTY_PIN_COMMUNICATION, &result, sizeof(result) ) == paNoError ){ + return result; + }else{ + return KSPIN_COMMUNICATION_NONE; + } +} + + +static KSPIN_DATAFLOW GetKSFilterPinPropertyDataflow( HANDLE deviceHandle, int pinId ) +{ + KSPIN_DATAFLOW result; + + if( WdmGetPinPropertySimple( deviceHandle, pinId, KSPROPERTY_PIN_DATAFLOW, &result, sizeof(result) ) == paNoError ){ + return result; + }else{ + return (KSPIN_DATAFLOW)0; + } +} + + +static int KSFilterPinPropertyIdentifiersInclude( + HANDLE deviceHandle, int pinId, unsigned long property, const GUID *identifierSet, unsigned long identifierId ) +{ + KSMULTIPLE_ITEM* item = NULL; + KSIDENTIFIER* identifier; + int i; + int result = 0; + + if( WdmGetPinPropertyMulti( deviceHandle, pinId, property, &item) != paNoError ) + return 0; + + identifier = (KSIDENTIFIER*)(item+1); + + for( i = 0; i < (int)item->Count; i++ ) + { + if( !memcmp( (void*)&identifier[i].Set, (void*)identifierSet, sizeof( GUID ) ) && + ( identifier[i].Id == identifierId ) ) + { + result = 1; + break; + } + } + + PaUtil_FreeMemory( item ); + + return result; +} + + +/* return the maximum channel count supported by any pin on the device. + if isInput is non-zero we query input pins, otherwise output pins. +*/ +int PaWin_WDMKS_QueryFilterMaximumChannelCount( void *wcharDevicePath, int isInput ) +{ + HANDLE deviceHandle; + int pinCount, pinId, i; + int result = 0; + KSPIN_DATAFLOW requiredDataflowDirection = (isInput ? KSPIN_DATAFLOW_OUT : KSPIN_DATAFLOW_IN ); + + if( !wcharDevicePath ) + return 0; + + deviceHandle = CreateFileW( (LPCWSTR)wcharDevicePath, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL ); + if( deviceHandle == INVALID_HANDLE_VALUE ) + return 0; + + pinCount = GetKSFilterPinCount( deviceHandle ); + for( pinId = 0; pinId < pinCount; ++pinId ) + { + KSPIN_COMMUNICATION communication = GetKSFilterPinPropertyCommunication( deviceHandle, pinId ); + KSPIN_DATAFLOW dataflow = GetKSFilterPinPropertyDataflow( deviceHandle, pinId ); + if( ( dataflow == requiredDataflowDirection ) && + (( communication == KSPIN_COMMUNICATION_SINK) || + ( communication == KSPIN_COMMUNICATION_BOTH)) + && ( KSFilterPinPropertyIdentifiersInclude( deviceHandle, pinId, + KSPROPERTY_PIN_INTERFACES, &KSINTERFACESETID_Standard, KSINTERFACE_STANDARD_STREAMING ) + || KSFilterPinPropertyIdentifiersInclude( deviceHandle, pinId, + KSPROPERTY_PIN_INTERFACES, &KSINTERFACESETID_Standard, KSINTERFACE_STANDARD_LOOPED_STREAMING ) ) + && KSFilterPinPropertyIdentifiersInclude( deviceHandle, pinId, + KSPROPERTY_PIN_MEDIUMS, &KSMEDIUMSETID_Standard, KSMEDIUM_STANDARD_DEVIO ) ) + { + KSMULTIPLE_ITEM* item = NULL; + if( WdmGetPinPropertyMulti( deviceHandle, pinId, KSPROPERTY_PIN_DATARANGES, &item ) == paNoError ) + { + KSDATARANGE *dataRange = (KSDATARANGE*)(item+1); + + for( i=0; i < item->Count; ++i ){ + + if( IS_VALID_WAVEFORMATEX_GUID(&dataRange->SubFormat) + || memcmp( (void*)&dataRange->SubFormat, (void*)&KSDATAFORMAT_SUBTYPE_PCM, sizeof(GUID) ) == 0 + || memcmp( (void*)&dataRange->SubFormat, (void*)&KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(GUID) ) == 0 + || ( ( memcmp( (void*)&dataRange->MajorFormat, (void*)&KSDATAFORMAT_TYPE_AUDIO, sizeof(GUID) ) == 0 ) + && ( memcmp( (void*)&dataRange->SubFormat, (void*)&KSDATAFORMAT_SUBTYPE_WILDCARD, sizeof(GUID) ) == 0 ) ) ) + { + KSDATARANGE_AUDIO *dataRangeAudio = (KSDATARANGE_AUDIO*)dataRange; + + /* + printf( ">>> %d %d %d %d %S\n", isInput, dataflow, communication, dataRangeAudio->MaximumChannels, devicePath ); + + if( memcmp((void*)&dataRange->Specifier, (void*)&KSDATAFORMAT_SPECIFIER_WAVEFORMATEX, sizeof(GUID) ) == 0 ) + printf( "\tspecifier: KSDATAFORMAT_SPECIFIER_WAVEFORMATEX\n" ); + else if( memcmp((void*)&dataRange->Specifier, (void*)&KSDATAFORMAT_SPECIFIER_DSOUND, sizeof(GUID) ) == 0 ) + printf( "\tspecifier: KSDATAFORMAT_SPECIFIER_DSOUND\n" ); + else if( memcmp((void*)&dataRange->Specifier, (void*)&KSDATAFORMAT_SPECIFIER_WILDCARD, sizeof(GUID) ) == 0 ) + printf( "\tspecifier: KSDATAFORMAT_SPECIFIER_WILDCARD\n" ); + else + printf( "\tspecifier: ?\n" ); + */ + + /* + We assume that very high values for MaximumChannels are not useful and indicate + that the driver isn't prepared to tell us the real number of channels which it supports. + */ + if( dataRangeAudio->MaximumChannels < 0xFFFFUL && (int)dataRangeAudio->MaximumChannels > result ) + result = (int)dataRangeAudio->MaximumChannels; + } + + dataRange = (KSDATARANGE*)( ((char*)dataRange) + dataRange->FormatSize); + } + + PaUtil_FreeMemory( item ); + } + } + } + + CloseHandle( deviceHandle ); + return result; +} diff --git a/pd/portaudio/src/os/win/pa_win_wdmks_utils.h b/pd/portaudio/src/os/win/pa_win_wdmks_utils.h new file mode 100644 index 00000000..f54035f4 --- /dev/null +++ b/pd/portaudio/src/os/win/pa_win_wdmks_utils.h @@ -0,0 +1,65 @@ +#ifndef PA_WIN_WDMKS_UTILS_H +#define PA_WIN_WDMKS_UTILS_H + +/* + * PortAudio Portable Real-Time Audio Library + * Windows WDM KS utilities + * + * Copyright (c) 1999 - 2007 Ross Bencina, Andrew Baldwin + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** @file + @brief Utilities for working with the Windows WDM KS API +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + Query for the maximum number of channels supported by any pin of the + specified device. Returns 0 if the query fails for any reason. + + @param wcharDevicePath A system level PnP interface path, supplied as a WCHAR unicode string. + Declard as void* to avoid introducing a dependency on wchar_t here. + + @param isInput A flag specifying whether to query for input (non-zero) or output (zero) channels. +*/ +int PaWin_WDMKS_QueryFilterMaximumChannelCount( void *wcharDevicePath, int isInput ); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* PA_WIN_WDMKS_UTILS_H */ \ No newline at end of file diff --git a/pd/portaudio/src/os/win/pa_x86_plain_converters.c b/pd/portaudio/src/os/win/pa_x86_plain_converters.c index 63b058df..d3d0fdaf 100644 --- a/pd/portaudio/src/os/win/pa_x86_plain_converters.c +++ b/pd/portaudio/src/os/win/pa_x86_plain_converters.c @@ -124,7 +124,7 @@ static const double ditheredInt16Scaler_ = 0x7FFE; #define PA_DITHER_BITS_ (15) /* Multiply by PA_FLOAT_DITHER_SCALE_ to get a float between -2.0 and +1.99999 */ -#define PA_FLOAT_DITHER_SCALE_ (1.0 / ((1<