diff --git a/src/audio/portaudio/pa_asio/ASIO-README.txt b/src/audio/portaudio/pa_asio/ASIO-README.txt new file mode 100644 index 0000000000000000000000000000000000000000..9fb74ae16d4897fd523e82340dc60fe2abcc78fe --- /dev/null +++ b/src/audio/portaudio/pa_asio/ASIO-README.txt @@ -0,0 +1,137 @@ +ASIO-README.txt + +This document contains information to help you compile PortAudio with +ASIO support. If you find any omissions or errors in this document +please notify Ross Bencina <rossb@audiomulch.com>. + + +Building PortAudio with ASIO support +------------------------------------ + +To build PortAudio with ASIO support you need to compile and link with +pa_asio.c, and files from the ASIO SDK (see below), along with the common +files from pa_common/ and platform specific files from pa_win/ (for Win32) +or pa_mac/ (for Macintosh). + +If you are compiling with a non-Microsoft compiler on windows, also +compile and link with iasiothiscallresolver.cpp (see below for +an explanation). + +For some platforms (MingW, possibly Mac), you may simply +be able to type: + +./configure --with-host_os=mingw --with-winapi=asio [--with-asiodir=/usr/local/asiosdk2] +make + +./configure --with-host_os=darwin --with-winapi=asio [--with-asiodir=/usr/local/asiosdk2] +make + +and life will be good. + + +Obtaining the ASIO SDK +---------------------- + +In order to build PortAudio with ASIO support, you need to download +the ASIO SDK (version 2.0) from Steinberg. Steinberg makes the ASIO +SDK available to anyone free of charge, however they do not permit its +source code to be distributed. + +NOTE: In some cases the ASIO SDK may require patching, see below +for further details. + +http://www.steinberg.net/en/ps/support/3rdparty/asio_sdk/ + +If the above link is broken search Google for: +"download steinberg ASIO SDK" + + + +Building the ASIO SDK on Macintosh +---------------------------------- + +To build the ASIO SDK on Macintosh you need to compile and link with the +following files from the ASIO SDK: + +host/asiodrivers.cpp +host/mac/asioshlib.cpp +host/mac/codefragements.cpp + + + +Building the ASIO SDK on Windows +-------------------------------- + +To build the ASIO SDK on Windows you need to compile and link with the +following files from the ASIO SDK: + +asio_sdk\common\asio.cpp +asio_sdk\host\asiodrivers.cpp +asio_sdk\host\pc\asiolist.cpp + +You may also need to adjust your include paths to support inclusion of +header files from the above directories. + +The ASIO SDK depends on the following COM API functions: +CoInitialize, CoUninitialize, CoCreateInstance, CLSIDFromString +For compilation with MinGW you will need to link with -lole32, for +Borland link with Import32.lib. + + + +Non-Microsoft (MSVC) Compilers on Windows including Borland and GCC +------------------------------------------------------------------- + +Steinberg did not specify a calling convention in the IASIO interface +definition. This causes the Microsoft compiler to use the proprietary +thiscall convention which is not compatible with other compilers, such +as compilers from Borland (BCC and C++Builder) and GNU (gcc). +Steinberg's ASIO SDK will compile but crash on initialization if +compiled with a non-Microsoft compiler on Windows. + +PortAudio solves this problem using the iasiothiscallresolver library +which is included in the distribution. When building ASIO support for +non-Microsoft compilers, be sure to compile and link with +iasiothiscallresolver.cpp. Note that iasiothiscallresolver includes +conditional directives which cause it to have no effect if it is +compiled with a Microsoft compiler, or on the Macintosh. + +If you use configure and make (see above), this should be handled +automatically for you. + +For further information about the IASIO thiscall problem see this page: +http://www.audiomulch.com/~rossb/code/calliasio + + + +Macintosh ASIO SDK Bug Patch +---------------------------- + +There is a bug in the ASIO SDK that causes the Macintosh version to +often fail during initialization. Below is a patch that you can apply. + +In codefragments.cpp replace getFrontProcessDirectory function with +the following one (GetFrontProcess replaced by GetCurrentProcess). + + +bool CodeFragments::getFrontProcessDirectory(void *specs) +{ + FSSpec *fss = (FSSpec *)specs; + ProcessInfoRec pif; + ProcessSerialNumber psn; + + memset(&psn,0,(long)sizeof(ProcessSerialNumber)); + // if(GetFrontProcess(&psn) == noErr) // wrong !!! + if(GetCurrentProcess(&psn) == noErr) // correct !!! + { + pif.processName = 0; + pif.processAppSpec = fss; + pif.processInfoLength = sizeof(ProcessInfoRec); + if(GetProcessInformation(&psn, &pif) == noErr) + return true; + } + return false; +} + + +--- diff --git a/src/audio/portaudio/pa_asio/Callback_adaptation_.pdf b/src/audio/portaudio/pa_asio/Callback_adaptation_.pdf new file mode 100644 index 0000000000000000000000000000000000000000..76bf67863524626aa079d74629c9dd9f720b0b54 Binary files /dev/null and b/src/audio/portaudio/pa_asio/Callback_adaptation_.pdf differ diff --git a/src/audio/portaudio/pa_asio/Pa_ASIO.pdf b/src/audio/portaudio/pa_asio/Pa_ASIO.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ac5ecadbc9c861e9fae4b77f5493951504f36f4f Binary files /dev/null and b/src/audio/portaudio/pa_asio/Pa_ASIO.pdf differ diff --git a/src/audio/portaudio/pa_asio/iasiothiscallresolver.cpp b/src/audio/portaudio/pa_asio/iasiothiscallresolver.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8dfefbd67b95ffc0d64194ab75ab7a05e1b1f72f --- /dev/null +++ b/src/audio/portaudio/pa_asio/iasiothiscallresolver.cpp @@ -0,0 +1,563 @@ +/* + IASIOThiscallResolver.cpp see the comments in iasiothiscallresolver.h for + the top level description - this comment describes the technical details of + the implementation. + + The latest version of this file is available from: + http://www.audiomulch.com/~rossb/code/calliasio + + please email comments to Ross Bencina <rossb@audiomulch.com> + + BACKGROUND + + The IASIO interface declared in the Steinberg ASIO 2 SDK declares + functions with no explicit calling convention. This causes MSVC++ to default + to using the thiscall convention, which is a proprietary convention not + implemented by some non-microsoft compilers - notably borland BCC, + C++Builder, and gcc. MSVC++ is the defacto standard compiler used by + Steinberg. As a result of this situation, the ASIO sdk will compile with + any compiler, however attempting to execute the compiled code will cause a + crash due to different default calling conventions on non-Microsoft + compilers. + + IASIOThiscallResolver solves the problem by providing an adapter class that + delegates to the IASIO interface using the correct calling convention + (thiscall). Due to the lack of support for thiscall in the Borland and GCC + compilers, the calls have been implemented in assembly language. + + A number of macros are defined for thiscall function calls with different + numbers of parameters, with and without return values - it may be possible + to modify the format of these macros to make them work with other inline + assemblers. + + + THISCALL DEFINITION + + A number of definitions of the thiscall calling convention are floating + around the internet. The following definition has been validated against + output from the MSVC++ compiler: + + For non-vararg functions, thiscall works as follows: the object (this) + pointer is passed in ECX. All arguments are passed on the stack in + right to left order. The return value is placed in EAX. The callee + clears the passed arguments from the stack. + + + FINDING FUNCTION POINTERS FROM AN IASIO POINTER + + The first field of a COM object is a pointer to its vtble. Thus a pointer + to an object implementing the IASIO interface also points to a pointer to + that object's vtbl. The vtble is a table of function pointers for all of + the virtual functions exposed by the implemented interfaces. + + If we consider a variable declared as a pointer to IASO: + + IASIO *theAsioDriver + + theAsioDriver points to: + + object implementing IASIO + { + IASIOvtbl *vtbl + other data + } + + in other words, theAsioDriver points to a pointer to an IASIOvtbl + + vtbl points to a table of function pointers: + + IASIOvtbl ( interface IASIO : public IUnknown ) + { + (IUnknown functions) + 0 virtual HRESULT STDMETHODCALLTYPE (*QueryInterface)(REFIID riid, void **ppv) = 0; + 4 virtual ULONG STDMETHODCALLTYPE (*AddRef)() = 0; + 8 virtual ULONG STDMETHODCALLTYPE (*Release)() = 0; + + (IASIO functions) + 12 virtual ASIOBool (*init)(void *sysHandle) = 0; + 16 virtual void (*getDriverName)(char *name) = 0; + 20 virtual long (*getDriverVersion)() = 0; + 24 virtual void (*getErrorMessage)(char *string) = 0; + 28 virtual ASIOError (*start)() = 0; + 32 virtual ASIOError (*stop)() = 0; + 36 virtual ASIOError (*getChannels)(long *numInputChannels, long *numOutputChannels) = 0; + 40 virtual ASIOError (*getLatencies)(long *inputLatency, long *outputLatency) = 0; + 44 virtual ASIOError (*getBufferSize)(long *minSize, long *maxSize, + long *preferredSize, long *granularity) = 0; + 48 virtual ASIOError (*canSampleRate)(ASIOSampleRate sampleRate) = 0; + 52 virtual ASIOError (*getSampleRate)(ASIOSampleRate *sampleRate) = 0; + 56 virtual ASIOError (*setSampleRate)(ASIOSampleRate sampleRate) = 0; + 60 virtual ASIOError (*getClockSources)(ASIOClockSource *clocks, long *numSources) = 0; + 64 virtual ASIOError (*setClockSource)(long reference) = 0; + 68 virtual ASIOError (*getSamplePosition)(ASIOSamples *sPos, ASIOTimeStamp *tStamp) = 0; + 72 virtual ASIOError (*getChannelInfo)(ASIOChannelInfo *info) = 0; + 76 virtual ASIOError (*createBuffers)(ASIOBufferInfo *bufferInfos, long numChannels, + long bufferSize, ASIOCallbacks *callbacks) = 0; + 80 virtual ASIOError (*disposeBuffers)() = 0; + 84 virtual ASIOError (*controlPanel)() = 0; + 88 virtual ASIOError (*future)(long selector,void *opt) = 0; + 92 virtual ASIOError (*outputReady)() = 0; + }; + + The numbers in the left column show the byte offset of each function ptr + from the beginning of the vtbl. These numbers are used in the code below + to select different functions. + + In order to find the address of a particular function, theAsioDriver + must first be dereferenced to find the value of the vtbl pointer: + + mov eax, theAsioDriver + mov edx, [theAsioDriver] // edx now points to vtbl[0] + + Then an offset must be added to the vtbl pointer to select a + particular function, for example vtbl+44 points to the slot containing + a pointer to the getBufferSize function. + + Finally vtbl+x must be dereferenced to obtain the value of the function + pointer stored in that address: + + call [edx+44] // call the function pointed to by + // the value in the getBufferSize field of the vtbl + + + SEE ALSO + + Martin Fay's OpenASIO DLL at http://www.martinfay.com solves the same + problem by providing a new COM interface which wraps IASIO with an + interface that uses portable calling conventions. OpenASIO must be compiled + with MSVC, and requires that you ship the OpenASIO DLL with your + application. + + + ACKNOWLEDGEMENTS + + Ross Bencina: worked out the thiscall details above, wrote the original + Borland asm macros, and a patch for asio.cpp (which is no longer needed). + Thanks to Martin Fay for introducing me to the issues discussed here, + and to Rene G. Ceballos for assisting with asm dumps from MSVC++. + + Antti Silvast: converted the original calliasio to work with gcc and NASM + by implementing the asm code in a separate file. + + Fraser Adams: modified the original calliasio containing the Borland inline + asm to add inline asm for gcc i.e. Intel syntax for Borland and AT&T syntax + for gcc. This seems a neater approach for gcc than to have a separate .asm + file and it means that we only need one version of the thiscall patch. + + Fraser Adams: rewrote the original calliasio patch in the form of the + IASIOThiscallResolver class in order to avoid modifications to files from + the Steinberg SDK, which may have had potential licence issues. + + Andrew Baldwin: contributed fixes for compatibility problems with more + recent versions of the gcc assembler. +*/ + + +// We only need IASIOThiscallResolver at all if we are on Win32. For other +// platforms we simply bypass the IASIOThiscallResolver definition to allow us +// to be safely #include'd whatever the platform to keep client code portable +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) + + +// If microsoft compiler we can call IASIO directly so IASIOThiscallResolver +// is not used. +#if !defined(_MSC_VER) + + +#include <new> +#include <assert.h> + +// We have a mechanism in iasiothiscallresolver.h to ensure that asio.h is +// #include'd before it in client code, we do NOT want to do this test here. +#define iasiothiscallresolver_sourcefile 1 +#include "iasiothiscallresolver.h" +#undef iasiothiscallresolver_sourcefile + +// iasiothiscallresolver.h redefines ASIOInit for clients, but we don't want +// this macro defined in this translation unit. +#undef ASIOInit + + +// theAsioDriver is a global pointer to the current IASIO instance which the +// ASIO SDK uses to perform all actions on the IASIO interface. We substitute +// our own forwarding interface into this pointer. +extern IASIO* theAsioDriver; + + +// The following macros define the inline assembler for BORLAND first then gcc + +#if defined(__BCPLUSPLUS__) || defined(__BORLANDC__) + + +#define CALL_THISCALL_0( resultName, thisPtr, funcOffset )\ + void *this_ = (thisPtr); \ + __asm { \ + mov ecx, this_ ; \ + mov eax, [ecx] ; \ + call [eax+funcOffset] ; \ + mov resultName, eax ; \ + } + + +#define CALL_VOID_THISCALL_1( thisPtr, funcOffset, param1 )\ + void *this_ = (thisPtr); \ + __asm { \ + mov eax, param1 ; \ + push eax ; \ + mov ecx, this_ ; \ + mov eax, [ecx] ; \ + call [eax+funcOffset] ; \ + } + + +#define CALL_THISCALL_1( resultName, thisPtr, funcOffset, param1 )\ + void *this_ = (thisPtr); \ + __asm { \ + mov eax, param1 ; \ + push eax ; \ + mov ecx, this_ ; \ + mov eax, [ecx] ; \ + call [eax+funcOffset] ; \ + mov resultName, eax ; \ + } + + +#define CALL_THISCALL_1_DOUBLE( resultName, thisPtr, funcOffset, param1 )\ + void *this_ = (thisPtr); \ + void *doubleParamPtr_ (¶m1); \ + __asm { \ + mov eax, doubleParamPtr_ ; \ + push [eax+4] ; \ + push [eax] ; \ + mov ecx, this_ ; \ + mov eax, [ecx] ; \ + call [eax+funcOffset] ; \ + mov resultName, eax ; \ + } + + +#define CALL_THISCALL_2( resultName, thisPtr, funcOffset, param1, param2 )\ + void *this_ = (thisPtr); \ + __asm { \ + mov eax, param2 ; \ + push eax ; \ + mov eax, param1 ; \ + push eax ; \ + mov ecx, this_ ; \ + mov eax, [ecx] ; \ + call [eax+funcOffset] ; \ + mov resultName, eax ; \ + } + + +#define CALL_THISCALL_4( resultName, thisPtr, funcOffset, param1, param2, param3, param4 )\ + void *this_ = (thisPtr); \ + __asm { \ + mov eax, param4 ; \ + push eax ; \ + mov eax, param3 ; \ + push eax ; \ + mov eax, param2 ; \ + push eax ; \ + mov eax, param1 ; \ + push eax ; \ + mov ecx, this_ ; \ + mov eax, [ecx] ; \ + call [eax+funcOffset] ; \ + mov resultName, eax ; \ + } + + +#elif defined(__GNUC__) + + +#define CALL_THISCALL_0( resultName, thisPtr, funcOffset ) \ + __asm__ __volatile__ ("movl (%1), %%edx\n\t" \ + "call *"#funcOffset"(%%edx)\n\t" \ + :"=a"(resultName) /* Output Operands */ \ + :"c"(thisPtr) /* Input Operands */ \ + ); \ + + +#define CALL_VOID_THISCALL_1( thisPtr, funcOffset, param1 ) \ + __asm__ __volatile__ ("pushl %0\n\t" \ + "movl (%1), %%edx\n\t" \ + "call *"#funcOffset"(%%edx)\n\t" \ + : /* Output Operands */ \ + :"r"(param1), /* Input Operands */ \ + "c"(thisPtr) \ + ); \ + + +#define CALL_THISCALL_1( resultName, thisPtr, funcOffset, param1 ) \ + __asm__ __volatile__ ("pushl %1\n\t" \ + "movl (%2), %%edx\n\t" \ + "call *"#funcOffset"(%%edx)\n\t" \ + :"=a"(resultName) /* Output Operands */ \ + :"r"(param1), /* Input Operands */ \ + "c"(thisPtr) \ + ); \ + + +#define CALL_THISCALL_1_DOUBLE( resultName, thisPtr, funcOffset, param1 ) \ + __asm__ __volatile__ ("pushl 4(%1)\n\t" \ + "pushl (%1)\n\t" \ + "movl (%2), %%edx\n\t" \ + "call *"#funcOffset"(%%edx);\n\t" \ + :"=a"(resultName) /* Output Operands */ \ + :"a"(¶m1), /* Input Operands */ \ + /* Note: Using "r" above instead of "a" fails */ \ + /* when using GCC 3.3.3, and maybe later versions*/\ + "c"(thisPtr) \ + ); \ + + +#define CALL_THISCALL_2( resultName, thisPtr, funcOffset, param1, param2 ) \ + __asm__ __volatile__ ("pushl %1\n\t" \ + "pushl %2\n\t" \ + "movl (%3), %%edx\n\t" \ + "call *"#funcOffset"(%%edx)\n\t" \ + :"=a"(resultName) /* Output Operands */ \ + :"r"(param2), /* Input Operands */ \ + "r"(param1), \ + "c"(thisPtr) \ + ); \ + + +#define CALL_THISCALL_4( resultName, thisPtr, funcOffset, param1, param2, param3, param4 )\ + __asm__ __volatile__ ("pushl %1\n\t" \ + "pushl %2\n\t" \ + "pushl %3\n\t" \ + "pushl %4\n\t" \ + "movl (%5), %%edx\n\t" \ + "call *"#funcOffset"(%%edx)\n\t" \ + :"=a"(resultName) /* Output Operands */ \ + :"r"(param4), /* Input Operands */ \ + "r"(param3), \ + "r"(param2), \ + "r"(param1), \ + "c"(thisPtr) \ + ); \ + +#endif + + + +// Our static singleton instance. +IASIOThiscallResolver IASIOThiscallResolver::instance; + +// Constructor called to initialize static Singleton instance above. Note that +// it is important not to clear that_ incase it has already been set by the call +// to placement new in ASIOInit(). +IASIOThiscallResolver::IASIOThiscallResolver() +{ +} + +// Constructor called from ASIOInit() below +IASIOThiscallResolver::IASIOThiscallResolver(IASIO* that) +: that_( that ) +{ +} + +// Implement IUnknown methods as assert(false). IASIOThiscallResolver is not +// really a COM object, just a wrapper which will work with the ASIO SDK. +// If you wanted to use ASIO without the SDK you might want to implement COM +// aggregation in these methods. +HRESULT STDMETHODCALLTYPE IASIOThiscallResolver::QueryInterface(REFIID riid, void **ppv) +{ + (void)riid; // suppress unused variable warning + + assert( false ); // this function should never be called by the ASIO SDK. + + *ppv = NULL; + return E_NOINTERFACE; +} + +ULONG STDMETHODCALLTYPE IASIOThiscallResolver::AddRef() +{ + assert( false ); // this function should never be called by the ASIO SDK. + + return 1; +} + +ULONG STDMETHODCALLTYPE IASIOThiscallResolver::Release() +{ + assert( false ); // this function should never be called by the ASIO SDK. + + return 1; +} + + +// Implement the IASIO interface methods by performing the vptr manipulation +// described above then delegating to the real implementation. +ASIOBool IASIOThiscallResolver::init(void *sysHandle) +{ + ASIOBool result; + CALL_THISCALL_1( result, that_, 12, sysHandle ); + return result; +} + +void IASIOThiscallResolver::getDriverName(char *name) +{ + CALL_VOID_THISCALL_1( that_, 16, name ); +} + +long IASIOThiscallResolver::getDriverVersion() +{ + ASIOBool result; + CALL_THISCALL_0( result, that_, 20 ); + return result; +} + +void IASIOThiscallResolver::getErrorMessage(char *string) +{ + CALL_VOID_THISCALL_1( that_, 24, string ); +} + +ASIOError IASIOThiscallResolver::start() +{ + ASIOBool result; + CALL_THISCALL_0( result, that_, 28 ); + return result; +} + +ASIOError IASIOThiscallResolver::stop() +{ + ASIOBool result; + CALL_THISCALL_0( result, that_, 32 ); + return result; +} + +ASIOError IASIOThiscallResolver::getChannels(long *numInputChannels, long *numOutputChannels) +{ + ASIOBool result; + CALL_THISCALL_2( result, that_, 36, numInputChannels, numOutputChannels ); + return result; +} + +ASIOError IASIOThiscallResolver::getLatencies(long *inputLatency, long *outputLatency) +{ + ASIOBool result; + CALL_THISCALL_2( result, that_, 40, inputLatency, outputLatency ); + return result; +} + +ASIOError IASIOThiscallResolver::getBufferSize(long *minSize, long *maxSize, + long *preferredSize, long *granularity) +{ + ASIOBool result; + CALL_THISCALL_4( result, that_, 44, minSize, maxSize, preferredSize, granularity ); + return result; +} + +ASIOError IASIOThiscallResolver::canSampleRate(ASIOSampleRate sampleRate) +{ + ASIOBool result; + CALL_THISCALL_1_DOUBLE( result, that_, 48, sampleRate ); + return result; +} + +ASIOError IASIOThiscallResolver::getSampleRate(ASIOSampleRate *sampleRate) +{ + ASIOBool result; + CALL_THISCALL_1( result, that_, 52, sampleRate ); + return result; +} + +ASIOError IASIOThiscallResolver::setSampleRate(ASIOSampleRate sampleRate) +{ + ASIOBool result; + CALL_THISCALL_1_DOUBLE( result, that_, 56, sampleRate ); + return result; +} + +ASIOError IASIOThiscallResolver::getClockSources(ASIOClockSource *clocks, long *numSources) +{ + ASIOBool result; + CALL_THISCALL_2( result, that_, 60, clocks, numSources ); + return result; +} + +ASIOError IASIOThiscallResolver::setClockSource(long reference) +{ + ASIOBool result; + CALL_THISCALL_1( result, that_, 64, reference ); + return result; +} + +ASIOError IASIOThiscallResolver::getSamplePosition(ASIOSamples *sPos, ASIOTimeStamp *tStamp) +{ + ASIOBool result; + CALL_THISCALL_2( result, that_, 68, sPos, tStamp ); + return result; +} + +ASIOError IASIOThiscallResolver::getChannelInfo(ASIOChannelInfo *info) +{ + ASIOBool result; + CALL_THISCALL_1( result, that_, 72, info ); + return result; +} + +ASIOError IASIOThiscallResolver::createBuffers(ASIOBufferInfo *bufferInfos, + long numChannels, long bufferSize, ASIOCallbacks *callbacks) +{ + ASIOBool result; + CALL_THISCALL_4( result, that_, 76, bufferInfos, numChannels, bufferSize, callbacks ); + return result; +} + +ASIOError IASIOThiscallResolver::disposeBuffers() +{ + ASIOBool result; + CALL_THISCALL_0( result, that_, 80 ); + return result; +} + +ASIOError IASIOThiscallResolver::controlPanel() +{ + ASIOBool result; + CALL_THISCALL_0( result, that_, 84 ); + return result; +} + +ASIOError IASIOThiscallResolver::future(long selector,void *opt) +{ + ASIOBool result; + CALL_THISCALL_2( result, that_, 88, selector, opt ); + return result; +} + +ASIOError IASIOThiscallResolver::outputReady() +{ + ASIOBool result; + CALL_THISCALL_0( result, that_, 92 ); + return result; +} + + +// Implement our substitute ASIOInit() method +ASIOError IASIOThiscallResolver::ASIOInit(ASIODriverInfo *info) +{ + // To ensure that our instance's vptr is correctly constructed, even if + // ASIOInit is called prior to main(), we explicitly call its constructor + // (potentially over the top of an existing instance). Note that this is + // pretty ugly, and is only safe because IASIOThiscallResolver has no + // destructor and contains no objects with destructors. + new((void*)&instance) IASIOThiscallResolver( theAsioDriver ); + + // Interpose between ASIO client code and the real driver. + theAsioDriver = &instance; + + // Note that we never need to switch theAsioDriver back to point to the + // real driver because theAsioDriver is reset to zero in ASIOExit(). + + // Delegate to the real ASIOInit + return ::ASIOInit(info); +} + + +#endif /* !defined(_MSC_VER) */ + +#endif /* Win32 */ + diff --git a/src/audio/portaudio/pa_asio/iasiothiscallresolver.h b/src/audio/portaudio/pa_asio/iasiothiscallresolver.h new file mode 100644 index 0000000000000000000000000000000000000000..2ecfed799aece45fa32ee15140f30705de9b5ec0 --- /dev/null +++ b/src/audio/portaudio/pa_asio/iasiothiscallresolver.h @@ -0,0 +1,197 @@ +// **************************************************************************** +// File: IASIOThiscallResolver.h +// Description: The IASIOThiscallResolver class implements the IASIO +// interface and acts as a proxy to the real IASIO interface by +// calling through its vptr table using the thiscall calling +// convention. To put it another way, we interpose +// IASIOThiscallResolver between ASIO SDK code and the driver. +// This is necessary because most non-Microsoft compilers don't +// implement the thiscall calling convention used by IASIO. +// +// iasiothiscallresolver.cpp contains the background of this +// problem plus a technical description of the vptr +// manipulations. +// +// In order to use this mechanism one simply has to add +// iasiothiscallresolver.cpp to the list of files to compile +// and #include <iasiothiscallresolver.h> +// +// Note that this #include must come after the other ASIO SDK +// #includes, for example: +// +// #include <windows.h> +// #include <asiosys.h> +// #include <asio.h> +// #include <asiodrivers.h> +// #include <iasiothiscallresolver.h> +// +// Actually the important thing is to #include +// <iasiothiscallresolver.h> after <asio.h>. We have +// incorporated a test to enforce this ordering. +// +// The code transparently takes care of the interposition by +// using macro substitution to intercept calls to ASIOInit() +// and ASIOExit(). We save the original ASIO global +// "theAsioDriver" in our "that" variable, and then set +// "theAsioDriver" to equal our IASIOThiscallResolver instance. +// +// Whilst this method of resolving the thiscall problem requires +// the addition of #include <iasiothiscallresolver.h> to client +// code it has the advantage that it does not break the terms +// of the ASIO licence by publishing it. We are NOT modifying +// any Steinberg code here, we are merely implementing the IASIO +// interface in the same way that we would need to do if we +// wished to provide an open source ASIO driver. +// +// For compilation with MinGW -lole32 needs to be added to the +// linker options. For BORLAND, linking with Import32.lib is +// sufficient. +// +// The dependencies are with: CoInitialize, CoUninitialize, +// CoCreateInstance, CLSIDFromString - used by asiolist.cpp +// and are required on Windows whether ThiscallResolver is used +// or not. +// +// Searching for the above strings in the root library path +// of your compiler should enable the correct libraries to be +// identified if they aren't immediately obvious. +// +// Note that the current implementation of IASIOThiscallResolver +// is not COM compliant - it does not correctly implement the +// IUnknown interface. Implementing it is not necessary because +// it is not called by parts of the ASIO SDK which call through +// theAsioDriver ptr. The IUnknown methods are implemented as +// assert(false) to ensure that the code fails if they are +// ever called. +// Restrictions: None. Public Domain & Open Source distribute freely +// You may use IASIOThiscallResolver commercially as well as +// privately. +// You the user assume the responsibility for the use of the +// files, binary or text, and there is no guarantee or warranty, +// expressed or implied, including but not limited to the +// implied warranties of merchantability and fitness for a +// particular purpose. You assume all responsibility and agree +// to hold no entity, copyright holder or distributors liable +// for any loss of data or inaccurate representations of data +// as a result of using IASIOThiscallResolver. +// Version: 1.4 Added separate macro CALL_THISCALL_1_DOUBLE from +// Andrew Baldwin, and volatile for whole gcc asm blocks, +// both for compatibility with newer gcc versions. Cleaned up +// Borland asm to use one less register. +// 1.3 Switched to including assert.h for better compatibility. +// Wrapped entire .h and .cpp contents with a check for +// _MSC_VER to provide better compatibility with MS compilers. +// Changed Singleton implementation to use static instance +// instead of freestore allocated instance. Removed ASIOExit +// macro as it is no longer needed. +// 1.2 Removed semicolons from ASIOInit and ASIOExit macros to +// allow them to be embedded in expressions (if statements). +// Cleaned up some comments. Removed combase.c dependency (it +// doesn't compile with BCB anyway) by stubbing IUnknown. +// 1.1 Incorporated comments from Ross Bencina including things +// such as changing name from ThiscallResolver to +// IASIOThiscallResolver, tidying up the constructor, fixing +// a bug in IASIOThiscallResolver::ASIOExit() and improving +// portability through the use of conditional compilation +// 1.0 Initial working version. +// Created: 6/09/2003 +// Authors: Fraser Adams +// Ross Bencina +// Rene G. Ceballos +// Martin Fay +// Antti Silvast +// Andrew Baldwin +// +// **************************************************************************** + + +#ifndef included_iasiothiscallresolver_h +#define included_iasiothiscallresolver_h + +// We only need IASIOThiscallResolver at all if we are on Win32. For other +// platforms we simply bypass the IASIOThiscallResolver definition to allow us +// to be safely #include'd whatever the platform to keep client code portable +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) + + +// If microsoft compiler we can call IASIO directly so IASIOThiscallResolver +// is not used. +#if !defined(_MSC_VER) + + +// The following is in order to ensure that this header is only included after +// the other ASIO headers (except for the case of iasiothiscallresolver.cpp). +// We need to do this because IASIOThiscallResolver works by eclipsing the +// original definition of ASIOInit() with a macro (see below). +#if !defined(iasiothiscallresolver_sourcefile) + #if !defined(__ASIO_H) + #error iasiothiscallresolver.h must be included AFTER asio.h + #endif +#endif + +#include <windows.h> +#include <asiodrvr.h> /* From ASIO SDK */ + + +class IASIOThiscallResolver : public IASIO { +private: + IASIO* that_; // Points to the real IASIO + + static IASIOThiscallResolver instance; // Singleton instance + + // Constructors - declared private so construction is limited to + // our Singleton instance + IASIOThiscallResolver(); + IASIOThiscallResolver(IASIO* that); +public: + + // Methods from the IUnknown interface. We don't fully implement IUnknown + // because the ASIO SDK never calls these methods through theAsioDriver ptr. + // These methods are implemented as assert(false). + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppv); + virtual ULONG STDMETHODCALLTYPE AddRef(); + virtual ULONG STDMETHODCALLTYPE Release(); + + // Methods from the IASIO interface, implemented as forwarning calls to that. + virtual ASIOBool init(void *sysHandle); + virtual void getDriverName(char *name); + virtual long getDriverVersion(); + virtual void getErrorMessage(char *string); + virtual ASIOError start(); + virtual ASIOError stop(); + virtual ASIOError getChannels(long *numInputChannels, long *numOutputChannels); + virtual ASIOError getLatencies(long *inputLatency, long *outputLatency); + virtual ASIOError getBufferSize(long *minSize, long *maxSize, long *preferredSize, long *granularity); + virtual ASIOError canSampleRate(ASIOSampleRate sampleRate); + virtual ASIOError getSampleRate(ASIOSampleRate *sampleRate); + virtual ASIOError setSampleRate(ASIOSampleRate sampleRate); + virtual ASIOError getClockSources(ASIOClockSource *clocks, long *numSources); + virtual ASIOError setClockSource(long reference); + virtual ASIOError getSamplePosition(ASIOSamples *sPos, ASIOTimeStamp *tStamp); + virtual ASIOError getChannelInfo(ASIOChannelInfo *info); + virtual ASIOError createBuffers(ASIOBufferInfo *bufferInfos, long numChannels, long bufferSize, ASIOCallbacks *callbacks); + virtual ASIOError disposeBuffers(); + virtual ASIOError controlPanel(); + virtual ASIOError future(long selector,void *opt); + virtual ASIOError outputReady(); + + // Class method, see ASIOInit() macro below. + static ASIOError ASIOInit(ASIODriverInfo *info); // Delegates to ::ASIOInit +}; + + +// Replace calls to ASIOInit with our interposing version. +// This macro enables us to perform thiscall resolution simply by #including +// <iasiothiscallresolver.h> after the asio #includes (this file _must_ be +// included _after_ the asio #includes) + +#define ASIOInit(name) IASIOThiscallResolver::ASIOInit((name)) + + +#endif /* !defined(_MSC_VER) */ + +#endif /* Win32 */ + +#endif /* included_iasiothiscallresolver_h */ + + diff --git a/src/audio/portaudio/pa_asio/pa_asio.cpp b/src/audio/portaudio/pa_asio/pa_asio.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e622ecb7436f4212dfe77210c944b056fb5fd3df --- /dev/null +++ b/src/audio/portaudio/pa_asio/pa_asio.cpp @@ -0,0 +1,2901 @@ +/* + * $Id$ + * 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 + * + * 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. + * + * 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. + * + * 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. + */ + +/* Modification History + + 08-03-01 First version : Stephane Letz + 08-06-01 Tweaks for PC, use C++, buffer allocation, Float32 to Int32 conversion : Phil Burk + 08-20-01 More conversion, PA_StreamTime, Pa_GetHostError : Stephane Letz + 08-21-01 PaUInt8 bug correction, implementation of ASIOSTFloat32LSB and ASIOSTFloat32MSB native formats : Stephane Letz + 08-24-01 MAX_INT32_FP hack, another Uint8 fix : Stephane and Phil + 08-27-01 Implementation of hostBufferSize < userBufferSize case, better management of the ouput buffer when + the stream is stopped : Stephane Letz + 08-28-01 Check the stream pointer for null in bufferSwitchTimeInfo, correct bug in bufferSwitchTimeInfo when + the stream is stopped : Stephane Letz + 10-12-01 Correct the PaHost_CalcNumHostBuffers function: computes FramesPerHostBuffer to be the lowest that + respect requested FramesPerUserBuffer and userBuffersPerHostBuffer : Stephane Letz + 10-26-01 Management of hostBufferSize and userBufferSize of any size : Stephane Letz + 10-27-01 Improve calculus of hostBufferSize to be multiple or divisor of userBufferSize if possible : Stephane and Phil + 10-29-01 Change MAX_INT32_FP to (2147483520.0f) to prevent roundup to 0x80000000 : Phil Burk + 10-31-01 Clear the ouput buffer and user buffers in PaHost_StartOutput, correct bug in GetFirstMultiple : Stephane Letz + 11-06-01 Rename functions : Stephane Letz + 11-08-01 New Pa_ASIO_Adaptor_Init function to init Callback adpatation variables, cleanup of Pa_ASIO_Callback_Input: Stephane Letz + 11-29-01 Break apart device loading to debug random failure in Pa_ASIO_QueryDeviceInfo ; Phil Burk + 01-03-02 Desallocate all resources in PaHost_Term for cases where Pa_CloseStream is not called properly : Stephane Letz + 02-01-02 Cleanup, test of multiple-stream opening : Stephane Letz + 19-02-02 New Pa_ASIO_loadDriver that calls CoInitialize on each thread on Windows : Stephane Letz + 09-04-02 Correct error code management in PaHost_Term, removes various compiler warning : Stephane Letz + 12-04-02 Add Mac includes for <Devices.h> and <Timer.h> : Phil Burk + 13-04-02 Removes another compiler warning : Stephane Letz + 30-04-02 Pa_ASIO_QueryDeviceInfo bug correction, memory allocation checking, better error handling : D Viens, P Burk, S Letz + 12-06-02 Rehashed into new multi-api infrastructure, added support for all ASIO sample formats : Ross Bencina + 18-06-02 Added pa_asio.h, PaAsio_GetAvailableLatencyValues() : Ross B. + 21-06-02 Added SelectHostBufferSize() which selects host buffer size based on user latency parameters : Ross Bencina + ** NOTE maintanance history is now stored in CVS ** +*/ + +/** @file + + 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 implement IsFormatSupported + + @todo work out how to implement stream stoppage from callback and + implement IsStreamActive properly. Stream stoppage could be implemented + using a high-priority thread blocked on an Event which is signalled + by the callback. Or, we could just call ASIO stop from the callback + and see what happens. + + @todo rigorously check asio return codes and convert to pa error codes + + @todo Different channels of a multichannel stream can have different sample + formats, but we assume that all are the same as the first channel for now. + Fixing this will require the block processor to maintain per-channel + conversion functions - could get nasty. + + @todo investigate whether the asio processNow flag needs to be honoured + + @todo handle asioMessages() callbacks in a useful way, or at least document + what cases we don't handle. + + @todo miscellaneous other FIXMEs + + @todo provide an asio-specific method for setting the systems specific + value (aka main window handle) - check that this matches the value + passed to PaAsio_ShowControlPanel, or remove it entirely from + PaAsio_ShowControlPanel. - this would allow PaAsio_ShowControlPanel + to be called for the currently open stream (at present all streams + must be closed). +*/ + + + +#include <stdio.h> +#include <assert.h> +#include <string.h> +//#include <values.h> + +#include <windows.h> +#include <mmsystem.h> + +#include "portaudio.h" +#include "pa_asio.h" +#include "pa_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" + +#include "asiosys.h" +#include "asio.h" +#include "asiodrivers.h" +#include "iasiothiscallresolver.h" + +/* +#if MAC +#include <Devices.h> +#include <Timer.h> +#include <Math64.h> +#else +*/ +/* +#include <math.h> +#include <windows.h> +#include <mmsystem.h> +*/ +/* +#endif +*/ + +/* external references */ +extern AsioDrivers* asioDrivers ; +bool loadAsioDriver(char *name); + + +/* 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) + + + + +/* prototypes for functions declared in this file */ + +extern "C" PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ); +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); +static PaError CloseStream( PaStream* stream ); +static PaError StartStream( PaStream *stream ); +static PaError StopStream( PaStream *stream ); +static PaError AbortStream( PaStream *stream ); +static PaError IsStreamStopped( PaStream *s ); +static PaError IsStreamActive( PaStream *stream ); +static PaTime GetStreamTime( PaStream *stream ); +static double GetStreamCpuLoad( PaStream* stream ); +static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames ); +static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames ); +static signed long GetStreamReadAvailable( PaStream* stream ); +static signed long GetStreamWriteAvailable( PaStream* stream ); + +/* our ASIO callback functions */ + +static void bufferSwitch(long index, ASIOBool processNow); +static ASIOTime *bufferSwitchTimeInfo(ASIOTime *timeInfo, long index, ASIOBool processNow); +static void sampleRateChanged(ASIOSampleRate sRate); +static long asioMessages(long selector, long value, void* message, double* opt); + +static ASIOCallbacks asioCallbacks_ = + { bufferSwitch, sampleRateChanged, asioMessages, bufferSwitchTimeInfo }; + + +#define PA_ASIO_SET_LAST_HOST_ERROR( errorCode, errorText ) \ + PaUtil_SetLastHostErrorInfo( paASIO, errorCode, errorText ) + + +static void PaAsio_SetLastSystemError( DWORD errorCode ) +{ + LPVOID lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + errorCode, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, + 0, + NULL + ); + PaUtil_SetLastHostErrorInfo( paASIO, errorCode, (const char*)lpMsgBuf ); + LocalFree( lpMsgBuf ); +} + +#define PA_ASIO_SET_LAST_SYSTEM_ERROR( errorCode ) \ + PaAsio_SetLastSystemError( errorCode ) + + +static const char* PaAsio_GetAsioErrorText( ASIOError asioError ) +{ + const char *result; + + switch( asioError ){ + case ASE_OK: + case ASE_SUCCESS: result = "Success"; break; + case ASE_NotPresent: result = "Hardware input or output is not present or available"; break; + case ASE_HWMalfunction: result = "Hardware is malfunctioning"; break; + case ASE_InvalidParameter: result = "Input parameter invalid"; break; + case ASE_InvalidMode: result = "Hardware is in a bad mode or used in a bad mode"; break; + case ASE_SPNotAdvancing: result = "Hardware is not running when sample position is inquired"; break; + case ASE_NoClock: result = "Sample clock or rate cannot be determined or is not present"; break; + case ASE_NoMemory: result = "Not enough memory for completing the request"; break; + default: result = "Unknown ASIO error"; break; + } + + return result; +} + + +#define PA_ASIO_SET_LAST_ASIO_ERROR( asioError ) \ + PaUtil_SetLastHostErrorInfo( paASIO, asioError, PaAsio_GetAsioErrorText( asioError ) ) + + + + +// Atomic increment and decrement operations +#if MAC + /* need to be implemented on Mac */ + inline long PaAsio_AtomicIncrement(volatile long* v) {return ++(*const_cast<long*>(v));} + inline long PaAsio_AtomicDecrement(volatile long* v) {return --(*const_cast<long*>(v));} +#elif WINDOWS + inline long PaAsio_AtomicIncrement(volatile long* v) {return InterlockedIncrement(const_cast<long*>(v));} + inline long PaAsio_AtomicDecrement(volatile long* v) {return InterlockedDecrement(const_cast<long*>(v));} +#endif + + + +typedef struct PaAsioDriverInfo +{ + ASIODriverInfo asioDriverInfo; + long inputChannelCount, outputChannelCount; + long bufferMinSize, bufferMaxSize, bufferPreferredSize, bufferGranularity; + bool postOutput; +} +PaAsioDriverInfo; + + +/* PaAsioHostApiRepresentation - host api datastructure specific to this implementation */ + +typedef struct +{ + PaUtilHostApiRepresentation inheritedHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup *allocations; + + void *systemSpecific; + + /* the ASIO C API only allows one ASIO driver to be open at a time, + so we keep track of whether we have the driver open here, and + use this information to return errors from OpenStream if the + driver is already open. + + openAsioDeviceIndex will be PaNoDevice if there is no device open + and a valid pa_asio (not global) device index otherwise. + + openAsioDriverInfo is populated with the driver info for the + currently open device (if any) + */ + PaDeviceIndex openAsioDeviceIndex; + PaAsioDriverInfo openAsioDriverInfo; +} +PaAsioHostApiRepresentation; + + +/* + Retrieve <driverCount> driver names from ASIO, returned in a char** + allocated in <group>. +*/ +static char **GetAsioDriverNames( PaUtilAllocationGroup *group, long driverCount ) +{ + char **result = 0; + int i; + + result =(char**)PaUtil_GroupAllocateMemory( + group, sizeof(char*) * driverCount ); + if( !result ) + goto error; + + result[0] = (char*)PaUtil_GroupAllocateMemory( + group, 32 * driverCount ); + if( !result[0] ) + goto error; + + for( i=0; i<driverCount; ++i ) + result[i] = result[0] + (32 * i); + + asioDrivers->getDriverNames( result, driverCount ); + +error: + return result; +} + + +static PaSampleFormat AsioSampleTypeToPaNativeSampleFormat(ASIOSampleType type) +{ + switch (type) { + case ASIOSTInt16MSB: + case ASIOSTInt16LSB: + return paInt16; + + case ASIOSTFloat32MSB: + case ASIOSTFloat32LSB: + case ASIOSTFloat64MSB: + case ASIOSTFloat64LSB: + return paFloat32; + + case ASIOSTInt32MSB: + case ASIOSTInt32LSB: + case ASIOSTInt32MSB16: + case ASIOSTInt32LSB16: + case ASIOSTInt32MSB18: + case ASIOSTInt32MSB20: + case ASIOSTInt32MSB24: + case ASIOSTInt32LSB18: + case ASIOSTInt32LSB20: + case ASIOSTInt32LSB24: + return paInt32; + + case ASIOSTInt24MSB: + case ASIOSTInt24LSB: + return paInt24; + + default: + return paCustomFormat; + } +} + +void AsioSampleTypeLOG(ASIOSampleType type) +{ + switch (type) { + case ASIOSTInt16MSB: PA_DEBUG(("ASIOSTInt16MSB\n")); break; + case ASIOSTInt16LSB: PA_DEBUG(("ASIOSTInt16LSB\n")); break; + case ASIOSTFloat32MSB:PA_DEBUG(("ASIOSTFloat32MSB\n"));break; + case ASIOSTFloat32LSB:PA_DEBUG(("ASIOSTFloat32LSB\n"));break; + case ASIOSTFloat64MSB:PA_DEBUG(("ASIOSTFloat64MSB\n"));break; + case ASIOSTFloat64LSB:PA_DEBUG(("ASIOSTFloat64LSB\n"));break; + case ASIOSTInt32MSB: PA_DEBUG(("ASIOSTInt32MSB\n")); break; + case ASIOSTInt32LSB: PA_DEBUG(("ASIOSTInt32LSB\n")); break; + case ASIOSTInt32MSB16:PA_DEBUG(("ASIOSTInt32MSB16\n"));break; + case ASIOSTInt32LSB16:PA_DEBUG(("ASIOSTInt32LSB16\n"));break; + case ASIOSTInt32MSB18:PA_DEBUG(("ASIOSTInt32MSB18\n"));break; + case ASIOSTInt32MSB20:PA_DEBUG(("ASIOSTInt32MSB20\n"));break; + case ASIOSTInt32MSB24:PA_DEBUG(("ASIOSTInt32MSB24\n"));break; + case ASIOSTInt32LSB18:PA_DEBUG(("ASIOSTInt32LSB18\n"));break; + case ASIOSTInt32LSB20:PA_DEBUG(("ASIOSTInt32LSB20\n"));break; + case ASIOSTInt32LSB24:PA_DEBUG(("ASIOSTInt32LSB24\n"));break; + case ASIOSTInt24MSB: PA_DEBUG(("ASIOSTInt24MSB\n")); break; + case ASIOSTInt24LSB: PA_DEBUG(("ASIOSTInt24LSB\n")); break; + default: PA_DEBUG(("Custom Format%d\n",type));break; + + } +} + +static int BytesPerAsioSample( ASIOSampleType sampleType ) +{ + switch (sampleType) { + case ASIOSTInt16MSB: + case ASIOSTInt16LSB: + return 2; + + case ASIOSTFloat64MSB: + case ASIOSTFloat64LSB: + return 8; + + case ASIOSTFloat32MSB: + case ASIOSTFloat32LSB: + case ASIOSTInt32MSB: + case ASIOSTInt32LSB: + case ASIOSTInt32MSB16: + case ASIOSTInt32LSB16: + case ASIOSTInt32MSB18: + case ASIOSTInt32MSB20: + case ASIOSTInt32MSB24: + case ASIOSTInt32LSB18: + case ASIOSTInt32LSB20: + case ASIOSTInt32LSB24: + return 4; + + case ASIOSTInt24MSB: + case ASIOSTInt24LSB: + return 3; + + default: + return 0; + } +} + + +static void Swap16( void *buffer, long shift, long count ) +{ + unsigned short *p = (unsigned short*)buffer; + unsigned short temp; + (void) shift; /* unused parameter */ + + while( count-- ) + { + temp = *p; + *p++ = (unsigned short)((temp<<8) | (temp>>8)); + } +} + +static void Swap24( void *buffer, long shift, long count ) +{ + unsigned char *p = (unsigned char*)buffer; + unsigned char temp; + (void) shift; /* unused parameter */ + + while( count-- ) + { + temp = *p; + *p = *(p+2); + *(p+2) = temp; + p += 3; + } +} + +#define PA_SWAP32_( x ) ((x>>24) | ((x>>8)&0xFF00) | ((x<<8)&0xFF0000) | (x<<24)); + +static void Swap32( void *buffer, long shift, long count ) +{ + unsigned long *p = (unsigned long*)buffer; + unsigned long temp; + (void) shift; /* unused parameter */ + + while( count-- ) + { + temp = *p; + *p++ = PA_SWAP32_( temp); + } +} + +static void SwapShiftLeft32( void *buffer, long shift, long count ) +{ + unsigned long *p = (unsigned long*)buffer; + unsigned long temp; + + while( count-- ) + { + temp = *p; + temp = PA_SWAP32_( temp); + *p++ = temp << shift; + } +} + +static void ShiftRightSwap32( void *buffer, long shift, long count ) +{ + unsigned long *p = (unsigned long*)buffer; + unsigned long temp; + + while( count-- ) + { + temp = *p >> shift; + *p++ = PA_SWAP32_( temp); + } +} + +static void ShiftLeft32( void *buffer, long shift, long count ) +{ + unsigned long *p = (unsigned long*)buffer; + unsigned long temp; + + while( count-- ) + { + temp = *p; + *p++ = temp << shift; + } +} + +static void ShiftRight32( void *buffer, long shift, long count ) +{ + unsigned long *p = (unsigned long*)buffer; + unsigned long temp; + + while( count-- ) + { + temp = *p; + *p++ = temp >> shift; + } +} + +#define PA_SWAP_( x, y ) temp=x; x = y; y = temp; + +static void Swap64ConvertFloat64ToFloat32( void *buffer, long shift, long count ) +{ + double *in = (double*)buffer; + float *out = (float*)buffer; + unsigned char *p; + unsigned char temp; + (void) shift; /* unused parameter */ + + while( count-- ) + { + p = (unsigned char*)in; + PA_SWAP_( p[0], p[7] ); + PA_SWAP_( p[1], p[6] ); + PA_SWAP_( p[2], p[5] ); + PA_SWAP_( p[3], p[4] ); + + *out++ = (float) (*in++); + } +} + +static void ConvertFloat64ToFloat32( void *buffer, long shift, long count ) +{ + double *in = (double*)buffer; + float *out = (float*)buffer; + (void) shift; /* unused parameter */ + + while( count-- ) + *out++ = (float) (*in++); +} + +static void ConvertFloat32ToFloat64Swap64( void *buffer, long shift, long count ) +{ + float *in = ((float*)buffer) + (count-1); + double *out = ((double*)buffer) + (count-1); + unsigned char *p; + unsigned char temp; + (void) shift; /* unused parameter */ + + while( count-- ) + { + *out = *in--; + + p = (unsigned char*)out; + PA_SWAP_( p[0], p[7] ); + PA_SWAP_( p[1], p[6] ); + PA_SWAP_( p[2], p[5] ); + PA_SWAP_( p[3], p[4] ); + + out--; + } +} + +static void ConvertFloat32ToFloat64( void *buffer, long shift, long count ) +{ + float *in = ((float*)buffer) + (count-1); + double *out = ((double*)buffer) + (count-1); + (void) shift; /* unused parameter */ + + while( count-- ) + *out-- = *in--; +} + +#ifdef MAC +#define PA_MSB_IS_NATIVE_ +#undef PA_LSB_IS_NATIVE_ +#endif + +#ifdef WINDOWS +#undef PA_MSB_IS_NATIVE_ +#define PA_LSB_IS_NATIVE_ +#endif + +typedef void PaAsioBufferConverter( void *, long, long ); + +static void SelectAsioToPaConverter( ASIOSampleType type, PaAsioBufferConverter **converter, long *shift ) +{ + *shift = 0; + *converter = 0; + + switch (type) { + case ASIOSTInt16MSB: + /* dest: paInt16, no conversion necessary, possible byte swap*/ + #ifdef PA_LSB_IS_NATIVE_ + *converter = Swap16; + #endif + break; + case ASIOSTInt16LSB: + /* dest: paInt16, no conversion necessary, possible byte swap*/ + #ifdef PA_MSB_IS_NATIVE_ + *converter = Swap16; + #endif + break; + case ASIOSTFloat32MSB: + /* dest: paFloat32, no conversion necessary, possible byte swap*/ + #ifdef PA_LSB_IS_NATIVE_ + *converter = Swap32; + #endif + break; + case ASIOSTFloat32LSB: + /* dest: paFloat32, no conversion necessary, possible byte swap*/ + #ifdef PA_MSB_IS_NATIVE_ + *converter = Swap32; + #endif + break; + case ASIOSTFloat64MSB: + /* dest: paFloat32, in-place conversion to/from float32, possible byte swap*/ + #ifdef PA_LSB_IS_NATIVE_ + *converter = Swap64ConvertFloat64ToFloat32; + #else + *converter = ConvertFloat64ToFloat32; + #endif + break; + case ASIOSTFloat64LSB: + /* dest: paFloat32, in-place conversion to/from float32, possible byte swap*/ + #ifdef PA_MSB_IS_NATIVE_ + *converter = Swap64ConvertFloat64ToFloat32; + #else + *converter = ConvertFloat64ToFloat32; + #endif + break; + case ASIOSTInt32MSB: + /* dest: paInt32, no conversion necessary, possible byte swap */ + #ifdef PA_LSB_IS_NATIVE_ + *converter = Swap32; + #endif + break; + case ASIOSTInt32LSB: + /* dest: paInt32, no conversion necessary, possible byte swap */ + #ifdef PA_MSB_IS_NATIVE_ + *converter = Swap32; + #endif + break; + case ASIOSTInt32MSB16: + /* dest: paInt32, 16 bit shift, possible byte swap */ + #ifdef PA_LSB_IS_NATIVE_ + *converter = SwapShiftLeft32; + #else + *converter = ShiftLeft32; + #endif + *shift = 16; + break; + case ASIOSTInt32MSB18: + /* dest: paInt32, 14 bit shift, possible byte swap */ + #ifdef PA_LSB_IS_NATIVE_ + *converter = SwapShiftLeft32; + #else + *converter = ShiftLeft32; + #endif + *shift = 14; + break; + case ASIOSTInt32MSB20: + /* dest: paInt32, 12 bit shift, possible byte swap */ + #ifdef PA_LSB_IS_NATIVE_ + *converter = SwapShiftLeft32; + #else + *converter = ShiftLeft32; + #endif + *shift = 12; + break; + case ASIOSTInt32MSB24: + /* dest: paInt32, 8 bit shift, possible byte swap */ + #ifdef PA_LSB_IS_NATIVE_ + *converter = SwapShiftLeft32; + #else + *converter = ShiftLeft32; + #endif + *shift = 8; + break; + case ASIOSTInt32LSB16: + /* dest: paInt32, 16 bit shift, possible byte swap */ + #ifdef PA_MSB_IS_NATIVE_ + *converter = SwapShiftLeft32; + #else + *converter = ShiftLeft32; + #endif + *shift = 16; + break; + case ASIOSTInt32LSB18: + /* dest: paInt32, 14 bit shift, possible byte swap */ + #ifdef PA_MSB_IS_NATIVE_ + *converter = SwapShiftLeft32; + #else + *converter = ShiftLeft32; + #endif + *shift = 14; + break; + case ASIOSTInt32LSB20: + /* dest: paInt32, 12 bit shift, possible byte swap */ + #ifdef PA_MSB_IS_NATIVE_ + *converter = SwapShiftLeft32; + #else + *converter = ShiftLeft32; + #endif + *shift = 12; + break; + case ASIOSTInt32LSB24: + /* dest: paInt32, 8 bit shift, possible byte swap */ + #ifdef PA_MSB_IS_NATIVE_ + *converter = SwapShiftLeft32; + #else + *converter = ShiftLeft32; + #endif + *shift = 8; + break; + case ASIOSTInt24MSB: + /* dest: paInt24, no conversion necessary, possible byte swap */ + #ifdef PA_LSB_IS_NATIVE_ + *converter = Swap24; + #endif + break; + case ASIOSTInt24LSB: + /* dest: paInt24, no conversion necessary, possible byte swap */ + #ifdef PA_MSB_IS_NATIVE_ + *converter = Swap24; + #endif + break; + } +} + + +static void SelectPaToAsioConverter( ASIOSampleType type, PaAsioBufferConverter **converter, long *shift ) +{ + *shift = 0; + *converter = 0; + + switch (type) { + case ASIOSTInt16MSB: + /* src: paInt16, no conversion necessary, possible byte swap*/ + #ifdef PA_LSB_IS_NATIVE_ + *converter = Swap16; + #endif + break; + case ASIOSTInt16LSB: + /* src: paInt16, no conversion necessary, possible byte swap*/ + #ifdef PA_MSB_IS_NATIVE_ + *converter = Swap16; + #endif + break; + case ASIOSTFloat32MSB: + /* src: paFloat32, no conversion necessary, possible byte swap*/ + #ifdef PA_LSB_IS_NATIVE_ + *converter = Swap32; + #endif + break; + case ASIOSTFloat32LSB: + /* src: paFloat32, no conversion necessary, possible byte swap*/ + #ifdef PA_MSB_IS_NATIVE_ + *converter = Swap32; + #endif + break; + case ASIOSTFloat64MSB: + /* src: paFloat32, in-place conversion to/from float32, possible byte swap*/ + #ifdef PA_LSB_IS_NATIVE_ + *converter = ConvertFloat32ToFloat64Swap64; + #else + *converter = ConvertFloat32ToFloat64; + #endif + break; + case ASIOSTFloat64LSB: + /* src: paFloat32, in-place conversion to/from float32, possible byte swap*/ + #ifdef PA_MSB_IS_NATIVE_ + *converter = ConvertFloat32ToFloat64Swap64; + #else + *converter = ConvertFloat32ToFloat64; + #endif + break; + case ASIOSTInt32MSB: + /* src: paInt32, no conversion necessary, possible byte swap */ + #ifdef PA_LSB_IS_NATIVE_ + *converter = Swap32; + #endif + break; + case ASIOSTInt32LSB: + /* src: paInt32, no conversion necessary, possible byte swap */ + #ifdef PA_MSB_IS_NATIVE_ + *converter = Swap32; + #endif + break; + case ASIOSTInt32MSB16: + /* src: paInt32, 16 bit shift, possible byte swap */ + #ifdef PA_LSB_IS_NATIVE_ + *converter = ShiftRightSwap32; + #else + *converter = ShiftRight32; + #endif + *shift = 16; + break; + case ASIOSTInt32MSB18: + /* src: paInt32, 14 bit shift, possible byte swap */ + #ifdef PA_LSB_IS_NATIVE_ + *converter = ShiftRightSwap32; + #else + *converter = ShiftRight32; + #endif + *shift = 14; + break; + case ASIOSTInt32MSB20: + /* src: paInt32, 12 bit shift, possible byte swap */ + #ifdef PA_LSB_IS_NATIVE_ + *converter = ShiftRightSwap32; + #else + *converter = ShiftRight32; + #endif + *shift = 12; + break; + case ASIOSTInt32MSB24: + /* src: paInt32, 8 bit shift, possible byte swap */ + #ifdef PA_LSB_IS_NATIVE_ + *converter = ShiftRightSwap32; + #else + *converter = ShiftRight32; + #endif + *shift = 8; + break; + case ASIOSTInt32LSB16: + /* src: paInt32, 16 bit shift, possible byte swap */ + #ifdef PA_MSB_IS_NATIVE_ + *converter = ShiftRightSwap32; + #else + *converter = ShiftRight32; + #endif + *shift = 16; + break; + case ASIOSTInt32LSB18: + /* src: paInt32, 14 bit shift, possible byte swap */ + #ifdef PA_MSB_IS_NATIVE_ + *converter = ShiftRightSwap32; + #else + *converter = ShiftRight32; + #endif + *shift = 14; + break; + case ASIOSTInt32LSB20: + /* src: paInt32, 12 bit shift, possible byte swap */ + #ifdef PA_MSB_IS_NATIVE_ + *converter = ShiftRightSwap32; + #else + *converter = ShiftRight32; + #endif + *shift = 12; + break; + case ASIOSTInt32LSB24: + /* src: paInt32, 8 bit shift, possible byte swap */ + #ifdef PA_MSB_IS_NATIVE_ + *converter = ShiftRightSwap32; + #else + *converter = ShiftRight32; + #endif + *shift = 8; + break; + case ASIOSTInt24MSB: + /* src: paInt24, no conversion necessary, possible byte swap */ + #ifdef PA_LSB_IS_NATIVE_ + *converter = Swap24; + #endif + break; + case ASIOSTInt24LSB: + /* src: paInt24, no conversion necessary, possible byte swap */ + #ifdef PA_MSB_IS_NATIVE_ + *converter = Swap24; + #endif + break; + } +} + + +typedef struct PaAsioDeviceInfo +{ + PaDeviceInfo commonDeviceInfo; + long minBufferSize; + long maxBufferSize; + long preferredBufferSize; + long bufferGranularity; + + ASIOChannelInfo *asioChannelInfos; +} +PaAsioDeviceInfo; + + +PaError PaAsio_GetAvailableLatencyValues( PaDeviceIndex device, + long *minLatency, long *maxLatency, long *preferredLatency, long *granularity ) +{ + PaError result; + PaUtilHostApiRepresentation *hostApi; + PaDeviceIndex hostApiDevice; + + result = PaUtil_GetHostApiRepresentation( &hostApi, paASIO ); + + if( result == paNoError ) + { + result = PaUtil_DeviceIndexToHostApiDeviceIndex( &hostApiDevice, device, hostApi ); + + if( result == paNoError ) + { + PaAsioDeviceInfo *asioDeviceInfo = + (PaAsioDeviceInfo*)hostApi->deviceInfos[hostApiDevice]; + + *minLatency = asioDeviceInfo->minBufferSize; + *maxLatency = asioDeviceInfo->maxBufferSize; + *preferredLatency = asioDeviceInfo->preferredBufferSize; + *granularity = asioDeviceInfo->bufferGranularity; + } + } + + return result; +} + + + +/* + load the asio driver named by <driverName> 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. +*/ +static PaError LoadAsioDriver( const char *driverName, + PaAsioDriverInfo *driverInfo, void *systemSpecific ) +{ + PaError result = paNoError; + ASIOError asioError; + int asioIsInitialized = 0; + + if( !loadAsioDriver( const_cast<char*>(driverName) ) ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_HOST_ERROR( 0, "Failed to load ASIO driver" ); + goto error; + } + + memset( &driverInfo->asioDriverInfo, 0, sizeof(ASIODriverInfo) ); + driverInfo->asioDriverInfo.asioVersion = 2; + driverInfo->asioDriverInfo.sysRef = systemSpecific; + if( (asioError = ASIOInit( &driverInfo->asioDriverInfo )) != ASE_OK ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + goto error; + } + else + { + asioIsInitialized = 1; + } + + if( (asioError = ASIOGetChannels(&driverInfo->inputChannelCount, + &driverInfo->outputChannelCount)) != ASE_OK ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + goto error; + } + + if( (asioError = ASIOGetBufferSize(&driverInfo->bufferMinSize, + &driverInfo->bufferMaxSize, &driverInfo->bufferPreferredSize, + &driverInfo->bufferGranularity)) != ASE_OK ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + goto error; + } + + if( ASIOOutputReady() == ASE_OK ) + driverInfo->postOutput = true; + else + driverInfo->postOutput = false; + + return result; + +error: + if( asioIsInitialized ) + ASIOExit(); + + return result; +} + + +#define PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_ 13 /* must be the same number of elements as in the array below */ +static ASIOSampleRate defaultSampleRateSearchOrder_[] + = {44100.0, 48000.0, 32000.0, 24000.0, 22050.0, 88200.0, 96000.0, + 192000.0, 16000.0, 12000.0, 11025.0, 9600.0, 8000.0 }; + + +PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + int i, driverCount; + PaAsioHostApiRepresentation *asioHostApi; + PaAsioDeviceInfo *deviceInfoArray; + char **names; + PaAsioDriverInfo paAsioDriverInfo; + + asioHostApi = (PaAsioHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaAsioHostApiRepresentation) ); + if( !asioHostApi ) + { + result = paInsufficientMemory; + goto error; + } + + asioHostApi->allocations = PaUtil_CreateAllocationGroup(); + if( !asioHostApi->allocations ) + { + result = paInsufficientMemory; + goto error; + } + + asioHostApi->systemSpecific = 0; + asioHostApi->openAsioDeviceIndex = paNoDevice; + + *hostApi = &asioHostApi->inheritedHostApiRep; + (*hostApi)->info.structVersion = 1; + + (*hostApi)->info.type = paASIO; + (*hostApi)->info.name = "ASIO"; + (*hostApi)->info.deviceCount = 0; + + #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(); + #elif WINDOWS + driverCount = asioDrivers->asioGetNumDev(); + #endif + + if( driverCount > 0 ) + { + names = GetAsioDriverNames( asioHostApi->allocations, driverCount ); + if( !names ) + { + result = paInsufficientMemory; + goto error; + } + + + /* allocate enough space for all drivers, even if some aren't installed */ + + (*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + asioHostApi->allocations, sizeof(PaDeviceInfo*) * driverCount ); + if( !(*hostApi)->deviceInfos ) + { + result = paInsufficientMemory; + goto error; + } + + /* allocate all device info structs in a contiguous block */ + deviceInfoArray = (PaAsioDeviceInfo*)PaUtil_GroupAllocateMemory( + asioHostApi->allocations, sizeof(PaAsioDeviceInfo) * driverCount ); + if( !deviceInfoArray ) + { + result = paInsufficientMemory; + goto error; + } + + for( i=0; i < driverCount; ++i ) + { + /* Attempt to load the asio driver... */ + if( LoadAsioDriver( names[i], &paAsioDriverInfo, asioHostApi->systemSpecific ) == paNoError ) + { + PaAsioDeviceInfo *asioDeviceInfo = &deviceInfoArray[ (*hostApi)->info.deviceCount ]; + PaDeviceInfo *deviceInfo = &asioDeviceInfo->commonDeviceInfo; + + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; + + deviceInfo->name = names[i]; + PA_DEBUG(("PaAsio_Initialize: drv:%d name = %s\n", i,deviceInfo->name)); + PA_DEBUG(("PaAsio_Initialize: drv:%d inputChannels = %d\n", i, paAsioDriverInfo.inputChannelCount)); + PA_DEBUG(("PaAsio_Initialize: drv:%d outputChannels = %d\n", i, paAsioDriverInfo.outputChannelCount)); + PA_DEBUG(("PaAsio_Initialize: drv:%d bufferMinSize = %d\n", i, paAsioDriverInfo.bufferMinSize)); + PA_DEBUG(("PaAsio_Initialize: drv:%d bufferMaxSize = %d\n", i, paAsioDriverInfo.bufferMaxSize)); + PA_DEBUG(("PaAsio_Initialize: drv:%d bufferPreferredSize = %d\n", i, paAsioDriverInfo.bufferPreferredSize)); + PA_DEBUG(("PaAsio_Initialize: drv:%d bufferGranularity = %d\n", i, paAsioDriverInfo.bufferGranularity)); + + deviceInfo->maxInputChannels = paAsioDriverInfo.inputChannelCount; + deviceInfo->maxOutputChannels = paAsioDriverInfo.outputChannelCount; + + deviceInfo->defaultSampleRate = 0.; + bool foundDefaultSampleRate = false; + for( int j=0; j < PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_; ++j ) + { + ASIOError asioError = ASIOCanSampleRate( defaultSampleRateSearchOrder_[j] ); + if( asioError != ASE_NoClock && asioError != ASE_NotPresent ) + { + deviceInfo->defaultSampleRate = defaultSampleRateSearchOrder_[j]; + foundDefaultSampleRate = true; + break; + } + } + + PA_DEBUG(("PaAsio_Initialize: drv:%d defaultSampleRate = %f\n", i, deviceInfo->defaultSampleRate)); + + if( foundDefaultSampleRate ){ + + /* calculate default latency values from bufferPreferredSize + for default low latency, and bufferPreferredSize * 3 + for default high latency. + use the default sample rate to convert from samples to + seconds. Without knowing what sample rate the user will + use this is the best we can do. + */ + + double defaultLowLatency = + paAsioDriverInfo.bufferPreferredSize / deviceInfo->defaultSampleRate; + + deviceInfo->defaultLowInputLatency = defaultLowLatency; + deviceInfo->defaultLowOutputLatency = defaultLowLatency; + + long defaultHighLatencyBufferSize = + paAsioDriverInfo.bufferPreferredSize * 3; + + if( defaultHighLatencyBufferSize > paAsioDriverInfo.bufferMaxSize ) + defaultHighLatencyBufferSize = paAsioDriverInfo.bufferMaxSize; + + double defaultHighLatency = + defaultHighLatencyBufferSize / deviceInfo->defaultSampleRate; + + if( defaultHighLatency < defaultLowLatency ) + defaultHighLatency = defaultLowLatency; /* just incase the driver returns something strange */ + + deviceInfo->defaultHighInputLatency = defaultHighLatency; + deviceInfo->defaultHighOutputLatency = defaultHighLatency; + + }else{ + + deviceInfo->defaultLowInputLatency = 0.; + deviceInfo->defaultLowOutputLatency = 0.; + deviceInfo->defaultHighInputLatency = 0.; + deviceInfo->defaultHighOutputLatency = 0.; + } + + PA_DEBUG(("PaAsio_Initialize: drv:%d defaultLowInputLatency = %f\n", i, deviceInfo->defaultLowInputLatency)); + PA_DEBUG(("PaAsio_Initialize: drv:%d defaultLowOutputLatency = %f\n", i, deviceInfo->defaultLowOutputLatency)); + PA_DEBUG(("PaAsio_Initialize: drv:%d defaultHighInputLatency = %f\n", i, deviceInfo->defaultHighInputLatency)); + PA_DEBUG(("PaAsio_Initialize: drv:%d defaultHighOutputLatency = %f\n", i, deviceInfo->defaultHighOutputLatency)); + + asioDeviceInfo->minBufferSize = paAsioDriverInfo.bufferMinSize; + asioDeviceInfo->maxBufferSize = paAsioDriverInfo.bufferMaxSize; + asioDeviceInfo->preferredBufferSize = paAsioDriverInfo.bufferPreferredSize; + asioDeviceInfo->bufferGranularity = paAsioDriverInfo.bufferGranularity; + + + asioDeviceInfo->asioChannelInfos = (ASIOChannelInfo*)PaUtil_GroupAllocateMemory( + asioHostApi->allocations, + sizeof(ASIOChannelInfo) * (deviceInfo->maxInputChannels + + deviceInfo->maxOutputChannels) ); + if( !asioDeviceInfo->asioChannelInfos ) + { + result = paInsufficientMemory; + goto error; + } + + int a; + + for( a=0; a < deviceInfo->maxInputChannels; ++a ){ + asioDeviceInfo->asioChannelInfos[a].channel = a; + asioDeviceInfo->asioChannelInfos[a].isInput = ASIOTrue; + ASIOError asioError = ASIOGetChannelInfo( &asioDeviceInfo->asioChannelInfos[a] ); + if( asioError != ASE_OK ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + goto error; + } + } + + for( a=0; a < deviceInfo->maxOutputChannels; ++a ){ + int b = deviceInfo->maxInputChannels + a; + asioDeviceInfo->asioChannelInfos[b].channel = a; + asioDeviceInfo->asioChannelInfos[b].isInput = ASIOFalse; + ASIOError asioError = ASIOGetChannelInfo( &asioDeviceInfo->asioChannelInfos[b] ); + if( asioError != ASE_OK ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + goto error; + } + } + + + /* unload the driver */ + ASIOExit(); + + (*hostApi)->deviceInfos[ (*hostApi)->info.deviceCount ] = deviceInfo; + ++(*hostApi)->info.deviceCount; + } + } + } + + if( (*hostApi)->info.deviceCount > 0 ) + { + (*hostApi)->info.defaultInputDevice = 0; + (*hostApi)->info.defaultOutputDevice = 0; + } + else + { + (*hostApi)->info.defaultInputDevice = paNoDevice; + (*hostApi)->info.defaultOutputDevice = paNoDevice; + } + + + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PaUtil_InitializeStreamInterface( &asioHostApi->callbackStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface( &asioHostApi->blockingStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable ); + + return result; + +error: + if( asioHostApi ) + { + if( asioHostApi->allocations ) + { + PaUtil_FreeAllAllocations( asioHostApi->allocations ); + PaUtil_DestroyAllocationGroup( asioHostApi->allocations ); + } + + PaUtil_FreeMemory( asioHostApi ); + } + return result; +} + + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaAsioHostApiRepresentation *asioHostApi = (PaAsioHostApiRepresentation*)hostApi; + + /* + IMPLEMENT ME: + - clean up any resources not handled by the allocation group + */ + + if( asioHostApi->allocations ) + { + PaUtil_FreeAllAllocations( asioHostApi->allocations ); + PaUtil_DestroyAllocationGroup( asioHostApi->allocations ); + } + + PaUtil_FreeMemory( asioHostApi ); +} + + +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + PaError result = paNoError; + PaAsioHostApiRepresentation *asioHostApi = (PaAsioHostApiRepresentation*)hostApi; + PaAsioDriverInfo *driverInfo = &asioHostApi->openAsioDriverInfo; + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + PaDeviceIndex asioDeviceIndex; + ASIOError asioError; + + if( inputParameters && outputParameters ) + { + /* full duplex ASIO stream must use the same device for input and output */ + + if( inputParameters->device != outputParameters->device ) + return paBadIODeviceCombination; + } + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* all standard sample formats are supported by the buffer adapter, + this implementation doesn't support any custom sample formats */ + if( inputSampleFormat & paCustomFormat ) + return paSampleFormatNotSupported; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + asioDeviceIndex = inputParameters->device; + + /* validate inputStreamInfo */ + /** @todo do more validation here */ + // if( inputParameters->hostApiSpecificStreamInfo ) + // return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + inputChannelCount = 0; + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* all standard sample formats are supported by the buffer adapter, + this implementation doesn't support any custom sample formats */ + if( outputSampleFormat & paCustomFormat ) + return paSampleFormatNotSupported; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + asioDeviceIndex = outputParameters->device; + + /* validate outputStreamInfo */ + /** @todo do more validation here */ + // if( outputParameters->hostApiSpecificStreamInfo ) + // return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + outputChannelCount = 0; + } + + + + /* if an ASIO device is open we can only get format information for the currently open device */ + + if( asioHostApi->openAsioDeviceIndex != paNoDevice + && asioHostApi->openAsioDeviceIndex != asioDeviceIndex ) + { + return paDeviceUnavailable; + } + + + /* NOTE: we load the driver and use its current settings + rather than the ones in our device info structure which may be stale */ + + /* open the device if it's not already open */ + if( asioHostApi->openAsioDeviceIndex == paNoDevice ) + { + result = LoadAsioDriver( asioHostApi->inheritedHostApiRep.deviceInfos[ asioDeviceIndex ]->name, + driverInfo, asioHostApi->systemSpecific ); + if( result != paNoError ) + return result; + } + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > 0 ) + { + if( inputChannelCount > driverInfo->inputChannelCount ) + { + result = paInvalidChannelCount; + goto done; + } + } + + /* check that output device can support outputChannelCount */ + if( outputChannelCount ) + { + if( outputChannelCount > driverInfo->outputChannelCount ) + { + result = paInvalidChannelCount; + goto done; + } + } + + /* query for sample rate support */ + asioError = ASIOCanSampleRate( sampleRate ); + if( asioError == ASE_NoClock || asioError == ASE_NotPresent ) + { + result = paInvalidSampleRate; + goto done; + } + +done: + /* close the device if it wasn't already open */ + if( asioHostApi->openAsioDeviceIndex == paNoDevice ) + { + ASIOExit(); /* not sure if we should check for errors here */ + } + + if( result == paNoError ) + return paFormatIsSupported; + else + return result; +} + + + +/* PaAsioStream - a stream data structure specifically for this implementation */ + +typedef struct PaAsioStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + + PaAsioHostApiRepresentation *asioHostApi; + unsigned long framesPerHostCallback; + + /* ASIO driver info - these may not be needed for the life of the stream, + but store them here until we work out how format conversion is going + to work. */ + + ASIOBufferInfo *asioBufferInfos; + ASIOChannelInfo *asioChannelInfos; + long inputLatency, outputLatency; // actual latencies returned by asio + + long inputChannelCount, outputChannelCount; + bool postOutput; + + void **bufferPtrs; /* this is carved up for inputBufferPtrs and outputBufferPtrs */ + void **inputBufferPtrs[2]; + void **outputBufferPtrs[2]; + + PaAsioBufferConverter *inputBufferConverter; + long inputShift; + PaAsioBufferConverter *outputBufferConverter; + long outputShift; + + volatile bool stopProcessing; + int stopPlayoutCount; + HANDLE completedBuffersPlayedEvent; + + bool streamFinishedCallbackCalled; + volatile int isActive; + volatile bool zeroOutput; /* all future calls to the callback will output silence */ + + volatile long reenterCount; + volatile long reenterError; + + PaStreamCallbackFlags callbackFlags; +} +PaAsioStream; + +static PaAsioStream *theAsioStream = 0; /* due to ASIO sdk limitations there can be only one stream */ + + +static void ZeroOutputBuffers( PaAsioStream *stream, long index ) +{ + int i; + + for( i=0; i < stream->outputChannelCount; ++i ) + { + void *buffer = stream->asioBufferInfos[ i + stream->inputChannelCount ].buffers[index]; + + int bytesPerSample = BytesPerAsioSample( stream->asioChannelInfos[ i + stream->inputChannelCount ].type ); + + memset( buffer, 0, stream->framesPerHostCallback * bytesPerSample ); + } +} + + +static unsigned long SelectHostBufferSize( unsigned long suggestedLatencyFrames, + PaAsioDriverInfo *driverInfo ) +{ + unsigned long result; + + if( suggestedLatencyFrames == 0 ) + { + result = driverInfo->bufferPreferredSize; + } + else{ + if( suggestedLatencyFrames <= (unsigned long)driverInfo->bufferMinSize ) + { + result = driverInfo->bufferMinSize; + } + else if( suggestedLatencyFrames >= (unsigned long)driverInfo->bufferMaxSize ) + { + result = driverInfo->bufferMaxSize; + } + else + { + if( driverInfo->bufferGranularity == -1 ) + { + /* power-of-two */ + result = 2; + + while( result < suggestedLatencyFrames ) + result *= 2; + + if( result < (unsigned long)driverInfo->bufferMinSize ) + result = driverInfo->bufferMinSize; + + if( result > (unsigned long)driverInfo->bufferMaxSize ) + result = driverInfo->bufferMaxSize; + } + else if( driverInfo->bufferGranularity == 0 ) + { + /* the documentation states that bufferGranularity should be + zero when bufferMinSize, bufferMaxSize and + bufferPreferredSize are the same. We assume that is the case. + */ + + result = driverInfo->bufferPreferredSize; + } + else + { + /* modulo granularity */ + + unsigned long remainder = + suggestedLatencyFrames % driverInfo->bufferGranularity; + + if( remainder == 0 ) + { + result = suggestedLatencyFrames; + } + else + { + result = suggestedLatencyFrames + + (driverInfo->bufferGranularity - remainder); + + if( result > (unsigned long)driverInfo->bufferMaxSize ) + result = driverInfo->bufferMaxSize; + } + } + } + } + + return result; +} + + +/* returns channelSelectors if present */ + +static PaError ValidateAsioSpecificStreamInfo( + const PaStreamParameters *streamParameters, + const PaAsioStreamInfo *streamInfo, + int deviceChannelCount, + int **channelSelectors ) +{ + if( streamInfo ) + { + if( streamInfo->size != sizeof( PaAsioStreamInfo ) + || streamInfo->version != 1 ) + { + return paIncompatibleHostApiSpecificStreamInfo; + } + + if( streamInfo->flags & paAsioUseChannelSelectors ) + *channelSelectors = streamInfo->channelSelectors; + + if( !(*channelSelectors) ) + return paIncompatibleHostApiSpecificStreamInfo; + + for( int i=0; i < streamParameters->channelCount; ++i ){ + if( (*channelSelectors)[i] < 0 + || (*channelSelectors)[i] >= deviceChannelCount ){ + return paInvalidChannelCount; + } + } + } + + return paNoError; +} + + +/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */ + +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ) +{ + PaError result = paNoError; + PaAsioHostApiRepresentation *asioHostApi = (PaAsioHostApiRepresentation*)hostApi; + PaAsioStream *stream = 0; + PaAsioStreamInfo *inputStreamInfo, *outputStreamInfo; + unsigned long framesPerHostBuffer; + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat; + unsigned long suggestedInputLatencyFrames; + unsigned long suggestedOutputLatencyFrames; + PaDeviceIndex asioDeviceIndex; + ASIOError asioError; + int asioIsInitialized = 0; + int asioBuffersCreated = 0; + int completedBuffersPlayedEventInited = 0; + int i; + PaAsioDriverInfo *driverInfo; + int *inputChannelSelectors = 0; + int *outputChannelSelectors = 0; + + /* unless we move to using lower level ASIO calls, we can only have + one device open at a time */ + if( asioHostApi->openAsioDeviceIndex != paNoDevice ){ + PA_DEBUG(("OpenStream paDeviceUnavailable\n")); + return paDeviceUnavailable; + } + + if( inputParameters && outputParameters ) + { + /* full duplex ASIO stream must use the same device for input and output */ + + if( inputParameters->device != outputParameters->device ){ + PA_DEBUG(("OpenStream paBadIODeviceCombination\n")); + return paBadIODeviceCombination; + } + } + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + suggestedInputLatencyFrames = (unsigned long)((inputParameters->suggestedLatency * sampleRate)+0.5f); + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + asioDeviceIndex = inputParameters->device; + + PaAsioDeviceInfo *asioDeviceInfo = (PaAsioDeviceInfo*)hostApi->deviceInfos[asioDeviceIndex]; + + /* validate hostApiSpecificStreamInfo */ + inputStreamInfo = (PaAsioStreamInfo*)inputParameters->hostApiSpecificStreamInfo; + result = ValidateAsioSpecificStreamInfo( inputParameters, inputStreamInfo, + asioDeviceInfo->commonDeviceInfo.maxInputChannels, + &inputChannelSelectors + ); + if( result != paNoError ) return result; + } + else + { + inputChannelCount = 0; + inputSampleFormat = 0; + suggestedInputLatencyFrames = 0; + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + suggestedOutputLatencyFrames = (unsigned long)((outputParameters->suggestedLatency * sampleRate)+0.5f); + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + asioDeviceIndex = outputParameters->device; + + PaAsioDeviceInfo *asioDeviceInfo = (PaAsioDeviceInfo*)hostApi->deviceInfos[asioDeviceIndex]; + + /* validate hostApiSpecificStreamInfo */ + outputStreamInfo = (PaAsioStreamInfo*)outputParameters->hostApiSpecificStreamInfo; + result = ValidateAsioSpecificStreamInfo( outputParameters, outputStreamInfo, + asioDeviceInfo->commonDeviceInfo.maxOutputChannels, + &outputChannelSelectors + ); + if( result != paNoError ) return result; + } + else + { + outputChannelCount = 0; + outputSampleFormat = 0; + suggestedOutputLatencyFrames = 0; + } + + driverInfo = &asioHostApi->openAsioDriverInfo; + + /* 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, + driverInfo, asioHostApi->systemSpecific ); + if( result == paNoError ) + asioIsInitialized = 1; + else{ + PA_DEBUG(("OpenStream ERROR1\n")); + goto error; + } + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > 0 ) + { + if( inputChannelCount > driverInfo->inputChannelCount ) + { + result = paInvalidChannelCount; + PA_DEBUG(("OpenStream ERROR2\n")); + goto error; + } + } + + /* check that output device can support outputChannelCount */ + if( outputChannelCount ) + { + if( outputChannelCount > driverInfo->outputChannelCount ) + { + result = paInvalidChannelCount; + PA_DEBUG(("OpenStream ERROR3\n")); + goto error; + } + } + + PA_DEBUG(("before ASIOSetSampleRate(%f)\n",sampleRate)); + asioError = ASIOSetSampleRate( sampleRate ); + /* Set sample rate */ + if( asioError != ASE_OK ) + { + result = paInvalidSampleRate; + PA_DEBUG(("ERROR: ASIOSetSampleRate: %s\n", PaAsio_GetAsioErrorText(asioError) )); + goto error; + } + PA_DEBUG(("after ASIOSetSampleRate(%f)\n",sampleRate)); + /* + IMPLEMENT ME: + - if a full duplex stream is requested, check that the combination + of input and output parameters is supported + */ + + /* validate platform specific flags */ + if( (streamFlags & paPlatformSpecificFlags) != 0 ){ + PA_DEBUG(("OpenStream invalid flags!!\n")); + return paInvalidFlag; /* unexpected platform specific flag */ + } + + + stream = (PaAsioStream*)PaUtil_AllocateMemory( sizeof(PaAsioStream) ); + if( !stream ) + { + result = paInsufficientMemory; + PA_DEBUG(("OpenStream ERROR5\n")); + goto error; + } + + stream->completedBuffersPlayedEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); + if( stream->completedBuffersPlayedEvent == NULL ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() ); + PA_DEBUG(("OpenStream ERROR6\n")); + goto error; + } + completedBuffersPlayedEventInited = 1; + + + stream->asioBufferInfos = 0; /* for deallocation in error */ + stream->asioChannelInfos = 0; /* for deallocation in error */ + stream->bufferPtrs = 0; /* for deallocation in error */ + + if( streamCallback ) + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &asioHostApi->callbackStreamInterface, streamCallback, userData ); + } + else + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &asioHostApi->blockingStreamInterface, streamCallback, userData ); + } + + + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); + + + stream->asioBufferInfos = (ASIOBufferInfo*)PaUtil_AllocateMemory( + sizeof(ASIOBufferInfo) * (inputChannelCount + outputChannelCount) ); + if( !stream->asioBufferInfos ) + { + result = paInsufficientMemory; + PA_DEBUG(("OpenStream ERROR7\n")); + goto error; + } + + + for( i=0; i < inputChannelCount; ++i ) + { + ASIOBufferInfo *info = &stream->asioBufferInfos[i]; + + info->isInput = ASIOTrue; + + if( inputChannelSelectors ){ + // inputChannelSelectors values have already been validated in + // ValidateAsioSpecificStreamInfo() above + info->channelNum = inputChannelSelectors[i]; + }else{ + info->channelNum = i; + } + + info->buffers[0] = info->buffers[1] = 0; + } + + for( i=0; i < outputChannelCount; ++i ){ + ASIOBufferInfo *info = &stream->asioBufferInfos[inputChannelCount+i]; + + info->isInput = ASIOFalse; + + if( outputChannelSelectors ){ + // outputChannelSelectors values have already been validated in + // ValidateAsioSpecificStreamInfo() above + info->channelNum = outputChannelSelectors[i]; + }else{ + info->channelNum = i; + } + + info->buffers[0] = info->buffers[1] = 0; + } + + + framesPerHostBuffer = SelectHostBufferSize( + (( suggestedInputLatencyFrames > suggestedOutputLatencyFrames ) + ? suggestedInputLatencyFrames : suggestedOutputLatencyFrames), + driverInfo ); + + + PA_DEBUG(("PaAsioOpenStream: framesPerHostBuffer :%d\n", framesPerHostBuffer)); + + asioError = ASIOCreateBuffers( stream->asioBufferInfos, + inputChannelCount+outputChannelCount, + framesPerHostBuffer, &asioCallbacks_ ); + + if( asioError != ASE_OK + && framesPerHostBuffer != (unsigned long)driverInfo->bufferPreferredSize ) + { + PA_DEBUG(("ERROR: ASIOCreateBuffers: %s\n", PaAsio_GetAsioErrorText(asioError) )); + /* + Some buggy drivers (like the Hoontech DSP24) give incorrect + [min, preferred, max] values They should work with the preferred size + value, thus if Pa_ASIO_CreateBuffers fails with the hostBufferSize + computed in SelectHostBufferSize, we try again with the preferred size. + */ + + framesPerHostBuffer = driverInfo->bufferPreferredSize; + + PA_DEBUG(("PaAsioOpenStream: CORRECTED framesPerHostBuffer :%d\n", framesPerHostBuffer)); + + ASIOError asioError2 = ASIOCreateBuffers( stream->asioBufferInfos, + inputChannelCount+outputChannelCount, + framesPerHostBuffer, &asioCallbacks_ ); + if( asioError2 == ASE_OK ) + asioError = ASE_OK; + } + + if( asioError != ASE_OK ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + PA_DEBUG(("OpenStream ERROR9\n")); + goto error; + } + + asioBuffersCreated = 1; + + stream->asioChannelInfos = (ASIOChannelInfo*)PaUtil_AllocateMemory( + sizeof(ASIOChannelInfo) * (inputChannelCount + outputChannelCount) ); + if( !stream->asioChannelInfos ) + { + result = paInsufficientMemory; + PA_DEBUG(("OpenStream ERROR10\n")); + goto error; + } + + for( i=0; i < inputChannelCount + outputChannelCount; ++i ) + { + stream->asioChannelInfos[i].channel = stream->asioBufferInfos[i].channelNum; + stream->asioChannelInfos[i].isInput = stream->asioBufferInfos[i].isInput; + asioError = ASIOGetChannelInfo( &stream->asioChannelInfos[i] ); + if( asioError != ASE_OK ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + PA_DEBUG(("OpenStream ERROR11\n")); + goto error; + } + } + + stream->bufferPtrs = (void**)PaUtil_AllocateMemory( + 2 * sizeof(void*) * (inputChannelCount + outputChannelCount) ); + if( !stream->bufferPtrs ) + { + result = paInsufficientMemory; + PA_DEBUG(("OpenStream ERROR12\n")); + goto error; + } + + if( inputChannelCount > 0 ) + { + stream->inputBufferPtrs[0] = stream-> bufferPtrs; + stream->inputBufferPtrs[1] = &stream->bufferPtrs[inputChannelCount]; + + for( i=0; i<inputChannelCount; ++i ) + { + stream->inputBufferPtrs[0][i] = stream->asioBufferInfos[i].buffers[0]; + stream->inputBufferPtrs[1][i] = stream->asioBufferInfos[i].buffers[1]; + } + } + else + { + stream->inputBufferPtrs[0] = 0; + stream->inputBufferPtrs[1] = 0; + } + + if( outputChannelCount > 0 ) + { + stream->outputBufferPtrs[0] = &stream->bufferPtrs[inputChannelCount*2]; + stream->outputBufferPtrs[1] = &stream->bufferPtrs[inputChannelCount*2 + outputChannelCount]; + + for( i=0; i<outputChannelCount; ++i ) + { + stream->outputBufferPtrs[0][i] = stream->asioBufferInfos[inputChannelCount+i].buffers[0]; + stream->outputBufferPtrs[1][i] = stream->asioBufferInfos[inputChannelCount+i].buffers[1]; + } + } + else + { + stream->outputBufferPtrs[0] = 0; + stream->outputBufferPtrs[1] = 0; + } + + if( inputChannelCount > 0 ) + { + /* FIXME: assume all channels use the same type for now */ + ASIOSampleType inputType = stream->asioChannelInfos[0].type; + + PA_DEBUG(("ASIO Input type:%d",inputType)); + AsioSampleTypeLOG(inputType); + hostInputSampleFormat = AsioSampleTypeToPaNativeSampleFormat( inputType ); + + SelectAsioToPaConverter( inputType, &stream->inputBufferConverter, &stream->inputShift ); + } + else + { + hostInputSampleFormat = 0; + stream->inputBufferConverter = 0; + } + + if( outputChannelCount > 0 ) + { + /* FIXME: assume all channels use the same type for now */ + ASIOSampleType outputType = stream->asioChannelInfos[inputChannelCount].type; + + PA_DEBUG(("ASIO Output type:%d",outputType)); + AsioSampleTypeLOG(outputType); + hostOutputSampleFormat = AsioSampleTypeToPaNativeSampleFormat( outputType ); + + SelectPaToAsioConverter( outputType, &stream->outputBufferConverter, &stream->outputShift ); + } + else + { + hostOutputSampleFormat = 0; + 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) + )); + + stream->asioHostApi = asioHostApi; + stream->framesPerHostCallback = framesPerHostBuffer; + + stream->inputChannelCount = inputChannelCount; + stream->outputChannelCount = outputChannelCount; + stream->postOutput = driverInfo->postOutput; + stream->isActive = 0; + + asioHostApi->openAsioDeviceIndex = asioDeviceIndex; + + *s = (PaStream*)stream; + + return result; + +error: + if( stream ) + { + if( completedBuffersPlayedEventInited ) + CloseHandle( stream->completedBuffersPlayedEvent ); + + if( stream->asioBufferInfos ) + PaUtil_FreeMemory( stream->asioBufferInfos ); + + if( stream->asioChannelInfos ) + PaUtil_FreeMemory( stream->asioChannelInfos ); + + if( stream->bufferPtrs ) + PaUtil_FreeMemory( stream->bufferPtrs ); + + PaUtil_FreeMemory( stream ); + } + + if( asioBuffersCreated ) + ASIODisposeBuffers(); + + if( asioIsInitialized ) + ASIOExit(); + + return result; +} + + +/* + When CloseStream() is called, the multi-api layer ensures that + the stream has already been stopped or aborted. +*/ +static PaError CloseStream( PaStream* s ) +{ + PaError result = paNoError; + PaAsioStream *stream = (PaAsioStream*)s; + + /* + IMPLEMENT ME: + - additional stream closing + cleanup + */ + + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + + stream->asioHostApi->openAsioDeviceIndex = paNoDevice; + + CloseHandle( stream->completedBuffersPlayedEvent ); + + PaUtil_FreeMemory( stream->asioBufferInfos ); + PaUtil_FreeMemory( stream->asioChannelInfos ); + PaUtil_FreeMemory( stream->bufferPtrs ); + PaUtil_FreeMemory( stream ); + + ASIODisposeBuffers(); + ASIOExit(); + + return result; +} + + +static void bufferSwitch(long index, ASIOBool directProcess) +{ +//TAKEN FROM THE ASIO SDK + + // the actual processing callback. + // Beware that this is normally in a seperate thread, hence be sure that + // you take care about thread synchronization. This is omitted here for + // simplicity. + + // as this is a "back door" into the bufferSwitchTimeInfo a timeInfo needs + // to be created though it will only set the timeInfo.samplePosition and + // timeInfo.systemTime fields and the according flags + + ASIOTime timeInfo; + memset( &timeInfo, 0, sizeof (timeInfo) ); + + // get the time stamp of the buffer, not necessary if no + // synchronization to other media is required + if( ASIOGetSamplePosition(&timeInfo.timeInfo.samplePosition, &timeInfo.timeInfo.systemTime) == ASE_OK) + timeInfo.timeInfo.flags = kSystemTimeValid | kSamplePositionValid; + + // Call the real callback + bufferSwitchTimeInfo( &timeInfo, index, directProcess ); +} + + +// conversion from 64 bit ASIOSample/ASIOTimeStamp to double float +#if NATIVE_INT64 + #define ASIO64toDouble(a) (a) +#else + const double twoRaisedTo32 = 4294967296.; + #define ASIO64toDouble(a) ((a).lo + (a).hi * twoRaisedTo32) +#endif + +static ASIOTime *bufferSwitchTimeInfo( ASIOTime *timeInfo, long index, ASIOBool directProcess ) +{ + // the actual processing callback. + // Beware that this is normally in a seperate thread, hence be sure that + // you take care about thread synchronization. + + + /* The SDK says the following about the directProcess flag: + suggests to the host whether it should immediately start processing + (directProcess == ASIOTrue), or whether its process should be deferred + because the call comes from a very low level (for instance, a high level + priority interrupt), and direct processing would cause timing instabilities for + the rest of the system. If in doubt, directProcess should be set to ASIOFalse. + + We just ignore directProcess. This could cause incompatibilities with + drivers which really don't want the audio processing to occur in this + callback, but none have been identified yet. + */ + + (void) directProcess; /* suppress unused parameter warning */ + +#if 0 + // store the timeInfo for later use + asioDriverInfo.tInfo = *timeInfo; + + // get the time stamp of the buffer, not necessary if no + // synchronization to other media is required + + if (timeInfo->timeInfo.flags & kSystemTimeValid) + asioDriverInfo.nanoSeconds = ASIO64toDouble(timeInfo->timeInfo.systemTime); + else + asioDriverInfo.nanoSeconds = 0; + + if (timeInfo->timeInfo.flags & kSamplePositionValid) + asioDriverInfo.samples = ASIO64toDouble(timeInfo->timeInfo.samplePosition); + else + asioDriverInfo.samples = 0; + + if (timeInfo->timeCode.flags & kTcValid) + asioDriverInfo.tcSamples = ASIO64toDouble(timeInfo->timeCode.timeCodeSamples); + else + asioDriverInfo.tcSamples = 0; + + // get the system reference time + asioDriverInfo.sysRefTime = get_sys_reference_time(); +#endif + +#if 0 + // a few debug messages for the Windows device driver developer + // tells you the time when driver got its interrupt and the delay until the app receives + // the event notification. + static double last_samples = 0; + char tmp[128]; + sprintf (tmp, "diff: %d / %d ms / %d ms / %d samples \n", asioDriverInfo.sysRefTime - (long)(asioDriverInfo.nanoSeconds / 1000000.0), asioDriverInfo.sysRefTime, (long)(asioDriverInfo.nanoSeconds / 1000000.0), (long)(asioDriverInfo.samples - last_samples)); + OutputDebugString (tmp); + last_samples = asioDriverInfo.samples; +#endif + + + if( !theAsioStream ) + return 0L; + + // Keep sample position + // FIXME: asioDriverInfo.pahsc_NumFramesDone = timeInfo->timeInfo.samplePosition.lo; + + + // protect against reentrancy + if( PaAsio_AtomicIncrement(&theAsioStream->reenterCount) ) + { + theAsioStream->reenterError++; + //DBUG(("bufferSwitchTimeInfo : reentrancy detection = %d\n", asioDriverInfo.reenterError)); + return 0L; + } + + int buffersDone = 0; + + do + { + if( buffersDone > 0 ) + { + // this is a reentered buffer, we missed processing it on time + // set the input overflow and output underflow flags as appropriate + + if( theAsioStream->inputChannelCount > 0 ) + theAsioStream->callbackFlags |= paInputOverflow; + + if( theAsioStream->outputChannelCount > 0 ) + theAsioStream->callbackFlags |= paOutputUnderflow; + } + else + { + if( theAsioStream->zeroOutput ) + { + ZeroOutputBuffers( theAsioStream, index ); + + // Finally if the driver supports the ASIOOutputReady() optimization, + // do it here, all data are in place + if( theAsioStream->postOutput ) + ASIOOutputReady(); + + if( theAsioStream->stopProcessing ) + { + if( theAsioStream->stopPlayoutCount < 2 ) + { + ++theAsioStream->stopPlayoutCount; + if( theAsioStream->stopPlayoutCount == 2 ) + { + theAsioStream->isActive = 0; + if( theAsioStream->streamRepresentation.streamFinishedCallback != 0 ) + theAsioStream->streamRepresentation.streamFinishedCallback( theAsioStream->streamRepresentation.userData ); + theAsioStream->streamFinishedCallbackCalled = true; + SetEvent( theAsioStream->completedBuffersPlayedEvent ); + } + } + } + } + else + { + +#if 0 +// test code to try to detect slip conditions... these may work on some systems +// but neither of them work on the RME Digi96 + +// check that sample delta matches buffer size (otherwise we must have skipped +// a buffer. +static double last_samples = -512; +double samples; +//if( timeInfo->timeCode.flags & kTcValid ) +// samples = ASIO64toDouble(timeInfo->timeCode.timeCodeSamples); +//else + samples = ASIO64toDouble(timeInfo->timeInfo.samplePosition); +int delta = samples - last_samples; +//printf( "%d\n", delta); +last_samples = samples; + +if( delta > theAsioStream->framesPerHostCallback ) +{ + if( theAsioStream->inputChannelCount > 0 ) + theAsioStream->callbackFlags |= paInputOverflow; + + if( theAsioStream->outputChannelCount > 0 ) + theAsioStream->callbackFlags |= paOutputUnderflow; +} + +// check that the buffer index is not the previous index (which would indicate +// that a buffer was skipped. +static int previousIndex = 1; +if( index == previousIndex ) +{ + if( theAsioStream->inputChannelCount > 0 ) + theAsioStream->callbackFlags |= paInputOverflow; + + if( theAsioStream->outputChannelCount > 0 ) + theAsioStream->callbackFlags |= paOutputUnderflow; +} +previousIndex = index; +#endif + + int i; + + PaUtil_BeginCpuLoadMeasurement( &theAsioStream->cpuLoadMeasurer ); + + PaStreamCallbackTimeInfo paTimeInfo; + + // asio systemTime is supposed to be measured according to the same + // clock as timeGetTime + paTimeInfo.currentTime = (ASIO64toDouble( timeInfo->timeInfo.systemTime ) * .000000001); + + /* patch from Paul Boege */ + paTimeInfo.inputBufferAdcTime = paTimeInfo.currentTime - + ((double)theAsioStream->inputLatency/theAsioStream->streamRepresentation.streamInfo.sampleRate); + + paTimeInfo.outputBufferDacTime = paTimeInfo.currentTime + + ((double)theAsioStream->outputLatency/theAsioStream->streamRepresentation.streamInfo.sampleRate); + + /* old version is buggy because the buffer processor also adds in its latency to the time parameters + paTimeInfo.inputBufferAdcTime = paTimeInfo.currentTime - theAsioStream->streamRepresentation.streamInfo.inputLatency; + paTimeInfo.outputBufferDacTime = paTimeInfo.currentTime + theAsioStream->streamRepresentation.streamInfo.outputLatency; + */ +#if 1 +// detect underflows by checking inter-callback time > 2 buffer period +static double previousTime = -1; +if( previousTime > 0 ){ + + double delta = paTimeInfo.currentTime - previousTime; + + if( delta >= 2. * (theAsioStream->framesPerHostCallback / theAsioStream->streamRepresentation.streamInfo.sampleRate) ){ + if( theAsioStream->inputChannelCount > 0 ) + theAsioStream->callbackFlags |= paInputOverflow; + + if( theAsioStream->outputChannelCount > 0 ) + theAsioStream->callbackFlags |= paOutputUnderflow; + } +} +previousTime = paTimeInfo.currentTime; +#endif + + // note that the above input and output times do not need to be + // adjusted for the latency of the buffer processor -- the buffer + // processor handles that. + + if( theAsioStream->inputBufferConverter ) + { + for( i=0; i<theAsioStream->inputChannelCount; i++ ) + { + theAsioStream->inputBufferConverter( theAsioStream->inputBufferPtrs[index][i], + theAsioStream->inputShift, theAsioStream->framesPerHostCallback ); + } + } + + PaUtil_BeginBufferProcessing( &theAsioStream->bufferProcessor, &paTimeInfo, theAsioStream->callbackFlags ); + + /* reset status flags once they've been passed to the callback */ + theAsioStream->callbackFlags = 0; + + PaUtil_SetInputFrameCount( &theAsioStream->bufferProcessor, 0 /* default to host buffer size */ ); + for( i=0; i<theAsioStream->inputChannelCount; ++i ) + PaUtil_SetNonInterleavedInputChannel( &theAsioStream->bufferProcessor, i, theAsioStream->inputBufferPtrs[index][i] ); + + PaUtil_SetOutputFrameCount( &theAsioStream->bufferProcessor, 0 /* default to host buffer size */ ); + for( i=0; i<theAsioStream->outputChannelCount; ++i ) + PaUtil_SetNonInterleavedOutputChannel( &theAsioStream->bufferProcessor, i, theAsioStream->outputBufferPtrs[index][i] ); + + int callbackResult; + if( theAsioStream->stopProcessing ) + callbackResult = paComplete; + else + callbackResult = paContinue; + unsigned long framesProcessed = PaUtil_EndBufferProcessing( &theAsioStream->bufferProcessor, &callbackResult ); + + if( theAsioStream->outputBufferConverter ) + { + for( i=0; i<theAsioStream->outputChannelCount; i++ ) + { + theAsioStream->outputBufferConverter( theAsioStream->outputBufferPtrs[index][i], + theAsioStream->outputShift, theAsioStream->framesPerHostCallback ); + } + } + + PaUtil_EndCpuLoadMeasurement( &theAsioStream->cpuLoadMeasurer, framesProcessed ); + + // Finally if the driver supports the ASIOOutputReady() optimization, + // do it here, all data are in place + if( theAsioStream->postOutput ) + ASIOOutputReady(); + + if( callbackResult == paContinue ) + { + /* nothing special to do */ + } + else if( callbackResult == paAbort ) + { + /* finish playback immediately */ + theAsioStream->isActive = 0; + if( theAsioStream->streamRepresentation.streamFinishedCallback != 0 ) + theAsioStream->streamRepresentation.streamFinishedCallback( theAsioStream->streamRepresentation.userData ); + theAsioStream->streamFinishedCallbackCalled = true; + SetEvent( theAsioStream->completedBuffersPlayedEvent ); + theAsioStream->zeroOutput = true; + } + else /* paComplete or other non-zero value indicating complete */ + { + /* Finish playback once currently queued audio has completed. */ + theAsioStream->stopProcessing = true; + + if( PaUtil_IsBufferProcessorOutputEmpty( &theAsioStream->bufferProcessor ) ) + { + theAsioStream->zeroOutput = true; + theAsioStream->stopPlayoutCount = 0; + } + } + } + } + + ++buffersDone; + }while( PaAsio_AtomicDecrement(&theAsioStream->reenterCount) >= 0 ); + + return 0L; +} + + +static void sampleRateChanged(ASIOSampleRate sRate) +{ + // TAKEN FROM THE ASIO SDK + // do whatever you need to do if the sample rate changed + // usually this only happens during external sync. + // Audio processing is not stopped by the driver, actual sample rate + // might not have even changed, maybe only the sample rate status of an + // AES/EBU or S/PDIF digital input at the audio device. + // You might have to update time/sample related conversion routines, etc. + + (void) sRate; /* unused parameter */ + PA_DEBUG( ("sampleRateChanged : %d \n", sRate)); +} + +static long asioMessages(long selector, long value, void* message, double* opt) +{ +// TAKEN FROM THE ASIO SDK + // currently the parameters "value", "message" and "opt" are not used. + long ret = 0; + + (void) message; /* unused parameters */ + (void) opt; + + PA_DEBUG( ("asioMessages : %d , %d \n", selector, value)); + + switch(selector) + { + case kAsioSelectorSupported: + if(value == kAsioResetRequest + || value == kAsioEngineVersion + || value == kAsioResyncRequest + || value == kAsioLatenciesChanged + // the following three were added for ASIO 2.0, you don't necessarily have to support them + || value == kAsioSupportsTimeInfo + || value == kAsioSupportsTimeCode + || value == kAsioSupportsInputMonitor) + ret = 1L; + break; + + case kAsioBufferSizeChange: + //printf("kAsioBufferSizeChange \n"); + break; + + case kAsioResetRequest: + // defer the task and perform the reset of the driver during the next "safe" situation + // You cannot reset the driver right now, as this code is called from the driver. + // Reset the driver is done by completely destruct is. I.e. ASIOStop(), ASIODisposeBuffers(), Destruction + // Afterwards you initialize the driver again. + + /*FIXME: commented the next line out */ + //asioDriverInfo.stopped; // In this sample the processing will just stop + ret = 1L; + break; + + case kAsioResyncRequest: + // This informs the application, that the driver encountered some non fatal data loss. + // It is used for synchronization purposes of different media. + // Added mainly to work around the Win16Mutex problems in Windows 95/98 with the + // Windows Multimedia system, which could loose data because the Mutex was hold too long + // by another thread. + // However a driver can issue it in other situations, too. + ret = 1L; + break; + + case kAsioLatenciesChanged: + // This will inform the host application that the drivers were latencies changed. + // Beware, it this does not mean that the buffer sizes have changed! + // You might need to update internal delay data. + ret = 1L; + //printf("kAsioLatenciesChanged \n"); + break; + + case kAsioEngineVersion: + // return the supported ASIO version of the host application + // If a host applications does not implement this selector, ASIO 1.0 is assumed + // by the driver + ret = 2L; + break; + + case kAsioSupportsTimeInfo: + // informs the driver wether the asioCallbacks.bufferSwitchTimeInfo() callback + // is supported. + // For compatibility with ASIO 1.0 drivers the host application should always support + // the "old" bufferSwitch method, too. + ret = 1; + break; + + case kAsioSupportsTimeCode: + // informs the driver wether application is interested in time code info. + // If an application does not need to know about time code, the driver has less work + // to do. + ret = 0; + break; + } + return ret; +} + + +static PaError StartStream( PaStream *s ) +{ + PaError result = paNoError; + PaAsioStream *stream = (PaAsioStream*)s; + ASIOError asioError; + + if( stream->outputChannelCount > 0 ) + { + ZeroOutputBuffers( stream, 0 ); + ZeroOutputBuffers( stream, 1 ); + } + + PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); + stream->stopProcessing = false; + stream->zeroOutput = false; + + /* Reentrancy counter initialisation */ + stream->reenterCount = -1; + stream->reenterError = 0; + + stream->callbackFlags = 0; + + if( ResetEvent( stream->completedBuffersPlayedEvent ) == 0 ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() ); + } + + if( result == paNoError ) + { + theAsioStream = stream; + asioError = ASIOStart(); + if( asioError == ASE_OK ) + { + stream->isActive = 1; + stream->streamFinishedCallbackCalled = false; + } + else + { + theAsioStream = 0; + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + } + } + + return result; +} + + +static PaError StopStream( PaStream *s ) +{ + PaError result = paNoError; + PaAsioStream *stream = (PaAsioStream*)s; + ASIOError asioError; + + if( stream->isActive ) + { + stream->stopProcessing = true; + + /* wait for the stream to finish playing out enqueued buffers. + timeout after four times the stream latency. + + @todo should use a better time out value - if the user buffer + length is longer than the asio buffer size then that should + be taken into account. + */ + if( WaitForSingleObject( theAsioStream->completedBuffersPlayedEvent, + (DWORD)(stream->streamRepresentation.streamInfo.outputLatency * 1000. * 4.) ) + == WAIT_TIMEOUT ) + { + PA_DEBUG(("WaitForSingleObject() timed out in StopStream()\n" )); + } + } + + asioError = ASIOStop(); + if( asioError != ASE_OK ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + } + + theAsioStream = 0; + stream->isActive = 0; + + if( !stream->streamFinishedCallbackCalled ) + { + if( stream->streamRepresentation.streamFinishedCallback != 0 ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + } + + return result; +} + + +static PaError AbortStream( PaStream *s ) +{ + PaError result = paNoError; + PaAsioStream *stream = (PaAsioStream*)s; + ASIOError asioError; + + stream->zeroOutput = true; + + asioError = ASIOStop(); + if( asioError != ASE_OK ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + } + 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; + } + } + + /* 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->isActive = 0; + + if( !stream->streamFinishedCallbackCalled ) + { + if( stream->streamRepresentation.streamFinishedCallback != 0 ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + } + + return result; +} + + +static PaError IsStreamStopped( PaStream *s ) +{ + //PaAsioStream *stream = (PaAsioStream*)s; + (void) s; /* unused parameter */ + return theAsioStream == 0; +} + + +static PaError IsStreamActive( PaStream *s ) +{ + PaAsioStream *stream = (PaAsioStream*)s; + + return stream->isActive; +} + + +static PaTime GetStreamTime( PaStream *s ) +{ + (void) s; /* unused parameter */ + return (double)timeGetTime() * .001; +} + + +static double GetStreamCpuLoad( PaStream* s ) +{ + PaAsioStream *stream = (PaAsioStream*)s; + + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); +} + + +/* + As separate stream interfaces are used for blocking and callback + streams, the following functions can be guaranteed to only be called + for blocking streams. +*/ + +static PaError ReadStream( PaStream* s, + void *buffer, + unsigned long frames ) +{ + PaAsioStream *stream = (PaAsioStream*)s; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + (void) stream; /* unused parameters */ + (void) buffer; + (void) frames; + + return paNoError; +} + + +static PaError WriteStream( PaStream* s, + const void *buffer, + unsigned long frames ) +{ + PaAsioStream *stream = (PaAsioStream*)s; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + (void) stream; /* unused parameters */ + (void) buffer; + (void) frames; + + return paNoError; +} + + +static signed long GetStreamReadAvailable( PaStream* s ) +{ + PaAsioStream *stream = (PaAsioStream*)s; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + (void) stream; /* unused parameter */ + + return 0; +} + + +static signed long GetStreamWriteAvailable( PaStream* s ) +{ + PaAsioStream *stream = (PaAsioStream*)s; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + (void) stream; /* unused parameter */ + + return 0; +} + + +PaError PaAsio_ShowControlPanel( PaDeviceIndex device, void* systemSpecific ) +{ + PaError result = paNoError; + PaUtilHostApiRepresentation *hostApi; + PaDeviceIndex hostApiDevice; + ASIODriverInfo asioDriverInfo; + ASIOError asioError; + int asioIsInitialized = 0; + PaAsioHostApiRepresentation *asioHostApi; + PaAsioDeviceInfo *asioDeviceInfo; + + + result = PaUtil_GetHostApiRepresentation( &hostApi, paASIO ); + if( result != paNoError ) + goto error; + + result = PaUtil_DeviceIndexToHostApiDeviceIndex( &hostApiDevice, device, hostApi ); + if( result != paNoError ) + goto error; + + /* + In theory we could proceed if the currently open device was the same + one for which the control panel was requested, however because the + window pointer is not available until this function is called we + currently need to call ASIOInit() again here, which of course can't be + done safely while a stream is open. + */ + + asioHostApi = (PaAsioHostApiRepresentation*)hostApi; + if( asioHostApi->openAsioDeviceIndex != paNoDevice ) + { + result = paDeviceUnavailable; + goto error; + } + + asioDeviceInfo = (PaAsioDeviceInfo*)hostApi->deviceInfos[hostApiDevice]; + + if( !loadAsioDriver( const_cast<char*>(asioDeviceInfo->commonDeviceInfo.name) ) ) + { + result = paUnanticipatedHostError; + goto error; + } + + /* CRUCIAL!!! */ + memset( &asioDriverInfo, 0, sizeof(ASIODriverInfo) ); + asioDriverInfo.asioVersion = 2; + asioDriverInfo.sysRef = systemSpecific; + asioError = ASIOInit( &asioDriverInfo ); + if( asioError != ASE_OK ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + goto error; + } + else + { + asioIsInitialized = 1; + } + +PA_DEBUG(("PaAsio_ShowControlPanel: ASIOInit(): %s\n", PaAsio_GetAsioErrorText(asioError) )); +PA_DEBUG(("asioVersion: ASIOInit(): %ld\n", asioDriverInfo.asioVersion )); +PA_DEBUG(("driverVersion: ASIOInit(): %ld\n", asioDriverInfo.driverVersion )); +PA_DEBUG(("Name: ASIOInit(): %s\n", asioDriverInfo.name )); +PA_DEBUG(("ErrorMessage: ASIOInit(): %s\n", asioDriverInfo.errorMessage )); + + asioError = ASIOControlPanel(); + if( asioError != ASE_OK ) + { + PA_DEBUG(("PaAsio_ShowControlPanel: ASIOControlPanel(): %s\n", PaAsio_GetAsioErrorText(asioError) )); + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + goto error; + } + +PA_DEBUG(("PaAsio_ShowControlPanel: ASIOControlPanel(): %s\n", PaAsio_GetAsioErrorText(asioError) )); + + asioError = ASIOExit(); + if( asioError != ASE_OK ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + asioIsInitialized = 0; + goto error; + } + +PA_DEBUG(("PaAsio_ShowControlPanel: ASIOExit(): %s\n", PaAsio_GetAsioErrorText(asioError) )); + + return result; + +error: + if( asioIsInitialized ) + ASIOExit(); + + return result; +} + + +PaError PaAsio_GetInputChannelName( PaDeviceIndex device, int channelIndex, + const char** channelName ) +{ + PaError result = paNoError; + PaUtilHostApiRepresentation *hostApi; + PaDeviceIndex hostApiDevice; + PaAsioDeviceInfo *asioDeviceInfo; + + + result = PaUtil_GetHostApiRepresentation( &hostApi, paASIO ); + if( result != paNoError ) + goto error; + + result = PaUtil_DeviceIndexToHostApiDeviceIndex( &hostApiDevice, device, hostApi ); + if( result != paNoError ) + goto error; + + asioDeviceInfo = (PaAsioDeviceInfo*)hostApi->deviceInfos[hostApiDevice]; + + if( channelIndex < 0 || channelIndex >= asioDeviceInfo->commonDeviceInfo.maxInputChannels ){ + result = paInvalidChannelCount; + goto error; + } + + *channelName = asioDeviceInfo->asioChannelInfos[channelIndex].name; + + return paNoError; + +error: + return result; +} + + +PaError PaAsio_GetOutputChannelName( PaDeviceIndex device, int channelIndex, + const char** channelName ) +{ + PaError result = paNoError; + PaUtilHostApiRepresentation *hostApi; + PaDeviceIndex hostApiDevice; + PaAsioDeviceInfo *asioDeviceInfo; + + + result = PaUtil_GetHostApiRepresentation( &hostApi, paASIO ); + if( result != paNoError ) + goto error; + + result = PaUtil_DeviceIndexToHostApiDeviceIndex( &hostApiDevice, device, hostApi ); + if( result != paNoError ) + goto error; + + asioDeviceInfo = (PaAsioDeviceInfo*)hostApi->deviceInfos[hostApiDevice]; + + if( channelIndex < 0 || channelIndex >= asioDeviceInfo->commonDeviceInfo.maxOutputChannels ){ + result = paInvalidChannelCount; + goto error; + } + + *channelName = asioDeviceInfo->asioChannelInfos[ + asioDeviceInfo->commonDeviceInfo.maxInputChannels + channelIndex].name; + + return paNoError; + +error: + return result; +} diff --git a/src/audio/portaudio/pa_asio/pa_asio.h b/src/audio/portaudio/pa_asio/pa_asio.h new file mode 100644 index 0000000000000000000000000000000000000000..573930572423c5b08dd942ba87fc968664569312 --- /dev/null +++ b/src/audio/portaudio/pa_asio/pa_asio.h @@ -0,0 +1,122 @@ +#ifndef PA_ASIO_H +#define PA_ASIO_H +/* + * $Id$ + * PortAudio Portable Real-Time Audio Library + * ASIO specific extensions + * + * 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. + * + * 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. + * + * 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. + * + */ + +/** @file + @brief ASIO-specific PortAudio API extension header file. +*/ + + +#include "portaudio.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +/** Retrieve legal latency settings for the specificed device, in samples. + + @param device The global index of the device about which the query is being made. + @param minLatency A pointer to the location which will recieve the minimum latency value. + @param maxLatency A pointer to the location which will recieve the maximum latency value. + @param preferredLatency A pointer to the location which will recieve the preferred latency value. + @param granularity A pointer to the location which will recieve the granularity. This value + determines which values between minLatency and maxLatency are available. ie the step size, + if granularity is -1 then available latency settings are powers of two. + + @see ASIOGetBufferSize in the ASIO SDK. + + @todo This function should have a better name, any suggestions? +*/ +PaError PaAsio_GetAvailableLatencyValues( PaDeviceIndex device, + long *minLatency, long *maxLatency, long *preferredLatency, long *granularity ); + + +/** Display the ASIO control panel for the specified device. + + @param device The global index of the device whose control panel is to be displayed. + @param systemSpecific On Windows, the calling application's main window handle, + on Macintosh this value should be zero. +*/ +PaError PaAsio_ShowControlPanel( PaDeviceIndex device, void* systemSpecific ); + + + + +/** Retrieve a pointer to a string containing the name of the specified + input channel. The string is valid until Pa_Terminate is called. + + The string will be no longer than 32 characters including the null terminator. +*/ +PaError PaAsio_GetInputChannelName( PaDeviceIndex device, int channelIndex, + const char** channelName ); + + +/** Retrieve a pointer to a string containing the name of the specified + input channel. The string is valid until Pa_Terminate is called. + + The string will be no longer than 32 characters including the null terminator. +*/ +PaError PaAsio_GetOutputChannelName( PaDeviceIndex device, int channelIndex, + const char** channelName ); + + +#define paAsioUseChannelSelectors (0x01) + +typedef struct PaAsioStreamInfo{ + unsigned long size; /**< sizeof(PaAsioStreamInfo) */ + PaHostApiTypeId hostApiType; /**< paASIO */ + unsigned long version; /**< 1 */ + + unsigned long flags; + + /* Support for opening only specific channels of an ASIO device. + If the paAsioUseChannelSelectors flag is set, channelSelectors is a + pointer to an array of integers specifying the device channels to use. + When used, the length of the channelSelectors array must match the + corresponding channelCount parameter to Pa_OpenStream() otherwise a + crash may result. + The values in the selectors array must specify channels within the + range of supported channels for the device or paInvalidChannelCount will + result. + */ + int *channelSelectors; +}PaAsioStreamInfo; + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* PA_ASIO_H */ diff --git a/src/audio/portaudio/pa_beos/PlaybackNode.cc b/src/audio/portaudio/pa_beos/PlaybackNode.cc new file mode 100644 index 0000000000000000000000000000000000000000..84d54534140620c162874a5fd452ae9eab08020c --- /dev/null +++ b/src/audio/portaudio/pa_beos/PlaybackNode.cc @@ -0,0 +1,538 @@ +/* + * $Id$ + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * BeOS Media Kit Implementation by Joshua Haberman + * + * Copyright (c) 2001 Joshua Haberman <joshua@haberman.com> + * + * 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. + * + * 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. + * + * 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. + * + * --- + * + * Significant portions of this file are based on sample code from Be. The + * Be Sample Code Licence follows: + * + * Copyright 1991-1999, Be Incorporated. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions, and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> + +#include <be/media/BufferGroup.h> +#include <be/media/Buffer.h> +#include <be/media/TimeSource.h> + +#include "PlaybackNode.h" + +#define PRINT(x) { printf x; fflush(stdout); } + +#ifdef DEBUG +#define DBUG(x) PRINT(x) +#else +#define DBUG(x) +#endif + + +PaPlaybackNode::PaPlaybackNode(uint32 channels, float frame_rate, uint32 frames_per_buffer, + PortAudioCallback* callback, void *user_data) : + BMediaNode("PortAudio input node"), + BBufferProducer(B_MEDIA_RAW_AUDIO), + BMediaEventLooper(), + mAborted(false), + mRunning(false), + mBufferGroup(NULL), + mDownstreamLatency(0), + mStartTime(0), + mCallback(callback), + mUserData(user_data), + mFramesPerBuffer(frames_per_buffer) +{ + DBUG(("Constructor called.\n")); + + mPreferredFormat.type = B_MEDIA_RAW_AUDIO; + mPreferredFormat.u.raw_audio.channel_count = channels; + mPreferredFormat.u.raw_audio.frame_rate = frame_rate; + mPreferredFormat.u.raw_audio.byte_order = + (B_HOST_IS_BENDIAN) ? B_MEDIA_BIG_ENDIAN : B_MEDIA_LITTLE_ENDIAN; + mPreferredFormat.u.raw_audio.buffer_size = + media_raw_audio_format::wildcard.buffer_size; + + mOutput.destination = media_destination::null; + mOutput.format = mPreferredFormat; + + /* The amount of time it takes for this node to produce a buffer when + * asked. Essentially, it is how long the user's callback takes to run. + * We set this to be the length of the sound data each buffer of the + * requested size can hold. */ + //mInternalLatency = (bigtime_t)(1000000 * frames_per_buffer / frame_rate); + + /* ACK! it seems that the mixer (at least on my machine) demands that IT + * specify the buffer size, so for now I'll just make a generic guess here */ + mInternalLatency = 1000000 / 20; +} + + + +PaPlaybackNode::~PaPlaybackNode() +{ + DBUG(("Destructor called.\n")); + Quit(); /* Stop the BMediaEventLooper thread */ +} + + +/************************* + * + * Local methods + * + */ + +bool PaPlaybackNode::IsRunning() +{ + return mRunning; +} + + +PaTimestamp PaPlaybackNode::GetStreamTime() +{ + BTimeSource *timeSource = TimeSource(); + PaTimestamp time = (timeSource->Now() - mStartTime) * + mPreferredFormat.u.raw_audio.frame_rate / 1000000; + return time; +} + + +void PaPlaybackNode::SetSampleFormat(PaSampleFormat inFormat, + PaSampleFormat outFormat) +{ + uint32 beOutFormat; + + switch(outFormat) + { + case paFloat32: + beOutFormat = media_raw_audio_format::B_AUDIO_FLOAT; + mOutputSampleWidth = 4; + break; + + case paInt16: + beOutFormat = media_raw_audio_format::B_AUDIO_SHORT; + mOutputSampleWidth = 2; + break; + + case paInt32: + beOutFormat = media_raw_audio_format::B_AUDIO_INT; + mOutputSampleWidth = 4; + break; + + case paInt8: + beOutFormat = media_raw_audio_format::B_AUDIO_CHAR; + mOutputSampleWidth = 1; + break; + + case paUInt8: + beOutFormat = media_raw_audio_format::B_AUDIO_UCHAR; + mOutputSampleWidth = 1; + break; + + case paInt24: + case paPackedInt24: + case paCustomFormat: + DBUG(("Unsupported output format: %x\n", outFormat)); + break; + + default: + DBUG(("Unknown output format: %x\n", outFormat)); + } + + mPreferredFormat.u.raw_audio.format = beOutFormat; + mFramesPerBuffer * mPreferredFormat.u.raw_audio.channel_count * mOutputSampleWidth; +} + +BBuffer *PaPlaybackNode::FillNextBuffer(bigtime_t time) +{ + /* Get a buffer from the buffer group */ + BBuffer *buf = mBufferGroup->RequestBuffer( + mOutput.format.u.raw_audio.buffer_size, BufferDuration()); + unsigned long frames = mOutput.format.u.raw_audio.buffer_size / + mOutputSampleWidth / mOutput.format.u.raw_audio.channel_count; + bigtime_t start_time; + int ret; + + if( !buf ) + { + DBUG(("Unable to allocate a buffer\n")); + return NULL; + } + + start_time = mStartTime + + (bigtime_t)((double)mSamplesSent / + (double)mOutput.format.u.raw_audio.frame_rate / + (double)mOutput.format.u.raw_audio.channel_count * + 1000000.0); + + /* Now call the user callback to get the data */ + ret = mCallback(NULL, /* Input buffer */ + buf->Data(), /* Output buffer */ + frames, /* Frames per buffer */ + mSamplesSent / mOutput.format.u.raw_audio.channel_count, /* timestamp */ + mUserData); + + if( ret ) + mAborted = true; + + media_header *hdr = buf->Header(); + + hdr->type = B_MEDIA_RAW_AUDIO; + hdr->size_used = mOutput.format.u.raw_audio.buffer_size; + hdr->time_source = TimeSource()->ID(); + hdr->start_time = start_time; + + return buf; +} + + + + +/************************* + * + * BMediaNode methods + * + */ + +BMediaAddOn *PaPlaybackNode::AddOn( int32 * ) const +{ + DBUG(("AddOn() called.\n")); + return NULL; /* we don't provide service to outside applications */ +} + + +status_t PaPlaybackNode::HandleMessage( int32 message, const void *data, + size_t size ) +{ + DBUG(("HandleMessage() called.\n")); + return B_ERROR; /* we don't define any custom messages */ +} + + + + +/************************* + * + * BMediaEventLooper methods + * + */ + +void PaPlaybackNode::NodeRegistered() +{ + DBUG(("NodeRegistered() called.\n")); + + /* Start the BMediaEventLooper thread */ + SetPriority(B_REAL_TIME_PRIORITY); + Run(); + + /* set up as much information about our output as we can */ + mOutput.source.port = ControlPort(); + mOutput.source.id = 0; + mOutput.node = Node(); + ::strcpy(mOutput.name, "PortAudio Playback"); +} + + +void PaPlaybackNode::HandleEvent( const media_timed_event *event, + bigtime_t lateness, bool realTimeEvent ) +{ + // DBUG(("HandleEvent() called.\n")); + status_t err; + + switch(event->type) + { + case BTimedEventQueue::B_START: + DBUG((" Handling a B_START event\n")); + if( RunState() != B_STARTED ) + { + mStartTime = event->event_time + EventLatency(); + mSamplesSent = 0; + mAborted = false; + mRunning = true; + media_timed_event firstEvent( mStartTime, + BTimedEventQueue::B_HANDLE_BUFFER ); + EventQueue()->AddEvent( firstEvent ); + } + break; + + case BTimedEventQueue::B_STOP: + DBUG((" Handling a B_STOP event\n")); + mRunning = false; + EventQueue()->FlushEvents( 0, BTimedEventQueue::B_ALWAYS, true, + BTimedEventQueue::B_HANDLE_BUFFER ); + break; + + case BTimedEventQueue::B_HANDLE_BUFFER: + //DBUG((" Handling a B_HANDLE_BUFFER event\n")); + + /* make sure we're started and connected */ + if( RunState() != BMediaEventLooper::B_STARTED || + mOutput.destination == media_destination::null ) + break; + + BBuffer *buffer = FillNextBuffer(event->event_time); + + /* make sure we weren't aborted while this routine was running. + * this can happen in one of two ways: either the callback returned + * nonzero (in which case mAborted is set in FillNextBuffer() ) or + * the client called AbortStream */ + if( mAborted ) + { + if( buffer ) + buffer->Recycle(); + Stop(0, true); + break; + } + + if( buffer ) + { + err = SendBuffer(buffer, mOutput.destination); + if( err != B_OK ) + buffer->Recycle(); + } + + mSamplesSent += mOutput.format.u.raw_audio.buffer_size / mOutputSampleWidth; + + /* Now schedule the next buffer event, so we can send another + * buffer when this one runs out. We calculate when it should + * happen by calculating when the data we just sent will finish + * playing. + * + * NOTE, however, that the event will actually get generated + * earlier than we specify, to account for the latency it will + * take to produce the buffer. It uses the latency value we + * specified in SetEventLatency() to determine just how early + * to generate it. */ + + /* totalPerformanceTime includes the time represented by the buffer + * we just sent */ + bigtime_t totalPerformanceTime = (bigtime_t)((double)mSamplesSent / + (double)mOutput.format.u.raw_audio.channel_count / + (double)mOutput.format.u.raw_audio.frame_rate * 1000000.0); + + bigtime_t nextEventTime = mStartTime + totalPerformanceTime; + + media_timed_event nextBufferEvent(nextEventTime, + BTimedEventQueue::B_HANDLE_BUFFER); + EventQueue()->AddEvent(nextBufferEvent); + + break; + + } +} + + + + +/************************* + * + * BBufferProducer methods + * + */ + +status_t PaPlaybackNode::FormatSuggestionRequested( media_type type, + int32 /*quality*/, media_format* format ) +{ + /* the caller wants to know this node's preferred format and provides + * a suggestion, asking if we support it */ + DBUG(("FormatSuggestionRequested() called.\n")); + + if(!format) + return B_BAD_VALUE; + + *format = mPreferredFormat; + + /* we only support raw audio (a wildcard is okay too) */ + if ( type == B_MEDIA_UNKNOWN_TYPE || type == B_MEDIA_RAW_AUDIO ) + return B_OK; + else + return B_MEDIA_BAD_FORMAT; +} + + +status_t PaPlaybackNode::FormatProposal( const media_source& output, + media_format* format ) +{ + /* This is similar to FormatSuggestionRequested(), but it is actually part + * of the negotiation process. We're given the opportunity to specify any + * properties that are wildcards (ie. properties that the other node doesn't + * care one way or another about) */ + DBUG(("FormatProposal() called.\n")); + + /* Make sure this proposal really applies to our output */ + if( output != mOutput.source ) + return B_MEDIA_BAD_SOURCE; + + /* We return two things: whether we support the proposed format, and our own + * preferred format */ + *format = mPreferredFormat; + + if( format->type == B_MEDIA_UNKNOWN_TYPE || format->type == B_MEDIA_RAW_AUDIO ) + return B_OK; + else + return B_MEDIA_BAD_FORMAT; +} + + +status_t PaPlaybackNode::FormatChangeRequested( const media_source& source, + const media_destination& destination, media_format* io_format, int32* ) +{ + /* we refuse to change formats, supporting only 1 */ + DBUG(("FormatChangeRequested() called.\n")); + + return B_ERROR; +} + + +status_t PaPlaybackNode::GetNextOutput( int32* cookie, media_output* out_output ) +{ + /* this is where we allow other to enumerate our outputs -- the cookie is + * an integer we can use to keep track of where we are in enumeration. */ + DBUG(("GetNextOutput() called.\n")); + + if( *cookie == 0 ) + { + *out_output = mOutput; + *cookie = 1; + return B_OK; + } + + return B_BAD_INDEX; +} + + +status_t PaPlaybackNode::DisposeOutputCookie( int32 cookie ) +{ + DBUG(("DisposeOutputCookie() called.\n")); + return B_OK; +} + + +void PaPlaybackNode::LateNoticeReceived( const media_source& what, + bigtime_t how_much, bigtime_t performance_time ) +{ + /* This function is called as notification that a buffer we sent wasn't + * received by the time we stamped it with -- it got there late. Basically, + * it means we underestimated our own latency, so we should increase it */ + DBUG(("LateNoticeReceived() called.\n")); + + if( what != mOutput.source ) + return; + + if( RunMode() == B_INCREASE_LATENCY ) + { + mInternalLatency += how_much; + SetEventLatency( mDownstreamLatency + mInternalLatency ); + DBUG(("Increasing latency to %Ld\n", mDownstreamLatency + mInternalLatency)); + } + else + DBUG(("I don't know what to do with this notice!")); +} + + +void PaPlaybackNode::EnableOutput( const media_source& what, bool enabled, + int32* ) +{ + DBUG(("EnableOutput() called.\n")); + /* stub -- we don't support this yet */ +} + + +status_t PaPlaybackNode::PrepareToConnect( const media_source& what, + const media_destination& where, media_format* format, + media_source* out_source, char* out_name ) +{ + /* the final stage of format negotiations. here we _must_ make specific any + * remaining wildcards */ + DBUG(("PrepareToConnect() called.\n")); + + /* make sure this really refers to our source */ + if( what != mOutput.source ) + return B_MEDIA_BAD_SOURCE; + + /* make sure we're not already connected */ + if( mOutput.destination != media_destination::null ) + return B_MEDIA_ALREADY_CONNECTED; + + if( format->type != B_MEDIA_RAW_AUDIO ) + return B_MEDIA_BAD_FORMAT; + + if( format->u.raw_audio.format != mPreferredFormat.u.raw_audio.format ) + return B_MEDIA_BAD_FORMAT; + + if( format->u.raw_audio.buffer_size == + media_raw_audio_format::wildcard.buffer_size ) + { + DBUG(("We were left to decide buffer size: choosing 2048")); + format->u.raw_audio.buffer_size = 2048; + } + else + DBUG(("Using consumer specified buffer size of %lu.\n", + format->u.raw_audio.buffer_size)); + + /* Reserve the connection, return the information */ + mOutput.destination = where; + mOutput.format = *format; + *out_source = mOutput.source; + strncpy( out_name, mOutput.name, B_MEDIA_NAME_LENGTH ); + + return B_OK; +} + + +void PaPlaybackNode::Connect(status_t error, const media_source& source, + const media_destination& destination, const media_format& format, char* io_name) +{ + DBUG(("Connect() called.\n")); + diff --git a/src/audio/portaudio/pa_beos/PlaybackNode.h b/src/audio/portaudio/pa_beos/PlaybackNode.h new file mode 100644 index 0000000000000000000000000000000000000000..64ee5b3bcb21090eb39d351a206365d13ff67989 --- /dev/null +++ b/src/audio/portaudio/pa_beos/PlaybackNode.h @@ -0,0 +1,108 @@ +/* + * $Id$ + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * BeOS Media Kit Implementation by Joshua Haberman + * + * Copyright (c) 2001 Joshua Haberman <joshua@haberman.com> + * + * 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. + * + * 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. + * + * 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. + * + */ + +#include <be/media/MediaRoster.h> +#include <be/media/MediaEventLooper.h> +#include <be/media/BufferProducer.h> + +#include "portaudio.h" + +class PaPlaybackNode : + public BBufferProducer, + public BMediaEventLooper +{ + +public: + PaPlaybackNode( uint32 channels, float frame_rate, uint32 frames_per_buffer, + PortAudioCallback *callback, void *user_data ); + ~PaPlaybackNode(); + + + /* Local methods ******************************************/ + + BBuffer *FillNextBuffer(bigtime_t time); + void SetSampleFormat(PaSampleFormat inFormat, PaSampleFormat outFormat); + bool IsRunning(); + PaTimestamp GetStreamTime(); + + /* BMediaNode methods *************************************/ + + BMediaAddOn* AddOn( int32 * ) const; + status_t HandleMessage( int32 message, const void *data, size_t size ); + + /* BMediaEventLooper methods ******************************/ + + void HandleEvent( const media_timed_event *event, bigtime_t lateness, + bool realTimeEvent ); + void NodeRegistered(); + + /* BBufferProducer methods ********************************/ + + status_t FormatSuggestionRequested( media_type type, int32 quality, + media_format* format ); + status_t FormatProposal( const media_source& output, media_format* format ); + status_t FormatChangeRequested( const media_source& source, + const media_destination& destination, media_format* io_format, int32* ); + + status_t GetNextOutput( int32* cookie, media_output* out_output ); + status_t DisposeOutputCookie( int32 cookie ); + + void LateNoticeReceived( const media_source& what, bigtime_t how_much, + bigtime_t performance_time ); + void EnableOutput( const media_source& what, bool enabled, int32* _deprecated_ ); + + status_t PrepareToConnect( const media_source& what, + const media_destination& where, media_format* format, + media_source* out_source, char* out_name ); + void Connect(status_t error, const media_source& source, + const media_destination& destination, const media_format& format, + char* io_name); + void Disconnect(const media_source& what, const media_destination& where); + + status_t SetBufferGroup(const media_source& for_source, BBufferGroup* newGroup); + + bool mAborted; + +private: + media_output mOutput; + media_format mPreferredFormat; + uint32 mOutputSampleWidth, mFramesPerBuffer; + BBufferGroup *mBufferGroup; + bigtime_t mDownstreamLatency, mInternalLatency, mStartTime; + uint64 mSamplesSent; + PortAudioCallback *mCallback; + void *mUserData; + bool mRunning; + +}; + diff --git a/src/audio/portaudio/pa_beos/pa_beos_mk.cc b/src/audio/portaudio/pa_beos/pa_beos_mk.cc new file mode 100644 index 0000000000000000000000000000000000000000..287ec5535cd6411d809656021eeb8db2f9be4bd7 --- /dev/null +++ b/src/audio/portaudio/pa_beos/pa_beos_mk.cc @@ -0,0 +1,441 @@ +/* + * $Id$ + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * BeOS Media Kit Implementation by Joshua Haberman + * + * Copyright (c) 2001 Joshua Haberman <joshua@haberman.com> + * + * 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. + * + * 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. + * + * 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. + * + */ + +#include <be/app/Application.h> +#include <be/kernel/OS.h> +#include <be/media/RealtimeAlloc.h> +#include <be/media/MediaRoster.h> +#include <be/media/TimeSource.h> + +#include <stdio.h> +#include <string.h> + +#include "portaudio.h" +#include "pa_host.h" + +#include "PlaybackNode.h" + +#define PRINT(x) { printf x; fflush(stdout); } + +#ifdef DEBUG +#define DBUG(x) PRINT(x) +#else +#define DBUG(x) +#endif + +typedef struct PaHostSoundControl +{ + /* These members are common to all modes of operation */ + media_node pahsc_TimeSource; /* the sound card's DAC. */ + media_format pahsc_Format; + + /* These methods are specific to playing mode */ + media_node pahsc_OutputNode; /* output to the mixer */ + media_node pahsc_InputNode; /* reads data from user callback -- PA specific */ + + media_input pahsc_MixerInput; /* input jack on the soundcard's mixer. */ + media_output pahsc_PaOutput; /* output jack from the PA node */ + + PaPlaybackNode *pahsc_InputNodeInstance; + +} +PaHostSoundControl; + +/*************************************************************************/ +PaDeviceID Pa_GetDefaultOutputDeviceID( void ) +{ + /* stub */ + return 0; +} + +/*************************************************************************/ +PaDeviceID Pa_GetDefaultInputDeviceID( void ) +{ + /* stub */ + return 0; +} + +/*************************************************************************/ +const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceID id ) +{ + /* stub */ + return NULL; +} + +/*************************************************************************/ +int Pa_CountDevices() +{ + /* stub */ + return 1; +} + +/*************************************************************************/ +PaError PaHost_Init( void ) +{ + /* we have to create this in order to use BMediaRoster. I hope it doesn't + * cause problems */ + be_app = new BApplication("application/x-vnd.portaudio-app"); + + return paNoError; +} + +PaError PaHost_Term( void ) +{ + delete be_app; + return paNoError; +} + +/*************************************************************************/ +PaError PaHost_StreamActive( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *)past->past_DeviceData; + DBUG(("IsRunning returning: %s\n", + pahsc->pahsc_InputNodeInstance->IsRunning() ? "true" : "false")); + + return (PaError)pahsc->pahsc_InputNodeInstance->IsRunning(); +} + +PaError PaHost_StartOutput( internalPortAudioStream *past ) +{ + return paNoError; +} + +/*************************************************************************/ +PaError PaHost_StartInput( internalPortAudioStream *past ) +{ + return paNoError; +} + +/*************************************************************************/ +PaError PaHost_StopInput( internalPortAudioStream *past, int abort ) +{ + return paNoError; +} + +/*************************************************************************/ +PaError PaHost_StopOutput( internalPortAudioStream *past, int abort ) +{ + return paNoError; +} + + +/*************************************************************************/ +PaError PaHost_StartEngine( internalPortAudioStream *past ) +{ + bigtime_t very_soon, start_latency; + status_t err; + BMediaRoster *roster = BMediaRoster::Roster(&err); + PaHostSoundControl *pahsc = (PaHostSoundControl *)past->past_DeviceData; + + /* for some reason, err indicates an error (though nothing it wrong) + * when the DBUG macro in pa_lib.c is enabled. It's reproducably + * linked. Weird. */ + if( !roster /* || err != B_OK */ ) + { + DBUG(("No media server! err=%d, roster=%x\n", err, roster)); + return paHostError; + } + + /* tell the node when to start -- since there aren't any other nodes + * starting that we have to wait for, just tell it to start now + */ + + BTimeSource *timeSource = roster->MakeTimeSourceFor(pahsc->pahsc_TimeSource); + very_soon = timeSource->PerformanceTimeFor( BTimeSource::RealTime() ); + timeSource->Release(); + + /* Add the latency of starting the network of nodes */ + err = roster->GetStartLatencyFor( pahsc->pahsc_TimeSource, &start_latency ); + very_soon += start_latency; + + err = roster->StartNode( pahsc->pahsc_InputNode, very_soon ); + /* No need to start the mixer -- it's always running */ + + return paNoError; +} + + +/*************************************************************************/ +PaError PaHost_StopEngine( internalPortAudioStream *past, int abort ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *)past->past_DeviceData; + BMediaRoster *roster = BMediaRoster::Roster(); + + if( !roster ) + { + DBUG(("No media roster!\n")); + return paHostError; + } + + if( !pahsc ) + return paHostError; + + /* this crashes, and I don't know why yet */ + // if( abort ) + // pahsc->pahsc_InputNodeInstance->mAborted = true; + + roster->StopNode(pahsc->pahsc_InputNode, 0, /* immediate = */ true); + + return paNoError; +} + + +/*************************************************************************/ +PaError PaHost_OpenStream( internalPortAudioStream *past ) +{ + status_t err; + BMediaRoster *roster = BMediaRoster::Roster(&err); + PaHostSoundControl *pahsc; + + /* Allocate and initialize host data. */ + pahsc = (PaHostSoundControl *) PaHost_AllocateFastMemory(sizeof(PaHostSoundControl)); + if( pahsc == NULL ) + { + goto error; + } + memset( pahsc, 0, sizeof(PaHostSoundControl) ); + past->past_DeviceData = (void *) pahsc; + + if( !roster /* || err != B_OK */ ) + { + /* no media server! */ + DBUG(("No media server.\n")); + goto error; + } + + if ( past->past_NumInputChannels > 0 && past->past_NumOutputChannels > 0 ) + { + /* filter -- not implemented yet */ + goto error; + } + else if ( past->past_NumInputChannels > 0 ) + { + /* recorder -- not implemented yet */ + goto error; + } + else + { + /* player ****************************************************************/ + + status_t err; + int32 num; + + /* First we need to create the three components (like components in a stereo + * system). The mixer component is our interface to the sound card, data + * we write there will get played. The BePA_InputNode component is the node + * which represents communication with the PA client (it is what calls the + * client's callbacks). The time source component is the sound card's DAC, + * which allows us to slave the other components to it instead of the system + * clock. */ + err = roster->GetAudioMixer( &pahsc->pahsc_OutputNode ); + if( err != B_OK ) + { + DBUG(("Couldn't get default mixer.\n")); + goto error; + } + + err = roster->GetTimeSource( &pahsc->pahsc_TimeSource ); + if( err != B_OK ) + { + DBUG(("Couldn't get time source.\n")); + goto error; + } + + pahsc->pahsc_InputNodeInstance = new PaPlaybackNode(2, 44100, + past->past_FramesPerUserBuffer, past->past_Callback, past->past_UserData ); + pahsc->pahsc_InputNodeInstance->SetSampleFormat(0, + past->past_OutputSampleFormat); + err = roster->RegisterNode( pahsc->pahsc_InputNodeInstance ); + if( err != B_OK ) + { + DBUG(("Unable to register node.\n")); + goto error; + } + + roster->GetNodeFor( pahsc->pahsc_InputNodeInstance->Node().node, + &pahsc->pahsc_InputNode ); + if( err != B_OK ) + { + DBUG(("Unable to get input node.\n")); + goto error; + } + + /* Now we have three components (nodes) sitting next to each other. The + * next step is to look at them and find their inputs and outputs so we can + * wire them together. */ + err = roster->GetFreeInputsFor( pahsc->pahsc_OutputNode, + &pahsc->pahsc_MixerInput, 1, &num, B_MEDIA_RAW_AUDIO ); + if( err != B_OK || num < 1 ) + { + DBUG(("Couldn't get the mixer input.\n")); + goto error; + } + + err = roster->GetFreeOutputsFor( pahsc->pahsc_InputNode, + &pahsc->pahsc_PaOutput, 1, &num, B_MEDIA_RAW_AUDIO ); + if( err != B_OK || num < 1 ) + { + DBUG(("Couldn't get PortAudio output.\n")); + goto error; + } + + + /* We've found the input and output -- the final step is to run a wire + * between them so they are connected. */ + + /* try to make the mixer input adapt to what PA sends it */ + pahsc->pahsc_Format = pahsc->pahsc_PaOutput.format; + roster->Connect( pahsc->pahsc_PaOutput.source, + pahsc->pahsc_MixerInput.destination, &pahsc->pahsc_Format, + &pahsc->pahsc_PaOutput, &pahsc->pahsc_MixerInput ); + + + /* Actually, there's one final step -- tell them all to sync to the + * sound card's DAC */ + roster->SetTimeSourceFor( pahsc->pahsc_InputNode.node, + pahsc->pahsc_TimeSource.node ); + roster->SetTimeSourceFor( pahsc->pahsc_OutputNode.node, + pahsc->pahsc_TimeSource.node ); + + } + + return paNoError; + +error: + PaHost_CloseStream( past ); + return paHostError; +} + +/*************************************************************************/ +PaError PaHost_CloseStream( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *)past->past_DeviceData; + status_t err; + BMediaRoster *roster = BMediaRoster::Roster(&err); + + if( !roster ) + { + DBUG(("Couldn't get media roster\n")); + return paHostError; + } + + if( !pahsc ) + return paHostError; + + /* Disconnect all the connections we made when opening the stream */ + + roster->Disconnect(pahsc->pahsc_InputNode.node, pahsc->pahsc_PaOutput.source, + pahsc->pahsc_OutputNode.node, pahsc->pahsc_MixerInput.destination); + + DBUG(("Calling ReleaseNode()")); + roster->ReleaseNode(pahsc->pahsc_InputNode); + + /* deleting the node shouldn't be necessary -- it is reference counted, and will + * delete itself when its references drop to zero. the call to ReleaseNode() + * above should decrease its reference count */ + pahsc->pahsc_InputNodeInstance = NULL; + + return paNoError; +} + +/*************************************************************************/ +PaTimestamp Pa_StreamTime( PortAudioStream *stream ) +{ + internalPortAudioStream *past = (internalPortAudioStream *) stream; + PaHostSoundControl *pahsc = (PaHostSoundControl *)past->past_DeviceData; + + return pahsc->pahsc_InputNodeInstance->GetStreamTime(); +} + +/*************************************************************************/ +void Pa_Sleep( long msec ) +{ + /* snooze() takes microseconds */ + snooze( msec * 1000 ); +} + +/************************************************************************* + * Allocate memory that can be accessed in real-time. + * This may need to be held in physical memory so that it is not + * paged to virtual memory. + * This call MUST be balanced with a call to PaHost_FreeFastMemory(). + * Memory will be set to zero. + */ +void *PaHost_AllocateFastMemory( long numBytes ) +{ + /* BeOS supports non-pagable memory through pools -- a pool is an area + * of physical memory that is locked. It would be best to pre-allocate + * that pool and then hand out memory from it, but we don't know in + * advance how much we'll need. So for now, we'll allocate a pool + * for every request we get, storing a pointer to the pool at the + * beginning of the allocated memory */ + rtm_pool *pool; + void *addr; + long size = numBytes + sizeof(rtm_pool *); + static int counter = 0; + char pool_name[100]; + + /* Every pool needs a unique name. */ + sprintf(pool_name, "PaPoolNumber%d", counter++); + + if( rtm_create_pool( &pool, size, pool_name ) != B_OK ) + return 0; + + addr = rtm_alloc( pool, size ); + if( addr == NULL ) + return 0; + + memset( addr, 0, numBytes ); + *((rtm_pool **)addr) = pool; // store the pointer to the pool + addr = (rtm_pool **)addr + 1; // and return the next location in memory + + return addr; +} + +/************************************************************************* + * Free memory that could be accessed in real-time. + * This call MUST be balanced with a call to PaHost_AllocateFastMemory(). + */ +void PaHost_FreeFastMemory( void *addr, long numBytes ) +{ + rtm_pool *pool; + + if( addr == NULL ) + return; + + addr = (rtm_pool **)addr - 1; + pool = *((rtm_pool **)addr); + + rtm_free( addr ); + rtm_delete_pool( pool ); +} diff --git a/src/audio/portaudio/pa_common/pa_allocation.c b/src/audio/portaudio/pa_common/pa_allocation.c new file mode 100644 index 0000000000000000000000000000000000000000..98239c48f00400932a826763f1b87c39cacabc0a --- /dev/null +++ b/src/audio/portaudio/pa_common/pa_allocation.c @@ -0,0 +1,234 @@ +/* + * $Id$ + * Portable Audio I/O Library allocation group implementation + * memory allocation group for tracking allocation groups + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, 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. + * + * 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. + * + * 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. + */ + +/** @file + @brief Allocation Group implementation. +*/ + + +#include "pa_allocation.h" +#include "pa_util.h" + + +/* + Maintain 3 singly linked lists... + linkBlocks: the buffers used to allocate the links + spareLinks: links available for use in the allocations list + allocations: the buffers currently allocated using PaUtil_ContextAllocateMemory() + + Link block size is doubled every time new links are allocated. +*/ + + +#define PA_INITIAL_LINK_COUNT_ 16 + +struct PaUtilAllocationGroupLink +{ + struct PaUtilAllocationGroupLink *next; + void *buffer; +}; + +/* + Allocate a block of links. The first link will have it's buffer member + pointing to the block, and it's next member set to <nextBlock>. The remaining + links will have NULL buffer members, and each link will point to + the next link except the last, which will point to <nextSpare> +*/ +static struct PaUtilAllocationGroupLink *AllocateLinks( long count, + struct PaUtilAllocationGroupLink *nextBlock, + struct PaUtilAllocationGroupLink *nextSpare ) +{ + struct PaUtilAllocationGroupLink *result; + int i; + + result = (struct PaUtilAllocationGroupLink *)PaUtil_AllocateMemory( + sizeof(struct PaUtilAllocationGroupLink) * count ); + if( result ) + { + /* the block link */ + result[0].buffer = result; + result[0].next = nextBlock; + + /* the spare links */ + for( i=1; i<count; ++i ) + { + result[i].buffer = 0; + result[i].next = &result[i+1]; + } + result[count-1].next = nextSpare; + } + + return result; +} + + +PaUtilAllocationGroup* PaUtil_CreateAllocationGroup( void ) +{ + PaUtilAllocationGroup* result = 0; + struct PaUtilAllocationGroupLink *links; + + + links = AllocateLinks( PA_INITIAL_LINK_COUNT_, 0, 0 ); + if( links != 0 ) + { + result = (PaUtilAllocationGroup*)PaUtil_AllocateMemory( sizeof(PaUtilAllocationGroup) ); + if( result ) + { + result->linkCount = PA_INITIAL_LINK_COUNT_; + result->linkBlocks = &links[0]; + result->spareLinks = &links[1]; + result->allocations = 0; + } + else + { + PaUtil_FreeMemory( links ); + } + } + + return result; +} + + +void PaUtil_DestroyAllocationGroup( PaUtilAllocationGroup* group ) +{ + struct PaUtilAllocationGroupLink *current = group->linkBlocks; + struct PaUtilAllocationGroupLink *next; + + while( current ) + { + next = current->next; + PaUtil_FreeMemory( current->buffer ); + current = next; + } + + PaUtil_FreeMemory( group ); +} + + +void* PaUtil_GroupAllocateMemory( PaUtilAllocationGroup* group, long size ) +{ + struct PaUtilAllocationGroupLink *links, *link; + void *result = 0; + + /* allocate more links if necessary */ + if( !group->spareLinks ) + { + /* double the link count on each block allocation */ + links = AllocateLinks( group->linkCount, group->linkBlocks, group->spareLinks ); + if( links ) + { + group->linkCount += group->linkCount; + group->linkBlocks = &links[0]; + group->spareLinks = &links[1]; + } + } + + if( group->spareLinks ) + { + result = PaUtil_AllocateMemory( size ); + if( result ) + { + link = group->spareLinks; + group->spareLinks = link->next; + + link->buffer = result; + link->next = group->allocations; + + group->allocations = link; + } + } + + return result; +} + + +void PaUtil_GroupFreeMemory( PaUtilAllocationGroup* group, void *buffer ) +{ + struct PaUtilAllocationGroupLink *current = group->allocations; + struct PaUtilAllocationGroupLink *previous = 0; + + if( buffer == 0 ) + return; + + /* find the right link and remove it */ + while( current ) + { + if( current->buffer == buffer ) + { + if( previous ) + { + previous->next = current->next; + } + else + { + group->allocations = current->next; + } + + current->buffer = 0; + current->next = group->spareLinks; + group->spareLinks = current; + + break; + } + + previous = current; + current = current->next; + } + + PaUtil_FreeMemory( buffer ); /* free the memory whether we found it in the list or not */ +} + + +void PaUtil_FreeAllAllocations( PaUtilAllocationGroup* group ) +{ + struct PaUtilAllocationGroupLink *current = group->allocations; + struct PaUtilAllocationGroupLink *previous = 0; + + /* free all buffers in the allocations list */ + while( current ) + { + PaUtil_FreeMemory( current->buffer ); + current->buffer = 0; + + previous = current; + current = current->next; + } + + /* link the former allocations list onto the front of the spareLinks list */ + if( previous ) + { + previous->next = group->spareLinks; + group->spareLinks = group->allocations; + group->allocations = 0; + } +} + diff --git a/src/audio/portaudio/pa_common/pa_allocation.h b/src/audio/portaudio/pa_common/pa_allocation.h new file mode 100644 index 0000000000000000000000000000000000000000..5bcfe75a67674da671de89962c866b6fc5c46285 --- /dev/null +++ b/src/audio/portaudio/pa_common/pa_allocation.h @@ -0,0 +1,95 @@ +#ifndef PA_ALLOCATION_H +#define PA_ALLOCATION_H +/* + * $Id$ + * 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 + * + * 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. + * + * 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. + * + * 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. + */ + +/** @file + @brief Allocation Group prototypes. An Allocation Group makes it easy to + allocate multiple blocks of memory and free them all simultanously. + + 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 + can be usefull for cleaning up after a partially initialized object fails. + + The allocation group implementation is built on top of the lower + level allocation functions defined in pa_util.h +*/ + + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +typedef struct +{ + long linkCount; + struct PaUtilAllocationGroupLink *linkBlocks; + struct PaUtilAllocationGroupLink *spareLinks; + struct PaUtilAllocationGroupLink *allocations; +}PaUtilAllocationGroup; + + + +/** Create an allocation group. +*/ +PaUtilAllocationGroup* PaUtil_CreateAllocationGroup( void ); + +/** Destroy an allocation group, but not the memory allocated through the group. +*/ +void PaUtil_DestroyAllocationGroup( PaUtilAllocationGroup* group ); + +/** Allocate a block of memory though an allocation group. +*/ +void* PaUtil_GroupAllocateMemory( PaUtilAllocationGroup* group, long size ); + +/** Free a block of memory that was previously allocated though an allocation + group. Calling this function is a relatively time consuming operation. + Under normal circumstances clients should call PaUtil_FreeAllAllocations to + free all allocated blocks simultaneously. + @see PaUtil_FreeAllAllocations +*/ +void PaUtil_GroupFreeMemory( PaUtilAllocationGroup* group, void *buffer ); + +/** Free all blocks of memory which have been allocated through the allocation + group. This function doesn't destroy the group itself. +*/ +void PaUtil_FreeAllAllocations( PaUtilAllocationGroup* group ); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PA_ALLOCATION_H */ diff --git a/src/audio/portaudio/pa_common/pa_converters.c b/src/audio/portaudio/pa_common/pa_converters.c new file mode 100644 index 0000000000000000000000000000000000000000..501be4eba87566ecf73e399b129ad0a7cf59eca5 --- /dev/null +++ b/src/audio/portaudio/pa_common/pa_converters.c @@ -0,0 +1,1926 @@ +/* + * $Id$ + * Portable Audio I/O Library sample conversion mechanism + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Phil Burk, 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. + * + * 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. + * + * 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. + */ + +/** @file + @brief Conversion functions implementations. + + If the C9x function lrintf() is available, define PA_USE_C99_LRINTF to use it + + @todo Consider whether functions which dither but don't clip should exist, + V18 automatically enabled clipping whenever dithering was selected. Perhaps + we should do the same. + + @todo implement the converters marked IMPLEMENT ME: Float32_To_UInt8_Dither, + Float32_To_UInt8_Clip, Float32_To_UInt8_DitherClip, Int32_To_Int24_Dither, + Int32_To_UInt8_Dither, Int24_To_Int16_Dither, Int24_To_Int8_Dither, + Int24_To_UInt8_Dither, Int16_To_Int8_Dither, Int16_To_UInt8_Dither, + + @todo review the converters marked REVIEW: Float32_To_Int32, + Float32_To_Int32_Dither, Float32_To_Int32_Clip, Float32_To_Int32_DitherClip, + Int32_To_Int16_Dither, Int32_To_Int8_Dither, Int16_To_Int32 +*/ + + +#include "pa_converters.h" +#include "pa_dither.h" +#include "pa_endianness.h" +#include "pa_types.h" + + +PaSampleFormat PaUtil_SelectClosestAvailableFormat( + PaSampleFormat availableFormats, PaSampleFormat format ) +{ + PaSampleFormat result; + + format &= ~paNonInterleaved; + availableFormats &= ~paNonInterleaved; + + if( (format & availableFormats) == 0 ) + { + /* NOTE: this code depends on the sample format constants being in + descending order of quality - ie best quality is 0 + FIXME: should write an assert which checks that all of the + known constants conform to that requirement. + */ + + if( format != 0x01 ) + { + /* scan for better formats */ + result = format; + do + { + result >>= 1; + } + while( (result & availableFormats) == 0 && result != 0 ); + } + else + { + result = 0; + } + + if( result == 0 ){ + /* scan for worse formats */ + result = format; + do + { + result <<= 1; + } + while( (result & availableFormats) == 0 && result != paCustomFormat ); + + if( (result & availableFormats) == 0 ) + result = paSampleFormatNotSupported; + } + + }else{ + result = format; + } + + return result; +} + +/* -------------------------------------------------------------------------- */ + +#define PA_SELECT_FORMAT_( format, float32, int32, int24, int16, int8, uint8 ) \ + switch( format & ~paNonInterleaved ){ \ + case paFloat32: \ + float32 \ + case paInt32: \ + int32 \ + case paInt24: \ + int24 \ + case paInt16: \ + int16 \ + case paInt8: \ + int8 \ + case paUInt8: \ + uint8 \ + default: return 0; \ + } + +/* -------------------------------------------------------------------------- */ + +#define PA_SELECT_CONVERTER_DITHER_CLIP_( flags, source, destination ) \ + if( flags & paClipOff ){ /* no clip */ \ + if( flags & paDitherOff ){ /* no dither */ \ + return paConverters. source ## _To_ ## destination; \ + }else{ /* dither */ \ + return paConverters. source ## _To_ ## destination ## _Dither; \ + } \ + }else{ /* clip */ \ + if( flags & paDitherOff ){ /* no dither */ \ + return paConverters. source ## _To_ ## destination ## _Clip; \ + }else{ /* dither */ \ + return paConverters. source ## _To_ ## destination ## _DitherClip; \ + } \ + } + +/* -------------------------------------------------------------------------- */ + +#define PA_SELECT_CONVERTER_DITHER_( flags, source, destination ) \ + if( flags & paDitherOff ){ /* no dither */ \ + return paConverters. source ## _To_ ## destination; \ + }else{ /* dither */ \ + return paConverters. source ## _To_ ## destination ## _Dither; \ + } + +/* -------------------------------------------------------------------------- */ + +#define PA_USE_CONVERTER_( source, destination )\ + return paConverters. source ## _To_ ## destination; + +/* -------------------------------------------------------------------------- */ + +#define PA_UNITY_CONVERSION_( wordlength )\ + return paConverters. Copy_ ## wordlength ## _To_ ## wordlength; + +/* -------------------------------------------------------------------------- */ + +PaUtilConverter* PaUtil_SelectConverter( PaSampleFormat sourceFormat, + PaSampleFormat destinationFormat, PaStreamFlags flags ) +{ + PA_SELECT_FORMAT_( sourceFormat, + /* paFloat32: */ + PA_SELECT_FORMAT_( destinationFormat, + /* paFloat32: */ PA_UNITY_CONVERSION_( 32 ), + /* paInt32: */ PA_SELECT_CONVERTER_DITHER_CLIP_( flags, Float32, Int32 ), + /* paInt24: */ PA_SELECT_CONVERTER_DITHER_CLIP_( flags, Float32, Int24 ), + /* paInt16: */ PA_SELECT_CONVERTER_DITHER_CLIP_( flags, Float32, Int16 ), + /* paInt8: */ PA_SELECT_CONVERTER_DITHER_CLIP_( flags, Float32, Int8 ), + /* paUInt8: */ PA_SELECT_CONVERTER_DITHER_CLIP_( flags, Float32, UInt8 ) + ), + /* paInt32: */ + PA_SELECT_FORMAT_( destinationFormat, + /* paFloat32: */ PA_USE_CONVERTER_( Int32, Float32 ), + /* paInt32: */ PA_UNITY_CONVERSION_( 32 ), + /* paInt24: */ PA_SELECT_CONVERTER_DITHER_( flags, Int32, Int24 ), + /* paInt16: */ PA_SELECT_CONVERTER_DITHER_( flags, Int32, Int16 ), + /* paInt8: */ PA_SELECT_CONVERTER_DITHER_( flags, Int32, Int8 ), + /* paUInt8: */ PA_SELECT_CONVERTER_DITHER_( flags, Int32, UInt8 ) + ), + /* paInt24: */ + PA_SELECT_FORMAT_( destinationFormat, + /* paFloat32: */ PA_USE_CONVERTER_( Int24, Float32 ), + /* paInt32: */ PA_USE_CONVERTER_( Int24, Int32 ), + /* paInt24: */ PA_UNITY_CONVERSION_( 24 ), + /* paInt16: */ PA_SELECT_CONVERTER_DITHER_( flags, Int24, Int16 ), + /* paInt8: */ PA_SELECT_CONVERTER_DITHER_( flags, Int24, Int8 ), + /* paUInt8: */ PA_SELECT_CONVERTER_DITHER_( flags, Int24, UInt8 ) + ), + /* paInt16: */ + PA_SELECT_FORMAT_( destinationFormat, + /* paFloat32: */ PA_USE_CONVERTER_( Int16, Float32 ), + /* paInt32: */ PA_USE_CONVERTER_( Int16, Int32 ), + /* paInt24: */ PA_USE_CONVERTER_( Int16, Int24 ), + /* paInt16: */ PA_UNITY_CONVERSION_( 16 ), + /* paInt8: */ PA_SELECT_CONVERTER_DITHER_( flags, Int16, Int8 ), + /* paUInt8: */ PA_SELECT_CONVERTER_DITHER_( flags, Int16, UInt8 ) + ), + /* paInt8: */ + PA_SELECT_FORMAT_( destinationFormat, + /* paFloat32: */ PA_USE_CONVERTER_( Int8, Float32 ), + /* paInt32: */ PA_USE_CONVERTER_( Int8, Int32 ), + /* paInt24: */ PA_USE_CONVERTER_( Int8, Int24 ), + /* paInt16: */ PA_USE_CONVERTER_( Int8, Int16 ), + /* paInt8: */ PA_UNITY_CONVERSION_( 8 ), + /* paUInt8: */ PA_USE_CONVERTER_( Int8, UInt8 ) + ), + /* paUInt8: */ + PA_SELECT_FORMAT_( destinationFormat, + /* paFloat32: */ PA_USE_CONVERTER_( UInt8, Float32 ), + /* paInt32: */ PA_USE_CONVERTER_( UInt8, Int32 ), + /* paInt24: */ PA_USE_CONVERTER_( UInt8, Int24 ), + /* paInt16: */ PA_USE_CONVERTER_( UInt8, Int16 ), + /* paInt8: */ PA_USE_CONVERTER_( UInt8, Int8 ), + /* paUInt8: */ PA_UNITY_CONVERSION_( 8 ) + ) + ) +} + +/* -------------------------------------------------------------------------- */ + +#ifdef PA_NO_STANDARD_CONVERTERS + +/* -------------------------------------------------------------------------- */ + +PaUtilConverterTable paConverters = { + 0, /* PaUtilConverter *Float32_To_Int32; */ + 0, /* PaUtilConverter *Float32_To_Int32_Dither; */ + 0, /* PaUtilConverter *Float32_To_Int32_Clip; */ + 0, /* PaUtilConverter *Float32_To_Int32_DitherClip; */ + + 0, /* PaUtilConverter *Float32_To_Int24; */ + 0, /* PaUtilConverter *Float32_To_Int24_Dither; */ + 0, /* PaUtilConverter *Float32_To_Int24_Clip; */ + 0, /* PaUtilConverter *Float32_To_Int24_DitherClip; */ + + 0, /* PaUtilConverter *Float32_To_Int16; */ + 0, /* PaUtilConverter *Float32_To_Int16_Dither; */ + 0, /* PaUtilConverter *Float32_To_Int16_Clip; */ + 0, /* PaUtilConverter *Float32_To_Int16_DitherClip; */ + + 0, /* PaUtilConverter *Float32_To_Int8; */ + 0, /* PaUtilConverter *Float32_To_Int8_Dither; */ + 0, /* PaUtilConverter *Float32_To_Int8_Clip; */ + 0, /* PaUtilConverter *Float32_To_Int8_DitherClip; */ + + 0, /* PaUtilConverter *Float32_To_UInt8; */ + 0, /* PaUtilConverter *Float32_To_UInt8_Dither; */ + 0, /* PaUtilConverter *Float32_To_UInt8_Clip; */ + 0, /* PaUtilConverter *Float32_To_UInt8_DitherClip; */ + + 0, /* PaUtilConverter *Int32_To_Float32; */ + 0, /* PaUtilConverter *Int32_To_Int24; */ + 0, /* PaUtilConverter *Int32_To_Int24_Dither; */ + 0, /* PaUtilConverter *Int32_To_Int16; */ + 0, /* PaUtilConverter *Int32_To_Int16_Dither; */ + 0, /* PaUtilConverter *Int32_To_Int8; */ + 0, /* PaUtilConverter *Int32_To_Int8_Dither; */ + 0, /* PaUtilConverter *Int32_To_UInt8; */ + 0, /* PaUtilConverter *Int32_To_UInt8_Dither; */ + + 0, /* PaUtilConverter *Int24_To_Float32; */ + 0, /* PaUtilConverter *Int24_To_Int32; */ + 0, /* PaUtilConverter *Int24_To_Int16; */ + 0, /* PaUtilConverter *Int24_To_Int16_Dither; */ + 0, /* PaUtilConverter *Int24_To_Int8; */ + 0, /* PaUtilConverter *Int24_To_Int8_Dither; */ + 0, /* PaUtilConverter *Int24_To_UInt8; */ + 0, /* PaUtilConverter *Int24_To_UInt8_Dither; */ + + 0, /* PaUtilConverter *Int16_To_Float32; */ + 0, /* PaUtilConverter *Int16_To_Int32; */ + 0, /* PaUtilConverter *Int16_To_Int24; */ + 0, /* PaUtilConverter *Int16_To_Int8; */ + 0, /* PaUtilConverter *Int16_To_Int8_Dither; */ + 0, /* PaUtilConverter *Int16_To_UInt8; */ + 0, /* PaUtilConverter *Int16_To_UInt8_Dither; */ + + 0, /* PaUtilConverter *Int8_To_Float32; */ + 0, /* PaUtilConverter *Int8_To_Int32; */ + 0, /* PaUtilConverter *Int8_To_Int24 */ + 0, /* PaUtilConverter *Int8_To_Int16; */ + 0, /* PaUtilConverter *Int8_To_UInt8; */ + + 0, /* PaUtilConverter *UInt8_To_Float32; */ + 0, /* PaUtilConverter *UInt8_To_Int32; */ + 0, /* PaUtilConverter *UInt8_To_Int24; */ + 0, /* PaUtilConverter *UInt8_To_Int16; */ + 0, /* PaUtilConverter *UInt8_To_Int8; */ + + 0, /* PaUtilConverter *Copy_8_To_8; */ + 0, /* PaUtilConverter *Copy_16_To_16; */ + 0, /* PaUtilConverter *Copy_24_To_24; */ + 0 /* PaUtilConverter *Copy_32_To_32; */ +}; + +/* -------------------------------------------------------------------------- */ + +#else /* PA_NO_STANDARD_CONVERTERS is not defined */ + +/* -------------------------------------------------------------------------- */ + +#define PA_CLIP_( val, min, max )\ + { val = ((val) < (min)) ? (min) : (((val) > (max)) ? (max) : (val)); } + + +static const float const_1_div_128_ = 1.0f / 128.0f; /* 8 bit multiplier */ + +static const float const_1_div_32768_ = 1.0f / 32768.f; /* 16 bit multiplier */ + +static const double const_1_div_2147483648_ = 1.0 / 2147483648.0; /* 32 bit multiplier */ + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int32( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + signed long *dest = (signed long*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + /* REVIEW */ +#ifdef PA_USE_C99_LRINTF + float scaled = *src * 0x7FFFFFFF; + *dest = lrintf(scaled-0.5f); +#else + double scaled = *src * 0x7FFFFFFF; + *dest = (signed long) scaled; +#endif + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int32_Dither( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + signed long *dest = (signed long*)destinationBuffer; + + while( count-- ) + { + /* REVIEW */ +#ifdef PA_USE_C99_LRINTF + float dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); + /* use smaller scaler to prevent overflow when we add the dither */ + float dithered = ((float)*src * (2147483646.0f)) + dither; + *dest = lrintf(dithered - 0.5f); +#else + double dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); + /* use smaller scaler to prevent overflow when we add the dither */ + double dithered = ((double)*src * (2147483646.0)) + dither; + *dest = (signed long) dithered; +#endif + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int32_Clip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + signed long *dest = (signed long*)destinationBuffer; + (void) ditherGenerator; /* unused parameter */ + + while( count-- ) + { + /* REVIEW */ +#ifdef PA_USE_C99_LRINTF + float scaled = *src * 0x7FFFFFFF; + PA_CLIP_( scaled, -2147483648.f, 2147483647.f ); + *dest = lrintf(scaled-0.5f); +#else + double scaled = *src * 0x7FFFFFFF; + PA_CLIP_( scaled, -2147483648., 2147483647. ); + *dest = (signed long) scaled; +#endif + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int32_DitherClip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + signed long *dest = (signed long*)destinationBuffer; + + while( count-- ) + { + /* REVIEW */ +#ifdef PA_USE_C99_LRINTF + float dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); + /* use smaller scaler to prevent overflow when we add the dither */ + float dithered = ((float)*src * (2147483646.0f)) + dither; + PA_CLIP_( dithered, -2147483648.f, 2147483647.f ); + *dest = lrintf(dithered-0.5f); +#else + double dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); + /* use smaller scaler to prevent overflow when we add the dither */ + double dithered = ((double)*src * (2147483646.0)) + dither; + PA_CLIP_( dithered, -2147483648., 2147483647. ); + *dest = (signed long) dithered; +#endif + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int24( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + signed long temp; + + (void) ditherGenerator; /* unused parameter */ + + while( count-- ) + { + /* convert to 32 bit and drop the low 8 bits */ + double scaled = *src * 0x7FFFFFFF; + temp = (signed long) scaled; + +#if defined(PA_LITTLE_ENDIAN) + dest[0] = (unsigned char)(temp >> 8); + dest[1] = (unsigned char)(temp >> 16); + dest[2] = (unsigned char)(temp >> 24); +#elif defined(PA_BIG_ENDIAN) + dest[0] = (unsigned char)(temp >> 24); + dest[1] = (unsigned char)(temp >> 16); + dest[2] = (unsigned char)(temp >> 8); +#endif + + src += sourceStride; + dest += destinationStride * 3; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int24_Dither( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + signed long temp; + + while( count-- ) + { + /* convert to 32 bit and drop the low 8 bits */ + + double dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); + /* use smaller scaler to prevent overflow when we add the dither */ + double dithered = ((double)*src * (2147483646.0)) + dither; + + temp = (signed long) dithered; + +#if defined(PA_LITTLE_ENDIAN) + dest[0] = (unsigned char)(temp >> 8); + dest[1] = (unsigned char)(temp >> 16); + dest[2] = (unsigned char)(temp >> 24); +#elif defined(PA_BIG_ENDIAN) + dest[0] = (unsigned char)(temp >> 24); + dest[1] = (unsigned char)(temp >> 16); + dest[2] = (unsigned char)(temp >> 8); +#endif + + src += sourceStride; + dest += destinationStride * 3; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int24_Clip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + signed long temp; + + (void) ditherGenerator; /* unused parameter */ + + while( count-- ) + { + /* convert to 32 bit and drop the low 8 bits */ + double scaled = *src * 0x7FFFFFFF; + PA_CLIP_( scaled, -2147483648., 2147483647. ); + temp = (signed long) scaled; + +#if defined(PA_LITTLE_ENDIAN) + dest[0] = (unsigned char)(temp >> 8); + dest[1] = (unsigned char)(temp >> 16); + dest[2] = (unsigned char)(temp >> 24); +#elif defined(PA_BIG_ENDIAN) + dest[0] = (unsigned char)(temp >> 24); + dest[1] = (unsigned char)(temp >> 16); + dest[2] = (unsigned char)(temp >> 8); +#endif + + src += sourceStride; + dest += destinationStride * 3; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int24_DitherClip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + signed long temp; + + while( count-- ) + { + /* convert to 32 bit and drop the low 8 bits */ + + double dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); + /* use smaller scaler to prevent overflow when we add the dither */ + double dithered = ((double)*src * (2147483646.0)) + dither; + PA_CLIP_( dithered, -2147483648., 2147483647. ); + + temp = (signed long) dithered; + +#if defined(PA_LITTLE_ENDIAN) + dest[0] = (unsigned char)(temp >> 8); + dest[1] = (unsigned char)(temp >> 16); + dest[2] = (unsigned char)(temp >> 24); +#elif defined(PA_BIG_ENDIAN) + dest[0] = (unsigned char)(temp >> 24); + dest[1] = (unsigned char)(temp >> 16); + dest[2] = (unsigned char)(temp >> 8); +#endif + + src += sourceStride; + dest += destinationStride * 3; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int16( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + signed short *dest = (signed short*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { +#ifdef PA_USE_C99_LRINTF + float tempf = (*src * (32767.0f)) ; + *dest = lrintf(tempf-0.5f); +#else + short samp = (short) (*src * (32767.0f)); + *dest = samp; +#endif + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int16_Dither( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + signed short *dest = (signed short*)destinationBuffer; + + while( count-- ) + { + + float dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); + /* use smaller scaler to prevent overflow when we add the dither */ + float dithered = (*src * (32766.0f)) + dither; + +#ifdef PA_USE_C99_LRINTF + *dest = lrintf(dithered-0.5f); +#else + *dest = (signed short) dithered; +#endif + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int16_Clip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + signed short *dest = (signed short*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { +#ifdef PA_USE_C99_LRINTF + long samp = lrintf((*src * (32767.0f)) -0.5f); +#else + long samp = (signed long) (*src * (32767.0f)); +#endif + PA_CLIP_( samp, -0x8000, 0x7FFF ); + *dest = (signed short) samp; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int16_DitherClip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + signed short *dest = (signed short*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + + float dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); + /* use smaller scaler to prevent overflow when we add the dither */ + float dithered = (*src * (32766.0f)) + dither; + signed long samp = (signed long) dithered; + PA_CLIP_( samp, -0x8000, 0x7FFF ); +#ifdef PA_USE_C99_LRINTF + *dest = lrintf(samp-0.5f); +#else + *dest = (signed short) samp; +#endif + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int8( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + signed char *dest = (signed char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + signed char samp = (signed char) (*src * (127.0f)); + *dest = samp; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int8_Dither( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + signed char *dest = (signed char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + float dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); + /* use smaller scaler to prevent overflow when we add the dither */ + float dithered = (*src * (126.0f)) + dither; + signed long samp = (signed long) dithered; + *dest = (signed char) samp; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int8_Clip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + signed char *dest = (signed char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + signed long samp = (signed long)(*src * (127.0f)); + PA_CLIP_( samp, -0x80, 0x7F ); + *dest = (signed char) samp; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int8_DitherClip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + signed char *dest = (signed char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + float dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); + /* use smaller scaler to prevent overflow when we add the dither */ + float dithered = (*src * (126.0f)) + dither; + signed long samp = (signed long) dithered; + PA_CLIP_( samp, -0x80, 0x7F ); + *dest = (signed char) samp; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_UInt8( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + unsigned char samp = (unsigned char)(128 + ((unsigned char) (*src * (127.0f)))); + *dest = samp; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_UInt8_Dither( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + /* IMPLEMENT ME */ + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_UInt8_Clip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + /* IMPLEMENT ME */ + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_UInt8_DitherClip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + float *src = (float*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + /* IMPLEMENT ME */ + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int32_To_Float32( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + signed long *src = (signed long*)sourceBuffer; + float *dest = (float*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + *dest = (float) ((double)*src * const_1_div_2147483648_); + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int32_To_Int24( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + signed long *src = (signed long*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + (void) ditherGenerator; /* unused parameter */ + + while( count-- ) + { + /* REVIEW */ +#if defined(PA_LITTLE_ENDIAN) + dest[0] = (unsigned char)(*src >> 8); + dest[1] = (unsigned char)(*src >> 16); + dest[2] = (unsigned char)(*src >> 24); +#elif defined(PA_BIG_ENDIAN) + dest[0] = (unsigned char)(*src >> 24); + dest[1] = (unsigned char)(*src >> 16); + dest[2] = (unsigned char)(*src >> 8); +#endif + src += sourceStride; + dest += destinationStride * 3; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int32_To_Int24_Dither( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + (void) destinationBuffer; /* unused parameters */ + (void) destinationStride; /* unused parameters */ + (void) sourceBuffer; /* unused parameters */ + (void) sourceStride; /* unused parameters */ + (void) count; /* unused parameters */ + (void) ditherGenerator; /* unused parameters */ + /* IMPLEMENT ME */ +} + +/* -------------------------------------------------------------------------- */ + +static void Int32_To_Int16( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + signed long *src = (signed long*)sourceBuffer; + signed short *dest = (signed short*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + *dest = (signed short) ((*src) >> 16); + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int32_To_Int16_Dither( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + signed long *src = (signed long*)sourceBuffer; + signed short *dest = (signed short*)destinationBuffer; + signed long dither; + + while( count-- ) + { + /* REVIEW */ + dither = PaUtil_Generate16BitTriangularDither( ditherGenerator ); + *dest = (signed short) ((((*src)>>1) + dither) >> 15); + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int32_To_Int8( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + signed long *src = (signed long*)sourceBuffer; + signed char *dest = (signed char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + *dest = (signed char) ((*src) >> 24); + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int32_To_Int8_Dither( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + signed long *src = (signed long*)sourceBuffer; + signed char *dest = (signed char*)destinationBuffer; + signed long dither; + + while( count-- ) + { + /* REVIEW */ + dither = PaUtil_Generate16BitTriangularDither( ditherGenerator ); + *dest = (signed char) ((((*src)>>1) + dither) >> 23); + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int32_To_UInt8( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + signed long *src = (signed long*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + (*dest) = (unsigned char)(((*src) >> 24) + 128); + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int32_To_UInt8_Dither( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + signed long *src = (signed long*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + /* IMPLEMENT ME */ + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int24_To_Float32( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + unsigned char *src = (unsigned char*)sourceBuffer; + float *dest = (float*)destinationBuffer; + signed long temp; + + (void) ditherGenerator; /* unused parameter */ + + while( count-- ) + { + +#if defined(PA_LITTLE_ENDIAN) + temp = (((long)src[0]) << 8); + temp = temp | (((long)src[1]) << 16); + temp = temp | (((long)src[2]) << 24); +#elif defined(PA_BIG_ENDIAN) + temp = (((long)src[0]) << 24); + temp = temp | (((long)src[1]) << 16); + temp = temp | (((long)src[2]) << 8); +#endif + + *dest = (float) ((double)temp * const_1_div_2147483648_); + + src += sourceStride * 3; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int24_To_Int32( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + unsigned char *src = (unsigned char*)sourceBuffer; + signed long *dest = (signed long*) destinationBuffer; + signed long temp; + + (void) ditherGenerator; /* unused parameter */ + + while( count-- ) + { + +#if defined(PA_LITTLE_ENDIAN) + temp = (((long)src[0]) << 8); + temp = temp | (((long)src[1]) << 16); + temp = temp | (((long)src[2]) << 24); +#elif defined(PA_BIG_ENDIAN) + temp = (((long)src[0]) << 24); + temp = temp | (((long)src[1]) << 16); + temp = temp | (((long)src[2]) << 8); +#endif + + *dest = temp; + + src += sourceStride * 3; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int24_To_Int16( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + unsigned char *src = (unsigned char*)sourceBuffer; + signed short *dest = (signed short*)destinationBuffer; + + signed short temp; + + (void) ditherGenerator; /* unused parameter */ + + while( count-- ) + { + +#if defined(PA_LITTLE_ENDIAN) + /* src[0] is discarded */ + temp = (((signed short)src[1])); + temp = temp | (signed short)(((signed short)src[2]) << 8); +#elif defined(PA_BIG_ENDIAN) + /* src[2] is discarded */ + temp = (signed short)(((signed short)src[0]) << 8); + temp = temp | (((signed short)src[1])); +#endif + + *dest = temp; + + src += sourceStride * 3; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int24_To_Int16_Dither( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + (void) destinationBuffer; /* unused parameters */ + (void) destinationStride; /* unused parameters */ + (void) sourceBuffer; /* unused parameters */ + (void) sourceStride; /* unused parameters */ + (void) count; /* unused parameters */ + (void) ditherGenerator; /* unused parameters */ + /* IMPLEMENT ME */ +} + +/* -------------------------------------------------------------------------- */ + +static void Int24_To_Int8( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + unsigned char *src = (unsigned char*)sourceBuffer; + signed char *dest = (signed char*)destinationBuffer; + + (void) ditherGenerator; /* unused parameter */ + + while( count-- ) + { + +#if defined(PA_LITTLE_ENDIAN) + /* src[0] is discarded */ + /* src[1] is discarded */ + *dest = src[2]; +#elif defined(PA_BIG_ENDIAN) + /* src[2] is discarded */ + /* src[1] is discarded */ + *dest = src[0]; +#endif + + src += sourceStride * 3; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int24_To_Int8_Dither( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + (void) destinationBuffer; /* unused parameters */ + (void) destinationStride; /* unused parameters */ + (void) sourceBuffer; /* unused parameters */ + (void) sourceStride; /* unused parameters */ + (void) count; /* unused parameters */ + (void) ditherGenerator; /* unused parameters */ + /* IMPLEMENT ME */ +} + +/* -------------------------------------------------------------------------- */ + +static void Int24_To_UInt8( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + unsigned char *src = (unsigned char*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + + (void) ditherGenerator; /* unused parameter */ + + while( count-- ) + { + +#if defined(PA_LITTLE_ENDIAN) + /* src[0] is discarded */ + /* src[1] is discarded */ + *dest = (unsigned char)(src[2] + 128); +#elif defined(PA_BIG_ENDIAN) + *dest = (unsigned char)(src[0] + 128); + /* src[1] is discarded */ + /* src[2] is discarded */ +#endif + + src += sourceStride * 3; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int24_To_UInt8_Dither( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + (void) destinationBuffer; /* unused parameters */ + (void) destinationStride; /* unused parameters */ + (void) sourceBuffer; /* unused parameters */ + (void) sourceStride; /* unused parameters */ + (void) count; /* unused parameters */ + (void) ditherGenerator; /* unused parameters */ + /* IMPLEMENT ME */ +} + +/* -------------------------------------------------------------------------- */ + +static void Int16_To_Float32( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + signed short *src = (signed short*)sourceBuffer; + float *dest = (float*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + float samp = *src * const_1_div_32768_; /* FIXME: i'm concerned about this being asymetrical with float->int16 -rb */ + *dest = samp; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int16_To_Int32( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + signed short *src = (signed short*)sourceBuffer; + signed long *dest = (signed long*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + /* REVIEW: we should consider something like + (*src << 16) | (*src & 0xFFFF) + */ + + *dest = *src << 16; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int16_To_Int24( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + signed short *src = (signed short*) sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + signed short temp; + + (void) ditherGenerator; /* unused parameter */ + + while( count-- ) + { + temp = *src; + +#if defined(PA_LITTLE_ENDIAN) + dest[0] = 0; + dest[1] = (unsigned char)(temp); + dest[2] = (unsigned char)(temp >> 8); +#elif defined(PA_BIG_ENDIAN) + dest[0] = (unsigned char)(temp >> 8); + dest[1] = (unsigned char)(temp); + dest[2] = 0; +#endif + + src += sourceStride; + dest += destinationStride * 3; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int16_To_Int8( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + signed short *src = (signed short*)sourceBuffer; + signed char *dest = (signed char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + (*dest) = (signed char)((*src) >> 8); + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int16_To_Int8_Dither( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + signed short *src = (signed short*)sourceBuffer; + signed char *dest = (signed char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + /* IMPLEMENT ME */ + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int16_To_UInt8( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + signed short *src = (signed short*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + (*dest) = (unsigned char)(((*src) >> 8) + 128); + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int16_To_UInt8_Dither( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + signed short *src = (signed short*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + /* IMPLEMENT ME */ + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int8_To_Float32( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + signed char *src = (signed char*)sourceBuffer; + float *dest = (float*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + float samp = *src * const_1_div_128_; + *dest = samp; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int8_To_Int32( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + signed char *src = (signed char*)sourceBuffer; + signed long *dest = (signed long*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + (*dest) = (*src) << 24; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int8_To_Int24( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + signed char *src = (signed char*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + +#if defined(PA_LITTLE_ENDIAN) + dest[0] = 0; + dest[1] = 0; + dest[2] = (*src); +#elif defined(PA_BIG_ENDIAN) + dest[0] = (*src); + dest[1] = 0; + dest[2] = 0; +#endif + + src += sourceStride; + dest += destinationStride * 3; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int8_To_Int16( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + signed char *src = (signed char*)sourceBuffer; + signed short *dest = (signed short*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + (*dest) = (signed short)((*src) << 8); + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Int8_To_UInt8( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + signed char *src = (signed char*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + (*dest) = (unsigned char)(*src + 128); + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void UInt8_To_Float32( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + unsigned char *src = (unsigned char*)sourceBuffer; + float *dest = (float*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + float samp = (*src - 128) * const_1_div_128_; + *dest = samp; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void UInt8_To_Int32( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + unsigned char *src = (unsigned char*)sourceBuffer; + signed long *dest = (signed long*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + (*dest) = (*src - 128) << 24; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void UInt8_To_Int24( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + unsigned char *src = (unsigned char*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + (void) ditherGenerator; /* unused parameters */ + + while( count-- ) + { + +#if defined(PA_LITTLE_ENDIAN) + dest[0] = 0; + dest[1] = 0; + dest[2] = (unsigned char)(*src - 128); +#elif defined(PA_BIG_ENDIAN) + dest[0] = (unsigned char)(*src - 128); + dest[1] = 0; + dest[2] = 0; +#endif + + src += sourceStride; + dest += destinationStride * 3; + } +} + +/* -------------------------------------------------------------------------- */ + +static void UInt8_To_Int16( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + unsigned char *src = (unsigned char*)sourceBuffer; + signed short *dest = (signed short*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + (*dest) = (signed short)((*src - 128) << 8); + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void UInt8_To_Int8( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + unsigned char *src = (unsigned char*)sourceBuffer; + signed char *dest = (signed char*)destinationBuffer; + (void)ditherGenerator; /* unused parameter */ + + while( count-- ) + { + (*dest) = (signed char)(*src - 128); + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Copy_8_To_8( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + unsigned char *src = (unsigned char*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + + (void) ditherGenerator; /* unused parameter */ + + while( count-- ) + { + *dest = *src; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Copy_16_To_16( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + PaUint16 *src = (PaUint16 *)sourceBuffer; + PaUint16 *dest = (PaUint16 *)destinationBuffer; + + (void) ditherGenerator; /* unused parameter */ + + while( count-- ) + { + *dest = *src; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Copy_24_To_24( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + unsigned char *src = (unsigned char*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + + (void) ditherGenerator; /* unused parameter */ + + while( count-- ) + { + dest[0] = src[0]; + dest[1] = src[1]; + dest[2] = src[2]; + + src += sourceStride * 3; + dest += destinationStride * 3; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Copy_32_To_32( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + PaUint32 *dest = (PaUint32 *)destinationBuffer; + PaUint32 *src = (PaUint32 *)sourceBuffer; + + (void) ditherGenerator; /* unused parameter */ + + while( count-- ) + { + *dest = *src; + + src += sourceStride; + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +PaUtilConverterTable paConverters = { + Float32_To_Int32, /* PaUtilConverter *Float32_To_Int32; */ + Float32_To_Int32_Dither, /* PaUtilConverter *Float32_To_Int32_Dither; */ + Float32_To_Int32_Clip, /* PaUtilConverter *Float32_To_Int32_Clip; */ + Float32_To_Int32_DitherClip, /* PaUtilConverter *Float32_To_Int32_DitherClip; */ + + Float32_To_Int24, /* PaUtilConverter *Float32_To_Int24; */ + Float32_To_Int24_Dither, /* PaUtilConverter *Float32_To_Int24_Dither; */ + Float32_To_Int24_Clip, /* PaUtilConverter *Float32_To_Int24_Clip; */ + Float32_To_Int24_DitherClip, /* PaUtilConverter *Float32_To_Int24_DitherClip; */ + + Float32_To_Int16, /* PaUtilConverter *Float32_To_Int16; */ + Float32_To_Int16_Dither, /* PaUtilConverter *Float32_To_Int16_Dither; */ + Float32_To_Int16_Clip, /* PaUtilConverter *Float32_To_Int16_Clip; */ + Float32_To_Int16_DitherClip, /* PaUtilConverter *Float32_To_Int16_DitherClip; */ + + Float32_To_Int8, /* PaUtilConverter *Float32_To_Int8; */ + Float32_To_Int8_Dither, /* PaUtilConverter *Float32_To_Int8_Dither; */ + Float32_To_Int8_Clip, /* PaUtilConverter *Float32_To_Int8_Clip; */ + Float32_To_Int8_DitherClip, /* PaUtilConverter *Float32_To_Int8_DitherClip; */ + + Float32_To_UInt8, /* PaUtilConverter *Float32_To_UInt8; */ + Float32_To_UInt8_Dither, /* PaUtilConverter *Float32_To_UInt8_Dither; */ + Float32_To_UInt8_Clip, /* PaUtilConverter *Float32_To_UInt8_Clip; */ + Float32_To_UInt8_DitherClip, /* PaUtilConverter *Float32_To_UInt8_DitherClip; */ + + Int32_To_Float32, /* PaUtilConverter *Int32_To_Float32; */ + Int32_To_Int24, /* PaUtilConverter *Int32_To_Int24; */ + Int32_To_Int24_Dither, /* PaUtilConverter *Int32_To_Int24_Dither; */ + Int32_To_Int16, /* PaUtilConverter *Int32_To_Int16; */ + Int32_To_Int16_Dither, /* PaUtilConverter *Int32_To_Int16_Dither; */ + Int32_To_Int8, /* PaUtilConverter *Int32_To_Int8; */ + Int32_To_Int8_Dither, /* PaUtilConverter *Int32_To_Int8_Dither; */ + Int32_To_UInt8, /* PaUtilConverter *Int32_To_UInt8; */ + Int32_To_UInt8_Dither, /* PaUtilConverter *Int32_To_UInt8_Dither; */ + + Int24_To_Float32, /* PaUtilConverter *Int24_To_Float32; */ + Int24_To_Int32, /* PaUtilConverter *Int24_To_Int32; */ + Int24_To_Int16, /* PaUtilConverter *Int24_To_Int16; */ + Int24_To_Int16_Dither, /* PaUtilConverter *Int24_To_Int16_Dither; */ + Int24_To_Int8, /* PaUtilConverter *Int24_To_Int8; */ + Int24_To_Int8_Dither, /* PaUtilConverter *Int24_To_Int8_Dither; */ + Int24_To_UInt8, /* PaUtilConverter *Int24_To_UInt8; */ + Int24_To_UInt8_Dither, /* PaUtilConverter *Int24_To_UInt8_Dither; */ + + Int16_To_Float32, /* PaUtilConverter *Int16_To_Float32; */ + Int16_To_Int32, /* PaUtilConverter *Int16_To_Int32; */ + Int16_To_Int24, /* PaUtilConverter *Int16_To_Int24; */ + Int16_To_Int8, /* PaUtilConverter *Int16_To_Int8; */ + Int16_To_Int8_Dither, /* PaUtilConverter *Int16_To_Int8_Dither; */ + Int16_To_UInt8, /* PaUtilConverter *Int16_To_UInt8; */ + Int16_To_UInt8_Dither, /* PaUtilConverter *Int16_To_UInt8_Dither; */ + + Int8_To_Float32, /* PaUtilConverter *Int8_To_Float32; */ + Int8_To_Int32, /* PaUtilConverter *Int8_To_Int32; */ + Int8_To_Int24, /* PaUtilConverter *Int8_To_Int24 */ + Int8_To_Int16, /* PaUtilConverter *Int8_To_Int16; */ + Int8_To_UInt8, /* PaUtilConverter *Int8_To_UInt8; */ + + UInt8_To_Float32, /* PaUtilConverter *UInt8_To_Float32; */ + UInt8_To_Int32, /* PaUtilConverter *UInt8_To_Int32; */ + UInt8_To_Int24, /* PaUtilConverter *UInt8_To_Int24; */ + UInt8_To_Int16, /* PaUtilConverter *UInt8_To_Int16; */ + UInt8_To_Int8, /* PaUtilConverter *UInt8_To_Int8; */ + + Copy_8_To_8, /* PaUtilConverter *Copy_8_To_8; */ + Copy_16_To_16, /* PaUtilConverter *Copy_16_To_16; */ + Copy_24_To_24, /* PaUtilConverter *Copy_24_To_24; */ + Copy_32_To_32 /* PaUtilConverter *Copy_32_To_32; */ +}; + +/* -------------------------------------------------------------------------- */ + +#endif /* PA_NO_STANDARD_CONVERTERS */ + +/* -------------------------------------------------------------------------- */ + +PaUtilZeroer* PaUtil_SelectZeroer( PaSampleFormat destinationFormat ) +{ + switch( destinationFormat & ~paNonInterleaved ){ + case paFloat32: + return paZeroers.Zero32; + case paInt32: + return paZeroers.Zero32; + case paInt24: + return paZeroers.Zero24; + case paInt16: + return paZeroers.Zero16; + case paInt8: + return paZeroers.Zero8; + case paUInt8: + return paZeroers.ZeroU8; + default: return 0; + } +} + +/* -------------------------------------------------------------------------- */ + +#ifdef PA_NO_STANDARD_ZEROERS + +/* -------------------------------------------------------------------------- */ + +PaUtilZeroerTable paZeroers = { + 0, /* PaUtilZeroer *ZeroU8; */ + 0, /* PaUtilZeroer *Zero8; */ + 0, /* PaUtilZeroer *Zero16; */ + 0, /* PaUtilZeroer *Zero24; */ + 0, /* PaUtilZeroer *Zero32; */ +}; + +/* -------------------------------------------------------------------------- */ + +#else /* PA_NO_STANDARD_ZEROERS is not defined */ + +/* -------------------------------------------------------------------------- */ + +static void ZeroU8( void *destinationBuffer, signed int destinationStride, + unsigned int count ) +{ + unsigned char *dest = (unsigned char*)destinationBuffer; + + while( count-- ) + { + *dest = 128; + + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Zero8( void *destinationBuffer, signed int destinationStride, + unsigned int count ) +{ + unsigned char *dest = (unsigned char*)destinationBuffer; + + while( count-- ) + { + *dest = 0; + + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Zero16( void *destinationBuffer, signed int destinationStride, + unsigned int count ) +{ + PaUint16 *dest = (PaUint16 *)destinationBuffer; + + while( count-- ) + { + *dest = 0; + + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Zero24( void *destinationBuffer, signed int destinationStride, + unsigned int count ) +{ + unsigned char *dest = (unsigned char*)destinationBuffer; + + while( count-- ) + { + dest[0] = 0; + dest[1] = 0; + dest[2] = 0; + + dest += destinationStride * 3; + } +} + +/* -------------------------------------------------------------------------- */ + +static void Zero32( void *destinationBuffer, signed int destinationStride, + unsigned int count ) +{ + PaUint32 *dest = (PaUint32 *)destinationBuffer; + + while( count-- ) + { + *dest = 0; + + dest += destinationStride; + } +} + +/* -------------------------------------------------------------------------- */ + +PaUtilZeroerTable paZeroers = { + ZeroU8, /* PaUtilZeroer *ZeroU8; */ + Zero8, /* PaUtilZeroer *Zero8; */ + Zero16, /* PaUtilZeroer *Zero16; */ + Zero24, /* PaUtilZeroer *Zero24; */ + Zero32, /* PaUtilZeroer *Zero32; */ +}; + +/* -------------------------------------------------------------------------- */ + +#endif /* PA_NO_STANDARD_ZEROERS */ diff --git a/src/audio/portaudio/pa_common/pa_converters.h b/src/audio/portaudio/pa_common/pa_converters.h new file mode 100644 index 0000000000000000000000000000000000000000..1d83c990bb48f3355801019aa914062d982bbefd --- /dev/null +++ b/src/audio/portaudio/pa_common/pa_converters.h @@ -0,0 +1,254 @@ +#ifndef PA_CONVERTERS_H +#define PA_CONVERTERS_H +/* + * $Id$ + * Portable Audio I/O Library sample conversion mechanism + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Phil Burk, 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. + * + * 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. + * + * 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. + */ + +/** @file + @brief Conversion functions used to convert buffers of samples from one + format to another. +*/ + + +#include "portaudio.h" /* for PaSampleFormat */ + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +struct PaUtilTriangularDitherGenerator; + + +/** Choose an available sample format which is most appropriate for + representing the requested format. If the requested format is not available + higher quality formats are considered before lower quality formates. + @param availableFormats A variable containing the logical OR of all available + formats. + @param format The desired format. + @return The most appropriate available format for representing the requested + format. +*/ +PaSampleFormat PaUtil_SelectClosestAvailableFormat( + PaSampleFormat availableFormats, PaSampleFormat format ); + + +/* high level conversions functions for use by implementations */ + + +/** The generic sample converter prototype. Sample converters convert count + samples from sourceBuffer to destinationBuffer. The actual type of the data + pointed to by these parameters varys for different converter functions. + @param destinationBuffer A pointer to the first sample of the destination. + @param destinationStride An offset between successive destination samples + expressed in samples (not bytes.) It may be negative. + @param sourceBuffer A pointer to the first sample of the source. + @param sourceStride An offset between successive source samples + expressed in samples (not bytes.) It may be negative. + @param count The number of samples to convert. + @param ditherState State information used to calculate dither. Converters + that do not perform dithering will ignore this parameter, in which case + NULL or invalid dither state may be passed. +*/ +typedef void PaUtilConverter( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ); + + +/** Find a sample converter function for the given source and destinations + formats and flags (clip and dither.) + @return + A pointer to a PaUtilConverter which will perform the requested + conversion, or NULL if the given format conversion is not supported. + For conversions where clipping or dithering is not necessary, the + clip and dither flags are ignored and a non-clipping or dithering + version is returned. + If the source and destination formats are the same, a function which + copies data of the appropriate size will be returned. +*/ +PaUtilConverter* PaUtil_SelectConverter( PaSampleFormat sourceFormat, + PaSampleFormat destinationFormat, PaStreamFlags flags ); + + +/** The generic buffer zeroer prototype. Buffer zeroers copy count zeros to + destinationBuffer. The actual type of the data pointed to varys for + different zeroer functions. + @param destinationBuffer A pointer to the first sample of the destination. + @param destinationStride An offset between successive destination samples + expressed in samples (not bytes.) It may be negative. + @param count The number of samples to zero. +*/ +typedef void PaUtilZeroer( + void *destinationBuffer, signed int destinationStride, unsigned int count ); + + +/** Find a buffer zeroer function for the given destination format. + @return + A pointer to a PaUtilZeroer which will perform the requested + zeroing. +*/ +PaUtilZeroer* PaUtil_SelectZeroer( PaSampleFormat destinationFormat ); + +/*----------------------------------------------------------------------------*/ +/* low level functions and data structures which may be used for + substituting conversion functions */ + + +/** The type used to store all sample conversion functions. + @see paConverters; +*/ +typedef struct{ + PaUtilConverter *Float32_To_Int32; + PaUtilConverter *Float32_To_Int32_Dither; + PaUtilConverter *Float32_To_Int32_Clip; + PaUtilConverter *Float32_To_Int32_DitherClip; + + PaUtilConverter *Float32_To_Int24; + PaUtilConverter *Float32_To_Int24_Dither; + PaUtilConverter *Float32_To_Int24_Clip; + PaUtilConverter *Float32_To_Int24_DitherClip; + + PaUtilConverter *Float32_To_Int16; + PaUtilConverter *Float32_To_Int16_Dither; + PaUtilConverter *Float32_To_Int16_Clip; + PaUtilConverter *Float32_To_Int16_DitherClip; + + PaUtilConverter *Float32_To_Int8; + PaUtilConverter *Float32_To_Int8_Dither; + PaUtilConverter *Float32_To_Int8_Clip; + PaUtilConverter *Float32_To_Int8_DitherClip; + + PaUtilConverter *Float32_To_UInt8; + PaUtilConverter *Float32_To_UInt8_Dither; + PaUtilConverter *Float32_To_UInt8_Clip; + PaUtilConverter *Float32_To_UInt8_DitherClip; + + PaUtilConverter *Int32_To_Float32; + PaUtilConverter *Int32_To_Int24; + PaUtilConverter *Int32_To_Int24_Dither; + PaUtilConverter *Int32_To_Int16; + PaUtilConverter *Int32_To_Int16_Dither; + PaUtilConverter *Int32_To_Int8; + PaUtilConverter *Int32_To_Int8_Dither; + PaUtilConverter *Int32_To_UInt8; + PaUtilConverter *Int32_To_UInt8_Dither; + + PaUtilConverter *Int24_To_Float32; + PaUtilConverter *Int24_To_Int32; + PaUtilConverter *Int24_To_Int16; + PaUtilConverter *Int24_To_Int16_Dither; + PaUtilConverter *Int24_To_Int8; + PaUtilConverter *Int24_To_Int8_Dither; + PaUtilConverter *Int24_To_UInt8; + PaUtilConverter *Int24_To_UInt8_Dither; + + PaUtilConverter *Int16_To_Float32; + PaUtilConverter *Int16_To_Int32; + PaUtilConverter *Int16_To_Int24; + PaUtilConverter *Int16_To_Int8; + PaUtilConverter *Int16_To_Int8_Dither; + PaUtilConverter *Int16_To_UInt8; + PaUtilConverter *Int16_To_UInt8_Dither; + + PaUtilConverter *Int8_To_Float32; + PaUtilConverter *Int8_To_Int32; + PaUtilConverter *Int8_To_Int24; + PaUtilConverter *Int8_To_Int16; + PaUtilConverter *Int8_To_UInt8; + + PaUtilConverter *UInt8_To_Float32; + PaUtilConverter *UInt8_To_Int32; + PaUtilConverter *UInt8_To_Int24; + PaUtilConverter *UInt8_To_Int16; + PaUtilConverter *UInt8_To_Int8; + + PaUtilConverter *Copy_8_To_8; /* copy without any conversion */ + PaUtilConverter *Copy_16_To_16; /* copy without any conversion */ + PaUtilConverter *Copy_24_To_24; /* copy without any conversion */ + PaUtilConverter *Copy_32_To_32; /* copy without any conversion */ +} PaUtilConverterTable; + + +/** A table of pointers to all required converter functions. + PaUtil_SelectConverter() uses this table to lookup the appropriate + conversion functions. The fields of this structure are initialized + with default conversion functions. Fields may be NULL, indicating that + no conversion function is available. User code may substitue optimised + conversion functions by assigning different function pointers to + these fields. + + @note + If the PA_NO_STANDARD_CONVERTERS preprocessor variable is defined, + PortAudio's standard converters will not be compiled, and all fields + of this structure will be initialized to NULL. In such cases, users + should supply their own conversion functions if the require PortAudio + to open a stream that requires sample conversion. + + @see PaUtilConverterTable, PaUtilConverter, PaUtil_SelectConverter +*/ +extern PaUtilConverterTable paConverters; + + +/** The type used to store all buffer zeroing functions. + @see paZeroers; +*/ +typedef struct{ + PaUtilZeroer *ZeroU8; /* unsigned 8 bit, zero == 128 */ + PaUtilZeroer *Zero8; + PaUtilZeroer *Zero16; + PaUtilZeroer *Zero24; + PaUtilZeroer *Zero32; +} PaUtilZeroerTable; + + +/** A table of pointers to all required zeroer functions. + PaUtil_SelectZeroer() uses this table to lookup the appropriate + conversion functions. The fields of this structure are initialized + with default conversion functions. User code may substitue optimised + conversion functions by assigning different function pointers to + these fields. + + @note + If the PA_NO_STANDARD_ZEROERS preprocessor variable is defined, + PortAudio's standard zeroers will not be compiled, and all fields + of this structure will be initialized to NULL. In such cases, users + should supply their own zeroing functions for the sample sizes which + they intend to use. + + @see PaUtilZeroerTable, PaUtilZeroer, PaUtil_SelectZeroer +*/ +extern PaUtilZeroerTable paZeroers; + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PA_CONVERTERS_H */ diff --git a/src/audio/portaudio/pa_common/pa_cpuload.c b/src/audio/portaudio/pa_common/pa_cpuload.c new file mode 100644 index 0000000000000000000000000000000000000000..ee2147dc33fdfbda07255729acd5d8b79560f8e0 --- /dev/null +++ b/src/audio/portaudio/pa_common/pa_cpuload.c @@ -0,0 +1,96 @@ +/* + * $Id$ + * Portable Audio I/O Library CPU Load measurement functions + * Portable CPU load measurement facility. + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 2002 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. + * + * 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. + * + * 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. + */ + +/** @file + @brief Functions to assist in measuring the CPU utilization of a callback + stream. Used to implement the Pa_GetStreamCpuLoad() function. + + @todo Dynamically calculate the coefficients used to smooth the CPU Load + Measurements over time to provide a uniform characterisation of CPU Load + independent of rate at which PaUtil_BeginCpuLoadMeasurement / + PaUtil_EndCpuLoadMeasurement are called. +*/ + + +#include "pa_cpuload.h" + +#include <assert.h> + +#include "pa_util.h" /* for PaUtil_GetTime() */ + + +void PaUtil_InitializeCpuLoadMeasurer( PaUtilCpuLoadMeasurer* measurer, double sampleRate ) +{ + assert( sampleRate > 0 ); + + measurer->samplingPeriod = 1. / sampleRate; + measurer->averageLoad = 0.; +} + +void PaUtil_ResetCpuLoadMeasurer( PaUtilCpuLoadMeasurer* measurer ) +{ + measurer->averageLoad = 0.; +} + +void PaUtil_BeginCpuLoadMeasurement( PaUtilCpuLoadMeasurer* measurer ) +{ + measurer->measurementStartTime = PaUtil_GetTime(); +} + + +void PaUtil_EndCpuLoadMeasurement( PaUtilCpuLoadMeasurer* measurer, unsigned long framesProcessed ) +{ + double measurementEndTime, secondsFor100Percent, measuredLoad; + + if( framesProcessed > 0 ){ + measurementEndTime = PaUtil_GetTime(); + + assert( framesProcessed > 0 ); + secondsFor100Percent = framesProcessed * measurer->samplingPeriod; + + measuredLoad = (measurementEndTime - measurer->measurementStartTime) / secondsFor100Percent; + + /* Low pass filter the calculated CPU load to reduce jitter using a simple IIR low pass filter. */ + /** FIXME @todo these coefficients shouldn't be hardwired */ +#define LOWPASS_COEFFICIENT_0 (0.9) +#define LOWPASS_COEFFICIENT_1 (0.99999 - LOWPASS_COEFFICIENT_0) + + measurer->averageLoad = (LOWPASS_COEFFICIENT_0 * measurer->averageLoad) + + (LOWPASS_COEFFICIENT_1 * measuredLoad); + } +} + + +double PaUtil_GetCpuLoad( PaUtilCpuLoadMeasurer* measurer ) +{ + return measurer->averageLoad; +} diff --git a/src/audio/portaudio/pa_common/pa_cpuload.h b/src/audio/portaudio/pa_common/pa_cpuload.h new file mode 100644 index 0000000000000000000000000000000000000000..131ad10bd30f706048328f6fe08430fbe7ca0197 --- /dev/null +++ b/src/audio/portaudio/pa_common/pa_cpuload.h @@ -0,0 +1,63 @@ +#ifndef PA_CPULOAD_H +#define PA_CPULOAD_H +/* + * $Id$ + * Portable Audio I/O Library CPU Load measurement functions + * Portable CPU load measurement facility. + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 2002 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. + * + * 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. + * + * 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. + */ + +/** @file + @brief Functions to assist in measuring the CPU utilization of a callback + stream. Used to implement the Pa_GetStreamCpuLoad() function. +*/ + + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +typedef struct { + double samplingPeriod; + double measurementStartTime; + double averageLoad; +} PaUtilCpuLoadMeasurer; /**< @todo need better name than measurer */ + +void PaUtil_InitializeCpuLoadMeasurer( PaUtilCpuLoadMeasurer* measurer, double sampleRate ); +void PaUtil_BeginCpuLoadMeasurement( PaUtilCpuLoadMeasurer* measurer ); +void PaUtil_EndCpuLoadMeasurement( PaUtilCpuLoadMeasurer* measurer, unsigned long framesProcessed ); +void PaUtil_ResetCpuLoadMeasurer( PaUtilCpuLoadMeasurer* measurer ); +double PaUtil_GetCpuLoad( PaUtilCpuLoadMeasurer* measurer ); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PA_CPULOAD_H */ diff --git a/src/audio/portaudio/pa_common/pa_dither.c b/src/audio/portaudio/pa_common/pa_dither.c new file mode 100644 index 0000000000000000000000000000000000000000..d44a060cd20312368973143e278d170556e77a3b --- /dev/null +++ b/src/audio/portaudio/pa_common/pa_dither.c @@ -0,0 +1,204 @@ +/* + * $Id$ + * Portable Audio I/O Library triangular dither generator + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Phil Burk, 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. + * + * 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. + * + * 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. + */ + +/** @file + @brief Functions for generating dither noise +*/ + + +#include "pa_dither.h" +#include "pa_types.h" + +#define PA_DITHER_BITS_ (15) + + +void PaUtil_InitializeTriangularDitherState( PaUtilTriangularDitherGenerator *state ) +{ + state->previous = 0; + state->randSeed1 = 22222; + state->randSeed2 = 5555555; +} + + +signed long PaUtil_Generate16BitTriangularDither( PaUtilTriangularDitherGenerator *state ) +{ + signed long current, highPass; + + /* Generate two random numbers. */ + state->randSeed1 = (state->randSeed1 * 196314165) + 907633515; + state->randSeed2 = (state->randSeed2 * 196314165) + 907633515; + + /* Generate triangular distribution about 0. + * 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_); + + /* High pass filter to reduce audibility. */ + highPass = current - state->previous; + state->previous = current; + return highPass; +} + + +/* Multiply by PA_FLOAT_DITHER_SCALE_ to get a float between -2.0 and +1.99999 */ +#define PA_FLOAT_DITHER_SCALE_ (1.0f / ((1<<PA_DITHER_BITS_)-1)) +static const float const_float_dither_scale_ = PA_FLOAT_DITHER_SCALE_; + +float PaUtil_GenerateFloatTriangularDither( PaUtilTriangularDitherGenerator *state ) +{ + signed long current, highPass; + + /* Generate two random numbers. */ + state->randSeed1 = (state->randSeed1 * 196314165) + 907633515; + state->randSeed2 = (state->randSeed2 * 196314165) + 907633515; + + /* Generate triangular distribution about 0. + * 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_); + + /* High pass filter to reduce audibility. */ + highPass = current - state->previous; + state->previous = current; + return ((float)highPass) * const_float_dither_scale_; +} + + +/* +The following alternate dither algorithms (from musicdsp.org) could be +considered +*/ + +/*Noise shaped dither (March 2000) +------------------- + +This is a simple implementation of highpass triangular-PDF dither with +2nd-order noise shaping, for use when truncating floating point audio +data to fixed point. + +The noise shaping lowers the noise floor by 11dB below 5kHz (@ 44100Hz +sample rate) compared to triangular-PDF dither. The code below assumes +input data is in the range +1 to -1 and doesn't check for overloads! + +To save time when generating dither for multiple channels you can do +things like this: r3=(r1 & 0x7F)<<8; instead of calling rand() again. + + + + int r1, r2; //rectangular-PDF random numbers + float s1, s2; //error feedback buffers + float s = 0.5f; //set to 0.0f for no noise shaping + float w = pow(2.0,bits-1); //word length (usually bits=16) + float wi= 1.0f/w; + float d = wi / RAND_MAX; //dither amplitude (2 lsb) + float o = wi * 0.5f; //remove dc offset + float in, tmp; + int out; + + +//for each sample... + + r2=r1; //can make HP-TRI dither by + r1=rand(); //subtracting previous rand() + + in += s * (s1 + s1 - s2); //error feedback + tmp = in + o + d * (float)(r1 - r2); //dc offset and dither + + out = (int)(w * tmp); //truncate downwards + if(tmp<0.0f) out--; //this is faster than floor() + + s2 = s1; + s1 = in - wi * (float)out; //error + + + +-- +paul.kellett@maxim.abel.co.uk +http://www.maxim.abel.co.uk +*/ + + +/* +16-to-8-bit first-order dither + +Type : First order error feedforward dithering code +References : Posted by Jon Watte + +Notes : +This is about as simple a dithering algorithm as you can implement, but it's +likely to sound better than just truncating to N bits. + +Note that you might not want to carry forward the full difference for infinity. +It's probably likely that the worst performance hit comes from the saturation +conditionals, which can be avoided with appropriate instructions on many DSPs +and integer SIMD type instructions, or CMOV. + +Last, if sound quality is paramount (such as when going from > 16 bits to 16 +bits) you probably want to use a higher-order dither function found elsewhere +on this site. + + +Code : +// This code will down-convert and dither a 16-bit signed short +// mono signal into an 8-bit unsigned char signal, using a first +// order forward-feeding error term dither. + +#define uchar unsigned char + +void dither_one_channel_16_to_8( short * input, uchar * output, int count, int * memory ) +{ + int m = *memory; + while( count-- > 0 ) { + int i = *input++; + i += m; + int j = i + 32768 - 128; + uchar o; + if( j < 0 ) { + o = 0; + } + else if( j > 65535 ) { + o = 255; + } + else { + o = (uchar)((j>>8)&0xff); + } + m = ((j-32768+128)-i); + *output++ = o; + } + *memory = m; +} +*/ diff --git a/src/audio/portaudio/pa_common/pa_dither.h b/src/audio/portaudio/pa_common/pa_dither.h new file mode 100644 index 0000000000000000000000000000000000000000..148db275fb8b77c3dbd51e19ae68cd537e5d08d9 --- /dev/null +++ b/src/audio/portaudio/pa_common/pa_dither.h @@ -0,0 +1,91 @@ +#ifndef PA_DITHER_H +#define PA_DITHER_H +/* + * $Id$ + * Portable Audio I/O Library triangular dither generator + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Phil Burk, 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. + * + * 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. + * + * 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. + */ + +/** @file + @brief Functions for generating dither noise +*/ + + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +/** @brief State needed to generate a dither signal */ +typedef struct PaUtilTriangularDitherGenerator{ + unsigned long previous; + unsigned long randSeed1; + unsigned long randSeed2; +} PaUtilTriangularDitherGenerator; + + +/** @brief Initialize dither state */ +void PaUtil_InitializeTriangularDitherState( PaUtilTriangularDitherGenerator *ditherState ); + + +/** + @brief Calculate 2 LSB dither signal with a triangular distribution. + Ranged for adding to a 1 bit right-shifted 32 bit integer + prior to >>15. eg: +<pre> + signed long in = * + signed long dither = PaUtil_Generate16BitTriangularDither( ditherState ); + signed short out = (signed short)(((in>>1) + dither) >> 15); +</pre> + @return + A signed long with a range of +32767 to -32768 +*/ +signed long PaUtil_Generate16BitTriangularDither( PaUtilTriangularDitherGenerator *ditherState ); + + +/** + @brief Calculate 2 LSB dither signal with a triangular distribution. + Ranged for adding to a pre-scaled float. +<pre> + float in = * + float dither = PaUtil_GenerateFloatTriangularDither( ditherState ); + // use smaller scaler to prevent overflow when we add the dither + signed short out = (signed short)(in*(32766.0f) + dither ); +</pre> + @return + A float with a range of -2.0 to +1.99999. +*/ +float PaUtil_GenerateFloatTriangularDither( PaUtilTriangularDitherGenerator *ditherState ); + + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PA_DITHER_H */ diff --git a/src/audio/portaudio/pa_common/pa_endianness.h b/src/audio/portaudio/pa_common/pa_endianness.h new file mode 100644 index 0000000000000000000000000000000000000000..3a573929b36edbf657441b868039bfee700e6752 --- /dev/null +++ b/src/audio/portaudio/pa_common/pa_endianness.h @@ -0,0 +1,111 @@ +#ifndef PA_ENDIANNESS_H +#define PA_ENDIANNESS_H +/* + * $Id$ + * Portable Audio I/O Library current platform endianness macros + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Phil Burk, 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. + * + * 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. + * + * 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. + */ + +/** @file + @brief Configure endianness symbols for the target processor. + + Arrange for either the PA_LITTLE_ENDIAN or PA_BIG_ENDIAN preprocessor symbols + to be defined. The one that is defined reflects the endianness of the target + platform and may be used to implement conditional compilation of byte-order + dependent code. + + If either PA_LITTLE_ENDIAN or PA_BIG_ENDIAN is defined already, then no attempt + is made to override that setting. This may be useful if you have a better way + of determining the platform's endianness. The autoconf mechanism uses this for + example. + + A PA_VALIDATE_ENDIANNESS macro is provided to compare the compile time + and runtime endiannes and raise an assertion if they don't match. +*/ + + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +#if defined(PA_LITTLE_ENDIAN) || defined(PA_BIG_ENDIAN) + /* endianness define has been set externally, such as by autoconf */ + + #if defined(PA_LITTLE_ENDIAN) && defined(PA_BIG_ENDIAN) + #error both PA_LITTLE_ENDIAN and PA_BIG_ENDIAN have been defined externally to pa_endianness.h - only one endianness at a time please + #endif + +#else + /* endianness define has not been set externally */ + + /* set PA_LITTLE_ENDIAN or PA_BIG_ENDIAN by testing well known platform specific defines */ + + #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + + #define PA_LITTLE_ENDIAN /* win32, assume intel byte order */ + + #else + +#endif + + #if !defined(PA_LITTLE_ENDIAN) && !defined(PA_BIG_ENDIAN) + /* + If the following error is raised, you either need to modify the code above + to automatically determine the endianness from other symbols defined on your + platform, or define either PA_LITTLE_ENDIAN or PA_BIG_ENDIAN externally. + */ + #error pa_endianness.h was unable to automatically determine the endianness of the target platform + #endif + +#endif + +/* PA_VALIDATE_ENDIANNESS compares the compile time and runtime endianness, + and raises an assertion if they don't match. <assert.h> 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 ); \ + } +#endif + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PA_ENDIANNESS_H */ diff --git a/src/audio/portaudio/pa_common/pa_front.c b/src/audio/portaudio/pa_common/pa_front.c new file mode 100644 index 0000000000000000000000000000000000000000..3b4a026b5524e31fa88d9f87cda7bf1c1fdd0ebc --- /dev/null +++ b/src/audio/portaudio/pa_common/pa_front.c @@ -0,0 +1,1976 @@ +/* + * $Id$ + * 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 + * + * 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. + * + * 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. + * + * 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. + */ + +/* doxygen index page */ +/** @mainpage + +PortAudio is an open-source cross-platform �C� library for audio input +and output. It is designed to simplify the porting of audio applications +between various platforms, and also to simplify the development of audio +software in general by hiding the complexities of device interfacing. + +See the PortAudio website for further information http://www.portaudio.com/ + +This documentation pertains to PortAudio V19, API version 2.0 which is +currently under development. API version 2.0 differs in a number of ways from +previous versions, please consult the enhancement proposals for further details: +http://www.portaudio.com/docs/proposals/index.html + +This documentation is under construction. Things you might be interested in +include: + +- The PortAudio API 2.0, as documented in portaudio.h + +- The <a href="todo.html">TODO List</a> + +Feel free to pick an item off TODO list and fix/implement it. You may want to +enquire about status on the PortAudio mailing list first. +*/ + + +/** @file + @brief Implements public PortAudio API, checks some errors, forwards 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). + + This file handles initialization and termination of Host API + implementations via initializers stored in the paHostApiInitializers + global variable. + + Some utility functions declared in pa_util.h are implemented in this file. + + All PortAudio API functions can be conditionally compiled with logging code. + To compile with logging, define the PA_LOG_API_CALLS precompiler symbol. + + @todo Consider adding host API specific error text in Pa_GetErrorText() for + paUnanticipatedHostError + + @todo Consider adding a new error code for when (inputParameters == NULL) + && (outputParameters == NULL) + + @todo review whether Pa_CloseStream() should call the interface's + CloseStream function if aborting the stream returns an error code. + + @todo Create new error codes if a NULL buffer pointer, or a + zero frame count is passed to Pa_ReadStream or Pa_WriteStream. +*/ + + +#include <stdio.h> +#include <stdarg.h> +#include <memory.h> +#include <string.h> +#include <assert.h> /* needed by PA_VALIDATE_ENDIANNESS */ + +#include "portaudio.h" +#include "pa_util.h" +#include "pa_endianness.h" +#include "pa_types.h" +#include "pa_hostapi.h" +#include "pa_stream.h" + +#include "pa_trace.h" + + +#define PA_VERSION_ 1899 +#define PA_VERSION_TEXT_ "PortAudio V19-devel" + + + +/* #define PA_LOG_API_CALLS */ + +/* + The basic format for log messages is described below. If you need to + add any log messages, please follow this format. + + Function entry (void function): + + "FunctionName called.\n" + + Function entry (non void function): + + "FunctionName called:\n" + "\tParam1Type param1: param1Value\n" + "\tParam2Type param2: param2Value\n" (etc...) + + + Function exit (no return value): + + "FunctionName returned.\n" + + Function exit (simple return value): + + "FunctionName returned:\n" + "\tReturnType: returnValue\n\n" + + If the return type is an error code, the error text is displayed in () + + If the return type is not an error code, but has taken a special value + because an error occurred, then the reason for the error is shown in [] + + If the return type is a struct ptr, the struct is dumped. + + See the code below for examples +*/ + + +int Pa_GetVersion( void ) +{ + return PA_VERSION_; +} + + +const char* Pa_GetVersionText( void ) +{ + return PA_VERSION_TEXT_; +} + + + +#define PA_LAST_HOST_ERROR_TEXT_LENGTH_ 1024 + +static char lastHostErrorText_[ PA_LAST_HOST_ERROR_TEXT_LENGTH_ + 1 ] = {0}; + +static PaHostErrorInfo lastHostErrorInfo_ = { (PaHostApiTypeId)-1, 0, lastHostErrorText_ }; + + +void PaUtil_SetLastHostErrorInfo( PaHostApiTypeId hostApiType, long errorCode, + const char *errorText ) +{ + lastHostErrorInfo_.hostApiType = hostApiType; + lastHostErrorInfo_.errorCode = errorCode; + + strncpy( lastHostErrorText_, errorText, PA_LAST_HOST_ERROR_TEXT_LENGTH_ ); +} + + +void PaUtil_DebugPrint( const char *format, ... ) +{ + va_list ap; + + va_start( ap, format ); + vfprintf( stderr, format, ap ); + va_end( ap ); + + fflush( stderr ); +} + + +static PaUtilHostApiRepresentation **hostApis_ = 0; +static int hostApisCount_ = 0; +static int initializationCount_ = 0; +static int deviceCount_ = 0; + +PaUtilStreamRepresentation *firstOpenStream_ = NULL; + + +#define PA_IS_INITIALISED_ (initializationCount_ != 0) + + +static int CountHostApiInitializers( void ) +{ + int result = 0; + + while( paHostApiInitializers[ result ] != 0 ) + ++result; + return result; +} + + +static void TerminateHostApis( void ) +{ + /* terminate in reverse order from initialization */ + + while( hostApisCount_ > 0 ) + { + --hostApisCount_; + hostApis_[hostApisCount_]->Terminate( hostApis_[hostApisCount_] ); + } + hostApisCount_ = 0; + deviceCount_ = 0; + + if( hostApis_ != 0 ) + PaUtil_FreeMemory( hostApis_ ); + hostApis_ = 0; +} + + +static PaError InitializeHostApis( void ) +{ + PaError result = paNoError; + int i, initializerCount, baseDeviceIndex; + + initializerCount = CountHostApiInitializers(); + + hostApis_ = (PaUtilHostApiRepresentation**)PaUtil_AllocateMemory( + sizeof(PaUtilHostApiRepresentation*) * initializerCount ); + if( !hostApis_ ) + { + result = paInsufficientMemory; + goto error; + } + + hostApisCount_ = 0; + deviceCount_ = 0; + baseDeviceIndex = 0; + + for( i=0; i< initializerCount; ++i ) + { + hostApis_[hostApisCount_] = NULL; + result = paHostApiInitializers[i]( &hostApis_[hostApisCount_], hostApisCount_ ); + if( result != paNoError ) + goto error; + + if( hostApis_[hostApisCount_] ) + { + + hostApis_[hostApisCount_]->privatePaFrontInfo.baseDeviceIndex = baseDeviceIndex; + + if( hostApis_[hostApisCount_]->info.defaultInputDevice != paNoDevice ) + hostApis_[hostApisCount_]->info.defaultInputDevice += baseDeviceIndex; + + if( hostApis_[hostApisCount_]->info.defaultOutputDevice != paNoDevice ) + hostApis_[hostApisCount_]->info.defaultOutputDevice += baseDeviceIndex; + + baseDeviceIndex += hostApis_[hostApisCount_]->info.deviceCount; + deviceCount_ += hostApis_[hostApisCount_]->info.deviceCount; + + ++hostApisCount_; + } + } + + return result; + +error: + TerminateHostApis(); + return result; +} + + +/* + FindHostApi() finds the index of the host api to which + <device> belongs and returns it. if <hostSpecificDeviceIndex> is + non-null, the host specific device index is returned in it. + returns -1 if <device> is out of range. + +*/ +static int FindHostApi( PaDeviceIndex device, int *hostSpecificDeviceIndex ) +{ + int i=0; + + if( !PA_IS_INITIALISED_ ) + return -1; + + if( device < 0 ) + return -1; + + while( i < hostApisCount_ + && device >= hostApis_[i]->info.deviceCount ) + { + + device -= hostApis_[i]->info.deviceCount; + ++i; + } + + if( i >= hostApisCount_ ) + return -1; + + if( hostSpecificDeviceIndex ) + *hostSpecificDeviceIndex = device; + + return i; +} + + +static void AddOpenStream( PaStream* stream ) +{ + ((PaUtilStreamRepresentation*)stream)->nextOpenStream = firstOpenStream_; + firstOpenStream_ = (PaUtilStreamRepresentation*)stream; +} + + +static void RemoveOpenStream( PaStream* stream ) +{ + PaUtilStreamRepresentation *previous = NULL; + PaUtilStreamRepresentation *current = firstOpenStream_; + + while( current != NULL ) + { + if( ((PaStream*)current) == stream ) + { + if( previous == NULL ) + { + firstOpenStream_ = current->nextOpenStream; + } + else + { + previous->nextOpenStream = current->nextOpenStream; + } + return; + } + else + { + previous = current; + current = current->nextOpenStream; + } + } +} + + +static void CloseOpenStreams( void ) +{ + /* we call Pa_CloseStream() here to ensure that the same destruction + logic is used for automatically closed streams */ + + while( firstOpenStream_ != NULL ) + Pa_CloseStream( firstOpenStream_ ); +} + + +PaError Pa_Initialize( void ) +{ + PaError result; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint( "Pa_Initialize called.\n" ); +#endif + + if( PA_IS_INITIALISED_ ) + { + ++initializationCount_; + result = paNoError; + } + else + { + PA_VALIDATE_TYPE_SIZES; + PA_VALIDATE_ENDIANNESS; + + PaUtil_InitializeClock(); + PaUtil_ResetTraceMessages(); + + result = InitializeHostApis(); + if( result == paNoError ) + ++initializationCount_; + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint( "Pa_Initialize returned:\n" ); + PaUtil_DebugPrint( "\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return result; +} + + +PaError Pa_Terminate( void ) +{ + PaError result; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_Terminate called.\n" ); +#endif + + if( PA_IS_INITIALISED_ ) + { + if( --initializationCount_ == 0 ) + { + CloseOpenStreams(); + + TerminateHostApis(); + + PaUtil_DumpTraceMessages(); + } + result = paNoError; + } + else + { + result= paNotInitialized; + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_Terminate returned:\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return result; +} + + +const PaHostErrorInfo* Pa_GetLastHostErrorInfo( void ) +{ + return &lastHostErrorInfo_; +} + + +const char *Pa_GetErrorText( PaError errorCode ) +{ + const char *result; + + switch( errorCode ) + { + case paNoError: result = "Success"; break; + case paNotInitialized: result = "PortAudio not initialized"; break; + /** @todo could catenate the last host error text to result in the case of paUnanticipatedHostError */ + case paUnanticipatedHostError: result = "Unanticipated host error"; break; + case paInvalidChannelCount: result = "Invalid number of channels"; break; + case paInvalidSampleRate: result = "Invalid sample rate"; break; + case paInvalidDevice: result = "Invalid device"; break; + case paInvalidFlag: result = "Invalid flag"; break; + case paSampleFormatNotSupported: result = "Sample format not supported"; break; + case paBadIODeviceCombination: result = "Illegal combination of I/O devices"; break; + case paInsufficientMemory: result = "Insufficient memory"; break; + case paBufferTooBig: result = "Buffer too big"; break; + case paBufferTooSmall: result = "Buffer too small"; break; + case paNullCallback: result = "No callback routine specified"; break; + case paBadStreamPtr: result = "Invalid stream pointer"; break; + case paTimedOut: result = "Wait timed out"; break; + case paInternalError: result = "Internal PortAudio error"; break; + case paDeviceUnavailable: result = "Device unavailable"; break; + case paIncompatibleHostApiSpecificStreamInfo: result = "Incompatible host API specific stream info"; break; + case paStreamIsStopped: result = "Stream is stopped"; break; + case paStreamIsNotStopped: result = "Stream is not stopped"; break; + case paInputOverflowed: result = "Input overflowed"; break; + case paOutputUnderflowed: result = "Output underflowed"; break; + case paHostApiNotFound: result = "Host API not found"; break; + case paInvalidHostApi: result = "Invalid host API"; break; + case paCanNotReadFromACallbackStream: result = "Can't read from a callback stream"; break; + 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; + default: result = "Illegal error number"; break; + } + return result; +} + + +PaHostApiIndex Pa_HostApiTypeIdToHostApiIndex( PaHostApiTypeId type ) +{ + PaHostApiIndex result; + int i; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_HostApiTypeIdToHostApiIndex called:\n" ); + PaUtil_DebugPrint("\tPaHostApiTypeId type: %d\n", type ); +#endif + + if( !PA_IS_INITIALISED_ ) + { + result = paNotInitialized; + } + else + { + result = paHostApiNotFound; + + for( i=0; i < hostApisCount_; ++i ) + { + if( hostApis_[i]->info.type == type ) + { + result = i; + break; + } + } + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_HostApiTypeIdToHostApiIndex returned:\n" ); + if( result < 0 ) + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); + else + PaUtil_DebugPrint("\tPaHostApiIndex: %d\n\n", result ); +#endif + + return result; +} + + +PaError PaUtil_GetHostApiRepresentation( struct PaUtilHostApiRepresentation **hostApi, + PaHostApiTypeId type ) +{ + PaError result; + int i; + + if( !PA_IS_INITIALISED_ ) + { + result = paNotInitialized; + } + else + { + result = paHostApiNotFound; + + for( i=0; i < hostApisCount_; ++i ) + { + if( hostApis_[i]->info.type == type ) + { + *hostApi = hostApis_[i]; + result = paNoError; + break; + } + } + } + + return result; +} + + +PaError PaUtil_DeviceIndexToHostApiDeviceIndex( + PaDeviceIndex *hostApiDevice, PaDeviceIndex device, struct PaUtilHostApiRepresentation *hostApi ) +{ + PaError result; + PaDeviceIndex x; + + x = device - hostApi->privatePaFrontInfo.baseDeviceIndex; + + if( x < 0 || x >= hostApi->info.deviceCount ) + { + result = paInvalidDevice; + } + else + { + *hostApiDevice = x; + result = paNoError; + } + + return result; +} + + +PaHostApiIndex Pa_GetHostApiCount( void ) +{ + int result; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetHostApiCount called.\n" ); +#endif + + if( !PA_IS_INITIALISED_ ) + { + result = paNotInitialized; + } + else + { + result = hostApisCount_; + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetHostApiCount returned:\n" ); + if( result < 0 ) + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); + else + PaUtil_DebugPrint("\tPaHostApiIndex %d\n\n", result ); +#endif + + return result; +} + + +PaHostApiIndex Pa_GetDefaultHostApi( void ) +{ + int result; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetDefaultHostApi called.\n" ); +#endif + + if( !PA_IS_INITIALISED_ ) + { + result = paNotInitialized; + } + else + { + result = paDefaultHostApiIndex; + + /* internal consistency check: make sure that the default host api + index is within range */ + + if( result < 0 || result >= hostApisCount_ ) + { + result = paInternalError; + } + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetDefaultHostApi returned:\n" ); + if( result < 0 ) + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); + else + PaUtil_DebugPrint("\tPaHostApiIndex %d\n\n", result ); +#endif + + return result; +} + + +const PaHostApiInfo* Pa_GetHostApiInfo( PaHostApiIndex hostApi ) +{ + PaHostApiInfo *info; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetHostApiInfo called:\n" ); + PaUtil_DebugPrint("\tPaHostApiIndex hostApi: %d\n", hostApi ); +#endif + + if( !PA_IS_INITIALISED_ ) + { + info = NULL; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetHostApiInfo returned:\n" ); + PaUtil_DebugPrint("\tPaHostApiInfo*: NULL [ PortAudio not initialized ]\n\n" ); +#endif + + } + else if( hostApi < 0 || hostApi >= hostApisCount_ ) + { + info = NULL; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetHostApiInfo returned:\n" ); + PaUtil_DebugPrint("\tPaHostApiInfo*: NULL [ hostApi out of range ]\n\n" ); +#endif + + } + else + { + info = &hostApis_[hostApi]->info; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetHostApiInfo returned:\n" ); + PaUtil_DebugPrint("\tPaHostApiInfo*: 0x%p\n", info ); + PaUtil_DebugPrint("\t{" ); + PaUtil_DebugPrint("\t\tint structVersion: %d\n", info->structVersion ); + PaUtil_DebugPrint("\t\tPaHostApiTypeId type: %d\n", info->type ); + PaUtil_DebugPrint("\t\tconst char *name: %s\n\n", info->name ); + PaUtil_DebugPrint("\t}\n\n" ); +#endif + + } + + return info; +} + + +PaDeviceIndex Pa_HostApiDeviceIndexToDeviceIndex( PaHostApiIndex hostApi, int hostApiDeviceIndex ) +{ + PaDeviceIndex result; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_HostApiDeviceIndexToPaDeviceIndex called:\n" ); + PaUtil_DebugPrint("\tPaHostApiIndex hostApi: %d\n", hostApi ); + PaUtil_DebugPrint("\tint hostApiDeviceIndex: %d\n", hostApiDeviceIndex ); +#endif + + if( !PA_IS_INITIALISED_ ) + { + result = paNotInitialized; + } + else + { + if( hostApi < 0 || hostApi >= hostApisCount_ ) + { + result = paInvalidHostApi; + } + else + { + if( hostApiDeviceIndex < 0 || + hostApiDeviceIndex >= hostApis_[hostApi]->info.deviceCount ) + { + result = paInvalidDevice; + } + else + { + result = hostApis_[hostApi]->privatePaFrontInfo.baseDeviceIndex + hostApiDeviceIndex; + } + } + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_HostApiDeviceIndexToPaDeviceIndex returned:\n" ); + if( result < 0 ) + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); + else + PaUtil_DebugPrint("\tPaDeviceIndex: %d\n\n", result ); +#endif + + return result; +} + + +PaDeviceIndex Pa_GetDeviceCount( void ) +{ + PaDeviceIndex result; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetDeviceCount called.\n" ); +#endif + + if( !PA_IS_INITIALISED_ ) + { + result = paNotInitialized; + } + else + { + result = deviceCount_; + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetDeviceCount returned:\n" ); + if( result < 0 ) + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); + else + PaUtil_DebugPrint("\tPaDeviceIndex: %d\n\n", result ); +#endif + + return result; +} + + +PaDeviceIndex Pa_GetDefaultInputDevice( void ) +{ + PaHostApiIndex hostApi; + PaDeviceIndex result; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetDefaultInputDevice called.\n" ); +#endif + + hostApi = Pa_GetDefaultHostApi(); + if( hostApi < 0 ) + { + result = paNoDevice; + } + else + { + result = hostApis_[hostApi]->info.defaultInputDevice; + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetDefaultInputDevice returned:\n" ); + PaUtil_DebugPrint("\tPaDeviceIndex: %d\n\n", result ); +#endif + + return result; +} + + +PaDeviceIndex Pa_GetDefaultOutputDevice( void ) +{ + PaHostApiIndex hostApi; + PaDeviceIndex result; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetDefaultOutputDevice called.\n" ); +#endif + + hostApi = Pa_GetDefaultHostApi(); + if( hostApi < 0 ) + { + result = paNoDevice; + } + else + { + result = hostApis_[hostApi]->info.defaultOutputDevice; + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetDefaultOutputDevice returned:\n" ); + PaUtil_DebugPrint("\tPaDeviceIndex: %d\n\n", result ); +#endif + + return result; +} + + +const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceIndex device ) +{ + int hostSpecificDeviceIndex; + int hostApiIndex = FindHostApi( device, &hostSpecificDeviceIndex ); + PaDeviceInfo *result; + + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetDeviceInfo called:\n" ); + PaUtil_DebugPrint("\tPaDeviceIndex device: %d\n", device ); +#endif + + if( hostApiIndex < 0 ) + { + result = NULL; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetDeviceInfo returned:\n" ); + PaUtil_DebugPrint("\tPaDeviceInfo* NULL [ invalid device index ]\n\n" ); +#endif + + } + else + { + result = hostApis_[hostApiIndex]->deviceInfos[ hostSpecificDeviceIndex ]; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetDeviceInfo returned:\n" ); + PaUtil_DebugPrint("\tPaDeviceInfo*: 0x%p:\n", result ); + PaUtil_DebugPrint("\t{\n" ); + + PaUtil_DebugPrint("\t\tint structVersion: %d\n", result->structVersion ); + PaUtil_DebugPrint("\t\tconst char *name: %s\n", result->name ); + PaUtil_DebugPrint("\t\tPaHostApiIndex hostApi: %d\n", result->hostApi ); + PaUtil_DebugPrint("\t\tint maxInputChannels: %d\n", result->maxInputChannels ); + PaUtil_DebugPrint("\t\tint maxOutputChannels: %d\n", result->maxOutputChannels ); + PaUtil_DebugPrint("\t}\n\n" ); +#endif + + } + + return result; +} + + +/* + SampleFormatIsValid() returns 1 if sampleFormat is a sample format + defined in portaudio.h, or 0 otherwise. +*/ +static int SampleFormatIsValid( PaSampleFormat format ) +{ + switch( format & ~paNonInterleaved ) + { + case paFloat32: return 1; + case paInt16: return 1; + case paInt32: return 1; + case paInt24: return 1; + case paInt8: return 1; + case paUInt8: return 1; + case paCustomFormat: return 1; + default: return 0; + } +} + +/* + NOTE: make sure this validation list is kept syncronised with the one in + pa_hostapi.h + + ValidateOpenStreamParameters() checks that parameters to Pa_OpenStream() + conform to the expected values as described below. This function is + also designed to be used with the proposed Pa_IsFormatSupported() function. + + There are basically two types of validation that could be performed: + Generic conformance validation, and device capability mismatch + validation. This function performs only generic conformance validation. + Validation that would require knowledge of device capabilities is + not performed because of potentially complex relationships between + combinations of parameters - for example, even if the sampleRate + seems ok, it might not be for a duplex stream - we have no way of + checking this in an API-neutral way, so we don't try. + + On success the function returns PaNoError and fills in hostApi, + hostApiInputDeviceID, and hostApiOutputDeviceID fields. On failure + the function returns an error code indicating the first encountered + parameter error. + + + If ValidateOpenStreamParameters() returns paNoError, the following + assertions are guaranteed to be true. + + - at least one of inputParameters & outputParmeters is valid (not NULL) + + - if inputParameters & outputParmeters are both valid, that + inputParameters->device & outputParmeters->device both use the same host api + + PaDeviceIndex inputParameters->device + - is within range (0 to Pa_GetDeviceCount-1) Or: + - is paUseHostApiSpecificDeviceSpecification and + inputParameters->hostApiSpecificStreamInfo is non-NULL and refers + to a valid host api + + int inputParameters->channelCount + - if inputParameters->device is not paUseHostApiSpecificDeviceSpecification, channelCount is > 0 + - upper bound is NOT validated against device capabilities + + PaSampleFormat inputParameters->sampleFormat + - is one of the sample formats defined in portaudio.h + + void *inputParameters->hostApiSpecificStreamInfo + - if supplied its hostApi field matches the input device's host Api + + PaDeviceIndex outputParmeters->device + - is within range (0 to Pa_GetDeviceCount-1) + + int outputParmeters->channelCount + - if inputDevice is valid, channelCount is > 0 + - upper bound is NOT validated against device capabilities + + PaSampleFormat outputParmeters->sampleFormat + - is one of the sample formats defined in portaudio.h + + void *outputParmeters->hostApiSpecificStreamInfo + - if supplied its hostApi field matches the output device's host Api + + double sampleRate + - is not an 'absurd' rate (less than 1000. or greater than 200000.) + - sampleRate is NOT validated against device capabilities + + PaStreamFlags streamFlags + - unused platform neutral flags are zero + - paNeverDropInput is only used for full-duplex callback streams with + variable buffer size (paFramesPerBufferUnspecified) +*/ +static PaError ValidateOpenStreamParameters( + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + PaUtilHostApiRepresentation **hostApi, + PaDeviceIndex *hostApiInputDevice, + PaDeviceIndex *hostApiOutputDevice ) +{ + int inputHostApiIndex = -1, /* Surpress uninitialised var warnings: compiler does */ + outputHostApiIndex = -1; /* not see that if inputParameters and outputParame- */ + /* ters are both nonzero, these indices are set. */ + + if( (inputParameters == NULL) && (outputParameters == NULL) ) + { + return paInvalidDevice; /** @todo should be a new error code "invalid device parameters" or something */ + } + else + { + if( inputParameters == NULL ) + { + *hostApiInputDevice = paNoDevice; + } + else if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + { + if( inputParameters->hostApiSpecificStreamInfo ) + { + inputHostApiIndex = Pa_HostApiTypeIdToHostApiIndex( + ((PaUtilHostApiSpecificStreamInfoHeader*)inputParameters->hostApiSpecificStreamInfo)->hostApiType ); + + if( inputHostApiIndex != -1 ) + { + *hostApiInputDevice = paUseHostApiSpecificDeviceSpecification; + *hostApi = hostApis_[inputHostApiIndex]; + } + else + { + return paInvalidDevice; + } + } + else + { + return paInvalidDevice; + } + } + else + { + if( inputParameters->device < 0 || inputParameters->device >= deviceCount_ ) + return paInvalidDevice; + + inputHostApiIndex = FindHostApi( inputParameters->device, hostApiInputDevice ); + if( inputHostApiIndex < 0 ) + return paInternalError; + + *hostApi = hostApis_[inputHostApiIndex]; + + if( inputParameters->channelCount <= 0 ) + return paInvalidChannelCount; + + if( !SampleFormatIsValid( inputParameters->sampleFormat ) ) + return paSampleFormatNotSupported; + + if( inputParameters->hostApiSpecificStreamInfo != NULL ) + { + if( ((PaUtilHostApiSpecificStreamInfoHeader*)inputParameters->hostApiSpecificStreamInfo)->hostApiType + != (*hostApi)->info.type ) + return paIncompatibleHostApiSpecificStreamInfo; + } + } + + if( outputParameters == NULL ) + { + *hostApiOutputDevice = paNoDevice; + } + else if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + { + if( outputParameters->hostApiSpecificStreamInfo ) + { + outputHostApiIndex = Pa_HostApiTypeIdToHostApiIndex( + ((PaUtilHostApiSpecificStreamInfoHeader*)outputParameters->hostApiSpecificStreamInfo)->hostApiType ); + + if( outputHostApiIndex != -1 ) + { + *hostApiOutputDevice = paUseHostApiSpecificDeviceSpecification; + *hostApi = hostApis_[outputHostApiIndex]; + } + else + { + return paInvalidDevice; + } + } + else + { + return paInvalidDevice; + } + } + else + { + if( outputParameters->device < 0 || outputParameters->device >= deviceCount_ ) + return paInvalidDevice; + + outputHostApiIndex = FindHostApi( outputParameters->device, hostApiOutputDevice ); + if( outputHostApiIndex < 0 ) + return paInternalError; + + *hostApi = hostApis_[outputHostApiIndex]; + + if( outputParameters->channelCount <= 0 ) + return paInvalidChannelCount; + + if( !SampleFormatIsValid( outputParameters->sampleFormat ) ) + return paSampleFormatNotSupported; + + if( outputParameters->hostApiSpecificStreamInfo != NULL ) + { + if( ((PaUtilHostApiSpecificStreamInfoHeader*)outputParameters->hostApiSpecificStreamInfo)->hostApiType + != (*hostApi)->info.type ) + return paIncompatibleHostApiSpecificStreamInfo; + } + } + + if( (inputParameters != NULL) && (outputParameters != NULL) ) + { + /* ensure that both devices use the same API */ + if( inputHostApiIndex != outputHostApiIndex ) + return paBadIODeviceCombination; + } + } + + + /* Check for absurd sample rates. */ + if( (sampleRate < 1000.0) || (sampleRate > 200000.0) ) + return paInvalidSampleRate; + + if( ((streamFlags & ~paPlatformSpecificFlags) & ~(paClipOff | paDitherOff | paNeverDropInput | paPrimeOutputBuffersUsingStreamCallback ) ) != 0 ) + return paInvalidFlag; + + if( streamFlags & paNeverDropInput ) + { + /* must be a callback stream */ + if( !streamCallback ) + return paInvalidFlag; + + /* must be a full duplex stream */ + if( (inputParameters == NULL) || (outputParameters == NULL) ) + return paInvalidFlag; + + /* must use paFramesPerBufferUnspecified */ + if( framesPerBuffer != paFramesPerBufferUnspecified ) + return paInvalidFlag; + } + + return paNoError; +} + + +PaError Pa_IsFormatSupported( const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + PaError result; + PaUtilHostApiRepresentation *hostApi; + PaDeviceIndex hostApiInputDevice, hostApiOutputDevice; + PaStreamParameters hostApiInputParameters, hostApiOutputParameters; + PaStreamParameters *hostApiInputParametersPtr, *hostApiOutputParametersPtr; + + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_IsFormatSupported called:\n" ); + + if( inputParameters == NULL ){ + PaUtil_DebugPrint("\tPaStreamParameters *inputParameters: NULL\n" ); + }else{ + PaUtil_DebugPrint("\tPaStreamParameters *inputParameters: 0x%p\n", inputParameters ); + PaUtil_DebugPrint("\tPaDeviceIndex inputParameters->device: %d\n", inputParameters->device ); + PaUtil_DebugPrint("\tint inputParameters->channelCount: %d\n", inputParameters->channelCount ); + PaUtil_DebugPrint("\tPaSampleFormat inputParameters->sampleFormat: %d\n", inputParameters->sampleFormat ); + PaUtil_DebugPrint("\tPaTime inputParameters->suggestedLatency: %f\n", inputParameters->suggestedLatency ); + PaUtil_DebugPrint("\tvoid *inputParameters->hostApiSpecificStreamInfo: 0x%p\n", inputParameters->hostApiSpecificStreamInfo ); + } + + if( outputParameters == NULL ){ + PaUtil_DebugPrint("\tPaStreamParameters *outputParameters: NULL\n" ); + }else{ + PaUtil_DebugPrint("\tPaStreamParameters *outputParameters: 0x%p\n", outputParameters ); + PaUtil_DebugPrint("\tPaDeviceIndex outputParameters->device: %d\n", outputParameters->device ); + PaUtil_DebugPrint("\tint outputParameters->channelCount: %d\n", outputParameters->channelCount ); + PaUtil_DebugPrint("\tPaSampleFormat outputParameters->sampleFormat: %d\n", outputParameters->sampleFormat ); + PaUtil_DebugPrint("\tPaTime outputParameters->suggestedLatency: %f\n", outputParameters->suggestedLatency ); + PaUtil_DebugPrint("\tvoid *outputParameters->hostApiSpecificStreamInfo: 0x%p\n", outputParameters->hostApiSpecificStreamInfo ); + } + + PaUtil_DebugPrint("\tdouble sampleRate: %g\n", sampleRate ); +#endif + + if( !PA_IS_INITIALISED_ ) + { + result = paNotInitialized; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_IsFormatSupported returned:\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + return result; + } + + result = ValidateOpenStreamParameters( inputParameters, + outputParameters, + sampleRate, 0, paNoFlag, 0, + &hostApi, + &hostApiInputDevice, + &hostApiOutputDevice ); + if( result != paNoError ) + { +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_IsFormatSupported returned:\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + return result; + } + + + if( inputParameters ) + { + hostApiInputParameters.device = hostApiInputDevice; + hostApiInputParameters.channelCount = inputParameters->channelCount; + hostApiInputParameters.sampleFormat = inputParameters->sampleFormat; + hostApiInputParameters.suggestedLatency = inputParameters->suggestedLatency; + hostApiInputParameters.hostApiSpecificStreamInfo = inputParameters->hostApiSpecificStreamInfo; + hostApiInputParametersPtr = &hostApiInputParameters; + } + else + { + hostApiInputParametersPtr = NULL; + } + + if( outputParameters ) + { + hostApiOutputParameters.device = hostApiOutputDevice; + hostApiOutputParameters.channelCount = outputParameters->channelCount; + hostApiOutputParameters.sampleFormat = outputParameters->sampleFormat; + hostApiOutputParameters.suggestedLatency = outputParameters->suggestedLatency; + hostApiOutputParameters.hostApiSpecificStreamInfo = outputParameters->hostApiSpecificStreamInfo; + hostApiOutputParametersPtr = &hostApiOutputParameters; + } + else + { + hostApiOutputParametersPtr = NULL; + } + + result = hostApi->IsFormatSupported( hostApi, + hostApiInputParametersPtr, hostApiOutputParametersPtr, + sampleRate ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_OpenStream returned:\n" ); + if( result == paFormatIsSupported ) + PaUtil_DebugPrint("\tPaError: 0 [ paFormatIsSupported ]\n\n" ); + else + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return result; +} + + +PaError Pa_OpenStream( PaStream** stream, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ) +{ + PaError result; + PaUtilHostApiRepresentation *hostApi; + PaDeviceIndex hostApiInputDevice, hostApiOutputDevice; + PaStreamParameters hostApiInputParameters, hostApiOutputParameters; + PaStreamParameters *hostApiInputParametersPtr, *hostApiOutputParametersPtr; + + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_OpenStream called:\n" ); + PaUtil_DebugPrint("\tPaStream** stream: 0x%p\n", stream ); + + if( inputParameters == NULL ){ + PaUtil_DebugPrint("\tPaStreamParameters *inputParameters: NULL\n" ); + }else{ + PaUtil_DebugPrint("\tPaStreamParameters *inputParameters: 0x%p\n", inputParameters ); + PaUtil_DebugPrint("\tPaDeviceIndex inputParameters->device: %d\n", inputParameters->device ); + PaUtil_DebugPrint("\tint inputParameters->channelCount: %d\n", inputParameters->channelCount ); + PaUtil_DebugPrint("\tPaSampleFormat inputParameters->sampleFormat: %d\n", inputParameters->sampleFormat ); + PaUtil_DebugPrint("\tPaTime inputParameters->suggestedLatency: %f\n", inputParameters->suggestedLatency ); + PaUtil_DebugPrint("\tvoid *inputParameters->hostApiSpecificStreamInfo: 0x%p\n", inputParameters->hostApiSpecificStreamInfo ); + } + + if( outputParameters == NULL ){ + PaUtil_DebugPrint("\tPaStreamParameters *outputParameters: NULL\n" ); + }else{ + PaUtil_DebugPrint("\tPaStreamParameters *outputParameters: 0x%p\n", outputParameters ); + PaUtil_DebugPrint("\tPaDeviceIndex outputParameters->device: %d\n", outputParameters->device ); + PaUtil_DebugPrint("\tint outputParameters->channelCount: %d\n", outputParameters->channelCount ); + PaUtil_DebugPrint("\tPaSampleFormat outputParameters->sampleFormat: %d\n", outputParameters->sampleFormat ); + PaUtil_DebugPrint("\tPaTime outputParameters->suggestedLatency: %f\n", outputParameters->suggestedLatency ); + PaUtil_DebugPrint("\tvoid *outputParameters->hostApiSpecificStreamInfo: 0x%p\n", outputParameters->hostApiSpecificStreamInfo ); + } + + PaUtil_DebugPrint("\tdouble sampleRate: %g\n", sampleRate ); + PaUtil_DebugPrint("\tunsigned long framesPerBuffer: %d\n", framesPerBuffer ); + PaUtil_DebugPrint("\tPaStreamFlags streamFlags: 0x%x\n", streamFlags ); + PaUtil_DebugPrint("\tPaStreamCallback *streamCallback: 0x%p\n", streamCallback ); + PaUtil_DebugPrint("\tvoid *userData: 0x%p\n", userData ); +#endif + + if( !PA_IS_INITIALISED_ ) + { + result = paNotInitialized; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_OpenStream returned:\n" ); + PaUtil_DebugPrint("\t*(PaStream** stream): undefined\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + return result; + } + + /* Check for parameter errors. + NOTE: make sure this validation list is kept syncronised with the one + in pa_hostapi.h + */ + + if( stream == NULL ) + { + result = paBadStreamPtr; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_OpenStream returned:\n" ); + PaUtil_DebugPrint("\t*(PaStream** stream): undefined\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + return result; + } + + result = ValidateOpenStreamParameters( inputParameters, + outputParameters, + sampleRate, framesPerBuffer, + streamFlags, streamCallback, + &hostApi, + &hostApiInputDevice, + &hostApiOutputDevice ); + if( result != paNoError ) + { +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_OpenStream returned:\n" ); + PaUtil_DebugPrint("\t*(PaStream** stream): undefined\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + return result; + } + + + if( inputParameters ) + { + hostApiInputParameters.device = hostApiInputDevice; + hostApiInputParameters.channelCount = inputParameters->channelCount; + hostApiInputParameters.sampleFormat = inputParameters->sampleFormat; + hostApiInputParameters.suggestedLatency = inputParameters->suggestedLatency; + hostApiInputParameters.hostApiSpecificStreamInfo = inputParameters->hostApiSpecificStreamInfo; + hostApiInputParametersPtr = &hostApiInputParameters; + } + else + { + hostApiInputParametersPtr = NULL; + } + + if( outputParameters ) + { + hostApiOutputParameters.device = hostApiOutputDevice; + hostApiOutputParameters.channelCount = outputParameters->channelCount; + hostApiOutputParameters.sampleFormat = outputParameters->sampleFormat; + hostApiOutputParameters.suggestedLatency = outputParameters->suggestedLatency; + hostApiOutputParameters.hostApiSpecificStreamInfo = outputParameters->hostApiSpecificStreamInfo; + hostApiOutputParametersPtr = &hostApiOutputParameters; + } + else + { + hostApiOutputParametersPtr = NULL; + } + + result = hostApi->OpenStream( hostApi, stream, + hostApiInputParametersPtr, hostApiOutputParametersPtr, + sampleRate, framesPerBuffer, streamFlags, streamCallback, userData ); + + if( result == paNoError ) + AddOpenStream( *stream ); + + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_OpenStream returned:\n" ); + PaUtil_DebugPrint("\t*(PaStream** stream): 0x%p\n", *stream ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return result; +} + + +PaError Pa_OpenDefaultStream( PaStream** stream, + int inputChannelCount, + int outputChannelCount, + PaSampleFormat sampleFormat, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamCallback *streamCallback, + void *userData ) +{ + PaError result; + PaStreamParameters hostApiInputParameters, hostApiOutputParameters; + PaStreamParameters *hostApiInputParametersPtr, *hostApiOutputParametersPtr; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_OpenDefaultStream called:\n" ); + PaUtil_DebugPrint("\tPaStream** stream: 0x%p\n", stream ); + PaUtil_DebugPrint("\tint inputChannelCount: %d\n", inputChannelCount ); + PaUtil_DebugPrint("\tint outputChannelCount: %d\n", outputChannelCount ); + PaUtil_DebugPrint("\tPaSampleFormat sampleFormat: %d\n", sampleFormat ); + PaUtil_DebugPrint("\tdouble sampleRate: %g\n", sampleRate ); + PaUtil_DebugPrint("\tunsigned long framesPerBuffer: %d\n", framesPerBuffer ); + PaUtil_DebugPrint("\tPaStreamCallback *streamCallback: 0x%p\n", streamCallback ); + PaUtil_DebugPrint("\tvoid *userData: 0x%p\n", userData ); +#endif + + + if( inputChannelCount > 0 ) + { + hostApiInputParameters.device = Pa_GetDefaultInputDevice(); + hostApiInputParameters.channelCount = inputChannelCount; + hostApiInputParameters.sampleFormat = sampleFormat; + /* defaultHighInputLatency is used below instead of + defaultLowInputLatency because it is more important for the default + stream to work reliably than it is for it to work with the lowest + latency. + */ + hostApiInputParameters.suggestedLatency = + Pa_GetDeviceInfo( hostApiInputParameters.device )->defaultHighInputLatency; + hostApiInputParameters.hostApiSpecificStreamInfo = NULL; + hostApiInputParametersPtr = &hostApiInputParameters; + } + else + { + hostApiInputParametersPtr = NULL; + } + + if( outputChannelCount > 0 ) + { + hostApiOutputParameters.device = Pa_GetDefaultOutputDevice(); + hostApiOutputParameters.channelCount = outputChannelCount; + hostApiOutputParameters.sampleFormat = sampleFormat; + /* defaultHighOutputLatency is used below instead of + defaultLowOutputLatency because it is more important for the default + stream to work reliably than it is for it to work with the lowest + latency. + */ + hostApiOutputParameters.suggestedLatency = + Pa_GetDeviceInfo( hostApiOutputParameters.device )->defaultHighOutputLatency; + hostApiOutputParameters.hostApiSpecificStreamInfo = NULL; + hostApiOutputParametersPtr = &hostApiOutputParameters; + } + else + { + hostApiOutputParametersPtr = NULL; + } + + + result = Pa_OpenStream( + stream, hostApiInputParametersPtr, hostApiOutputParametersPtr, + sampleRate, framesPerBuffer, paNoFlag, streamCallback, userData ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_OpenDefaultStream returned:\n" ); + PaUtil_DebugPrint("\t*(PaStream** stream): 0x%p", *stream ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return result; +} + + +PaError PaUtil_ValidateStreamPointer( PaStream* stream ) +{ + if( !PA_IS_INITIALISED_ ) return paNotInitialized; + + if( stream == NULL ) return paBadStreamPtr; + + if( ((PaUtilStreamRepresentation*)stream)->magic != PA_STREAM_MAGIC ) + return paBadStreamPtr; + + return paNoError; +} + + +PaError Pa_CloseStream( PaStream* stream ) +{ + PaUtilStreamInterface *interface; + PaError result = PaUtil_ValidateStreamPointer( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_CloseStream called:\n" ); + PaUtil_DebugPrint("\tPaStream* stream: 0x%p\n", stream ); +#endif + + /* always remove the open stream from our list, even if this function + eventually returns an error. Otherwise CloseOpenStreams() will + get stuck in an infinite loop */ + RemoveOpenStream( stream ); /* be sure to call this _before_ closing the stream */ + + if( result == paNoError ) + { + interface = PA_STREAM_INTERFACE(stream); + + /* abort the stream if it isn't stopped */ + result = interface->IsStopped( stream ); + if( result == 1 ) + result = paNoError; + else if( result == 0 ) + result = interface->Abort( stream ); + + if( result == paNoError ) /** @todo REVIEW: shouldn't we close anyway? */ + result = interface->Close( stream ); + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_CloseStream returned:\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return result; +} + + +PaError Pa_SetStreamFinishedCallback( PaStream *stream, PaStreamFinishedCallback* streamFinishedCallback ) +{ + PaError result = PaUtil_ValidateStreamPointer( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_SetStreamFinishedCallback called:\n" ); + PaUtil_DebugPrint("\tPaStream* stream: 0x%p\n", stream ); + PaUtil_DebugPrint("\tPaStreamFinishedCallback* streamFinishedCallback: 0x%p\n", streamFinishedCallback ); +#endif + + if( result == paNoError ) + { + result = PA_STREAM_INTERFACE(stream)->IsStopped( stream ); + if( result == 0 ) + { + result = paStreamIsNotStopped ; + } + if( result == 1 ) + { + PA_STREAM_REP( stream )->streamFinishedCallback = streamFinishedCallback; + result = paNoError; + } + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_SetStreamFinishedCallback returned:\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return result; + +} + + +PaError Pa_StartStream( PaStream *stream ) +{ + PaError result = PaUtil_ValidateStreamPointer( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_StartStream called:\n" ); + PaUtil_DebugPrint("\tPaStream* stream: 0x%p\n", stream ); +#endif + + if( result == paNoError ) + { + result = PA_STREAM_INTERFACE(stream)->IsStopped( stream ); + if( result == 0 ) + { + result = paStreamIsNotStopped ; + } + else if( result == 1 ) + { + result = PA_STREAM_INTERFACE(stream)->Start( stream ); + } + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_StartStream returned:\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return result; +} + + +PaError Pa_StopStream( PaStream *stream ) +{ + PaError result = PaUtil_ValidateStreamPointer( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_StopStream called\n" ); + PaUtil_DebugPrint("\tPaStream* stream: 0x%p\n", stream ); +#endif + + if( result == paNoError ) + { + result = PA_STREAM_INTERFACE(stream)->IsStopped( stream ); + if( result == 0 ) + { + result = PA_STREAM_INTERFACE(stream)->Stop( stream ); + } + else if( result == 1 ) + { + result = paStreamIsStopped; + } + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_StopStream returned:\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return result; +} + + +PaError Pa_AbortStream( PaStream *stream ) +{ + PaError result = PaUtil_ValidateStreamPointer( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_AbortStream called:\n" ); + PaUtil_DebugPrint("\tPaStream* stream: 0x%p\n", stream ); +#endif + + if( result == paNoError ) + { + result = PA_STREAM_INTERFACE(stream)->IsStopped( stream ); + if( result == 0 ) + { + result = PA_STREAM_INTERFACE(stream)->Abort( stream ); + } + else if( result == 1 ) + { + result = paStreamIsStopped; + } + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_AbortStream returned:\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return result; +} + + +PaError Pa_IsStreamStopped( PaStream *stream ) +{ + PaError result = PaUtil_ValidateStreamPointer( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_IsStreamStopped called:\n" ); + PaUtil_DebugPrint("\tPaStream* stream: 0x%p\n", stream ); +#endif + + if( result == paNoError ) + result = PA_STREAM_INTERFACE(stream)->IsStopped( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_IsStreamStopped returned:\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return result; +} + + +PaError Pa_IsStreamActive( PaStream *stream ) +{ + PaError result = PaUtil_ValidateStreamPointer( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_IsStreamActive called:\n" ); + PaUtil_DebugPrint("\tPaStream* stream: 0x%p\n", stream ); +#endif + + if( result == paNoError ) + result = PA_STREAM_INTERFACE(stream)->IsActive( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_IsStreamActive returned:\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return result; +} + + +const PaStreamInfo* Pa_GetStreamInfo( PaStream *stream ) +{ + PaError error = PaUtil_ValidateStreamPointer( stream ); + const PaStreamInfo *result; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamInfo called:\n" ); + PaUtil_DebugPrint("\tPaStream* stream: 0x%p\n", stream ); +#endif + + if( error != paNoError ) + { + result = 0; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamInfo returned:\n" ); + PaUtil_DebugPrint("\tconst PaStreamInfo*: 0 [PaError error:%d ( %s )]\n\n", result, error, Pa_GetErrorText( error ) ); +#endif + + } + else + { + result = &PA_STREAM_REP( stream )->streamInfo; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamInfo returned:\n" ); + PaUtil_DebugPrint("\tconst PaStreamInfo*: 0x%p:\n", result ); + PaUtil_DebugPrint("\t{" ); + + PaUtil_DebugPrint("\t\tint structVersion: %d\n", result->structVersion ); + PaUtil_DebugPrint("\t\tPaTime inputLatency: %f\n", result->inputLatency ); + PaUtil_DebugPrint("\t\tPaTime outputLatency: %f\n", result->outputLatency ); + PaUtil_DebugPrint("\t\tdouble sampleRate: %f\n", result->sampleRate ); + PaUtil_DebugPrint("\t}\n\n" ); +#endif + + } + + return result; +} + + +PaTime Pa_GetStreamTime( PaStream *stream ) +{ + PaError error = PaUtil_ValidateStreamPointer( stream ); + PaTime result; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamTime called:\n" ); + PaUtil_DebugPrint("\tPaStream* stream: 0x%p\n", stream ); +#endif + + if( error != paNoError ) + { + result = 0; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamTime returned:\n" ); + PaUtil_DebugPrint("\tPaTime: 0 [PaError error:%d ( %s )]\n\n", result, error, Pa_GetErrorText( error ) ); +#endif + + } + else + { + result = PA_STREAM_INTERFACE(stream)->GetTime( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamTime returned:\n" ); + PaUtil_DebugPrint("\tPaTime: %g\n\n", result ); +#endif + + } + + return result; +} + + +double Pa_GetStreamCpuLoad( PaStream* stream ) +{ + PaError error = PaUtil_ValidateStreamPointer( stream ); + double result; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamCpuLoad called:\n" ); + PaUtil_DebugPrint("\tPaStream* stream: 0x%p\n", stream ); +#endif + + if( error != paNoError ) + { + + result = 0.0; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamCpuLoad returned:\n" ); + PaUtil_DebugPrint("\tdouble: 0.0 [PaError error: %d ( %s )]\n\n", error, Pa_GetErrorText( error ) ); +#endif + + } + else + { + result = PA_STREAM_INTERFACE(stream)->GetCpuLoad( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamCpuLoad returned:\n" ); + PaUtil_DebugPrint("\tdouble: %g\n\n", result ); +#endif + + } + + return result; +} + + +PaError Pa_ReadStream( PaStream* stream, + void *buffer, + unsigned long frames ) +{ + PaError result = PaUtil_ValidateStreamPointer( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_ReadStream called:\n" ); + PaUtil_DebugPrint("\tPaStream* stream: 0x%p\n", stream ); +#endif + + if( result == paNoError ) + { + if( frames == 0 ) + { + result = paInternalError; /** @todo should return a different error code */ + } + else if( buffer == 0 ) + { + result = paInternalError; /** @todo should return a different error code */ + } + else + { + result = PA_STREAM_INTERFACE(stream)->IsStopped( stream ); + if( result == 0 ) + { + result = PA_STREAM_INTERFACE(stream)->Read( stream, buffer, frames ); + } + else if( result == 1 ) + { + result = paStreamIsStopped; + } + } + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_ReadStream returned:\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return result; +} + + +PaError Pa_WriteStream( PaStream* stream, + const void *buffer, + unsigned long frames ) +{ + PaError result = PaUtil_ValidateStreamPointer( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_WriteStream called:\n" ); + PaUtil_DebugPrint("\tPaStream* stream: 0x%p\n", stream ); +#endif + + if( result == paNoError ) + { + if( frames == 0 ) + { + result = paInternalError; /** @todo should return a different error code */ + } + else if( buffer == 0 ) + { + result = paInternalError; /** @todo should return a different error code */ + } + else + { + result = PA_STREAM_INTERFACE(stream)->IsStopped( stream ); + if( result == 0 ) + { + result = PA_STREAM_INTERFACE(stream)->Write( stream, buffer, frames ); + } + else if( result == 1 ) + { + result = paStreamIsStopped; + } + } + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_WriteStream returned:\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return result; +} + +signed long Pa_GetStreamReadAvailable( PaStream* stream ) +{ + PaError error = PaUtil_ValidateStreamPointer( stream ); + signed long result; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamReadAvailable called:\n" ); + PaUtil_DebugPrint("\tPaStream* stream: 0x%p\n", stream ); +#endif + + if( error != paNoError ) + { + result = 0; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamReadAvailable returned:\n" ); + PaUtil_DebugPrint("\tunsigned long: 0 [ PaError error: %d ( %s ) ]\n\n", error, Pa_GetErrorText( error ) ); +#endif + + } + else + { + result = PA_STREAM_INTERFACE(stream)->GetReadAvailable( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamReadAvailable returned:\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + } + + return result; +} + + +signed long Pa_GetStreamWriteAvailable( PaStream* stream ) +{ + PaError error = PaUtil_ValidateStreamPointer( stream ); + signed long result; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamWriteAvailable called:\n" ); + PaUtil_DebugPrint("\tPaStream* stream: 0x%p\n", stream ); +#endif + + if( error != paNoError ) + { + result = 0; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamWriteAvailable returned:\n" ); + PaUtil_DebugPrint("\tunsigned long: 0 [ PaError error: %d ( %s ) ]\n\n", error, Pa_GetErrorText( error ) ); +#endif + + } + else + { + result = PA_STREAM_INTERFACE(stream)->GetWriteAvailable( stream ); + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetStreamWriteAvailable returned:\n" ); + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + } + + return result; +} + + +PaError Pa_GetSampleSize( PaSampleFormat format ) +{ + int result; + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetSampleSize called:\n" ); + PaUtil_DebugPrint("\tPaSampleFormat format: %d\n", format ); +#endif + + switch( format & ~paNonInterleaved ) + { + + case paUInt8: + case paInt8: + result = 1; + break; + + case paInt16: + result = 2; + break; + + case paInt24: + result = 3; + break; + + case paFloat32: + case paInt32: + result = 4; + break; + + default: + result = paSampleFormatNotSupported; + break; + } + +#ifdef PA_LOG_API_CALLS + PaUtil_DebugPrint("Pa_GetSampleSize returned:\n" ); + if( result > 0 ) + PaUtil_DebugPrint("\tint: %d\n\n", result ); + else + PaUtil_DebugPrint("\tPaError: %d ( %s )\n\n", result, Pa_GetErrorText( result ) ); +#endif + + return (PaError) result; +} + diff --git a/src/audio/portaudio/pa_common/pa_hostapi.h b/src/audio/portaudio/pa_common/pa_hostapi.h new file mode 100644 index 0000000000000000000000000000000000000000..fec4e4871bbd7bce7a46237a139fee10644bdc8b --- /dev/null +++ b/src/audio/portaudio/pa_common/pa_hostapi.h @@ -0,0 +1,244 @@ +#ifndef PA_HOSTAPI_H +#define PA_HOSTAPI_H +/* + * $Id$ + * 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 + * + * 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. + * + * 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. + * + * 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. + */ + +/** @file + @brief Interface used by pa_front to virtualize functions which operate on + host APIs. +*/ + + +#include "portaudio.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +/** **FOR THE USE OF pa_front.c ONLY** + Do NOT use fields in this structure, they my change at any time. + Use functions defined in pa_util.h if you think you need functionality + which can be derived from here. +*/ +typedef struct PaUtilPrivatePaFrontHostApiInfo { + + + unsigned long baseDeviceIndex; +}PaUtilPrivatePaFrontHostApiInfo; + + +/** The common header for all data structures whose pointers are passed through + the hostApiSpecificStreamInfo field of the PaStreamParameters structure. + Note that in order to keep the public PortAudio interface clean, this structure + is not used explicitly when declaring hostApiSpecificStreamInfo data structures. + However, some code in pa_front depends on the first 3 members being equivalent + with this structure. + @see PaStreamParameters +*/ +typedef struct PaUtilHostApiSpecificStreamInfoHeader +{ + unsigned long size; /**< size of whole structure including this header */ + PaHostApiTypeId hostApiType; /**< host API for which this data is intended */ + unsigned long version; /**< structure version */ +} PaUtilHostApiSpecificStreamInfoHeader; + + + +/** A structure representing the interface to a host API. Contains both + concrete data and pointers to functions which implement the interface. +*/ +typedef struct PaUtilHostApiRepresentation { + PaUtilPrivatePaFrontHostApiInfo privatePaFrontInfo; + + /** The host api implementation should populate the info field. In the + case of info.defaultInputDevice and info.defaultOutputDevice the + values stored should be 0 based indices within the host api's own + device index range (0 to deviceCount). These values will be converted + to global device indices by pa_front after PaUtilHostApiInitializer() + returns. + */ + PaHostApiInfo info; + + PaDeviceInfo** deviceInfos; + + /** + (*Terminate)() is guaranteed to be called with a valid <hostApi> + parameter, which was previously returned from the same implementation's + initializer. + */ + void (*Terminate)( struct PaUtilHostApiRepresentation *hostApi ); + + /** + The inputParameters and outputParameters pointers should not be saved + as they will not remain valid after OpenStream is called. + + + The following guarantees are made about parameters to (*OpenStream)(): + + [NOTE: the following list up to *END PA FRONT VALIDATIONS* should be + kept in sync with the one for ValidateOpenStreamParameters and + Pa_OpenStream in pa_front.c] + + PaHostApiRepresentation *hostApi + - is valid for this implementation + + PaStream** stream + - is non-null + + - at least one of inputParameters & outputParmeters is valid (not NULL) + + - if inputParameters & outputParmeters are both valid, that + inputParameters->device & outputParmeters->device both use the same host api + + PaDeviceIndex inputParameters->device + - is within range (0 to Pa_CountDevices-1) Or: + - is paUseHostApiSpecificDeviceSpecification and + inputParameters->hostApiSpecificStreamInfo is non-NULL and refers + to a valid host api + + int inputParameters->numChannels + - if inputParameters->device is not paUseHostApiSpecificDeviceSpecification, numInputChannels is > 0 + - upper bound is NOT validated against device capabilities + + PaSampleFormat inputParameters->sampleFormat + - is one of the sample formats defined in portaudio.h + + void *inputParameters->hostApiSpecificStreamInfo + - if supplied its hostApi field matches the input device's host Api + + PaDeviceIndex outputParmeters->device + - is within range (0 to Pa_CountDevices-1) + + int outputParmeters->numChannels + - if inputDevice is valid, numInputChannels is > 0 + - upper bound is NOT validated against device capabilities + + PaSampleFormat outputParmeters->sampleFormat + - is one of the sample formats defined in portaudio.h + + void *outputParmeters->hostApiSpecificStreamInfo + - if supplied its hostApi field matches the output device's host Api + + double sampleRate + - is not an 'absurd' rate (less than 1000. or greater than 200000.) + - sampleRate is NOT validated against device capabilities + + PaStreamFlags streamFlags + - unused platform neutral flags are zero + - paNeverDropInput is only used for full-duplex callback streams + with variable buffer size (paFramesPerBufferUnspecified) + + [*END PA FRONT VALIDATIONS*] + + + The following validations MUST be performed by (*OpenStream)(): + + - check that input device can support numInputChannels + + - check that input device can support inputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + + - if inputStreamInfo is supplied, validate its contents, + or return an error if no inputStreamInfo is expected + + - check that output device can support numOutputChannels + + - check that output device can support outputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + + - if outputStreamInfo is supplied, validate its contents, + or return an error if no outputStreamInfo is expected + + - if a full duplex stream is requested, check that the combination + of input and output parameters is supported + + - check that the device supports sampleRate + + - alter sampleRate to a close allowable rate if necessary + + - validate inputLatency and outputLatency + + - validate any platform specific flags, if flags are supplied they + must be valid. + */ + PaError (*OpenStream)( struct PaUtilHostApiRepresentation *hostApi, + PaStream** stream, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerCallback, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); + + + PaError (*IsFormatSupported)( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); +} PaUtilHostApiRepresentation; + + +/** Prototype for the initialization function which must be implemented by every + host API. + + @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 + when the client calls Pa_Initialize(). + + There is a platform specific file which defines paHostApiInitializers for that + platform, pa_win/pa_win_hostapis.c contains the Win32 definitions for example. +*/ +extern PaUtilHostApiInitializer *paHostApiInitializers[]; + + +/** The index of the default host API in the paHostApiInitializers array. + + There is a platform specific file which defines paDefaultHostApiIndex for that + platform, see pa_win/pa_win_hostapis.c for example. +*/ +extern int paDefaultHostApiIndex; + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PA_HOSTAPI_H */ diff --git a/src/audio/portaudio/pa_common/pa_process.c b/src/audio/portaudio/pa_common/pa_process.c new file mode 100644 index 0000000000000000000000000000000000000000..b58b5d791dcc27a1c719389d994e0cd634bb25e7 --- /dev/null +++ b/src/audio/portaudio/pa_common/pa_process.c @@ -0,0 +1,1756 @@ +/* + * $Id$ + * Portable Audio I/O Library + * streamCallback <-> host buffer processing adapter + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, 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. + * + * 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. + * + * 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. + */ + +/** @file + @brief Buffer Processor implementation. + + The code in this file is not optimised yet - although it's not clear that + it needs to be. there may appear to be redundancies + that could be factored into common functions, but the redundanceis are left + intentionally as each appearance may have different optimisation possibilities. + + The optimisations which are planned involve only converting data in-place + where possible, rather than copying to the temp buffer(s). + + Note that in the extreme case of being able to convert in-place, and there + being no conversion necessary there should be some code which short-circuits + the operation. + + @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. + + @todo provide support for priming the buffers with data from the callback. + The client interface is now implemented through PaUtil_SetNoInput() + which sets bp->hostInputChannels[0][0].data to zero. However this is + currently only implemented in NonAdaptingProcess(). It shouldn't be + needed for AdaptingInputOnlyProcess() (no priming should ever be + requested for AdaptingInputOnlyProcess()). + Not sure if additional work should be required to make it work with + AdaptingOutputOnlyProcess, but it definitely is required for + AdaptingProcess. + + @todo implement PaUtil_SetNoOutput for AdaptingProcess + + @todo don't allocate temp buffers for blocking streams unless they are + needed. At the moment they are needed, but perhaps for host APIs + where the implementation passes a buffer to the host they could be + used. +*/ + + +#include <assert.h> +#include <string.h> /* memset() */ + +#include "pa_process.h" +#include "pa_util.h" + + +#define PA_FRAMES_PER_TEMP_BUFFER_WHEN_HOST_BUFFER_SIZE_IS_UNKNOWN_ 1024 + +#define PA_MIN_( a, b ) ( ((a)<(b)) ? (a) : (b) ) + + +/* greatest common divisor - PGCD in French */ +static unsigned long GCD( unsigned long a, unsigned long b ) +{ + return (b==0) ? a : GCD( b, a%b); +} + +/* least common multiple - PPCM in French */ +static unsigned long LCM( unsigned long a, unsigned long b ) +{ + return (a*b) / GCD(a,b); +} + +#define PA_MAX_( a, b ) (((a) > (b)) ? (a) : (b)) + +static unsigned long CalculateFrameShift( unsigned long M, unsigned long N ) +{ + unsigned long result = 0; + unsigned long i; + unsigned long lcm; + + assert( M > 0 ); + assert( N > 0 ); + + lcm = LCM( M, N ); + for( i = M; i < lcm; i += M ) + result = PA_MAX_( result, i % N ); + + return result; +} + + +PaError PaUtil_InitializeBufferProcessor( PaUtilBufferProcessor* bp, + int inputChannelCount, PaSampleFormat userInputSampleFormat, + PaSampleFormat hostInputSampleFormat, + int outputChannelCount, PaSampleFormat userOutputSampleFormat, + PaSampleFormat hostOutputSampleFormat, + double sampleRate, + PaStreamFlags streamFlags, + unsigned long framesPerUserBuffer, + unsigned long framesPerHostBuffer, + PaUtilHostBufferSizeMode hostBufferSizeMode, + PaStreamCallback *streamCallback, void *userData ) +{ + PaError result = paNoError; + PaError bytesPerSample; + unsigned long tempInputBufferSize, tempOutputBufferSize; + + /* initialize buffer ptrs to zero so they can be freed if necessary in error */ + bp->tempInputBuffer = 0; + bp->tempInputBufferPtrs = 0; + bp->tempOutputBuffer = 0; + bp->tempOutputBufferPtrs = 0; + + bp->framesPerUserBuffer = framesPerUserBuffer; + bp->framesPerHostBuffer = framesPerHostBuffer; + + bp->inputChannelCount = inputChannelCount; + bp->outputChannelCount = outputChannelCount; + + bp->hostBufferSizeMode = hostBufferSizeMode; + + bp->hostInputChannels[0] = bp->hostInputChannels[1] = 0; + bp->hostOutputChannels[0] = bp->hostOutputChannels[1] = 0; + + if( framesPerUserBuffer == 0 ) /* streamCallback will accept any buffer size */ + { + bp->useNonAdaptingProcess = 1; + bp->initialFramesInTempInputBuffer = 0; + bp->initialFramesInTempOutputBuffer = 0; + + if( hostBufferSizeMode == paUtilFixedHostBufferSize + || hostBufferSizeMode == paUtilBoundedHostBufferSize ) + { + bp->framesPerTempBuffer = framesPerHostBuffer; + } + else /* unknown host buffer size */ + { + bp->framesPerTempBuffer = PA_FRAMES_PER_TEMP_BUFFER_WHEN_HOST_BUFFER_SIZE_IS_UNKNOWN_; + } + } + else + { + bp->framesPerTempBuffer = framesPerUserBuffer; + + if( hostBufferSizeMode == paUtilFixedHostBufferSize + && framesPerHostBuffer % framesPerUserBuffer == 0 ) + { + bp->useNonAdaptingProcess = 1; + bp->initialFramesInTempInputBuffer = 0; + bp->initialFramesInTempOutputBuffer = 0; + } + else + { + bp->useNonAdaptingProcess = 0; + + if( inputChannelCount > 0 && outputChannelCount > 0 ) + { + /* full duplex */ + if( hostBufferSizeMode == paUtilFixedHostBufferSize ) + { + unsigned long frameShift = + CalculateFrameShift( framesPerHostBuffer, framesPerUserBuffer ); + + if( framesPerUserBuffer > framesPerHostBuffer ) + { + bp->initialFramesInTempInputBuffer = frameShift; + bp->initialFramesInTempOutputBuffer = 0; + } + else + { + bp->initialFramesInTempInputBuffer = 0; + bp->initialFramesInTempOutputBuffer = frameShift; + } + } + else /* variable host buffer size, add framesPerUserBuffer latency */ + { + bp->initialFramesInTempInputBuffer = 0; + bp->initialFramesInTempOutputBuffer = framesPerUserBuffer; + } + } + else + { + /* half duplex */ + bp->initialFramesInTempInputBuffer = 0; + bp->initialFramesInTempOutputBuffer = 0; + } + } + } + + + bp->framesInTempInputBuffer = bp->initialFramesInTempInputBuffer; + bp->framesInTempOutputBuffer = bp->initialFramesInTempOutputBuffer; + + + if( inputChannelCount > 0 ) + { + bytesPerSample = Pa_GetSampleSize( hostInputSampleFormat ); + if( bytesPerSample > 0 ) + { + bp->bytesPerHostInputSample = bytesPerSample; + } + else + { + result = bytesPerSample; + goto error; + } + + bytesPerSample = Pa_GetSampleSize( userInputSampleFormat ); + if( bytesPerSample > 0 ) + { + bp->bytesPerUserInputSample = bytesPerSample; + } + else + { + result = bytesPerSample; + goto error; + } + + bp->inputConverter = + PaUtil_SelectConverter( hostInputSampleFormat, userInputSampleFormat, streamFlags ); + + bp->inputZeroer = PaUtil_SelectZeroer( hostInputSampleFormat ); + + bp->userInputIsInterleaved = (userInputSampleFormat & paNonInterleaved)?0:1; + + + tempInputBufferSize = + bp->framesPerTempBuffer * bp->bytesPerUserInputSample * inputChannelCount; + + bp->tempInputBuffer = PaUtil_AllocateMemory( tempInputBufferSize ); + if( bp->tempInputBuffer == 0 ) + { + result = paInsufficientMemory; + goto error; + } + + if( bp->framesInTempInputBuffer > 0 ) + memset( bp->tempInputBuffer, 0, tempInputBufferSize ); + + if( userInputSampleFormat & paNonInterleaved ) + { + bp->tempInputBufferPtrs = + (void **)PaUtil_AllocateMemory( sizeof(void*)*inputChannelCount ); + if( bp->tempInputBufferPtrs == 0 ) + { + result = paInsufficientMemory; + goto error; + } + } + + bp->hostInputChannels[0] = (PaUtilChannelDescriptor*) + PaUtil_AllocateMemory( sizeof(PaUtilChannelDescriptor) * inputChannelCount * 2); + if( bp->hostInputChannels[0] == 0 ) + { + result = paInsufficientMemory; + goto error; + } + + bp->hostInputChannels[1] = &bp->hostInputChannels[0][inputChannelCount]; + } + + if( outputChannelCount > 0 ) + { + bytesPerSample = Pa_GetSampleSize( hostOutputSampleFormat ); + if( bytesPerSample > 0 ) + { + bp->bytesPerHostOutputSample = bytesPerSample; + } + else + { + result = bytesPerSample; + goto error; + } + + bytesPerSample = Pa_GetSampleSize( userOutputSampleFormat ); + if( bytesPerSample > 0 ) + { + bp->bytesPerUserOutputSample = bytesPerSample; + } + else + { + result = bytesPerSample; + goto error; + } + + bp->outputConverter = + PaUtil_SelectConverter( userOutputSampleFormat, hostOutputSampleFormat, streamFlags ); + + bp->outputZeroer = PaUtil_SelectZeroer( hostOutputSampleFormat ); + + bp->userOutputIsInterleaved = (userOutputSampleFormat & paNonInterleaved)?0:1; + + tempOutputBufferSize = + bp->framesPerTempBuffer * bp->bytesPerUserOutputSample * outputChannelCount; + + bp->tempOutputBuffer = PaUtil_AllocateMemory( tempOutputBufferSize ); + if( bp->tempOutputBuffer == 0 ) + { + result = paInsufficientMemory; + goto error; + } + + if( bp->framesInTempOutputBuffer > 0 ) + memset( bp->tempOutputBuffer, 0, tempOutputBufferSize ); + + if( userOutputSampleFormat & paNonInterleaved ) + { + bp->tempOutputBufferPtrs = + (void **)PaUtil_AllocateMemory( sizeof(void*)*outputChannelCount ); + if( bp->tempOutputBufferPtrs == 0 ) + { + result = paInsufficientMemory; + goto error; + } + } + + bp->hostOutputChannels[0] = (PaUtilChannelDescriptor*) + PaUtil_AllocateMemory( sizeof(PaUtilChannelDescriptor)*outputChannelCount * 2 ); + if( bp->hostOutputChannels[0] == 0 ) + { + result = paInsufficientMemory; + goto error; + } + + bp->hostOutputChannels[1] = &bp->hostOutputChannels[0][outputChannelCount]; + } + + PaUtil_InitializeTriangularDitherState( &bp->ditherGenerator ); + + bp->samplePeriod = 1. / sampleRate; + + bp->streamCallback = streamCallback; + bp->userData = userData; + + return result; + +error: + if( bp->tempInputBuffer ) + PaUtil_FreeMemory( bp->tempInputBuffer ); + + if( bp->tempInputBufferPtrs ) + PaUtil_FreeMemory( bp->tempInputBufferPtrs ); + + if( bp->hostInputChannels[0] ) + PaUtil_FreeMemory( bp->hostInputChannels[0] ); + + if( bp->tempOutputBuffer ) + PaUtil_FreeMemory( bp->tempOutputBuffer ); + + if( bp->tempOutputBufferPtrs ) + PaUtil_FreeMemory( bp->tempOutputBufferPtrs ); + + if( bp->hostOutputChannels[0] ) + PaUtil_FreeMemory( bp->hostOutputChannels[0] ); + + return result; +} + + +void PaUtil_TerminateBufferProcessor( PaUtilBufferProcessor* bp ) +{ + if( bp->tempInputBuffer ) + PaUtil_FreeMemory( bp->tempInputBuffer ); + + if( bp->tempInputBufferPtrs ) + PaUtil_FreeMemory( bp->tempInputBufferPtrs ); + + if( bp->hostInputChannels[0] ) + PaUtil_FreeMemory( bp->hostInputChannels[0] ); + + if( bp->tempOutputBuffer ) + PaUtil_FreeMemory( bp->tempOutputBuffer ); + + if( bp->tempOutputBufferPtrs ) + PaUtil_FreeMemory( bp->tempOutputBufferPtrs ); + + if( bp->hostOutputChannels[0] ) + PaUtil_FreeMemory( bp->hostOutputChannels[0] ); +} + + +void PaUtil_ResetBufferProcessor( PaUtilBufferProcessor* bp ) +{ + unsigned long tempInputBufferSize, tempOutputBufferSize; + + bp->framesInTempInputBuffer = bp->initialFramesInTempInputBuffer; + bp->framesInTempOutputBuffer = bp->initialFramesInTempOutputBuffer; + + if( bp->framesInTempInputBuffer > 0 ) + { + tempInputBufferSize = + bp->framesPerTempBuffer * bp->bytesPerUserInputSample * bp->inputChannelCount; + memset( bp->tempInputBuffer, 0, tempInputBufferSize ); + } + + if( bp->framesInTempOutputBuffer > 0 ) + { + tempOutputBufferSize = + bp->framesPerTempBuffer * bp->bytesPerUserOutputSample * bp->outputChannelCount; + memset( bp->tempOutputBuffer, 0, tempOutputBufferSize ); + } +} + + +unsigned long PaUtil_GetBufferProcessorInputLatency( PaUtilBufferProcessor* bp ) +{ + return bp->initialFramesInTempInputBuffer; +} + + +unsigned long PaUtil_GetBufferProcessorOutputLatency( PaUtilBufferProcessor* bp ) +{ + return bp->initialFramesInTempOutputBuffer; +} + + +void PaUtil_SetInputFrameCount( PaUtilBufferProcessor* bp, + unsigned long frameCount ) +{ + if( frameCount == 0 ) + bp->hostInputFrameCount[0] = bp->framesPerHostBuffer; + else + bp->hostInputFrameCount[0] = frameCount; +} + + +void PaUtil_SetNoInput( PaUtilBufferProcessor* bp ) +{ + assert( bp->inputChannelCount > 0 ); + + bp->hostInputChannels[0][0].data = 0; +} + + +void PaUtil_SetInputChannel( PaUtilBufferProcessor* bp, + unsigned int channel, void *data, unsigned int stride ) +{ + assert( channel < bp->inputChannelCount ); + + bp->hostInputChannels[0][channel].data = data; + bp->hostInputChannels[0][channel].stride = stride; +} + + +void PaUtil_SetInterleavedInputChannels( PaUtilBufferProcessor* bp, + unsigned int firstChannel, void *data, unsigned int channelCount ) +{ + unsigned int i; + unsigned int channel = firstChannel; + unsigned char *p = (unsigned char*)data; + + if( channelCount == 0 ) + channelCount = bp->inputChannelCount; + + assert( firstChannel < bp->inputChannelCount ); + assert( firstChannel + channelCount <= bp->inputChannelCount ); + + for( i=0; i< channelCount; ++i ) + { + bp->hostInputChannels[0][channel+i].data = p; + p += bp->bytesPerHostInputSample; + bp->hostInputChannels[0][channel+i].stride = channelCount; + } +} + + +void PaUtil_SetNonInterleavedInputChannel( PaUtilBufferProcessor* bp, + unsigned int channel, void *data ) +{ + assert( channel < bp->inputChannelCount ); + + bp->hostInputChannels[0][channel].data = data; + bp->hostInputChannels[0][channel].stride = 1; +} + + +void PaUtil_Set2ndInputFrameCount( PaUtilBufferProcessor* bp, + unsigned long frameCount ) +{ + bp->hostInputFrameCount[1] = frameCount; +} + + +void PaUtil_Set2ndInputChannel( PaUtilBufferProcessor* bp, + unsigned int channel, void *data, unsigned int stride ) +{ + assert( channel < bp->inputChannelCount ); + + bp->hostInputChannels[1][channel].data = data; + bp->hostInputChannels[1][channel].stride = stride; +} + + +void PaUtil_Set2ndInterleavedInputChannels( PaUtilBufferProcessor* bp, + unsigned int firstChannel, void *data, unsigned int channelCount ) +{ + unsigned int i; + unsigned int channel = firstChannel; + unsigned char *p = (unsigned char*)data; + + if( channelCount == 0 ) + channelCount = bp->inputChannelCount; + + assert( firstChannel < bp->inputChannelCount ); + assert( firstChannel + channelCount <= bp->inputChannelCount ); + + for( i=0; i< channelCount; ++i ) + { + bp->hostInputChannels[1][channel+i].data = p; + p += bp->bytesPerHostInputSample; + bp->hostInputChannels[1][channel+i].stride = channelCount; + } +} + + +void PaUtil_Set2ndNonInterleavedInputChannel( PaUtilBufferProcessor* bp, + unsigned int channel, void *data ) +{ + assert( channel < bp->inputChannelCount ); + + bp->hostInputChannels[1][channel].data = data; + bp->hostInputChannels[1][channel].stride = 1; +} + + +void PaUtil_SetOutputFrameCount( PaUtilBufferProcessor* bp, + unsigned long frameCount ) +{ + if( frameCount == 0 ) + bp->hostOutputFrameCount[0] = bp->framesPerHostBuffer; + else + bp->hostOutputFrameCount[0] = frameCount; +} + + +void PaUtil_SetNoOutput( PaUtilBufferProcessor* bp ) +{ + assert( bp->outputChannelCount > 0 ); + + bp->hostOutputChannels[0][0].data = 0; +} + + +void PaUtil_SetOutputChannel( PaUtilBufferProcessor* bp, + unsigned int channel, void *data, unsigned int stride ) +{ + assert( channel < bp->outputChannelCount ); + + bp->hostOutputChannels[0][channel].data = data; + bp->hostOutputChannels[0][channel].stride = stride; +} + + +void PaUtil_SetInterleavedOutputChannels( PaUtilBufferProcessor* bp, + unsigned int firstChannel, void *data, unsigned int channelCount ) +{ + unsigned int i; + unsigned int channel = firstChannel; + unsigned char *p = (unsigned char*)data; + + if( channelCount == 0 ) + channelCount = bp->outputChannelCount; + + assert( firstChannel < bp->outputChannelCount ); + assert( firstChannel + channelCount <= bp->outputChannelCount ); + + for( i=0; i< channelCount; ++i ) + { + bp->hostOutputChannels[0][channel+i].data = p; + p += bp->bytesPerHostOutputSample; + bp->hostOutputChannels[0][channel+i].stride = channelCount; + } +} + + +void PaUtil_SetNonInterleavedOutputChannel( PaUtilBufferProcessor* bp, + unsigned int channel, void *data ) +{ + assert( channel < bp->outputChannelCount ); + + bp->hostOutputChannels[0][channel].data = data; + bp->hostOutputChannels[0][channel].stride = 1; +} + + +void PaUtil_Set2ndOutputFrameCount( PaUtilBufferProcessor* bp, + unsigned long frameCount ) +{ + bp->hostOutputFrameCount[1] = frameCount; +} + + +void PaUtil_Set2ndOutputChannel( PaUtilBufferProcessor* bp, + unsigned int channel, void *data, unsigned int stride ) +{ + assert( channel < bp->outputChannelCount ); + + bp->hostOutputChannels[1][channel].data = data; + bp->hostOutputChannels[1][channel].stride = stride; +} + + +void PaUtil_Set2ndInterleavedOutputChannels( PaUtilBufferProcessor* bp, + unsigned int firstChannel, void *data, unsigned int channelCount ) +{ + unsigned int i; + unsigned int channel = firstChannel; + unsigned char *p = (unsigned char*)data; + + if( channelCount == 0 ) + channelCount = bp->outputChannelCount; + + assert( firstChannel < bp->outputChannelCount ); + assert( firstChannel + channelCount <= bp->outputChannelCount ); + + for( i=0; i< channelCount; ++i ) + { + bp->hostOutputChannels[1][channel+i].data = p; + p += bp->bytesPerHostOutputSample; + bp->hostOutputChannels[1][channel+i].stride = channelCount; + } +} + + +void PaUtil_Set2ndNonInterleavedOutputChannel( PaUtilBufferProcessor* bp, + unsigned int channel, void *data ) +{ + assert( channel < bp->outputChannelCount ); + + bp->hostOutputChannels[1][channel].data = data; + bp->hostOutputChannels[1][channel].stride = 1; +} + + +void PaUtil_BeginBufferProcessing( PaUtilBufferProcessor* bp, + PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags callbackStatusFlags ) +{ + bp->timeInfo = timeInfo; + + /* the first streamCallback will be called to process samples which are + currently in the input buffer before the ones starting at the timeInfo time */ + + bp->timeInfo->inputBufferAdcTime -= bp->framesInTempInputBuffer * bp->samplePeriod; + + bp->timeInfo->currentTime = 0; /** FIXME: @todo time info currentTime not implemented */ + + /* the first streamCallback will be called to generate samples which will be + outputted after the frames currently in the output buffer have been + outputted. */ + bp->timeInfo->outputBufferDacTime += bp->framesInTempOutputBuffer * bp->samplePeriod; + + bp->callbackStatusFlags = callbackStatusFlags; + + bp->hostInputFrameCount[1] = 0; + bp->hostOutputFrameCount[1] = 0; +} + + +/* + NonAdaptingProcess() is a simple buffer copying adaptor that can handle + both full and half duplex copies. It processes framesToProcess frames, + broken into blocks bp->framesPerTempBuffer long. + This routine can be used when the streamCallback doesn't care what length + the buffers are, or when framesToProcess is an integer multiple of + bp->framesPerTempBuffer, in which case streamCallback will always be called + with bp->framesPerTempBuffer samples. +*/ +static unsigned long NonAdaptingProcess( PaUtilBufferProcessor *bp, + int *streamCallbackResult, + PaUtilChannelDescriptor *hostInputChannels, + PaUtilChannelDescriptor *hostOutputChannels, + unsigned long framesToProcess ) +{ + void *userInput, *userOutput; + unsigned char *srcBytePtr, *destBytePtr; + unsigned int srcSampleStrideSamples; /* stride from one sample to the next within a channel, in samples */ + unsigned int srcChannelStrideBytes; /* stride from one channel to the next, in bytes */ + unsigned int destSampleStrideSamples; /* stride from one sample to the next within a channel, in samples */ + unsigned int destChannelStrideBytes; /* stride from one channel to the next, in bytes */ + unsigned int i; + unsigned long frameCount; + unsigned long framesToGo = framesToProcess; + unsigned long framesProcessed = 0; + + + if( *streamCallbackResult == paContinue ) + { + do + { + frameCount = PA_MIN_( bp->framesPerTempBuffer, framesToGo ); + + /* configure user input buffer and convert input data (host -> user) */ + if( bp->inputChannelCount == 0 ) + { + /* no input */ + userInput = 0; + } + else /* there are input channels */ + { + /* + could use more elaborate logic here and sometimes process + buffers in-place. + */ + + destBytePtr = (unsigned char *)bp->tempInputBuffer; + + if( bp->userInputIsInterleaved ) + { + destSampleStrideSamples = bp->inputChannelCount; + destChannelStrideBytes = bp->bytesPerUserInputSample; + userInput = bp->tempInputBuffer; + } + else /* user input is not interleaved */ + { + destSampleStrideSamples = 1; + destChannelStrideBytes = frameCount * bp->bytesPerUserInputSample; + + /* setup non-interleaved ptrs */ + for( i=0; i<bp->inputChannelCount; ++i ) + { + bp->tempInputBufferPtrs[i] = ((unsigned char*)bp->tempInputBuffer) + + i * bp->bytesPerUserInputSample * frameCount; + } + + userInput = bp->tempInputBufferPtrs; + } + + if( !bp->hostInputChannels[0][0].data ) + { + /* no input was supplied (see PaUtil_SetNoInput), so + zero the input buffer */ + + for( i=0; i<bp->inputChannelCount; ++i ) + { + bp->inputZeroer( destBytePtr, destSampleStrideSamples, frameCount ); + destBytePtr += destChannelStrideBytes; /* skip to next destination channel */ + } + } + else + { + for( i=0; i<bp->inputChannelCount; ++i ) + { + bp->inputConverter( destBytePtr, destSampleStrideSamples, + hostInputChannels[i].data, + hostInputChannels[i].stride, + frameCount, &bp->ditherGenerator ); + + destBytePtr += destChannelStrideBytes; /* skip to next destination channel */ + + /* advance src ptr for next iteration */ + hostInputChannels[i].data = ((unsigned char*)hostInputChannels[i].data) + + frameCount * hostInputChannels[i].stride * bp->bytesPerHostInputSample; + } + } + } + + /* configure user output buffer */ + if( bp->outputChannelCount == 0 ) + { + /* no output */ + userOutput = 0; + } + else /* there are output channels */ + { + if( bp->userOutputIsInterleaved ) + { + userOutput = bp->tempOutputBuffer; + } + else /* user output is not interleaved */ + { + for( i = 0; i < bp->outputChannelCount; ++i ) + { + bp->tempOutputBufferPtrs[i] = ((unsigned char*)bp->tempOutputBuffer) + + i * bp->bytesPerUserOutputSample * frameCount; + } + + userOutput = bp->tempOutputBufferPtrs; + } + } + + *streamCallbackResult = bp->streamCallback( userInput, userOutput, + frameCount, bp->timeInfo, bp->callbackStatusFlags, bp->userData ); + + if( *streamCallbackResult == paAbort ) + { + /* callback returned paAbort, don't advance framesProcessed + and framesToGo, they will be handled below */ + } + else + { + bp->timeInfo->inputBufferAdcTime += frameCount * bp->samplePeriod; + bp->timeInfo->outputBufferDacTime += frameCount * bp->samplePeriod; + + /* convert output data (user -> host) */ + + if( bp->outputChannelCount != 0 && bp->hostOutputChannels[0][0].data ) + { + /* + could use more elaborate logic here and sometimes process + buffers in-place. + */ + + srcBytePtr = (unsigned char *)bp->tempOutputBuffer; + + if( bp->userOutputIsInterleaved ) + { + srcSampleStrideSamples = bp->outputChannelCount; + srcChannelStrideBytes = bp->bytesPerUserOutputSample; + } + else /* user output is not interleaved */ + { + srcSampleStrideSamples = 1; + srcChannelStrideBytes = frameCount * bp->bytesPerUserOutputSample; + } + + for( i=0; i<bp->outputChannelCount; ++i ) + { + bp->outputConverter( hostOutputChannels[i].data, + hostOutputChannels[i].stride, + srcBytePtr, srcSampleStrideSamples, + frameCount, &bp->ditherGenerator ); + + srcBytePtr += srcChannelStrideBytes; /* skip to next source channel */ + + /* advance dest ptr for next iteration */ + hostOutputChannels[i].data = ((unsigned char*)hostOutputChannels[i].data) + + frameCount * hostOutputChannels[i].stride * bp->bytesPerHostOutputSample; + } + } + + framesProcessed += frameCount; + + framesToGo -= frameCount; + } + } + while( framesToGo > 0 && *streamCallbackResult == paContinue ); + } + + if( framesToGo > 0 ) + { + /* zero any remaining frames output. There will only be remaining frames + if the callback has returned paComplete or paAbort */ + + frameCount = framesToGo; + + if( bp->outputChannelCount != 0 && bp->hostOutputChannels[0][0].data ) + { + for( i=0; i<bp->outputChannelCount; ++i ) + { + bp->outputZeroer( hostOutputChannels[i].data, + hostOutputChannels[i].stride, + frameCount ); + + /* advance dest ptr for next iteration */ + hostOutputChannels[i].data = ((unsigned char*)hostOutputChannels[i].data) + + frameCount * hostOutputChannels[i].stride * bp->bytesPerHostOutputSample; + } + } + + framesProcessed += frameCount; + } + + return framesProcessed; +} + + +/* + AdaptingInputOnlyProcess() is a half duplex input buffer processor. It + converts data from the input buffers into the temporary input buffer, + when the temporary input buffer is full, it calls the streamCallback. +*/ +static unsigned long AdaptingInputOnlyProcess( PaUtilBufferProcessor *bp, + int *streamCallbackResult, + PaUtilChannelDescriptor *hostInputChannels, + unsigned long framesToProcess ) +{ + void *userInput, *userOutput; + unsigned char *destBytePtr; + unsigned int destSampleStrideSamples; /* stride from one sample to the next within a channel, in samples */ + unsigned int destChannelStrideBytes; /* stride from one channel to the next, in bytes */ + unsigned int i; + unsigned long frameCount; + unsigned long framesToGo = framesToProcess; + unsigned long framesProcessed = 0; + + userOutput = 0; + + do + { + frameCount = ( bp->framesInTempInputBuffer + framesToGo > bp->framesPerUserBuffer ) + ? ( bp->framesPerUserBuffer - bp->framesInTempInputBuffer ) + : framesToGo; + + /* convert frameCount samples into temp buffer */ + + if( bp->userInputIsInterleaved ) + { + destBytePtr = ((unsigned char*)bp->tempInputBuffer) + + bp->bytesPerUserInputSample * bp->inputChannelCount * + bp->framesInTempInputBuffer; + + destSampleStrideSamples = bp->inputChannelCount; + destChannelStrideBytes = bp->bytesPerUserInputSample; + + userInput = bp->tempInputBuffer; + } + else /* user input is not interleaved */ + { + destBytePtr = ((unsigned char*)bp->tempInputBuffer) + + bp->bytesPerUserInputSample * bp->framesInTempInputBuffer; + + destSampleStrideSamples = 1; + destChannelStrideBytes = bp->framesPerUserBuffer * bp->bytesPerUserInputSample; + + /* setup non-interleaved ptrs */ + for( i=0; i<bp->inputChannelCount; ++i ) + { + bp->tempInputBufferPtrs[i] = ((unsigned char*)bp->tempInputBuffer) + + i * bp->bytesPerUserInputSample * bp->framesPerUserBuffer; + } + + userInput = bp->tempInputBufferPtrs; + } + + for( i=0; i<bp->inputChannelCount; ++i ) + { + bp->inputConverter( destBytePtr, destSampleStrideSamples, + hostInputChannels[i].data, + hostInputChannels[i].stride, + frameCount, &bp->ditherGenerator ); + + destBytePtr += destChannelStrideBytes; /* skip to next destination channel */ + + /* advance src ptr for next iteration */ + hostInputChannels[i].data = ((unsigned char*)hostInputChannels[i].data) + + frameCount * hostInputChannels[i].stride * bp->bytesPerHostInputSample; + } + + bp->framesInTempInputBuffer += frameCount; + + if( bp->framesInTempInputBuffer == bp->framesPerUserBuffer ) + { + /** + @todo (non-critical optimisation) + The conditional below implements the continue/complete/abort mechanism + simply by continuing on iterating through the input buffer, but not + passing the data to the callback. With care, the outer loop could be + terminated earlier, thus some unneeded conversion cycles would be + saved. + */ + if( *streamCallbackResult == paContinue ) + { + bp->timeInfo->outputBufferDacTime = 0; + + *streamCallbackResult = bp->streamCallback( userInput, userOutput, + bp->framesPerUserBuffer, bp->timeInfo, + bp->callbackStatusFlags, bp->userData ); + + bp->timeInfo->inputBufferAdcTime += frameCount * bp->samplePeriod; + } + + bp->framesInTempInputBuffer = 0; + } + + framesProcessed += frameCount; + + framesToGo -= frameCount; + }while( framesToGo > 0 ); + + return framesProcessed; +} + + +/* + AdaptingOutputOnlyProcess() is a half duplex output buffer processor. + It converts data from the temporary output buffer, to the output buffers, + when the temporary output buffer is empty, it calls the streamCallback. +*/ +static unsigned long AdaptingOutputOnlyProcess( PaUtilBufferProcessor *bp, + int *streamCallbackResult, + PaUtilChannelDescriptor *hostOutputChannels, + unsigned long framesToProcess ) +{ + void *userInput, *userOutput; + unsigned char *srcBytePtr; + unsigned int srcSampleStrideSamples; /* stride from one sample to the next within a channel, in samples */ + unsigned int srcChannelStrideBytes; /* stride from one channel to the next, in bytes */ + unsigned int i; + unsigned long frameCount; + unsigned long framesToGo = framesToProcess; + unsigned long framesProcessed = 0; + + do + { + if( bp->framesInTempOutputBuffer == 0 && *streamCallbackResult == paContinue ) + { + userInput = 0; + + /* setup userOutput */ + if( bp->userOutputIsInterleaved ) + { + userOutput = bp->tempOutputBuffer; + } + else /* user output is not interleaved */ + { + for( i = 0; i < bp->outputChannelCount; ++i ) + { + bp->tempOutputBufferPtrs[i] = ((unsigned char*)bp->tempOutputBuffer) + + i * bp->framesPerUserBuffer * bp->bytesPerUserOutputSample; + } + + userOutput = bp->tempOutputBufferPtrs; + } + + bp->timeInfo->inputBufferAdcTime = 0; + + *streamCallbackResult = bp->streamCallback( userInput, userOutput, + bp->framesPerUserBuffer, bp->timeInfo, + bp->callbackStatusFlags, bp->userData ); + + if( *streamCallbackResult == paAbort ) + { + /* if the callback returned paAbort, we disregard its output */ + } + else + { + bp->timeInfo->outputBufferDacTime += bp->framesPerUserBuffer * bp->samplePeriod; + + bp->framesInTempOutputBuffer = bp->framesPerUserBuffer; + } + } + + if( bp->framesInTempOutputBuffer > 0 ) + { + /* convert frameCount frames from user buffer to host buffer */ + + frameCount = PA_MIN_( bp->framesInTempOutputBuffer, framesToGo ); + + if( bp->userOutputIsInterleaved ) + { + srcBytePtr = ((unsigned char*)bp->tempOutputBuffer) + + bp->bytesPerUserOutputSample * bp->outputChannelCount * + (bp->framesPerUserBuffer - bp->framesInTempOutputBuffer); + + srcSampleStrideSamples = bp->outputChannelCount; + srcChannelStrideBytes = bp->bytesPerUserOutputSample; + } + else /* user output is not interleaved */ + { + srcBytePtr = ((unsigned char*)bp->tempOutputBuffer) + + bp->bytesPerUserOutputSample * + (bp->framesPerUserBuffer - bp->framesInTempOutputBuffer); + + srcSampleStrideSamples = 1; + srcChannelStrideBytes = bp->framesPerUserBuffer * bp->bytesPerUserOutputSample; + } + + for( i=0; i<bp->outputChannelCount; ++i ) + { + bp->outputConverter( hostOutputChannels[i].data, + hostOutputChannels[i].stride, + srcBytePtr, srcSampleStrideSamples, + frameCount, &bp->ditherGenerator ); + + srcBytePtr += srcChannelStrideBytes; /* skip to next source channel */ + + /* advance dest ptr for next iteration */ + hostOutputChannels[i].data = ((unsigned char*)hostOutputChannels[i].data) + + frameCount * hostOutputChannels[i].stride * bp->bytesPerHostOutputSample; + } + + bp->framesInTempOutputBuffer -= frameCount; + } + else + { + /* no more user data is available because the callback has returned + paComplete or paAbort. Fill the remainder of the host buffer + with zeros. + */ + + frameCount = framesToGo; + + for( i=0; i<bp->outputChannelCount; ++i ) + { + bp->outputZeroer( hostOutputChannels[i].data, + hostOutputChannels[i].stride, + frameCount ); + + /* advance dest ptr for next iteration */ + hostOutputChannels[i].data = ((unsigned char*)hostOutputChannels[i].data) + + frameCount * hostOutputChannels[i].stride * bp->bytesPerHostOutputSample; + } + } + + framesProcessed += frameCount; + + framesToGo -= frameCount; + + }while( framesToGo > 0 ); + + return framesProcessed; +} + +/* CopyTempOutputBuffersToHostOutputBuffers is called from AdaptingProcess to copy frames from + tempOutputBuffer to hostOutputChannels. This includes data conversion + and interleaving. +*/ +static void CopyTempOutputBuffersToHostOutputBuffers( PaUtilBufferProcessor *bp) +{ + unsigned long maxFramesToCopy; + PaUtilChannelDescriptor *hostOutputChannels; + unsigned int frameCount; + unsigned char *srcBytePtr; + unsigned int srcSampleStrideSamples; /* stride from one sample to the next within a channel, in samples */ + unsigned int srcChannelStrideBytes; /* stride from one channel to the next, in bytes */ + unsigned int i; + + /* copy frames from user to host output buffers */ + while( bp->framesInTempOutputBuffer > 0 && + ((bp->hostOutputFrameCount[0] + bp->hostOutputFrameCount[1]) > 0) ) + { + maxFramesToCopy = bp->framesInTempOutputBuffer; + + /* select the output buffer set (1st or 2nd) */ + if( bp->hostOutputFrameCount[0] > 0 ) + { + hostOutputChannels = bp->hostOutputChannels[0]; + frameCount = PA_MIN_( bp->hostOutputFrameCount[0], maxFramesToCopy ); + } + else + { + hostOutputChannels = bp->hostOutputChannels[1]; + frameCount = PA_MIN_( bp->hostOutputFrameCount[1], maxFramesToCopy ); + } + + if( bp->userOutputIsInterleaved ) + { + srcBytePtr = ((unsigned char*)bp->tempOutputBuffer) + + bp->bytesPerUserOutputSample * bp->outputChannelCount * + (bp->framesPerUserBuffer - bp->framesInTempOutputBuffer); + + srcSampleStrideSamples = bp->outputChannelCount; + srcChannelStrideBytes = bp->bytesPerUserOutputSample; + } + else /* user output is not interleaved */ + { + srcBytePtr = ((unsigned char*)bp->tempOutputBuffer) + + bp->bytesPerUserOutputSample * + (bp->framesPerUserBuffer - bp->framesInTempOutputBuffer); + + srcSampleStrideSamples = 1; + srcChannelStrideBytes = bp->framesPerUserBuffer * bp->bytesPerUserOutputSample; + } + + for( i=0; i<bp->outputChannelCount; ++i ) + { + bp->outputConverter( hostOutputChannels[i].data, + hostOutputChannels[i].stride, + srcBytePtr, srcSampleStrideSamples, + frameCount, &bp->ditherGenerator ); + + srcBytePtr += srcChannelStrideBytes; /* skip to next source channel */ + + /* advance dest ptr for next iteration */ + hostOutputChannels[i].data = ((unsigned char*)hostOutputChannels[i].data) + + frameCount * hostOutputChannels[i].stride * bp->bytesPerHostOutputSample; + } + + if( bp->hostOutputFrameCount[0] > 0 ) + bp->hostOutputFrameCount[0] -= frameCount; + else + bp->hostOutputFrameCount[1] -= frameCount; + + bp->framesInTempOutputBuffer -= frameCount; + } +} + +/* + AdaptingProcess is a full duplex adapting buffer processor. It converts + data from the temporary output buffer into the host output buffers, then + from the host input buffers into the temporary input buffers. Calling the + streamCallback when necessary. + When processPartialUserBuffers is 0, all available input data will be + consumed and all available output space will be filled. When + processPartialUserBuffers is non-zero, as many full user buffers + as possible will be processed, but partial buffers will not be consumed. +*/ +static unsigned long AdaptingProcess( PaUtilBufferProcessor *bp, + int *streamCallbackResult, int processPartialUserBuffers ) +{ + void *userInput, *userOutput; + unsigned long framesProcessed = 0; + unsigned long framesAvailable; + unsigned long endProcessingMinFrameCount; + unsigned long maxFramesToCopy; + PaUtilChannelDescriptor *hostInputChannels, *hostOutputChannels; + unsigned int frameCount; + unsigned char *destBytePtr; + unsigned int destSampleStrideSamples; /* stride from one sample to the next within a channel, in samples */ + unsigned int destChannelStrideBytes; /* stride from one channel to the next, in bytes */ + unsigned int i, j; + + + framesAvailable = bp->hostInputFrameCount[0] + bp->hostInputFrameCount[1];/* this is assumed to be the same as the output buffer's frame count */ + + if( processPartialUserBuffers ) + endProcessingMinFrameCount = 0; + else + endProcessingMinFrameCount = (bp->framesPerUserBuffer - 1); + + /* Fill host output with remaining frames in user output (tempOutputBuffer) */ + CopyTempOutputBuffersToHostOutputBuffers( bp ); + + while( framesAvailable > endProcessingMinFrameCount ) + { + + if( bp->framesInTempOutputBuffer == 0 && *streamCallbackResult != paContinue ) + { + /* the callback will not be called any more, so zero what remains + of the host output buffers */ + + for( i=0; i<2; ++i ) + { + frameCount = bp->hostOutputFrameCount[i]; + if( frameCount > 0 ) + { + hostOutputChannels = bp->hostOutputChannels[i]; + + for( j=0; j<bp->outputChannelCount; ++j ) + { + bp->outputZeroer( hostOutputChannels[j].data, + hostOutputChannels[j].stride, + frameCount ); + + /* advance dest ptr for next iteration */ + hostOutputChannels[j].data = ((unsigned char*)hostOutputChannels[j].data) + + frameCount * hostOutputChannels[j].stride * bp->bytesPerHostOutputSample; + } + bp->hostOutputFrameCount[i] = 0; + } + } + } + + + /* copy frames from host to user input buffers */ + while( bp->framesInTempInputBuffer < bp->framesPerUserBuffer && + ((bp->hostInputFrameCount[0] + bp->hostInputFrameCount[1]) > 0) ) + { + maxFramesToCopy = bp->framesPerUserBuffer - bp->framesInTempInputBuffer; + + /* select the input buffer set (1st or 2nd) */ + if( bp->hostInputFrameCount[0] > 0 ) + { + hostInputChannels = bp->hostInputChannels[0]; + frameCount = PA_MIN_( bp->hostInputFrameCount[0], maxFramesToCopy ); + } + else + { + hostInputChannels = bp->hostInputChannels[1]; + frameCount = PA_MIN_( bp->hostInputFrameCount[1], maxFramesToCopy ); + } + + /* configure conversion destination pointers */ + if( bp->userInputIsInterleaved ) + { + destBytePtr = ((unsigned char*)bp->tempInputBuffer) + + bp->bytesPerUserInputSample * bp->inputChannelCount * + bp->framesInTempInputBuffer; + + destSampleStrideSamples = bp->inputChannelCount; + destChannelStrideBytes = bp->bytesPerUserInputSample; + } + else /* user input is not interleaved */ + { + destBytePtr = ((unsigned char*)bp->tempInputBuffer) + + bp->bytesPerUserInputSample * bp->framesInTempInputBuffer; + + destSampleStrideSamples = 1; + destChannelStrideBytes = bp->framesPerUserBuffer * bp->bytesPerUserInputSample; + } + + for( i=0; i<bp->inputChannelCount; ++i ) + { + bp->inputConverter( destBytePtr, destSampleStrideSamples, + hostInputChannels[i].data, + hostInputChannels[i].stride, + frameCount, &bp->ditherGenerator ); + + destBytePtr += destChannelStrideBytes; /* skip to next destination channel */ + + /* advance src ptr for next iteration */ + hostInputChannels[i].data = ((unsigned char*)hostInputChannels[i].data) + + frameCount * hostInputChannels[i].stride * bp->bytesPerHostInputSample; + } + + if( bp->hostInputFrameCount[0] > 0 ) + bp->hostInputFrameCount[0] -= frameCount; + else + bp->hostInputFrameCount[1] -= frameCount; + + bp->framesInTempInputBuffer += frameCount; + + /* update framesAvailable and framesProcessed based on input consumed + unless something is very wrong this will also correspond to the + amount of output generated */ + framesAvailable -= frameCount; + framesProcessed += frameCount; + } + + /* call streamCallback */ + if( bp->framesInTempInputBuffer == bp->framesPerUserBuffer && + bp->framesInTempOutputBuffer == 0 ) + { + if( *streamCallbackResult == paContinue ) + { + /* setup userInput */ + if( bp->userInputIsInterleaved ) + { + userInput = bp->tempInputBuffer; + } + else /* user input is not interleaved */ + { + for( i = 0; i < bp->inputChannelCount; ++i ) + { + bp->tempInputBufferPtrs[i] = ((unsigned char*)bp->tempInputBuffer) + + i * bp->framesPerUserBuffer * bp->bytesPerUserInputSample; + } + + userInput = bp->tempInputBufferPtrs; + } + + /* setup userOutput */ + if( bp->userOutputIsInterleaved ) + { + userOutput = bp->tempOutputBuffer; + } + else /* user output is not interleaved */ + { + for( i = 0; i < bp->outputChannelCount; ++i ) + { + bp->tempOutputBufferPtrs[i] = ((unsigned char*)bp->tempOutputBuffer) + + i * bp->framesPerUserBuffer * bp->bytesPerUserOutputSample; + } + + userOutput = bp->tempOutputBufferPtrs; + } + + /* call streamCallback */ + + *streamCallbackResult = bp->streamCallback( userInput, userOutput, + bp->framesPerUserBuffer, bp->timeInfo, + bp->callbackStatusFlags, bp->userData ); + + bp->timeInfo->inputBufferAdcTime += bp->framesPerUserBuffer * bp->samplePeriod; + bp->timeInfo->outputBufferDacTime += bp->framesPerUserBuffer * bp->samplePeriod; + + bp->framesInTempInputBuffer = 0; + + if( *streamCallbackResult == paAbort ) + bp->framesInTempOutputBuffer = 0; + else + bp->framesInTempOutputBuffer = bp->framesPerUserBuffer; + } + else + { + /* paComplete or paAbort has already been called. */ + + bp->framesInTempInputBuffer = 0; + } + } + + /* copy frames from user (tempOutputBuffer) to host output buffers (hostOutputChannels) + Means to process the user output provided by the callback. Has to be called after + each callback. */ + CopyTempOutputBuffersToHostOutputBuffers( bp ); + + } + + return framesProcessed; +} + + +unsigned long PaUtil_EndBufferProcessing( PaUtilBufferProcessor* bp, int *streamCallbackResult ) +{ + unsigned long framesToProcess, framesToGo; + unsigned long framesProcessed = 0; + + if( bp->inputChannelCount != 0 && bp->outputChannelCount != 0 + && bp->hostInputChannels[0][0].data /* input was supplied (see PaUtil_SetNoInput) */ + && bp->hostOutputChannels[0][0].data /* output was supplied (see PaUtil_SetNoOutput) */ ) + { + assert( (bp->hostInputFrameCount[0] + bp->hostInputFrameCount[1]) == + (bp->hostOutputFrameCount[0] + bp->hostOutputFrameCount[1]) ); + } + + assert( *streamCallbackResult == paContinue + || *streamCallbackResult == paComplete + || *streamCallbackResult == paAbort ); /* don't forget to pass in a valid callback result value */ + + if( bp->useNonAdaptingProcess ) + { + if( bp->inputChannelCount != 0 && bp->outputChannelCount != 0 ) + { + /* full duplex non-adapting process, splice buffers if they are + different lengths */ + + framesToGo = bp->hostOutputFrameCount[0] + bp->hostOutputFrameCount[1]; /* relies on assert above for input/output equivalence */ + + do{ + unsigned long noInputInputFrameCount; + unsigned long *hostInputFrameCount; + PaUtilChannelDescriptor *hostInputChannels; + unsigned long noOutputOutputFrameCount; + unsigned long *hostOutputFrameCount; + PaUtilChannelDescriptor *hostOutputChannels; + unsigned long framesProcessedThisIteration; + + if( !bp->hostInputChannels[0][0].data ) + { + /* no input was supplied (see PaUtil_SetNoInput) + NonAdaptingProcess knows how to deal with this + */ + noInputInputFrameCount = framesToGo; + hostInputFrameCount = &noInputInputFrameCount; + hostInputChannels = 0; + } + else if( bp->hostInputFrameCount[0] != 0 ) + { + hostInputFrameCount = &bp->hostInputFrameCount[0]; + hostInputChannels = bp->hostInputChannels[0]; + } + else + { + hostInputFrameCount = &bp->hostInputFrameCount[1]; + hostInputChannels = bp->hostInputChannels[1]; + } + + if( !bp->hostOutputChannels[0][0].data ) + { + /* no output was supplied (see PaUtil_SetNoOutput) + NonAdaptingProcess knows how to deal with this + */ + noOutputOutputFrameCount = framesToGo; + hostOutputFrameCount = &noOutputOutputFrameCount; + hostOutputChannels = 0; + } + if( bp->hostOutputFrameCount[0] != 0 ) + { + hostOutputFrameCount = &bp->hostOutputFrameCount[0]; + hostOutputChannels = bp->hostOutputChannels[0]; + } + else + { + hostOutputFrameCount = &bp->hostOutputFrameCount[1]; + hostOutputChannels = bp->hostOutputChannels[1]; + } + + framesToProcess = PA_MIN_( *hostInputFrameCount, + *hostOutputFrameCount ); + + assert( framesToProcess != 0 ); + + framesProcessedThisIteration = NonAdaptingProcess( bp, streamCallbackResult, + hostInputChannels, hostOutputChannels, + framesToProcess ); + + *hostInputFrameCount -= framesProcessedThisIteration; + *hostOutputFrameCount -= framesProcessedThisIteration; + + framesProcessed += framesProcessedThisIteration; + framesToGo -= framesProcessedThisIteration; + + }while( framesToGo > 0 ); + } + else + { + /* half duplex non-adapting process, just process 1st and 2nd buffer */ + /* process first buffer */ + + framesToProcess = (bp->inputChannelCount != 0) + ? bp->hostInputFrameCount[0] + : bp->hostOutputFrameCount[0]; + + framesProcessed = NonAdaptingProcess( bp, streamCallbackResult, + bp->hostInputChannels[0], bp->hostOutputChannels[0], + framesToProcess ); + + /* process second buffer if provided */ + + framesToProcess = (bp->inputChannelCount != 0) + ? bp->hostInputFrameCount[1] + : bp->hostOutputFrameCount[1]; + if( framesToProcess > 0 ) + { + framesProcessed += NonAdaptingProcess( bp, streamCallbackResult, + bp->hostInputChannels[1], bp->hostOutputChannels[1], + framesToProcess ); + } + } + } + else /* block adaption necessary*/ + { + + if( bp->inputChannelCount != 0 && bp->outputChannelCount != 0 ) + { + /* full duplex */ + + if( bp->hostBufferSizeMode == paUtilVariableHostBufferSizePartialUsageAllowed ) + { + framesProcessed = AdaptingProcess( bp, streamCallbackResult, + 0 /* dont process partial user buffers */ ); + } + else + { + framesProcessed = AdaptingProcess( bp, streamCallbackResult, + 1 /* process partial user buffers */ ); + } + } + else if( bp->inputChannelCount != 0 ) + { + /* input only */ + framesToProcess = bp->hostInputFrameCount[0]; + + framesProcessed = AdaptingInputOnlyProcess( bp, streamCallbackResult, + bp->hostInputChannels[0], framesToProcess ); + + framesToProcess = bp->hostInputFrameCount[1]; + if( framesToProcess > 0 ) + { + framesProcessed += AdaptingInputOnlyProcess( bp, streamCallbackResult, + bp->hostInputChannels[1], framesToProcess ); + } + } + else + { + /* output only */ + framesToProcess = bp->hostOutputFrameCount[0]; + + framesProcessed = AdaptingOutputOnlyProcess( bp, streamCallbackResult, + bp->hostOutputChannels[0], framesToProcess ); + + framesToProcess = bp->hostOutputFrameCount[1]; + if( framesToProcess > 0 ) + { + framesProcessed += AdaptingOutputOnlyProcess( bp, streamCallbackResult, + bp->hostOutputChannels[1], framesToProcess ); + } + } + } + + return framesProcessed; +} + + +int PaUtil_IsBufferProcessorOutputEmpty( PaUtilBufferProcessor* bp ) +{ + return (bp->framesInTempOutputBuffer) ? 0 : 1; +} + + +unsigned long PaUtil_CopyInput( PaUtilBufferProcessor* bp, + void **buffer, unsigned long frameCount ) +{ + PaUtilChannelDescriptor *hostInputChannels; + unsigned int framesToCopy; + unsigned char *destBytePtr; + void **nonInterleavedDestPtrs; + unsigned int destSampleStrideSamples; /* stride from one sample to the next within a channel, in samples */ + unsigned int destChannelStrideBytes; /* stride from one channel to the next, in bytes */ + unsigned int i; + + hostInputChannels = bp->hostInputChannels[0]; + framesToCopy = PA_MIN_( bp->hostInputFrameCount[0], frameCount ); + + if( bp->userInputIsInterleaved ) + { + destBytePtr = (unsigned char*)*buffer; + + destSampleStrideSamples = bp->inputChannelCount; + destChannelStrideBytes = bp->bytesPerUserInputSample; + + for( i=0; i<bp->inputChannelCount; ++i ) + { + bp->inputConverter( destBytePtr, destSampleStrideSamples, + hostInputChannels[i].data, + hostInputChannels[i].stride, + framesToCopy, &bp->ditherGenerator ); + + destBytePtr += destChannelStrideBytes; /* skip to next source channel */ + + /* advance dest ptr for next iteration */ + hostInputChannels[i].data = ((unsigned char*)hostInputChannels[i].data) + + framesToCopy * hostInputChannels[i].stride * bp->bytesPerHostInputSample; + } + + /* advance callers dest pointer (buffer) */ + *buffer = ((unsigned char *)*buffer) + + framesToCopy * bp->inputChannelCount * bp->bytesPerUserInputSample; + } + else + { + /* user input is not interleaved */ + + nonInterleavedDestPtrs = (void**)*buffer; + + destSampleStrideSamples = 1; + + for( i=0; i<bp->inputChannelCount; ++i ) + { + destBytePtr = (unsigned char*)nonInterleavedDestPtrs[i]; + + bp->inputConverter( destBytePtr, destSampleStrideSamples, + hostInputChannels[i].data, + hostInputChannels[i].stride, + framesToCopy, &bp->ditherGenerator ); + + /* advance callers dest pointer (nonInterleavedDestPtrs[i]) */ + destBytePtr += bp->bytesPerUserInputSample * framesToCopy; + nonInterleavedDestPtrs[i] = destBytePtr; + + /* advance dest ptr for next iteration */ + hostInputChannels[i].data = ((unsigned char*)hostInputChannels[i].data) + + framesToCopy * hostInputChannels[i].stride * bp->bytesPerHostInputSample; + } + } + + bp->hostInputFrameCount[0] -= framesToCopy; + + return framesToCopy; +} + +unsigned long PaUtil_CopyOutput( PaUtilBufferProcessor* bp, + const void ** buffer, unsigned long frameCount ) +{ + PaUtilChannelDescriptor *hostOutputChannels; + unsigned int framesToCopy; + unsigned char *srcBytePtr; + void **nonInterleavedSrcPtrs; + unsigned int srcSampleStrideSamples; /* stride from one sample to the next within a channel, in samples */ + unsigned int srcChannelStrideBytes; /* stride from one channel to the next, in bytes */ + unsigned int i; + + hostOutputChannels = bp->hostOutputChannels[0]; + framesToCopy = PA_MIN_( bp->hostOutputFrameCount[0], frameCount ); + + if( bp->userOutputIsInterleaved ) + { + srcBytePtr = (unsigned char*)*buffer; + + srcSampleStrideSamples = bp->outputChannelCount; + srcChannelStrideBytes = bp->bytesPerUserOutputSample; + + for( i=0; i<bp->outputChannelCount; ++i ) + { + bp->outputConverter( hostOutputChannels[i].data, + hostOutputChannels[i].stride, + srcBytePtr, srcSampleStrideSamples, + framesToCopy, &bp->ditherGenerator ); + + srcBytePtr += srcChannelStrideBytes; /* skip to next source channel */ + + /* advance dest ptr for next iteration */ + hostOutputChannels[i].data = ((unsigned char*)hostOutputChannels[i].data) + + framesToCopy * hostOutputChannels[i].stride * bp->bytesPerHostOutputSample; + } + + /* advance callers source pointer (buffer) */ + *buffer = ((unsigned char *)*buffer) + + framesToCopy * bp->outputChannelCount * bp->bytesPerUserOutputSample; + + } + else + { + /* user output is not interleaved */ + + nonInterleavedSrcPtrs = (void**)*buffer; + + srcSampleStrideSamples = 1; + + for( i=0; i<bp->outputChannelCount; ++i ) + { + srcBytePtr = (unsigned char*)nonInterleavedSrcPtrs[i]; + + bp->outputConverter( hostOutputChannels[i].data, + hostOutputChannels[i].stride, + srcBytePtr, srcSampleStrideSamples, + framesToCopy, &bp->ditherGenerator ); + + + /* advance callers source pointer (nonInterleavedSrcPtrs[i]) */ + srcBytePtr += bp->bytesPerUserOutputSample * framesToCopy; + nonInterleavedSrcPtrs[i] = srcBytePtr; + + /* advance dest ptr for next iteration */ + hostOutputChannels[i].data = ((unsigned char*)hostOutputChannels[i].data) + + framesToCopy * hostOutputChannels[i].stride * bp->bytesPerHostOutputSample; + } + } + + bp->hostOutputFrameCount[0] += framesToCopy; + + return framesToCopy; +} + + +unsigned long PaUtil_ZeroOutput( PaUtilBufferProcessor* bp, unsigned long frameCount ) +{ + PaUtilChannelDescriptor *hostOutputChannels; + unsigned int framesToZero; + unsigned int i; + + hostOutputChannels = bp->hostOutputChannels[0]; + framesToZero = PA_MIN_( bp->hostOutputFrameCount[0], frameCount ); + + for( i=0; i<bp->outputChannelCount; ++i ) + { + bp->outputZeroer( hostOutputChannels[i].data, + hostOutputChannels[i].stride, + framesToZero ); + + + /* advance dest ptr for next iteration */ + hostOutputChannels[i].data = ((unsigned char*)hostOutputChannels[i].data) + + framesToZero * hostOutputChannels[i].stride * bp->bytesPerHostOutputSample; + } + + bp->hostOutputFrameCount[0] += framesToZero; + + return framesToZero; +} diff --git a/src/audio/portaudio/pa_common/pa_process.h b/src/audio/portaudio/pa_common/pa_process.h new file mode 100644 index 0000000000000000000000000000000000000000..8d13d903a4428416ec1d176f8afc548509006bb4 --- /dev/null +++ b/src/audio/portaudio/pa_common/pa_process.h @@ -0,0 +1,741 @@ +#ifndef PA_PROCESS_H +#define PA_PROCESS_H +/* + * $Id$ + * Portable Audio I/O Library callback buffer processing adapters + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Phil Burk, 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. + * + * 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. + * + * 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. + */ + +/** @file + @brief Buffer Processor prototypes. A Buffer Processor performs buffer length + adaption, coordinates sample format conversion, and interleaves/deinterleaves + channels. + + <h3>Overview</h3> + + The "Buffer Processor" (PaUtilBufferProcessor) manages conversion of audio + data from host buffers to user buffers and back again. Where required, the + buffer processor takes care of converting between host and user sample formats, + interleaving and deinterleaving multichannel buffers, and adapting between host + and user buffers with different lengths. The buffer processor may be used with + full and half duplex streams, for both callback streams and blocking read/write + streams. + + One of the important capabilities provided by the buffer processor is + the ability to adapt between user and host buffer sizes of different lengths + with minimum latency. Although this task is relatively easy to perform when + the host buffer size is an integer multiple of the user buffer size, the + problem is more complicated when this is not the case - especially for + full-duplex callback streams. Where necessary the adaption is implemented by + internally buffering some input and/or output data. The buffer adation + algorithm used by the buffer processor was originally implemented by + Stephan Letz for the ASIO version of PortAudio, and is described in his + Callback_adaption_.pdf which is included in the distribution. + + The buffer processor performs sample conversion using the functions provided + by pa_converters.c. + + The following sections provide an overview of how to use the buffer processor. + Interested readers are advised to consult the host API implementations for + examples of buffer processor usage. + + + <h4>Initialization, resetting and termination</h4> + + When a stream is opened, the buffer processor should be initialized using + PaUtil_InitializeBufferProcessor. This function initializes internal state + and allocates temporary buffers as neccesary according to the supplied + configuration parameters. Some of the parameters correspond to those requested + by the user in their call to Pa_OpenStream(), others reflect the requirements + of the host API implementation - they indicate host buffer sizes, formats, + and the type of buffering which the Host API uses. The buffer processor should + be initialized for callback streams and blocking read/write streams. + + Call PaUtil_ResetBufferProcessor to clear any sample data which is present + in the buffer processor before starting to use it (for example when + Pa_StartStream is called). + + When the buffer processor is no longer used call + PaUtil_TerminateBufferProcessor. + + + <h4>Using the buffer processor for a callback stream</h4> + + The buffer processor's role in a callback stream is to take host input buffers + process them with the stream callback, and fill host output buffers. For a + full duplex stream, the buffer processor handles input and output simultaneously + due to the requirements of the minimum-latency buffer adation algorithm. + + When a host buffer becomes available, the implementation should call + the buffer processor to process the buffer. The buffer processor calls the + stream callback to consume and/or produce audio data as necessary. The buffer + processor will convert sample formats, interleave/deinterleave channels, + and slice or chunk the data to the appropriate buffer lengths according to + the requirements of the stream callback and the host API. + + To process a host buffer (or a pair of host buffers for a full-duplex stream) + use the following calling sequence: + + -# Call PaUtil_BeginBufferProcessing + -# For a stream which takes input: + - Call PaUtil_SetInputFrameCount with the number of frames in the host input + buffer. + - Call one of the following functions one or more times to tell the + buffer processor about the host input buffer(s): PaUtil_SetInputChannel, + PaUtil_SetInterleavedInputChannels, PaUtil_SetNonInterleavedInputChannel. + Which function you call will depend on whether the host buffer(s) are + interleaved or not. + - If the available host data is split accross two buffers (for example a + data range at the end of a circular buffer and another range at the + beginning of the circular buffer), also call + PaUtil_Set2ndInputFrameCount, PaUtil_Set2ndInputChannel, + PaUtil_Set2ndInterleavedInputChannels, + PaUtil_Set2ndNonInterleavedInputChannel as necessary to tell the buffer + processor about the second buffer. + -# For a stream which generates output: + - Call PaUtil_SetOutputFrameCount with the number of frames in the host + output buffer. + - Call one of the following functions one or more times to tell the + buffer processor about the host output buffer(s): PaUtil_SetOutputChannel, + PaUtil_SetInterleavedOutputChannels, PaUtil_SetNonInterleavedOutputChannel. + Which function you call will depend on whether the host buffer(s) are + interleaved or not. + - If the available host output buffer space is split accross two buffers + (for example a data range at the end of a circular buffer and another + range at the beginning of the circular buffer), call + PaUtil_Set2ndOutputFrameCount, PaUtil_Set2ndOutputChannel, + PaUtil_Set2ndInterleavedOutputChannels, + PaUtil_Set2ndNonInterleavedOutputChannel as necessary to tell the buffer + processor about the second buffer. + -# Call PaUtil_EndBufferProcessing, this function performs the actual data + conversion and processing. + + + <h4>Using the buffer processor for a blocking read/write stream</h4> + + Blocking read/write streams use the buffer processor to convert and copy user + output data to a host buffer, and to convert and copy host input data to + the user's buffer. The buffer processor does not perform any buffer adaption. + When using the buffer processor in a blocking read/write stream the input and + output conversion are performed separately by the PaUtil_CopyInput and + PaUtil_CopyOutput functions. + + To copy data from a host input buffer to the buffer(s) which the user supplies + to Pa_ReadStream, use the following calling sequence. + + - Repeat the following three steps until the user buffer(s) have been filled + with samples from the host input buffers: + -# Call PaUtil_SetInputFrameCount with the number of frames in the host + input buffer. + -# Call one of the following functions one or more times to tell the + buffer processor about the host input buffer(s): PaUtil_SetInputChannel, + PaUtil_SetInterleavedInputChannels, PaUtil_SetNonInterleavedInputChannel. + Which function you call will depend on whether the host buffer(s) are + interleaved or not. + -# Call PaUtil_CopyInput with the user buffer pointer (or a copy of the + array of buffer pointers for a non-interleaved stream) passed to + Pa_ReadStream, along with the number of frames in the user buffer(s). + Be careful to pass a <i>copy</i> of the user buffer pointers to + PaUtil_CopyInput because PaUtil_CopyInput advances the pointers to + the start of the next region to copy. + - PaUtil_CopyInput will not copy more data than is available in the + host buffer(s), so the above steps need to be repeated until the user + buffer(s) are full. + + + To copy data to the host output buffer from the user buffers(s) supplied + to Pa_WriteStream use the following calling sequence. + + - Repeat the following three steps until all frames from the user buffer(s) + have been copied to the host API: + -# Call PaUtil_SetOutputFrameCount with the number of frames in the host + output buffer. + -# Call one of the following functions one or more times to tell the + buffer processor about the host output buffer(s): PaUtil_SetOutputChannel, + PaUtil_SetInterleavedOutputChannels, PaUtil_SetNonInterleavedOutputChannel. + Which function you call will depend on whether the host buffer(s) are + interleaved or not. + -# Call PaUtil_CopyOutput with the user buffer pointer (or a copy of the + array of buffer pointers for a non-interleaved stream) passed to + Pa_WriteStream, along with the number of frames in the user buffer(s). + Be careful to pass a <i>copy</i> of the user buffer pointers to + PaUtil_CopyOutput because PaUtil_CopyOutput advances the pointers to + the start of the next region to copy. + - PaUtil_CopyOutput will not copy more data than fits in the host buffer(s), + so the above steps need to be repeated until all user data is copied. +*/ + + +#include "portaudio.h" +#include "pa_converters.h" +#include "pa_dither.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +/** @brief Mode flag passed to PaUtil_InitializeBufferProcessor indicating the type + of buffering that the host API uses. + + The mode used depends on whether the host API or the implementation manages + the buffers, and how these buffers are used (scatter gather, circular buffer). +*/ +typedef enum { +/** The host buffer size is a fixed known size. */ + paUtilFixedHostBufferSize, + +/** The host buffer size may vary, but has a known maximum size. */ + paUtilBoundedHostBufferSize, + +/** Nothing is known about the host buffer size. */ + paUtilUnknownHostBufferSize, + +/** The host buffer size varies, and the client does not require the buffer + processor to consume all of the input and fill all of the output buffer. This + is useful when the implementation has access to the host API's circular buffer + and only needs to consume/fill some of it, not necessarily all of it, with each + call to the buffer processor. This is the only mode where + PaUtil_EndBufferProcessing() may not consume the whole buffer. +*/ + paUtilVariableHostBufferSizePartialUsageAllowed +}PaUtilHostBufferSizeMode; + + +/** @brief An auxilliary data structure used internally by the buffer processor + to represent host input and output buffers. */ +typedef struct PaUtilChannelDescriptor{ + void *data; + unsigned int stride; /**< stride in samples, not bytes */ +}PaUtilChannelDescriptor; + + +/** @brief The main buffer processor data structure. + + Allocate one of these, initialize it with PaUtil_InitializeBufferProcessor + and terminate it with PaUtil_TerminateBufferProcessor. +*/ +typedef struct { + unsigned long framesPerUserBuffer; + unsigned long framesPerHostBuffer; + + PaUtilHostBufferSizeMode hostBufferSizeMode; + int useNonAdaptingProcess; + unsigned long framesPerTempBuffer; + + unsigned int inputChannelCount; + unsigned int bytesPerHostInputSample; + unsigned int bytesPerUserInputSample; + int userInputIsInterleaved; + PaUtilConverter *inputConverter; + PaUtilZeroer *inputZeroer; + + unsigned int outputChannelCount; + unsigned int bytesPerHostOutputSample; + unsigned int bytesPerUserOutputSample; + int userOutputIsInterleaved; + PaUtilConverter *outputConverter; + PaUtilZeroer *outputZeroer; + + unsigned long initialFramesInTempInputBuffer; + unsigned long initialFramesInTempOutputBuffer; + + void *tempInputBuffer; /**< used for slips, block adaption, and conversion. */ + void **tempInputBufferPtrs; /**< storage for non-interleaved buffer pointers, NULL for interleaved user input */ + unsigned long framesInTempInputBuffer; /**< frames remaining in input buffer from previous adaption iteration */ + + void *tempOutputBuffer; /**< used for slips, block adaption, and conversion. */ + void **tempOutputBufferPtrs; /**< storage for non-interleaved buffer pointers, NULL for interleaved user output */ + unsigned long framesInTempOutputBuffer; /**< frames remaining in input buffer from previous adaption iteration */ + + PaStreamCallbackTimeInfo *timeInfo; + + PaStreamCallbackFlags callbackStatusFlags; + + unsigned long hostInputFrameCount[2]; + PaUtilChannelDescriptor *hostInputChannels[2]; /**< pointers to arrays of channel descriptors. + pointers are NULL for half-duplex output processing. + hostInputChannels[i].data is NULL when the caller + calls PaUtil_SetNoInput() + */ + unsigned long hostOutputFrameCount[2]; + PaUtilChannelDescriptor *hostOutputChannels[2]; /**< pointers to arrays of channel descriptors. + pointers are NULL for half-duplex input processing. + hostOutputChannels[i].data is NULL when the caller + calls PaUtil_SetNoOutput() + */ + + PaUtilTriangularDitherGenerator ditherGenerator; + + double samplePeriod; + + PaStreamCallback *streamCallback; + void *userData; +} PaUtilBufferProcessor; + + +/** @name Initialization, termination, resetting and info */ +/*@{*/ + +/** Initialize a buffer processor's representation stored in a + PaUtilBufferProcessor structure. Be sure to call + PaUtil_TerminateBufferProcessor after finishing with a buffer processor. + + @param bufferProcessor The buffer processor structure to initialize. + + @param inputChannelCount The number of input channels as passed to + Pa_OpenStream or 0 for an output-only stream. + + @param userInputSampleFormat Format of user input samples, as passed to + Pa_OpenStream. This parameter is ignored for ouput-only streams. + + @param hostInputSampleFormat Format of host input samples. This parameter is + ignored for output-only streams. See note about host buffer interleave below. + + @param outputChannelCount The number of output channels as passed to + Pa_OpenStream or 0 for an input-only stream. + + @param userOutputSampleFormat Format of user output samples, as passed to + Pa_OpenStream. This parameter is ignored for input-only streams. + + @param hostOutputSampleFormat Format of host output samples. This parameter is + ignored for input-only streams. See note about host buffer interleave below. + + @param sampleRate Sample rate of the stream. The more accurate this is the + better - it is used for updating time stamps when adapting buffers. + + @param streamFlags Stream flags as passed to Pa_OpenStream, this parameter is + used for selecting special sample conversion options such as clipping and + dithering. + + @param framesPerUserBuffer Number of frames per user buffer, as requested + by the framesPerBuffer parameter to Pa_OpenStream. This parameter may be + zero to indicate that the user will accept any (and varying) buffer sizes. + + @param framesPerHostBuffer Specifies the number of frames per host buffer + for the fixed buffer size mode, and the maximum number of frames + per host buffer for the bounded host buffer size mode. It is ignored for + the other modes. + + @param hostBufferSizeMode A mode flag indicating the size variability of + host buffers that will be passed to the buffer processor. See + PaUtilHostBufferSizeMode for further details. + + @param streamCallback The user stream callback passed to Pa_OpenStream. + + @param userData The user data field passed to Pa_OpenStream. + + @note The interleave flag is ignored for host buffer formats. Host + interleave is determined by the use of different SetInput and SetOutput + functions. + + @return An error code indicating whether the initialization was successful. + If the error code is not PaNoError, the buffer processor was not initialized + and should not be used. + + @see Pa_OpenStream, PaUtilHostBufferSizeMode, PaUtil_TerminateBufferProcessor +*/ +PaError PaUtil_InitializeBufferProcessor( PaUtilBufferProcessor* bufferProcessor, + int inputChannelCount, PaSampleFormat userInputSampleFormat, + PaSampleFormat hostInputSampleFormat, + int outputChannelCount, PaSampleFormat userOutputSampleFormat, + PaSampleFormat hostOutputSampleFormat, + double sampleRate, + PaStreamFlags streamFlags, + unsigned long framesPerUserBuffer, /* 0 indicates don't care */ + unsigned long framesPerHostBuffer, + PaUtilHostBufferSizeMode hostBufferSizeMode, + PaStreamCallback *streamCallback, void *userData ); + + +/** Terminate a buffer processor's representation. Deallocates any temporary + buffers allocated by PaUtil_InitializeBufferProcessor. + + @param bufferProcessor The buffer processor structure to terminate. + + @see PaUtil_InitializeBufferProcessor. +*/ +void PaUtil_TerminateBufferProcessor( PaUtilBufferProcessor* bufferProcessor ); + + +/** Clear any internally buffered data. If you call + PaUtil_InitializeBufferProcessor in your OpenStream routine, make sure you + call PaUtil_ResetBufferProcessor in your StartStream call. + + @param bufferProcessor The buffer processor to reset. +*/ +void PaUtil_ResetBufferProcessor( PaUtilBufferProcessor* bufferProcessor ); + + +/** Retrieve the input latency of a buffer processor. + + @param bufferProcessor The buffer processor examine. + + @return The input latency introduced by the buffer processor, in frames. + + @see PaUtil_GetBufferProcessorOutputLatency +*/ +unsigned long PaUtil_GetBufferProcessorInputLatency( PaUtilBufferProcessor* bufferProcessor ); + +/** Retrieve the output latency of a buffer processor. + + @param bufferProcessor The buffer processor examine. + + @return The output latency introduced by the buffer processor, in frames. + + @see PaUtil_GetBufferProcessorInputLatency +*/ +unsigned long PaUtil_GetBufferProcessorOutputLatency( PaUtilBufferProcessor* bufferProcessor ); + +/*@}*/ + + +/** @name Host buffer pointer configuration + + Functions to set host input and output buffers, used by both callback streams + and blocking read/write streams. +*/ +/*@{*/ + + +/** Set the number of frames in the input host buffer(s) specified by the + PaUtil_Set*InputChannel functions. + + @param bufferProcessor The buffer processor. + + @param frameCount The number of host input frames. A 0 frameCount indicates to + use the framesPerHostBuffer value passed to PaUtil_InitializeBufferProcessor. + + @see PaUtil_SetNoInput, PaUtil_SetInputChannel, + PaUtil_SetInterleavedInputChannels, PaUtil_SetNonInterleavedInputChannel +*/ +void PaUtil_SetInputFrameCount( PaUtilBufferProcessor* bufferProcessor, + unsigned long frameCount ); + + +/** Indicate that no input is avalable. This function should be used when + priming the output of a full-duplex stream opened with the + paPrimeOutputBuffersUsingStreamCallback flag. Note that it is not necessary + to call this or any othe PaUtil_Set*Input* functions for ouput-only streams. + + @param bufferProcessor The buffer processor. +*/ +void PaUtil_SetNoInput( PaUtilBufferProcessor* bufferProcessor ); + + +/** Provide the buffer processor with a pointer to a host input channel. + + @param bufferProcessor The buffer processor. + @param channel The channel number. + @param data The buffer. + @param stride The stride from one sample to the next, in samples. For + interleaved host buffers, the stride will usually be the same as the number of + channels in the buffer. +*/ +void PaUtil_SetInputChannel( PaUtilBufferProcessor* bufferProcessor, + unsigned int channel, void *data, unsigned int stride ); + + +/** Provide the buffer processor with a pointer to an number of interleaved + host input channels. + + @param bufferProcessor The buffer processor. + @param firstChannel The first channel number. + @param data The buffer. + @param channelCount The number of interleaved channels in the buffer. If + channelCount is zero, the number of channels specified to + PaUtil_InitializeBufferProcessor will be used. +*/ +void PaUtil_SetInterleavedInputChannels( PaUtilBufferProcessor* bufferProcessor, + unsigned int firstChannel, void *data, unsigned int channelCount ); + + +/** Provide the buffer processor with a pointer to one non-interleaved host + output channel. + + @param bufferProcessor The buffer processor. + @param channel The channel number. + @param data The buffer. +*/ +void PaUtil_SetNonInterleavedInputChannel( PaUtilBufferProcessor* bufferProcessor, + unsigned int channel, void *data ); + + +/** Use for the second buffer half when the input buffer is split in two halves. + @see PaUtil_SetInputFrameCount +*/ +void PaUtil_Set2ndInputFrameCount( PaUtilBufferProcessor* bufferProcessor, + unsigned long frameCount ); + +/** Use for the second buffer half when the input buffer is split in two halves. + @see PaUtil_SetInputChannel +*/ +void PaUtil_Set2ndInputChannel( PaUtilBufferProcessor* bufferProcessor, + unsigned int channel, void *data, unsigned int stride ); + +/** Use for the second buffer half when the input buffer is split in two halves. + @see PaUtil_SetInterleavedInputChannels +*/ +void PaUtil_Set2ndInterleavedInputChannels( PaUtilBufferProcessor* bufferProcessor, + unsigned int firstChannel, void *data, unsigned int channelCount ); + +/** Use for the second buffer half when the input buffer is split in two halves. + @see PaUtil_SetNonInterleavedInputChannel +*/ +void PaUtil_Set2ndNonInterleavedInputChannel( PaUtilBufferProcessor* bufferProcessor, + unsigned int channel, void *data ); + + +/** Set the number of frames in the output host buffer(s) specified by the + PaUtil_Set*OutputChannel functions. + + @param bufferProcessor The buffer processor. + + @param frameCount The number of host output frames. A 0 frameCount indicates to + use the framesPerHostBuffer value passed to PaUtil_InitializeBufferProcessor. + + @see PaUtil_SetOutputChannel, PaUtil_SetInterleavedOutputChannels, + PaUtil_SetNonInterleavedOutputChannel +*/ +void PaUtil_SetOutputFrameCount( PaUtilBufferProcessor* bufferProcessor, + unsigned long frameCount ); + + +/** Indicate that the output will be discarded. This function should be used + when implementing the paNeverDropInput mode for full duplex streams. + + @param bufferProcessor The buffer processor. +*/ +void PaUtil_SetNoOutput( PaUtilBufferProcessor* bufferProcessor ); + + +/** Provide the buffer processor with a pointer to a host output channel. + + @param bufferProcessor The buffer processor. + @param channel The channel number. + @param data The buffer. + @param stride The stride from one sample to the next, in samples. For + interleaved host buffers, the stride will usually be the same as the number of + channels in the buffer. +*/ +void PaUtil_SetOutputChannel( PaUtilBufferProcessor* bufferProcessor, + unsigned int channel, void *data, unsigned int stride ); + + +/** Provide the buffer processor with a pointer to a number of interleaved + host output channels. + + @param bufferProcessor The buffer processor. + @param firstChannel The first channel number. + @param data The buffer. + @param channelCount The number of interleaved channels in the buffer. If + channelCount is zero, the number of channels specified to + PaUtil_InitializeBufferProcessor will be used. +*/ +void PaUtil_SetInterleavedOutputChannels( PaUtilBufferProcessor* bufferProcessor, + unsigned int firstChannel, void *data, unsigned int channelCount ); + + +/** Provide the buffer processor with a pointer to one non-interleaved host + output channel. + + @param bufferProcessor The buffer processor. + @param channel The channel number. + @param data The buffer. +*/ +void PaUtil_SetNonInterleavedOutputChannel( PaUtilBufferProcessor* bufferProcessor, + unsigned int channel, void *data ); + + +/** Use for the second buffer half when the output buffer is split in two halves. + @see PaUtil_SetOutputFrameCount +*/ +void PaUtil_Set2ndOutputFrameCount( PaUtilBufferProcessor* bufferProcessor, + unsigned long frameCount ); + +/** Use for the second buffer half when the output buffer is split in two halves. + @see PaUtil_SetOutputChannel +*/ +void PaUtil_Set2ndOutputChannel( PaUtilBufferProcessor* bufferProcessor, + unsigned int channel, void *data, unsigned int stride ); + +/** Use for the second buffer half when the output buffer is split in two halves. + @see PaUtil_SetInterleavedOutputChannels +*/ +void PaUtil_Set2ndInterleavedOutputChannels( PaUtilBufferProcessor* bufferProcessor, + unsigned int firstChannel, void *data, unsigned int channelCount ); + +/** Use for the second buffer half when the output buffer is split in two halves. + @see PaUtil_SetNonInterleavedOutputChannel +*/ +void PaUtil_Set2ndNonInterleavedOutputChannel( PaUtilBufferProcessor* bufferProcessor, + unsigned int channel, void *data ); + +/*@}*/ + + +/** @name Buffer processing functions for callback streams +*/ +/*@{*/ + +/** Commence processing a host buffer (or a pair of host buffers in the + full-duplex case) for a callback stream. + + @param bufferProcessor The buffer processor. + + @param timeInfo Timing information for the first sample of the host + buffer(s). This information may be adjusted when buffer adaption is being + performed. + + @param callbackStatusFlags Flags indicating whether underruns and overruns + have occurred since the last time the buffer processor was called. +*/ +void PaUtil_BeginBufferProcessing( PaUtilBufferProcessor* bufferProcessor, + PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags callbackStatusFlags ); + + +/** Finish processing a host buffer (or a pair of host buffers in the + full-duplex case) for a callback stream. + + @param bufferProcessor The buffer processor. + + @param callbackResult On input, indicates a previous callback result, and on + exit, the result of the user stream callback, if it is called. + On entry callbackResult should contain one of { paContinue, paComplete, or + paAbort}. If paComplete is passed, the stream callback will not be called + but any audio that was generated by previous stream callbacks will be copied + to the output buffer(s). You can check whether the buffer processor's internal + buffer is empty by calling PaUtil_IsBufferProcessorOutputEmpty. + + If the stream callback is called its result is stored in *callbackResult. If + the stream callback returns paComplete or paAbort, all output buffers will be + full of valid data - some of which may be zeros to acount for data that + wasn't generated by the terminating callback. + + @return The number of frames processed. This usually corresponds to the + number of frames specified by the PaUtil_Set*FrameCount functions, exept in + the paUtilVariableHostBufferSizePartialUsageAllowed buffer size mode when a + smaller value may be returned. +*/ +unsigned long PaUtil_EndBufferProcessing( PaUtilBufferProcessor* bufferProcessor, + int *callbackResult ); + + +/** Determine whether any callback generated output remains in the bufffer + processor's internal buffers. This method may be used to determine when to + continue calling PaUtil_EndBufferProcessing() after the callback has returned + a callbackResult of paComplete. + + @param bufferProcessor The buffer processor. + + @return Returns non-zero when callback generated output remains in the internal + buffer and zero (0) when there internal buffer contains no callback generated + data. +*/ +int PaUtil_IsBufferProcessorOutputEmpty( PaUtilBufferProcessor* bufferProcessor ); + +/*@}*/ + + +/** @name Buffer processing functions for blocking read/write streams +*/ +/*@{*/ + +/** Copy samples from host input channels set up by the PaUtil_Set*InputChannels + functions to a user supplied buffer. This function is intended for use with + blocking read/write streams. Copies the minimum of the number of + user frames (specified by the frameCount parameter) and the number of available + host frames (specified in a previous call to SetInputFrameCount()). + + @param bufferProcessor The buffer processor. + + @param buffer A pointer to the user buffer pointer, or a pointer to a pointer + to an array of user buffer pointers for a non-interleaved stream. It is + important that this parameter points to a copy of the user buffer pointers, + not to the actual user buffer pointers, because this function updates the + pointers before returning. + + @param frameCount The number of frames of data in the buffer(s) pointed to by + the buffer parameter. + + @return The number of frames copied. The buffer pointer(s) pointed to by the + buffer parameter are advanced to point to the frame(s) following the last one + filled. +*/ +unsigned long PaUtil_CopyInput( PaUtilBufferProcessor* bufferProcessor, + void **buffer, unsigned long frameCount ); + + +/* Copy samples from a user supplied buffer to host output channels set up by + the PaUtil_Set*OutputChannels functions. This function is intended for use with + blocking read/write streams. Copies the minimum of the number of + user frames (specified by the frameCount parameter) and the number of + host frames (specified in a previous call to SetOutputFrameCount()). + + @param bufferProcessor The buffer processor. + + @param buffer A pointer to the user buffer pointer, or a pointer to a pointer + to an array of user buffer pointers for a non-interleaved stream. It is + important that this parameter points to a copy of the user buffer pointers, + not to the actual user buffer pointers, because this function updates the + pointers before returning. + + @param frameCount The number of frames of data in the buffer(s) pointed to by + the buffer parameter. + + @return The number of frames copied. The buffer pointer(s) pointed to by the + buffer parameter are advanced to point to the frame(s) following the last one + copied. +*/ +unsigned long PaUtil_CopyOutput( PaUtilBufferProcessor* bufferProcessor, + const void ** buffer, unsigned long frameCount ); + + +/* Zero samples in host output channels set up by the PaUtil_Set*OutputChannels + functions. This function is useful for flushing streams. + Zeros the minimum of frameCount and the number of host frames specified in a + previous call to SetOutputFrameCount(). + + @param bufferProcessor The buffer processor. + + @param frameCount The maximum number of frames to zero. + + @return The number of frames zeroed. +*/ +unsigned long PaUtil_ZeroOutput( PaUtilBufferProcessor* bufferProcessor, + unsigned long frameCount ); + + +/*@}*/ + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PA_PROCESS_H */ diff --git a/src/audio/portaudio/pa_common/pa_skeleton.c b/src/audio/portaudio/pa_common/pa_skeleton.c new file mode 100644 index 0000000000000000000000000000000000000000..51f95c8a3201d5605baf89dece660c402e8c9a7e --- /dev/null +++ b/src/audio/portaudio/pa_common/pa_skeleton.c @@ -0,0 +1,807 @@ +/* + * $Id$ + * Portable Audio I/O Library skeleton implementation + * demonstrates how to use the common functions to implement support + * for a host API + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, 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. + * + * 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. + * + * 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. + */ + +/** @file + @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 + which much be customised for each implementation. +*/ + + +#include <string.h> /* strlen() */ + +#include "pa_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" + + +/* prototypes for functions declared in this file */ + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +PaError PaSkeleton_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); +static PaError CloseStream( PaStream* stream ); +static PaError StartStream( PaStream *stream ); +static PaError StopStream( PaStream *stream ); +static PaError AbortStream( PaStream *stream ); +static PaError IsStreamStopped( PaStream *s ); +static PaError IsStreamActive( PaStream *stream ); +static PaTime GetStreamTime( PaStream *stream ); +static double GetStreamCpuLoad( PaStream* stream ); +static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames ); +static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames ); +static signed long GetStreamReadAvailable( PaStream* stream ); +static signed long GetStreamWriteAvailable( PaStream* stream ); + + +/* IMPLEMENT ME: a macro like the following one should be used for reporting + host errors */ +#define PA_SKELETON_SET_LAST_HOST_ERROR( errorCode, errorText ) \ + PaUtil_SetLastHostErrorInfo( paInDevelopment, errorCode, errorText ) + +/* PaSkeletonHostApiRepresentation - host api datastructure specific to this implementation */ + +typedef struct +{ + PaUtilHostApiRepresentation inheritedHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup *allocations; + + /* implementation specific data goes here */ +} +PaSkeletonHostApiRepresentation; /* IMPLEMENT ME: rename this */ + + +PaError PaSkeleton_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + int i, deviceCount; + PaSkeletonHostApiRepresentation *skeletonHostApi; + PaDeviceInfo *deviceInfoArray; + + skeletonHostApi = (PaSkeletonHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaSkeletonHostApiRepresentation) ); + if( !skeletonHostApi ) + { + result = paInsufficientMemory; + goto error; + } + + skeletonHostApi->allocations = PaUtil_CreateAllocationGroup(); + if( !skeletonHostApi->allocations ) + { + result = paInsufficientMemory; + goto error; + } + + *hostApi = &skeletonHostApi->inheritedHostApiRep; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paInDevelopment; /* IMPLEMENT ME: change to correct type id */ + (*hostApi)->info.name = "skeleton implementation"; /* IMPLEMENT ME: change to correct name */ + + (*hostApi)->info.defaultInputDevice = paNoDevice; /* IMPLEMENT ME */ + (*hostApi)->info.defaultOutputDevice = paNoDevice; /* IMPLEMENT ME */ + + (*hostApi)->info.deviceCount = 0; + + deviceCount = 0; /* IMPLEMENT ME */ + + if( deviceCount > 0 ) + { + (*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + skeletonHostApi->allocations, sizeof(PaDeviceInfo*) * deviceCount ); + if( !(*hostApi)->deviceInfos ) + { + result = paInsufficientMemory; + goto error; + } + + /* allocate all device info structs in a contiguous block */ + deviceInfoArray = (PaDeviceInfo*)PaUtil_GroupAllocateMemory( + skeletonHostApi->allocations, sizeof(PaDeviceInfo) * deviceCount ); + if( !deviceInfoArray ) + { + result = paInsufficientMemory; + goto error; + } + + for( i=0; i < deviceCount; ++i ) + { + PaDeviceInfo *deviceInfo = &deviceInfoArray[i]; + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; + deviceInfo->name = 0; /* IMPLEMENT ME: allocate block and copy name eg: + deviceName = (char*)PaUtil_GroupAllocateMemory( skeletonHostApi->allocations, strlen(srcName) + 1 ); + if( !deviceName ) + { + result = paInsufficientMemory; + goto error; + } + strcpy( deviceName, srcName ); + deviceInfo->name = deviceName; + */ + + deviceInfo->maxInputChannels = 0; /* IMPLEMENT ME */ + deviceInfo->maxOutputChannels = 0; /* IMPLEMENT ME */ + + deviceInfo->defaultLowInputLatency = 0.; /* IMPLEMENT ME */ + deviceInfo->defaultLowOutputLatency = 0.; /* IMPLEMENT ME */ + deviceInfo->defaultHighInputLatency = 0.; /* IMPLEMENT ME */ + deviceInfo->defaultHighOutputLatency = 0.; /* IMPLEMENT ME */ + + deviceInfo->defaultSampleRate = 0.; /* IMPLEMENT ME */ + + (*hostApi)->deviceInfos[i] = deviceInfo; + ++(*hostApi)->info.deviceCount; + } + } + + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PaUtil_InitializeStreamInterface( &skeletonHostApi->callbackStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface( &skeletonHostApi->blockingStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable ); + + return result; + +error: + if( skeletonHostApi ) + { + if( skeletonHostApi->allocations ) + { + PaUtil_FreeAllAllocations( skeletonHostApi->allocations ); + PaUtil_DestroyAllocationGroup( skeletonHostApi->allocations ); + } + + PaUtil_FreeMemory( skeletonHostApi ); + } + return result; +} + + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaSkeletonHostApiRepresentation *skeletonHostApi = (PaSkeletonHostApiRepresentation*)hostApi; + + /* + IMPLEMENT ME: + - clean up any resources not handled by the allocation group + */ + + if( skeletonHostApi->allocations ) + { + PaUtil_FreeAllAllocations( skeletonHostApi->allocations ); + PaUtil_DestroyAllocationGroup( skeletonHostApi->allocations ); + } + + PaUtil_FreeMemory( skeletonHostApi ); +} + + +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* all standard sample formats are supported by the buffer adapter, + this implementation doesn't support any custom sample formats */ + if( inputSampleFormat & paCustomFormat ) + return paSampleFormatNotSupported; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + + /* validate inputStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + inputChannelCount = 0; + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* all standard sample formats are supported by the buffer adapter, + this implementation doesn't support any custom sample formats */ + if( outputSampleFormat & paCustomFormat ) + return paSampleFormatNotSupported; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that output device can support outputChannelCount */ + if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + + /* validate outputStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + outputChannelCount = 0; + } + + /* + IMPLEMENT ME: + + - if a full duplex stream is requested, check that the combination + of input and output parameters is supported if necessary + + - check that the device supports sampleRate + + Because the buffer adapter handles conversion between all standard + sample formats, the following checks are only required if paCustomFormat + is implemented, or under some other unusual conditions. + + - check that input device can support inputSampleFormat, or that + we have the capability to convert from inputSampleFormat to + a native format + + - check that output device can support outputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + */ + + + /* suppress unused variable warnings */ + (void) sampleRate; + + return paFormatIsSupported; +} + +/* PaSkeletonStream - a stream data structure specifically for this implementation */ + +typedef struct PaSkeletonStream +{ /* IMPLEMENT ME: rename this */ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + + /* IMPLEMENT ME: + - implementation specific data goes here + */ + unsigned long framesPerHostCallback; /* just an example */ +} +PaSkeletonStream; + +/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */ + +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ) +{ + PaError result = paNoError; + PaSkeletonHostApiRepresentation *skeletonHostApi = (PaSkeletonHostApiRepresentation*)hostApi; + PaSkeletonStream *stream = 0; + unsigned long framesPerHostBuffer = framesPerBuffer; /* these may not be equivalent for all implementations */ + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat; + + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + + /* validate inputStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + + /* IMPLEMENT ME - establish which host formats are available */ + hostInputSampleFormat = + PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, inputSampleFormat ); + } + else + { + inputChannelCount = 0; + inputSampleFormat = hostInputSampleFormat = paInt16; /* Surpress 'uninitialised var' warnings. */ + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that output device can support inputChannelCount */ + if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + + /* validate outputStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + + /* IMPLEMENT ME - establish which host formats are available */ + hostOutputSampleFormat = + PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, outputSampleFormat ); + } + else + { + outputChannelCount = 0; + outputSampleFormat = hostOutputSampleFormat = paInt16; /* Surpress 'uninitialized var' warnings. */ + } + + /* + IMPLEMENT ME: + + ( the following two checks are taken care of by PaUtil_InitializeBufferProcessor() FIXME - checks needed? ) + + - check that input device can support inputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + + - check that output device can support outputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + + - if a full duplex stream is requested, check that the combination + of input and output parameters is supported + + - check that the device supports sampleRate + + - alter sampleRate to a close allowable rate if possible / necessary + + - validate suggestedInputLatency and suggestedOutputLatency parameters, + use default values where necessary + */ + + + + + /* validate platform specific flags */ + if( (streamFlags & paPlatformSpecificFlags) != 0 ) + return paInvalidFlag; /* unexpected platform specific flag */ + + + stream = (PaSkeletonStream*)PaUtil_AllocateMemory( sizeof(PaSkeletonStream) ); + if( !stream ) + { + result = paInsufficientMemory; + goto error; + } + + if( streamCallback ) + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &skeletonHostApi->callbackStreamInterface, streamCallback, userData ); + } + else + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &skeletonHostApi->blockingStreamInterface, streamCallback, userData ); + } + + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); + + + /* we assume a fixed host buffer size in this example, but the buffer processor + can also support bounded and unknown host buffer sizes by passing + paUtilBoundedHostBufferSize or paUtilUnknownHostBufferSize instead of + paUtilFixedHostBufferSize below. */ + + result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, + inputChannelCount, inputSampleFormat, hostInputSampleFormat, + outputChannelCount, outputSampleFormat, hostOutputSampleFormat, + sampleRate, streamFlags, framesPerBuffer, + framesPerHostBuffer, paUtilFixedHostBufferSize, + streamCallback, userData ); + if( result != paNoError ) + goto error; + + + /* + IMPLEMENT ME: initialise the following fields with estimated or actual + values. + */ + stream->streamRepresentation.streamInfo.inputLatency = + PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor); + stream->streamRepresentation.streamInfo.outputLatency = + PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor); + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + + + /* + IMPLEMENT ME: + - additional stream setup + opening + */ + + stream->framesPerHostCallback = framesPerHostBuffer; + + *s = (PaStream*)stream; + + return result; + +error: + if( stream ) + PaUtil_FreeMemory( stream ); + + return result; +} + +/* + ExampleHostProcessingLoop() illustrates the kind of processing which may + occur in a host implementation. + +*/ +static void ExampleHostProcessingLoop( void *inputBuffer, void *outputBuffer, void *userData ) +{ + PaSkeletonStream *stream = (PaSkeletonStream*)userData; + PaStreamCallbackTimeInfo timeInfo = {0,0,0}; /* IMPLEMENT ME */ + int callbackResult; + unsigned long framesProcessed; + + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + + /* + IMPLEMENT ME: + - generate timing information + - handle buffer slips + */ + + /* + If you need to byte swap or shift inputBuffer to convert it into a + portaudio format, do it here. + */ + + + + PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, 0 /* IMPLEMENT ME: pass underflow/overflow flags when necessary */ ); + + /* + depending on whether the host buffers are interleaved, non-interleaved + or a mixture, you will want to call PaUtil_SetInterleaved*Channels(), + PaUtil_SetNonInterleaved*Channel() or PaUtil_Set*Channel() here. + */ + + PaUtil_SetInputFrameCount( &stream->bufferProcessor, 0 /* default to host buffer size */ ); + PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, + 0, /* first channel of inputBuffer is channel 0 */ + inputBuffer, + 0 ); /* 0 - use inputChannelCount passed to init buffer processor */ + + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, 0 /* default to host buffer size */ ); + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, + 0, /* first channel of outputBuffer is channel 0 */ + outputBuffer, + 0 ); /* 0 - use outputChannelCount passed to init buffer processor */ + + /* you must pass a valid value of callback result to PaUtil_EndBufferProcessing() + in general you would pass paContinue for normal operation, and + paComplete to drain the buffer processor's internal output buffer. + You can check whether the buffer processor's output buffer is empty + using PaUtil_IsBufferProcessorOuputEmpty( bufferProcessor ) + */ + callbackResult = paContinue; + framesProcessed = PaUtil_EndBufferProcessing( &stream->bufferProcessor, &callbackResult ); + + + /* + If you need to byte swap or shift outputBuffer to convert it to + host format, do it here. + */ + + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed ); + + + if( callbackResult == paContinue ) + { + /* nothing special to do */ + } + else if( callbackResult == paAbort ) + { + /* IMPLEMENT ME - finish playback immediately */ + + /* once finished, call the finished callback */ + if( stream->streamRepresentation.streamFinishedCallback != 0 ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + } + else + { + /* User callback has asked us to stop with paComplete or other non-zero value */ + + /* IMPLEMENT ME - finish playback once currently queued audio has completed */ + + /* once finished, call the finished callback */ + if( stream->streamRepresentation.streamFinishedCallback != 0 ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + } +} + + +/* + When CloseStream() is called, the multi-api layer ensures that + the stream has already been stopped or aborted. +*/ +static PaError CloseStream( PaStream* s ) +{ + PaError result = paNoError; + PaSkeletonStream *stream = (PaSkeletonStream*)s; + + /* + IMPLEMENT ME: + - additional stream closing + cleanup + */ + + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + PaUtil_FreeMemory( stream ); + + return result; +} + + +static PaError StartStream( PaStream *s ) +{ + PaError result = paNoError; + PaSkeletonStream *stream = (PaSkeletonStream*)s; + + PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); + + /* IMPLEMENT ME, see portaudio.h for required behavior */ + + /* suppress unused function warning. the code in ExampleHostProcessingLoop or + something similar should be implemented to feed samples to and from the + host after StartStream() is called. + */ + (void) ExampleHostProcessingLoop; + + return result; +} + + +static PaError StopStream( PaStream *s ) +{ + PaError result = paNoError; + PaSkeletonStream *stream = (PaSkeletonStream*)s; + + /* suppress unused variable warnings */ + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior */ + + return result; +} + + +static PaError AbortStream( PaStream *s ) +{ + PaError result = paNoError; + PaSkeletonStream *stream = (PaSkeletonStream*)s; + + /* suppress unused variable warnings */ + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior */ + + return result; +} + + +static PaError IsStreamStopped( PaStream *s ) +{ + PaSkeletonStream *stream = (PaSkeletonStream*)s; + + /* suppress unused variable warnings */ + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior */ + + return 0; +} + + +static PaError IsStreamActive( PaStream *s ) +{ + PaSkeletonStream *stream = (PaSkeletonStream*)s; + + /* suppress unused variable warnings */ + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior */ + + return 0; +} + + +static PaTime GetStreamTime( PaStream *s ) +{ + PaSkeletonStream *stream = (PaSkeletonStream*)s; + + /* suppress unused variable warnings */ + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + + return 0; +} + + +static double GetStreamCpuLoad( PaStream* s ) +{ + PaSkeletonStream *stream = (PaSkeletonStream*)s; + + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); +} + + +/* + As separate stream interfaces are used for blocking and callback + streams, the following functions can be guaranteed to only be called + for blocking streams. +*/ + +static PaError ReadStream( PaStream* s, + void *buffer, + unsigned long frames ) +{ + PaSkeletonStream *stream = (PaSkeletonStream*)s; + + /* suppress unused variable warnings */ + (void) buffer; + (void) frames; + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + + return paNoError; +} + + +static PaError WriteStream( PaStream* s, + const void *buffer, + unsigned long frames ) +{ + PaSkeletonStream *stream = (PaSkeletonStream*)s; + + /* suppress unused variable warnings */ + (void) buffer; + (void) frames; + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + + return paNoError; +} + + +static signed long GetStreamReadAvailable( PaStream* s ) +{ + PaSkeletonStream *stream = (PaSkeletonStream*)s; + + /* suppress unused variable warnings */ + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + + return 0; +} + + +static signed long GetStreamWriteAvailable( PaStream* s ) +{ + PaSkeletonStream *stream = (PaSkeletonStream*)s; + + /* suppress unused variable warnings */ + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + + return 0; +} + + + + diff --git a/src/audio/portaudio/pa_common/pa_stream.c b/src/audio/portaudio/pa_common/pa_stream.c new file mode 100644 index 0000000000000000000000000000000000000000..e820671bc1c5b0daf47b364d94ff0d8471f8f8d3 --- /dev/null +++ b/src/audio/portaudio/pa_common/pa_stream.c @@ -0,0 +1,141 @@ +/* + * $Id$ + * Portable Audio I/O Library + * + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 2002 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. + * + * 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. + * + * 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. + */ + +/** @file + @brief Interface used by pa_front to virtualize functions which operate on + streams. +*/ + + +#include "pa_stream.h" + + +void PaUtil_InitializeStreamInterface( PaUtilStreamInterface *streamInterface, + PaError (*Close)( PaStream* ), + PaError (*Start)( PaStream* ), + PaError (*Stop)( PaStream* ), + PaError (*Abort)( PaStream* ), + PaError (*IsStopped)( PaStream* ), + PaError (*IsActive)( PaStream* ), + PaTime (*GetTime)( PaStream* ), + double (*GetCpuLoad)( PaStream* ), + PaError (*Read)( PaStream*, void *, unsigned long ), + PaError (*Write)( PaStream*, const void *, unsigned long ), + signed long (*GetReadAvailable)( PaStream* ), + signed long (*GetWriteAvailable)( PaStream* ) ) +{ + streamInterface->Close = Close; + streamInterface->Start = Start; + streamInterface->Stop = Stop; + streamInterface->Abort = Abort; + streamInterface->IsStopped = IsStopped; + streamInterface->IsActive = IsActive; + streamInterface->GetTime = GetTime; + streamInterface->GetCpuLoad = GetCpuLoad; + streamInterface->Read = Read; + streamInterface->Write = Write; + streamInterface->GetReadAvailable = GetReadAvailable; + streamInterface->GetWriteAvailable = GetWriteAvailable; +} + + +void PaUtil_InitializeStreamRepresentation( PaUtilStreamRepresentation *streamRepresentation, + PaUtilStreamInterface *streamInterface, + PaStreamCallback *streamCallback, + void *userData ) +{ + streamRepresentation->magic = PA_STREAM_MAGIC; + streamRepresentation->nextOpenStream = 0; + streamRepresentation->streamInterface = streamInterface; + streamRepresentation->streamCallback = streamCallback; + streamRepresentation->streamFinishedCallback = 0; + + streamRepresentation->userData = userData; + + streamRepresentation->streamInfo.inputLatency = 0.; + streamRepresentation->streamInfo.outputLatency = 0.; + streamRepresentation->streamInfo.sampleRate = 0.; +} + + +void PaUtil_TerminateStreamRepresentation( PaUtilStreamRepresentation *streamRepresentation ) +{ + streamRepresentation->magic = 0; +} + + +PaError PaUtil_DummyRead( PaStream* stream, + void *buffer, + unsigned long frames ) +{ + (void)stream; /* unused parameter */ + (void)buffer; /* unused parameter */ + (void)frames; /* unused parameter */ + + return paCanNotReadFromACallbackStream; +} + + +PaError PaUtil_DummyWrite( PaStream* stream, + const void *buffer, + unsigned long frames ) +{ + (void)stream; /* unused parameter */ + (void)buffer; /* unused parameter */ + (void)frames; /* unused parameter */ + + return paCanNotWriteToACallbackStream; +} + + +signed long PaUtil_DummyGetReadAvailable( PaStream* stream ) +{ + (void)stream; /* unused parameter */ + + return paCanNotReadFromACallbackStream; +} + + +signed long PaUtil_DummyGetWriteAvailable( PaStream* stream ) +{ + (void)stream; /* unused parameter */ + + return paCanNotWriteToACallbackStream; +} + + +double PaUtil_DummyGetCpuLoad( PaStream* stream ) +{ + (void)stream; /* unused parameter */ + + return 0.0; +} diff --git a/src/audio/portaudio/pa_common/pa_stream.h b/src/audio/portaudio/pa_common/pa_stream.h new file mode 100644 index 0000000000000000000000000000000000000000..aaf89cf210c6f02367b1fbc57176c73a7d64b930 --- /dev/null +++ b/src/audio/portaudio/pa_common/pa_stream.h @@ -0,0 +1,196 @@ +#ifndef PA_STREAM_H +#define PA_STREAM_H +/* + * $Id$ + * 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 + * + * 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. + * + * 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. + * + * 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. + */ + +/** @file + @brief Interface used by pa_front to virtualize functions which operate on + streams. +*/ + + +#include "portaudio.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +#define PA_STREAM_MAGIC (0x18273645) + + +/** A structure representing an (abstract) interface to a host API. Contains + pointers to functions which implement the interface. + + All PaStreamInterface functions are guaranteed to be called with a non-null, + valid stream parameter. +*/ +typedef struct { + PaError (*Close)( PaStream* stream ); + PaError (*Start)( PaStream *stream ); + PaError (*Stop)( PaStream *stream ); + PaError (*Abort)( PaStream *stream ); + PaError (*IsStopped)( PaStream *stream ); + PaError (*IsActive)( PaStream *stream ); + PaTime (*GetTime)( PaStream *stream ); + double (*GetCpuLoad)( PaStream* stream ); + PaError (*Read)( PaStream* stream, void *buffer, unsigned long frames ); + PaError (*Write)( PaStream* stream, const void *buffer, unsigned long frames ); + signed long (*GetReadAvailable)( PaStream* stream ); + signed long (*GetWriteAvailable)( PaStream* stream ); +} PaUtilStreamInterface; + + +/** Initialize the fields of a PaUtilStreamInterface structure. +*/ +void PaUtil_InitializeStreamInterface( PaUtilStreamInterface *streamInterface, + PaError (*Close)( PaStream* ), + PaError (*Start)( PaStream* ), + PaError (*Stop)( PaStream* ), + PaError (*Abort)( PaStream* ), + PaError (*IsStopped)( PaStream* ), + PaError (*IsActive)( PaStream* ), + PaTime (*GetTime)( PaStream* ), + double (*GetCpuLoad)( PaStream* ), + PaError (*Read)( PaStream* stream, void *buffer, unsigned long frames ), + PaError (*Write)( PaStream* stream, const void *buffer, unsigned long frames ), + signed long (*GetReadAvailable)( PaStream* stream ), + signed long (*GetWriteAvailable)( PaStream* stream ) ); + + +/** Dummy Read function for use in interfaces to a callback based streams. + Pass to the Read parameter of PaUtil_InitializeStreamInterface. + @return An error code indicating that the function has no effect + because the stream is a callback stream. +*/ +PaError PaUtil_DummyRead( PaStream* stream, + void *buffer, + unsigned long frames ); + + +/** Dummy Write function for use in an interfaces to callback based streams. + Pass to the Write parameter of PaUtil_InitializeStreamInterface. + @return An error code indicating that the function has no effect + because the stream is a callback stream. +*/ +PaError PaUtil_DummyWrite( PaStream* stream, + const void *buffer, + unsigned long frames ); + + +/** Dummy GetReadAvailable function for use in interfaces to callback based + streams. Pass to the GetReadAvailable parameter of PaUtil_InitializeStreamInterface. + @return An error code indicating that the function has no effect + because the stream is a callback stream. +*/ +signed long PaUtil_DummyGetReadAvailable( PaStream* stream ); + + +/** Dummy GetWriteAvailable function for use in interfaces to callback based + streams. Pass to the GetWriteAvailable parameter of PaUtil_InitializeStreamInterface. + @return An error code indicating that the function has no effect + because the stream is a callback stream. +*/ +signed long PaUtil_DummyGetWriteAvailable( PaStream* stream ); + + + +/** Dummy GetCpuLoad function for use in an interface to a read/write stream. + Pass to the GetCpuLoad parameter of PaUtil_InitializeStreamInterface. + @return Returns 0. +*/ +double PaUtil_DummyGetCpuLoad( PaStream* stream ); + + +/** Non host specific data for a stream. This data is used by pa_front to + forward to the appropriate functions in the streamInterface structure. +*/ +typedef struct PaUtilStreamRepresentation { + unsigned long magic; /**< set to PA_STREAM_MAGIC */ + struct PaUtilStreamRepresentation *nextOpenStream; /**< field used by multi-api code */ + PaUtilStreamInterface *streamInterface; + PaStreamCallback *streamCallback; + PaStreamFinishedCallback *streamFinishedCallback; + void *userData; + PaStreamInfo streamInfo; +} PaUtilStreamRepresentation; + + +/** Initialize a PaUtilStreamRepresentation structure. + + @see PaUtil_InitializeStreamRepresentation +*/ +void PaUtil_InitializeStreamRepresentation( + PaUtilStreamRepresentation *streamRepresentation, + PaUtilStreamInterface *streamInterface, + PaStreamCallback *streamCallback, + void *userData ); + + +/** Clean up a PaUtilStreamRepresentation structure previously initialized + by a call to PaUtil_InitializeStreamRepresentation. + + @see PaUtil_InitializeStreamRepresentation +*/ +void PaUtil_TerminateStreamRepresentation( PaUtilStreamRepresentation *streamRepresentation ); + + +/** Check that the stream pointer is valid. + + @return Returns paNoError if the stream pointer appears to be OK, otherwise + returns an error indicating the cause of failure. +*/ +PaError PaUtil_ValidateStreamPointer( PaStream *stream ); + + +/** Cast an opaque stream pointer into a pointer to a PaUtilStreamRepresentation. + + @see PaUtilStreamRepresentation +*/ +#define PA_STREAM_REP( stream )\ + ((PaUtilStreamRepresentation*) (stream) ) + + +/** Cast an opaque stream pointer into a pointer to a PaUtilStreamInterface. + + @see PaUtilStreamRepresentation, PaUtilStreamInterface +*/ +#define PA_STREAM_INTERFACE( stream )\ + PA_STREAM_REP( (stream) )->streamInterface + + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PA_STREAM_H */ diff --git a/src/audio/portaudio/pa_common/pa_trace.c b/src/audio/portaudio/pa_common/pa_trace.c new file mode 100644 index 0000000000000000000000000000000000000000..d0dabf9eb7f308e60dee6136d23eea54d18f8c33 --- /dev/null +++ b/src/audio/portaudio/pa_common/pa_trace.c @@ -0,0 +1,88 @@ +/* + * $Id$ + * Portable Audio I/O Library Trace Facility + * Store trace information in real-time for later printing. + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2000 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. + * + * 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. + * + * 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. + */ + +/** @file + @brief Event trace mechanism for debugging. +*/ + + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "pa_trace.h" + +#if PA_TRACE_REALTIME_EVENTS + +static char *traceTextArray[MAX_TRACE_RECORDS]; +static int traceIntArray[MAX_TRACE_RECORDS]; +static int traceIndex = 0; +static int traceBlock = 0; + +/*********************************************************************/ +void PaUtil_ResetTraceMessages() +{ + traceIndex = 0; +} + +/*********************************************************************/ +void PaUtil_DumpTraceMessages() +{ + int i; + int messageCount = (traceIndex < PA_MAX_TRACE_RECORDS) ? traceIndex : PA_MAX_TRACE_RECORDS; + + printf("DumpTraceMessages: traceIndex = %d\n", traceIndex ); + for( i=0; i<messageCount; i++ ) + { + printf("%3d: %s = 0x%08X\n", + i, traceTextArray[i], traceIntArray[i] ); + } + ResetTraceMessages(); + fflush(stdout); +} + +/*********************************************************************/ +void PaUtil_AddTraceMessage( const char *msg, int data ) +{ + if( (traceIndex == PA_MAX_TRACE_RECORDS) && (traceBlock == 0) ) + { + traceBlock = 1; + /* PaUtil_DumpTraceMessages(); */ + } + else if( traceIndex < PA_MAX_TRACE_RECORDS ) + { + traceTextArray[traceIndex] = msg; + traceIntArray[traceIndex] = data; + traceIndex++; + } +} + +#endif /* TRACE_REALTIME_EVENTS */ diff --git a/src/audio/portaudio/pa_common/pa_trace.h b/src/audio/portaudio/pa_common/pa_trace.h new file mode 100644 index 0000000000000000000000000000000000000000..256c3e6911bf887d76b5a36d9ed9a972ab34f81f --- /dev/null +++ b/src/audio/portaudio/pa_common/pa_trace.h @@ -0,0 +1,70 @@ +#ifndef PA_TRACE_H +#define PA_TRACE_H +/* + * $Id$ + * Portable Audio I/O Library Trace Facility + * Store trace information in real-time for later printing. + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2000 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. + * + * 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. + * + * 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. + */ + +/** @file + @brief Event trace mechanism for debugging. + + Allows data to be written to the buffer at interrupt time and dumped later. +*/ + + +#define PA_TRACE_REALTIME_EVENTS (0) /* Keep log of various real-time events. */ +#define PA_MAX_TRACE_RECORDS (2048) + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +#if PA_TRACE_REALTIME_EVENTS + +void PaUtil_ResetTraceMessages(); +void PaUtil_AddTraceMessage( const char *msg, int data ); +void PaUtil_DumpTraceMessages(); + +#else + +#define PaUtil_ResetTraceMessages() /* noop */ +#define PaUtil_AddTraceMessage(msg,data) /* noop */ +#define PaUtil_DumpTraceMessages() /* noop */ + +#endif + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* PA_TRACE_H */ diff --git a/src/audio/portaudio/pa_common/pa_types.h b/src/audio/portaudio/pa_common/pa_types.h new file mode 100644 index 0000000000000000000000000000000000000000..343dc8cf8a69284ef906fe62c7e76f7a3eb8fb35 --- /dev/null +++ b/src/audio/portaudio/pa_common/pa_types.h @@ -0,0 +1,65 @@ +#ifndef PA_TYPES_H +#define PA_TYPES_H + +/* + SIZEOF_SHORT, SIZEOF_INT and SIZEOF_LONG are set by the configure script + when it is used. Otherwise we default to the common 32 bit values, if your + platform doesn't use configure, and doesn't use the default values below + you will need to explicitly define these symbols in your make file. + + A PA_VALIDATE_SIZES macro is provided to assert that the values set in this + file are correct. +*/ + +#ifndef SIZEOF_SHORT +#define SIZEOF_SHORT 2 +#endif + +#ifndef SIZEOF_INT +#define SIZEOF_INT 4 +#endif + +#ifndef SIZEOF_LONG +#define SIZEOF_LONG 4 +#endif + + +#if SIZEOF_SHORT == 2 +typedef signed short PaInt16; +typedef unsigned short PaUint16; +#elif SIZEOF_INT == 2 +typedef signed int PaInt16; +typedef unsigned int PaUint16; +#else +#error pa_types.h was unable to determine which type to use for 16bit integers on the target platform +#endif + +#if SIZEOF_SHORT == 4 +typedef signed short PaInt32; +typedef unsigned short PaUint32; +#elif SIZEOF_INT == 4 +typedef signed int PaInt32; +typedef unsigned int PaUint32; +#elif SIZEOF_LONG == 4 +typedef signed long PaInt32; +typedef unsigned long PaUint32; +#else +#error pa_types.h was unable to determine which type to use for 32bit integers on the target platform +#endif + + +/* PA_VALIDATE_TYPE_SIZES compares the size of the integer types at runtime to + ensure that PortAudio was configured correctly, and raises an assertion if + they don't match the expected values. <assert.h> must be included in the + context in which this macro is used. +*/ +#define PA_VALIDATE_TYPE_SIZES \ + { \ + assert( "PortAudio: type sizes are not correct in pa_types.h" && sizeof( PaUint16 ) == 2 ); \ + assert( "PortAudio: type sizes are not correct in pa_types.h" && sizeof( PaInt16 ) == 2 ); \ + assert( "PortAudio: type sizes are not correct in pa_types.h" && sizeof( PaUint32 ) == 4 ); \ + assert( "PortAudio: type sizes are not correct in pa_types.h" && sizeof( PaInt32 ) == 4 ); \ + } + + +#endif /* PA_TYPES_H */ diff --git a/src/audio/portaudio/pa_common/pa_util.h b/src/audio/portaudio/pa_common/pa_util.h new file mode 100644 index 0000000000000000000000000000000000000000..4d060d814a7f95891c45e5eb5b251e25af230109 --- /dev/null +++ b/src/audio/portaudio/pa_common/pa_util.h @@ -0,0 +1,167 @@ +#ifndef PA_UTIL_H +#define PA_UTIL_H +/* + * $Id$ + * 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 + * + * 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. + * + * 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. + * + * 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. + */ + +/** @file + @brief Prototypes for utility functions used by PortAudio implementations. + + @todo Document and adhere to the alignment guarantees provided by + PaUtil_AllocateMemory(). +*/ + + +#include "portaudio.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +struct PaUtilHostApiRepresentation; + + +/** Retrieve a specific host API representation. This function can be used + by implementations to retrieve a pointer to their representation in + host api specific extension functions which aren't passed a rep pointer + by pa_front.c. + + @param hostApi A pointer to a host API represenation pointer. Apon success + this will receive the requested representation pointer. + + @param type A valid host API type identifier. + + @returns An error code. If the result is PaNoError then a pointer to the + requested host API representation will be stored in *hostApi. If the host API + specified by type is not found, this function returns paHostApiNotFound. +*/ +PaError PaUtil_GetHostApiRepresentation( struct PaUtilHostApiRepresentation **hostApi, + PaHostApiTypeId type ); + + +/** Convert a PortAudio device index into a host API specific device index. + @param hostApiDevice Pointer to a device index, on success this will recieve the + converted device index value. + @param device The PortAudio device index to convert. + @param hostApi The host api which the index should be converted for. + + @returns On success returns PaNoError and places the converted index in the + hostApiDevice parameter. +*/ +PaError PaUtil_DeviceIndexToHostApiDeviceIndex( + PaDeviceIndex *hostApiDevice, PaDeviceIndex device, + struct PaUtilHostApiRepresentation *hostApi ); + + +/** Set the host error information returned by Pa_GetLastHostErrorInfo. This + function and the paUnanticipatedHostError error code should be used as a + last resort. Implementors should use existing PA error codes where possible, + or nominate new ones. Note that at it is always better to use + PaUtil_SetLastHostErrorInfo() and paUnanticipatedHostError than to return an + ambiguous or inaccurate PaError code. + + @param hostApiType The host API which encountered the error (ie of the caller) + + @param errorCode The error code returned by the native API function. + + @param errorText A string describing the error. PaUtil_SetLastHostErrorInfo + makes a copy of the string, so it is not necessary for the pointer to remain + valid after the call to PaUtil_SetLastHostErrorInfo() returns. + +*/ +void PaUtil_SetLastHostErrorInfo( PaHostApiTypeId hostApiType, long errorCode, + const char *errorText ); + + + +/** PA_DEBUG() provides a simple debug message printing facility. The macro + passes it's argument to a printf-like function called PaUtil_DebugPrint() + which prints to stderr and always flushes the stream after printing. + Because preprocessor macros cannot directly accept variable length argument + lists, calls to the macro must include an additional set of parenthesis, eg: + PA_DEBUG(("errorno: %d", 1001 )); +*/ + +void PaUtil_DebugPrint( const char *format, ... ); + +#if (0) /* set to 1 to print debug messages */ +#define PA_DEBUG(x) PaUtil_DebugPrint x ; +#else +#define PA_DEBUG(x) +#endif + + +/* the following functions are implemented in a platform platform specific + .c file +*/ + +/** Allocate size bytes, guaranteed to be aligned to a FIXME byte boundary */ +void *PaUtil_AllocateMemory( long size ); + + +/** Realease block if non-NULL. block may be NULL */ +void PaUtil_FreeMemory( void *block ); + + +/** Return the number of currently allocated blocks. This function can be + used for detecting memory leaks. + + @note Allocations will only be tracked if PA_TRACK_MEMORY is #defined. If + it isn't, this function will always return 0. +*/ +int PaUtil_CountCurrentlyAllocatedBlocks( void ); + + +/** Initialize the clock used by PaUtil_GetTime(). Call this before calling + PaUtil_GetTime. + + @see PaUtil_GetTime +*/ +void PaUtil_InitializeClock( void ); + + +/** Return the system time in seconds. Used to implement CPU load functions + + @see PaUtil_InitializeClock +*/ +double PaUtil_GetTime( void ); + + +/* void Pa_Sleep( long msec ); must also be implemented in per-platform .c file */ + + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PA_UTIL_H */ diff --git a/src/audio/portaudio/pa_common/portaudio.h b/src/audio/portaudio/pa_common/portaudio.h new file mode 100644 index 0000000000000000000000000000000000000000..ac071d7eac66743eb495fdeb69a25d0195ec784d --- /dev/null +++ b/src/audio/portaudio/pa_common/portaudio.h @@ -0,0 +1,1123 @@ + +#ifndef PORTAUDIO_H +#define PORTAUDIO_H +/* + * $Id$ + * PortAudio Portable Real-Time Audio Library + * PortAudio API Header File + * Latest version available at: http://www.portaudio.com/ + * + * Copyright (c) 1999-2002 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. + * + * 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. + * + * 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. + */ + +/** @file + @brief The PortAudio API. +*/ + + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +/** Retrieve the release number of the currently running PortAudio build, + eg 1900. +*/ +int Pa_GetVersion( void ); + + +/** Retrieve a textual description of the current PortAudio build, + eg "PortAudio V19-devel 13 October 2002". +*/ +const char* Pa_GetVersionText( void ); + + +/** Error codes returned by PortAudio functions. + Note that with the exception of paNoError, all PaErrorCodes are negative. +*/ + +typedef int PaError; +typedef enum PaErrorCode +{ + paNoError = 0, + + paNotInitialized = -10000, + paUnanticipatedHostError, + paInvalidChannelCount, + paInvalidSampleRate, + paInvalidDevice, + paInvalidFlag, + paSampleFormatNotSupported, + paBadIODeviceCombination, + paInsufficientMemory, + paBufferTooBig, + paBufferTooSmall, + paNullCallback, + paBadStreamPtr, + paTimedOut, + paInternalError, + paDeviceUnavailable, + paIncompatibleHostApiSpecificStreamInfo, + paStreamIsStopped, + paStreamIsNotStopped, + paInputOverflowed, + paOutputUnderflowed, + paHostApiNotFound, + paInvalidHostApi, + paCanNotReadFromACallbackStream, /**< @todo review error code name */ + paCanNotWriteToACallbackStream, /**< @todo review error code name */ + paCanNotReadFromAnOutputOnlyStream, /**< @todo review error code name */ + paCanNotWriteToAnInputOnlyStream, /**< @todo review error code name */ + paIncompatibleStreamHostApi +} PaErrorCode; + + +/** Translate the supplied PortAudio error code into a human readable + message. +*/ +const char *Pa_GetErrorText( PaError errorCode ); + + +/** Library initialization function - call this before using PortAudio. + This function initialises internal data structures and prepares underlying + host APIs for use. This function MUST be called before using any other + PortAudio API functions. + + If Pa_Initialize() is called multiple times, each successful + call must be matched with a corresponding call to Pa_Terminate(). + Pairs of calls to Pa_Initialize()/Pa_Terminate() may overlap, and are not + required to be fully nested. + + Note that if Pa_Initialize() returns an error code, Pa_Terminate() should + NOT be called. + + @return paNoError if successful, otherwise an error code indicating the cause + of failure. + + @see Pa_Terminate +*/ +PaError Pa_Initialize( void ); + + +/** Library termination function - call this when finished using PortAudio. + This function deallocates all resources allocated by PortAudio since it was + initializied by a call to Pa_Initialize(). In cases where Pa_Initialise() has + been called multiple times, each call must be matched with a corresponding call + to Pa_Terminate(). The final matching call to Pa_Terminate() will automatically + close any PortAudio streams that are still open. + + Pa_Terminate() MUST be called before exiting a program which uses PortAudio. + Failure to do so may result in serious resource leaks, such as audio devices + not being available until the next reboot. + + @return paNoError if successful, otherwise an error code indicating the cause + of failure. + + @see Pa_Initialize +*/ +PaError Pa_Terminate( void ); + + + +/** The type used to refer to audio devices. Values of this type usually + range from 0 to (Pa_DeviceCount-1), and may also take on the PaNoDevice + and paUseHostApiSpecificDeviceSpecification values. + + @see Pa_DeviceCount, paNoDevice, paUseHostApiSpecificDeviceSpecification +*/ +typedef int PaDeviceIndex; + + +/** A special PaDeviceIndex value indicating that no device is available, + or should be used. + + @see PaDeviceIndex +*/ +#define paNoDevice ((PaDeviceIndex)-1) + + +/** A special PaDeviceIndex value indicating that the device(s) to be used + are specified in the host api specific stream info structure. + + @see PaDeviceIndex +*/ +#define paUseHostApiSpecificDeviceSpecification ((PaDeviceIndex)-2) + + +/* Host API enumeration mechanism */ + +/** The type used to enumerate to host APIs at runtime. Values of this type + range from 0 to (Pa_GetHostApiCount()-1). + + @see Pa_GetHostApiCount +*/ +typedef int PaHostApiIndex; + + +/** Retrieve the number of available host APIs. Even if a host API is + available it may have no devices available. + + @return A non-negative value indicating the number of available host APIs + or, a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. + + @see PaHostApiIndex +*/ +PaHostApiIndex Pa_GetHostApiCount( void ); + + +/** Retrieve the index of the default host API. The default host API will be + the lowest common denominator host API on the current platform and is + unlikely to provide the best performance. + + @return A non-negative value ranging from 0 to (Pa_GetHostApiCount()-1) + indicating the default host API index or, a PaErrorCode (which are always + negative) if PortAudio is not initialized or an error is encountered. +*/ +PaHostApiIndex Pa_GetDefaultHostApi( void ); + + +/** Unchanging unique identifiers for each supported host API. This type + is used in the PaHostApiInfo structure. The values are guaranteed to be + unique and to never change, thus allowing code to be written that + conditionally uses host API specific extensions. + + New type ids will be allocated when support for a host API reaches + "public alpha" status, prior to that developers should use the + paInDevelopment type id. + + @see PaHostApiInfo +*/ +typedef enum PaHostApiTypeId +{ + paInDevelopment=0, /* use while developing support for a new host API */ + paDirectSound=1, + paMME=2, + paASIO=3, + paSoundManager=4, + paCoreAudio=5, + paOSS=7, + paALSA=8, + paAL=9, + paBeOS=10, + paWDMKS=11, + paJACK=12 +} PaHostApiTypeId; + + +/** A structure containing information about a particular host API. */ + +typedef struct PaHostApiInfo +{ + /** this is struct version 1 */ + int structVersion; + /** The well known unique identifier of this host API @see PaHostApiTypeId */ + PaHostApiTypeId type; + /** A textual description of the host API for display on user interfaces. */ + const char *name; + + /** The number of devices belonging to this host API. This field may be + used in conjunction with Pa_HostApiDeviceIndexToDeviceIndex() to enumerate + all devices for this host API. + @see Pa_HostApiDeviceIndexToDeviceIndex + */ + int deviceCount; + + /** The the default input device for this host API. The value will be a + device index ranging from 0 to (Pa_GetDeviceCount()-1), or paNoDevice + if no default input device is available. + */ + PaDeviceIndex defaultInputDevice; + + /** The the default output device for this host API. The value will be a + device index ranging from 0 to (Pa_GetDeviceCount()-1), or paNoDevice + if no default output device is available. + */ + PaDeviceIndex defaultOutputDevice; + +} PaHostApiInfo; + + +/** Retrieve a pointer to a structure containing information about a specific + host Api. + + @param hostApi A valid host API index ranging from 0 to (Pa_GetHostApiCount()-1) + + @return A pointer to an immutable PaHostApiInfo structure describing + a specific host API. If the hostApi parameter is out of range or an error + is encountered, the function returns NULL. + + The returned structure is owned by the PortAudio implementation and must not + be manipulated or freed. The pointer is only guaranteed to be valid between + calls to Pa_Initialize() and Pa_Terminate(). +*/ +const PaHostApiInfo * Pa_GetHostApiInfo( PaHostApiIndex hostApi ); + + +/** Convert a static host API unique identifier, into a runtime + host API index. + + @param type A unique host API identifier belonging to the PaHostApiTypeId + enumeration. + + @return A valid PaHostApiIndex ranging from 0 to (Pa_GetHostApiCount()-1) or, + a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. + + The paHostApiNotFound error code indicates that the host API specified by the + type parameter is not available. + + @see PaHostApiTypeId +*/ +PaHostApiIndex Pa_HostApiTypeIdToHostApiIndex( PaHostApiTypeId type ); + + +/** Convert a host-API-specific device index to standard PortAudio device index. + This function may be used in conjunction with the deviceCount field of + PaHostApiInfo to enumerate all devices for the specified host API. + + @param hostApi A valid host API index ranging from 0 to (Pa_GetHostApiCount()-1) + + @param hostApiDeviceIndex A valid per-host device index in the range + 0 to (Pa_GetHostApiInfo(hostApi)->deviceCount-1) + + @return A non-negative PaDeviceIndex ranging from 0 to (Pa_GetDeviceCount()-1) + or, a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. + + A paInvalidHostApi error code indicates that the host API index specified by + the hostApi parameter is out of range. + + A paInvalidDevice error code indicates that the hostApiDeviceIndex parameter + is out of range. + + @see PaHostApiInfo +*/ +PaDeviceIndex Pa_HostApiDeviceIndexToDeviceIndex( PaHostApiIndex hostApi, + int hostApiDeviceIndex ); + + + +/** Structure used to return information about a host error condition. +*/ +typedef struct PaHostErrorInfo{ + PaHostApiTypeId hostApiType; /**< the host API which returned the error code */ + long errorCode; /**< the error code returned */ + const char *errorText; /**< a textual description of the error if available, otherwise a zero-length string */ +}PaHostErrorInfo; + + +/** Return information about the last host error encountered. The error + information returned by Pa_GetLastHostErrorInfo() will never be modified + asyncronously by errors occurring in other PortAudio owned threads + (such as the thread that manages the stream callback.) + + This function is provided as a last resort, primarily to enhance debugging + by providing clients with access to all available error information. + + @return A pointer to an immutable structure constaining information about + the host error. The values in this structure will only be valid if a + PortAudio function has previously returned the paUnanticipatedHostError + error code. +*/ +const PaHostErrorInfo* Pa_GetLastHostErrorInfo( void ); + + + +/* Device enumeration and capabilities */ + +/** Retrieve the number of available devices. The number of available devices + may be zero. + + @return A non-negative value indicating the number of available devices or, + a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. +*/ +PaDeviceIndex Pa_GetDeviceCount( void ); + + +/** Retrieve the index of the default input device. The result can be + used in the inputDevice parameter to Pa_OpenStream(). + + @return The default input device index for the default host API, or paNoDevice + if no default input device is available or an error was encountered. +*/ +PaDeviceIndex Pa_GetDefaultInputDevice( void ); + + +/** Retrieve the index of the default output device. The result can be + used in the outputDevice parameter to Pa_OpenStream(). + + @return The default output device index for the defualt host API, or paNoDevice + if no default output device is available or an error was encountered. + + @note + On the PC, the user can specify a default device by + setting an environment variable. For example, to use device #1. +<pre> + set PA_RECOMMENDED_OUTPUT_DEVICE=1 +</pre> + The user should first determine the available device ids by using + the supplied application "pa_devs". +*/ +PaDeviceIndex Pa_GetDefaultOutputDevice( void ); + + +/** The type used to represent monotonic time in seconds that can be used + for syncronisation. The type is used for the outTime argument to the + PaStreamCallback and as the result of Pa_GetStreamTime(). + + @see PaStreamCallback, Pa_GetStreamTime +*/ +typedef double PaTime; + + +/** A type used to specify one or more sample formats. Each value indicates + a possible format for sound data passed to and from the stream callback, + Pa_ReadStream and Pa_WriteStream. + + The standard formats paFloat32, paInt16, paInt32, paInt24, paInt8 + and aUInt8 are usually implemented by all implementations. + + The floating point representation (paFloat32) uses +1.0 and -1.0 as the + maximum and minimum respectively. + + paUInt8 is an unsigned 8 bit format where 128 is considered "ground" + + The paNonInterleaved flag indicates that a multichannel buffer is passed + as a set of non-interleaved pointers. + + @see Pa_OpenStream, Pa_OpenDefaultStream, PaDeviceInfo + @see paFloat32, paInt16, paInt32, paInt24, paInt8 + @see paUInt8, paCustomFormat, paNonInterleaved +*/ +typedef unsigned long PaSampleFormat; + + +#define paFloat32 ((PaSampleFormat) 0x00000001) /**< @see PaSampleFormat */ +#define paInt32 ((PaSampleFormat) 0x00000002) /**< @see PaSampleFormat */ +#define paInt24 ((PaSampleFormat) 0x00000004) /**< Packed 24 bit format. @see PaSampleFormat */ +#define paInt16 ((PaSampleFormat) 0x00000008) /**< @see PaSampleFormat */ +#define paInt8 ((PaSampleFormat) 0x00000010) /**< @see PaSampleFormat */ +#define paUInt8 ((PaSampleFormat) 0x00000020) /**< @see PaSampleFormat */ +#define paCustomFormat ((PaSampleFormat) 0x00010000)/**< @see PaSampleFormat */ + +#define paNonInterleaved ((PaSampleFormat) 0x80000000) + +/** A structure providing information and capabilities of PortAudio devices. + Devices may support input, output or both input and output. +*/ +typedef struct PaDeviceInfo +{ + int structVersion; /* this is struct version 2 */ + const char *name; + PaHostApiIndex hostApi; /* note this is a host API index, not a type id*/ + + int maxInputChannels; + int maxOutputChannels; + + /* Default latency values for interactive performance. */ + PaTime defaultLowInputLatency; + PaTime defaultLowOutputLatency; + /* Default latency values for robust non-interactive applications (eg. playing sound files). */ + PaTime defaultHighInputLatency; + PaTime defaultHighOutputLatency; + + double defaultSampleRate; +} PaDeviceInfo; + + +/** Retrieve a pointer to a PaDeviceInfo structure containing information + about the specified device. + @return A pointer to an immutable PaDeviceInfo structure. If the device + parameter is out of range the function returns NULL. + + @param device A valid device index in the range 0 to (Pa_GetDeviceCount()-1) + + @note PortAudio manages the memory referenced by the returned pointer, + the client must not manipulate or free the memory. The pointer is only + guaranteed to be valid between calls to Pa_Initialize() and Pa_Terminate(). + + @see PaDeviceInfo, PaDeviceIndex +*/ +const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceIndex device ); + + +/** Parameters for one direction (input or output) of a stream. +*/ +typedef struct PaStreamParameters +{ + /** A valid device index in the range 0 to (Pa_GetDeviceCount()-1) + specifying the device to be used or the special constant + paUseHostApiSpecificDeviceSpecification which indicates that the actual + device(s) to use are specified in hostApiSpecificStreamInfo. + This field must not be set to paNoDevice. + */ + PaDeviceIndex device; + + /** The number of channels of sound to be delivered to the + stream callback or accessed by Pa_ReadStream() or Pa_WriteStream(). + It can range from 1 to the value of maxInputChannels in the + PaDeviceInfo record for the device specified by the device parameter. + */ + int channelCount; + + /** The sample format of the buffer provided to the stream callback, + a_ReadStream() or Pa_WriteStream(). It may be any of the formats described + by the PaSampleFormat enumeration. + */ + PaSampleFormat sampleFormat; + + /** The desired latency in seconds. Where practical, implementations should + configure their latency based on these parameters, otherwise they may + choose the closest viable latency instead. Unless the suggested latency + is greater than the absolute upper limit for the device implementations + shouldround the suggestedLatency up to the next practial value - ie to + provide an equal or higher latency than suggestedLatency whereever possibe. + Actual latency values for an open stream may be retrieved using the + inputLatency and outputLatency fields of the PaStreamInfo structure + returned by Pa_GetStreamInfo(). + @see default*Latency in PaDeviceInfo, *Latency in PaStreamInfo + */ + PaTime suggestedLatency; + + /** An optional pointer to a host api specific data structure + containing additional information for device setup and/or stream processing. + hostApiSpecificStreamInfo is never required for correct operation, + if not used it should be set to NULL. + */ + void *hostApiSpecificStreamInfo; + +} PaStreamParameters; + + +/** Return code for Pa_IsFormatSupported indicating success. */ +#define paFormatIsSupported (0) + +/** Determine whether it would be possible to open a stream with the specified + parameters. + + @param inputParameters A structure that describes the input parameters used to + open a stream. The suggestedLatency field is ignored. See PaStreamParameters + for a description of these parameters. inputParameters must be NULL for + output-only streams. + + @param outputParameters A structure that describes the output parameters used + to open a stream. The suggestedLatency field is ignored. See PaStreamParameters + for a description of these parameters. outputParameters must be NULL for + input-only streams. + + @param sampleRate The required sampleRate. For full-duplex streams it is the + sample rate for both input and output + + @return Returns 0 if the format is supported, and an error code indicating why + the format is not supported otherwise. The constant paFormatIsSupported is + provided to compare with the return value for success. + + @see paFormatIsSupported, PaStreamParameters +*/ +PaError Pa_IsFormatSupported( const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); + + + +/* Streaming types and functions */ + + +/** + A single PaStream can provide multiple channels of real-time + streaming audio input and output to a client application. A stream + provides access to audio hardware represented by one or more + PaDevices. Depending on the underlying Host API, it may be possible + to open multiple streams using the same device, however this behavior + is implementation defined. Portable applications should assume that + a PaDevice may be simultaneously used by at most one PaStream. + + Pointers to PaStream objects are passed between PortAudio functions that + operate on streams. + + @see Pa_OpenStream, Pa_OpenDefaultStream, Pa_OpenDefaultStream, Pa_CloseStream, + Pa_StartStream, Pa_StopStream, Pa_AbortStream, Pa_IsStreamActive, + Pa_GetStreamTime, Pa_GetStreamCpuLoad + +*/ +typedef void PaStream; + + +/** Can be passed as the framesPerBuffer parameter to Pa_OpenStream() + or Pa_OpenDefaultStream() to indicate that the stream callback will + accept buffers of any size. +*/ +#define paFramesPerBufferUnspecified (0) + + +/** Flags used to control the behavior of a stream. They are passed as + parameters to Pa_OpenStream or Pa_OpenDefaultStream. Multiple flags may be + ORed together. + + @see Pa_OpenStream, Pa_OpenDefaultStream + @see paNoFlag, paClipOff, paDitherOff, paNeverDropInput, + paPrimeOutputBuffersUsingStreamCallback, paPlatformSpecificFlags +*/ +typedef unsigned long PaStreamFlags; + +/** @see PaStreamFlags */ +#define paNoFlag ((PaStreamFlags) 0) + +/** Disable default clipping of out of range samples. + @see PaStreamFlags +*/ +#define paClipOff ((PaStreamFlags) 0x00000001) + +/** Disable default dithering. + @see PaStreamFlags +*/ +#define paDitherOff ((PaStreamFlags) 0x00000002) + +/** Flag requests that where possible a full duplex stream will not discard + overflowed input samples without calling the stream callback. This flag is + only valid for full duplex callback streams and only when used in combination + with the paFramesPerBufferUnspecified (0) framesPerBuffer parameter. Using + this flag incorrectly results in a paInvalidFlag error being returned from + Pa_OpenStream and Pa_OpenDefaultStream. + + @see PaStreamFlags, paFramesPerBufferUnspecified +*/ +#define paNeverDropInput ((PaStreamFlags) 0x00000004) + +/** Call the stream callback to fill initial output buffers, rather than the + default behavior of priming the buffers with zeros (silence). This flag has + no effect for input-only and blocking read/write streams. + + @see PaStreamFlags +*/ +#define paPrimeOutputBuffersUsingStreamCallback ((PaStreamFlags) 0x00000008) + +/** A mask specifying the platform specific bits. + @see PaStreamFlags +*/ +#define paPlatformSpecificFlags ((PaStreamFlags)0xFFFF0000) + +/** + Timing information for the buffers passed to the stream callback. +*/ +typedef struct PaStreamCallbackTimeInfo{ + PaTime inputBufferAdcTime; + PaTime currentTime; + PaTime outputBufferDacTime; +} PaStreamCallbackTimeInfo; + + +/** + Flag bit constants for the statusFlags to PaStreamCallback. + + @see paInputUnderflow, paInputOverflow, paOutputUnderflow, paOutputOverflow, + paPrimingOutput +*/ +typedef unsigned long PaStreamCallbackFlags; + +/** In a stream opened with paFramesPerBufferUnspecified, indicates that + input data is all silence (zeros) because no real data is available. In a + stream opened without paFramesPerBufferUnspecified, it indicates that one or + more zero samples have been inserted into the input buffer to compensate + for an input underflow. + @see PaStreamCallbackFlags +*/ +#define paInputUnderflow ((PaStreamCallbackFlags) 0x00000001) + +/** In a stream opened with paFramesPerBufferUnspecified, indicates that data + prior to the first sample of the input buffer was discarded due to an + overflow, possibly because the stream callback is using too much CPU time. + Otherwise indicates that data prior to one or more samples in the + input buffer was discarded. + @see PaStreamCallbackFlags +*/ +#define paInputOverflow ((PaStreamCallbackFlags) 0x00000002) + +/** Indicates that output data (or a gap) was inserted, possibly because the + stream callback is using too much CPU time. + @see PaStreamCallbackFlags +*/ +#define paOutputUnderflow ((PaStreamCallbackFlags) 0x00000004) + +/** Indicates that output data will be discarded because no room is available. + @see PaStreamCallbackFlags +*/ +#define paOutputOverflow ((PaStreamCallbackFlags) 0x00000008) + +/** Some of all of the output data will be used to prime the stream, input + data may be zero. + @see PaStreamCallbackFlags +*/ +#define paPrimingOutput ((PaStreamCallbackFlags) 0x00000010) + +/** + Allowable return values for the PaStreamCallback. + @see PaStreamCallback +*/ +typedef enum PaStreamCallbackResult +{ + paContinue=0, + paComplete=1, + paAbort=2 +} PaStreamCallbackResult; + + +/** + Functions of type PaStreamCallback are implemented by PortAudio clients. + They consume, process or generate audio in response to requests from an + active PortAudio stream. + + @param input and @param output are arrays of interleaved samples, + the format, packing and number of channels used by the buffers are + determined by parameters to Pa_OpenStream(). + + @param frameCount The number of sample frames to be processed by + the stream callback. + + @param timeInfo The time in seconds when the first sample of the input + buffer was received at the audio input, the time in seconds when the first + sample of the output buffer will begin being played at the audio output, and + the time in seconds when the stream callback was called. + See also Pa_GetStreamTime() + + @param statusFlags Flags indicating whether input and/or output buffers + have been inserted or will be dropped to overcome underflow or overflow + conditions. + + @param userData The value of a user supplied pointer passed to + Pa_OpenStream() intended for storing synthesis data etc. + + @return + The stream callback should return one of the values in the + PaStreamCallbackResult enumeration. To ensure that the callback continues + to be called, it should return paContinue (0). Either paComplete or paAbort + can be returned to finish stream processing, after either of these values is + returned the callback will not be called again. If paAbort is returned the + stream will finish as soon as possible. If paComplete is returned, the stream + will continue until all buffers generated by the callback have been played. + This may be useful in applications such as soundfile players where a specific + duration of output is required. However, it is not necessary to utilise this + mechanism as Pa_StopStream(), Pa_AbortStream() or Pa_CloseStream() can also + be used to stop the stream. The callback must always fill the entire output + buffer irrespective of its return value. + + @see Pa_OpenStream, Pa_OpenDefaultStream + + @note With the exception of Pa_GetStreamCpuLoad() it is not permissable to call + PortAudio API functions from within the stream callback. +*/ +typedef int PaStreamCallback( + const void *input, void *output, + unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ); + + +/** Opens a stream for either input, output or both. + + @param stream The address of a PaStream pointer which will receive + a pointer to the newly opened stream. + + @param inputParameters A structure that describes the input parameters used by + the opened stream. See PaStreamParameters for a description of these parameters. + inputParameters must be NULL for output-only streams. + + @param outputParameters A structure that describes the output parameters used by + the opened stream. See PaStreamParameters for a description of these parameters. + outputParameters must be NULL for input-only streams. + + @param sampleRate The desired sampleRate. For full-duplex streams it is the + sample rate for both input and output + + @param framesPerBuffer The number of frames passed to the stream callback + function, or the preferred block granularity for a blocking read/write stream. + The special value paFramesPerBufferUnspecified (0) may be used to request that + the stream callback will recieve an optimal (and possibly varying) number of + frames based on host requirements and the requested latency settings. + Note: With some host APIs, the use of non-zero framesPerBuffer for a callback + stream may introduce an additional layer of buffering which could introduce + additional latency. PortAudio guarantees that the additional latency + will be kept to the theoretical minimum however, it is strongly recommended + that a non-zero framesPerBuffer value only be used when your algorithm + requires a fixed number of frames per stream callback. + + @param streamFlags Flags which modify the behaviour of the streaming process. + This parameter may contain a combination of flags ORed together. Some flags may + only be relevant to certain buffer formats. + + @param streamCallback A pointer to a client supplied function that is responsible + for processing and filling input and output buffers. If this parameter is NULL + the stream will be opened in 'blocking read/write' mode. In blocking mode, + the client can receive sample data using Pa_ReadStream and write sample data + using Pa_WriteStream, the number of samples that may be read or written + without blocking is returned by Pa_GetStreamReadAvailable and + Pa_GetStreamWriteAvailable respectively. + + @param userData A client supplied pointer which is passed to the stream callback + function. It could for example, contain a pointer to instance data necessary + for processing the audio buffers. This parameter is ignored if streamCallback + is NULL. + + @return + Upon success Pa_OpenStream() returns paNoError and places a pointer to a + valid PaStream in the stream argument. The stream is inactive (stopped). + If a call to Pa_OpenStream() fails, a non-zero error code is returned (see + PaError for possible error codes) and the value of stream is invalid. + + @see PaStreamParameters, PaStreamCallback, Pa_ReadStream, Pa_WriteStream, + Pa_GetStreamReadAvailable, Pa_GetStreamWriteAvailable +*/ +PaError Pa_OpenStream( PaStream** stream, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); + + +/** A simplified version of Pa_OpenStream() that opens the default input + and/or output devices. + + @param stream The address of a PaStream pointer which will receive + a pointer to the newly opened stream. + + @param numInputChannels The number of channels of sound that will be supplied + to the stream callback or returned by Pa_ReadStream. It can range from 1 to + the value of maxInputChannels in the PaDeviceInfo record for the default input + device. If 0 the stream is opened as an output-only stream. + + @param numOutputChannels The number of channels of sound to be delivered to the + stream callback or passed to Pa_WriteStream. It can range from 1 to the value + of maxOutputChannels in the PaDeviceInfo record for the default output dvice. + If 0 the stream is opened as an output-only stream. + + @param sampleFormat The sample format of both the input and output buffers + provided to the callback or passed to and from Pa_ReadStream and Pa_WriteStream. + sampleFormat may be any of the formats described by the PaSampleFormat + enumeration. + + @param sampleRate Same as Pa_OpenStream parameter of the same name. + @param framesPerBuffer Same as Pa_OpenStream parameter of the same name. + @param streamCallback Same as Pa_OpenStream parameter of the same name. + @param userData Same as Pa_OpenStream parameter of the same name. + + @return As for Pa_OpenStream + + @see Pa_OpenStream, PaStreamCallback +*/ +PaError Pa_OpenDefaultStream( PaStream** stream, + int numInputChannels, + int numOutputChannels, + PaSampleFormat sampleFormat, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamCallback *streamCallback, + void *userData ); + + +/** Closes an audio stream. If the audio stream is active it + discards any pending buffers as if Pa_AbortStream() had been called. +*/ +PaError Pa_CloseStream( PaStream *stream ); + + +/** Functions of type PaStreamFinishedCallback are implemented by PortAudio + clients. They can be registered with a stream using the Pa_SetStreamFinishedCallback + function. Once registered they are called when the stream becomes inactive + (ie once a call to Pa_StopStream() will not block). + A stream will become inactive after the stream callback returns non-zero, + or when Pa_StopStream or Pa_AbortStream is called. For a stream providing audio + output, if the stream callback returns paComplete, or Pa_StopStream is called, + the stream finished callback will not be called until all generated sample data + has been played. + + @param userData The userData parameter supplied to Pa_OpenStream() + + @see Pa_SetStreamFinishedCallback +*/ +typedef void PaStreamFinishedCallback( void *userData ); + + +/** Register a stream finished callback function which will be called when the + stream becomes inactive. See the description of PaStreamFinishedCallback for + further details about when the callback will be called. + + @param stream a pointer to a PaStream that is in the stopped state - if the + stream is not stopped, the stream's finished callback will remain unchanged + and an error code will be returned. + + @param streamFinishedCallback a pointer to a function with the same signature + as PaStreamFinishedCallback, that will be called when the stream becomes + inactive. Passing NULL for this parameter will un-register a previously + registered stream finished callback function. + + @return on success returns paNoError, otherwise an error code indicating the cause + of the error. + + @see PaStreamFinishedCallback +*/ +PaError Pa_SetStreamFinishedCallback( PaStream *stream, PaStreamFinishedCallback* streamFinishedCallback ); + + +/** Commences audio processing. +*/ +PaError Pa_StartStream( PaStream *stream ); + + +/** Terminates audio processing. It waits until all pending + audio buffers have been played before it returns. +*/ +PaError Pa_StopStream( PaStream *stream ); + + +/** Terminates audio processing immediately without waiting for pending + buffers to complete. +*/ +PaError Pa_AbortStream( PaStream *stream ); + + +/** Determine whether the stream is stopped. + A stream is considered to be stopped prior to a successful call to + Pa_StartStream and after a successful call to Pa_StopStream or Pa_AbortStream. + If a stream callback returns a value other than paContinue the stream is NOT + considered to be stopped. + + @return Returns one (1) when the stream is stopped, zero (0) when + the stream is running or, a PaErrorCode (which are always negative) if + PortAudio is not initialized or an error is encountered. + + @see Pa_StopStream, Pa_AbortStream, Pa_IsStreamActive +*/ +PaError Pa_IsStreamStopped( PaStream *stream ); + + +/** Determine whether the stream is active. + A stream is active after a successful call to Pa_StartStream(), until it + becomes inactive either as a result of a call to Pa_StopStream() or + Pa_AbortStream(), or as a result of a return value other than paContinue from + the stream callback. In the latter case, the stream is considered inactive + after the last buffer has finished playing. + + @return Returns one (1) when the stream is active (ie playing or recording + audio), zero (0) when not playing or, a PaErrorCode (which are always negative) + if PortAudio is not initialized or an error is encountered. + + @see Pa_StopStream, Pa_AbortStream, Pa_IsStreamStopped +*/ +PaError Pa_IsStreamActive( PaStream *stream ); + + + +/** A structure containing unchanging information about an open stream. + @see Pa_GetStreamInfo +*/ + +typedef struct PaStreamInfo +{ + /** this is struct version 1 */ + int structVersion; + + /** The input latency of the stream in seconds. This value provides the most + accurate estimate of input latency available to the implementation. It may + differ significantly from the suggestedLatency value passed to Pa_OpenStream(). + The value of this field will be zero (0.) for output-only streams. + @see PaTime + */ + PaTime inputLatency; + + /** The output latency of the stream in seconds. This value provides the most + accurate estimate of output latency available to the implementation. It may + differ significantly from the suggestedLatency value passed to Pa_OpenStream(). + The value of this field will be zero (0.) for input-only streams. + @see PaTime + */ + PaTime outputLatency; + + /** The sample rate of the stream in Hertz (samples per second). In cases + where the hardware sample rate is inaccurate and PortAudio is aware of it, + the value of this field may be different from the sampleRate parameter + passed to Pa_OpenStream(). If information about the actual hardware sample + rate is not available, this field will have the same value as the sampleRate + parameter passed to Pa_OpenStream(). + */ + double sampleRate; + +} PaStreamInfo; + + +/** Retrieve a pointer to a PaStreamInfo structure containing information + about the specified stream. + @return A pointer to an immutable PaStreamInfo structure. If the stream + parameter invalid, or an error is encountered, the function returns NULL. + + @param stream A pointer to an open stream previously created with Pa_OpenStream. + + @note PortAudio manages the memory referenced by the returned pointer, + the client must not manipulate or free the memory. The pointer is only + guaranteed to be valid until the specified stream is closed. + + @see PaStreamInfo +*/ +const PaStreamInfo* Pa_GetStreamInfo( PaStream *stream ); + + +/** Determine the current time for the stream according to the same clock used + to generate buffer timestamps. This time may be used for syncronising other + events to the audio stream, for example synchronizing audio to MIDI. + + @return The stream's current time in seconds, or 0 if an error occurred. + + @see PaTime, PaStreamCallback +*/ +PaTime Pa_GetStreamTime( PaStream *stream ); + + +/** Retrieve CPU usage information for the specified stream. + The "CPU Load" is a fraction of total CPU time consumed by a callback stream's + audio processing routines including, but not limited to the client supplied + stream callback. This function does not work with blocking read/write streams. + + This function may be called from the stream callback function or the + application. + + @return + A floating point value, typically between 0.0 and 1.0, where 1.0 indicates + that the stream callback is consuming the maximum number of CPU cycles possible + to maintain real-time operation. A value of 0.5 would imply that PortAudio and + the stream callback was consuming roughly 50% of the available CPU time. The + return value may exceed 1.0. A value of 0.0 will always be returned for a + blocking read/write stream, or if an error occurrs. +*/ +double Pa_GetStreamCpuLoad( PaStream* stream ); + + +/** Read samples from an input stream. The function doesn't return until + the entire buffer has been filled - this may involve waiting for the operating + system to supply the data. + + @param stream A pointer to an open stream previously created with Pa_OpenStream. + + @param buffer A pointer to a buffer of sample frames. The buffer contains + samples in the format specified by the inputParameters->sampleFormat field + used to open the stream, and the number of channels specified by + inputParameters->numChannels. If non-interleaved samples were requested, + buffer is a pointer to the first element of an array of non-interleaved + buffer pointers, one for each channel. + + @param frames The number of frames to be read into buffer. This parameter + is not constrained to a specific range, however high performance applications + will want to match this parameter to the framesPerBuffer parameter used + when opening the stream. + + @return On success PaNoError will be returned, or PaInputOverflowed if input + data was discarded by PortAudio after the previous call and before this call. +*/ +PaError Pa_ReadStream( PaStream* stream, + void *buffer, + unsigned long frames ); + + +/** Write samples to an output stream. This function doesn't return until the + entire buffer has been consumed - this may involve waiting for the operating + system to consume the data. + + @param stream A pointer to an open stream previously created with Pa_OpenStream. + + @param buffer A pointer to a buffer of sample frames. The buffer contains + samples in the format specified by the outputParameters->sampleFormat field + used to open the stream, and the number of channels specified by + outputParameters->numChannels. If non-interleaved samples were requested, + buffer is a pointer to the first element of an array of non-interleaved + buffer pointers, one for each channel. + + @param frames The number of frames to be written from buffer. This parameter + is not constrained to a specific range, however high performance applications + will want to match this parameter to the framesPerBuffer parameter used + when opening the stream. + + @return On success PaNoError will be returned, or paOutputUnderflowed if + additional output data was inserted after the previous call and before this + call. +*/ +PaError Pa_WriteStream( PaStream* stream, + const void *buffer, + unsigned long frames ); + + +/** Retrieve the number of frames that can be read from the stream without + waiting. + + @return Returns a non-negative value representing the maximum number of frames + that can be read from the stream without blocking or busy waiting or, a + PaErrorCode (which are always negative) if PortAudio is not initialized or an + error is encountered. +*/ +signed long Pa_GetStreamReadAvailable( PaStream* stream ); + + +/** Retrieve the number of frames that can be written to the stream without + waiting. + + @return Returns a non-negative value representing the maximum number of frames + that can be written to the stream without blocking or busy waiting or, a + PaErrorCode (which are always negative) if PortAudio is not initialized or an + error is encountered. +*/ +signed long Pa_GetStreamWriteAvailable( PaStream* stream ); + + +/* Miscellaneous utilities */ + + +/** Retrieve the size of a given sample format in bytes. + + @return The size in bytes of a single sample in the specified format, + or paSampleFormatNotSupported if the format is not supported. +*/ +PaError Pa_GetSampleSize( PaSampleFormat format ); + + +/** Put the caller to sleep for at least 'msec' milliseconds. This function is + provided only as a convenience for authors of portable code (such as the tests + and examples in the PortAudio distribution.) + + The function may sleep longer than requested so don't rely on this for accurate + musical timing. +*/ +void Pa_Sleep( long msec ); + + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PORTAUDIO_H */ diff --git a/src/audio/portaudio/pa_dll_switch/PaDllEntry.h b/src/audio/portaudio/pa_dll_switch/PaDllEntry.h new file mode 100644 index 0000000000000000000000000000000000000000..e070054b39d7b2f52f9b224c8399cec857f778e7 --- /dev/null +++ b/src/audio/portaudio/pa_dll_switch/PaDllEntry.h @@ -0,0 +1,184 @@ + +/* + * PortAudio Portable Real-Time Audio Library + * PortAudio DLL Header File + * Latest version available at: http://www.audiomulch.com/portaudio/ + * + * 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. + * + * 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. + * + * 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. + * + */ + +// changed by zplane.developement in order to generate a DLL + +#ifndef __PADLLENTRY_HEADER_INCLUDED__ + +#define __PADLLENTRY_HEADER_INCLUDED__ + +typedef int PaError; +typedef enum { + paNoError = 0, + + paHostError = -10000, + paInvalidChannelCount, + paInvalidSampleRate, + paInvalidDeviceId, + paInvalidFlag, + paSampleFormatNotSupported, + paBadIODeviceCombination, + paInsufficientMemory, + paBufferTooBig, + paBufferTooSmall, + paNullCallback, + paBadStreamPtr, + paTimedOut, + paInternalError +} PaErrorNum; + +typedef unsigned long PaSampleFormat; +#define paFloat32 ((PaSampleFormat) (1<<0)) /*always available*/ +#define paInt16 ((PaSampleFormat) (1<<1)) /*always available*/ +#define paInt32 ((PaSampleFormat) (1<<2)) /*always available*/ +#define paInt24 ((PaSampleFormat) (1<<3)) +#define paPackedInt24 ((PaSampleFormat) (1<<4)) +#define paInt8 ((PaSampleFormat) (1<<5)) +#define paUInt8 ((PaSampleFormat) (1<<6)) /* unsigned 8 bit, 128 is "ground" */ +#define paCustomFormat ((PaSampleFormat) (1<<16)) + + +typedef int PaDeviceID; +#define paNoDevice -1 + +typedef struct +{ + int structVersion; + const char *name; + int maxInputChannels; + int maxOutputChannels; + /* Number of discrete rates, or -1 if range supported. */ + int numSampleRates; + /* Array of supported sample rates, or {min,max} if range supported. */ + const double *sampleRates; + PaSampleFormat nativeSampleFormats; +} +PaDeviceInfo; + + +typedef double PaTimestamp; + + +typedef int (PortAudioCallback)( + void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ); + + +#define paNoFlag (0) +#define paClipOff (1<<0) /* disable default clipping of out of range samples */ +#define paDitherOff (1<<1) /* disable default dithering */ +#define paPlatformSpecificFlags (0x00010000) +typedef unsigned long PaStreamFlags; + +typedef void PortAudioStream; +#define PaStream PortAudioStream + +extern PaError (__cdecl* Pa_Initialize)( void ); + + + +extern PaError (__cdecl* Pa_Terminate)( void ); + + +extern long (__cdecl* Pa_GetHostError)( void ); + + +extern const char* (__cdecl* Pa_GetErrorText)( PaError ); + + + +extern int (__cdecl* Pa_CountDevices)(void); + +extern PaDeviceID (__cdecl* Pa_GetDefaultInputDeviceID)( void ); + +extern PaDeviceID (__cdecl* Pa_GetDefaultOutputDeviceID)( void ); + + +extern const PaDeviceInfo* (__cdecl* Pa_GetDeviceInfo)( PaDeviceID); + + + +extern PaError (__cdecl* Pa_OpenStream)( + PortAudioStream ** , + PaDeviceID , + int , + PaSampleFormat , + void *, + PaDeviceID , + int , + PaSampleFormat , + void *, + double , + unsigned long , + unsigned long , + unsigned long , + PortAudioCallback *, + void * ); + + + +extern PaError (__cdecl* Pa_OpenDefaultStream)( PortAudioStream** stream, + int numInputChannels, + int numOutputChannels, + PaSampleFormat sampleFormat, + double sampleRate, + unsigned long framesPerBuffer, + unsigned long numberOfBuffers, + PortAudioCallback *callback, + void *userData ); + + +extern PaError (__cdecl* Pa_CloseStream)( PortAudioStream* ); + + +extern PaError (__cdecl* Pa_StartStream)( PortAudioStream *stream ); + +extern PaError (__cdecl* Pa_StopStream)( PortAudioStream *stream ); + +extern PaError (__cdecl* Pa_AbortStream)( PortAudioStream *stream ); + +extern PaError (__cdecl* Pa_StreamActive)( PortAudioStream *stream ); + +extern PaTimestamp (__cdecl* Pa_StreamTime)( PortAudioStream *stream ); + +extern double (__cdecl* Pa_GetCPULoad)( PortAudioStream* stream ); + +extern int (__cdecl* Pa_GetMinNumBuffers)( int framesPerBuffer, double sampleRate ); + +extern void (__cdecl* Pa_Sleep)( long msec ); + +extern PaError (__cdecl* Pa_GetSampleSize)( PaSampleFormat format ); + +#endif // __PADLLENTRY_HEADER_INCLUDED__ + diff --git a/src/audio/portaudio/pa_dll_switch/letter_from_tim_010817.txt b/src/audio/portaudio/pa_dll_switch/letter_from_tim_010817.txt new file mode 100644 index 0000000000000000000000000000000000000000..a535cd1dae6afccc58d304b141166f7f7600dbd5 Binary files /dev/null and b/src/audio/portaudio/pa_dll_switch/letter_from_tim_010817.txt differ diff --git a/src/audio/portaudio/pa_dll_switch/loadPA_DLL.cpp b/src/audio/portaudio/pa_dll_switch/loadPA_DLL.cpp new file mode 100644 index 0000000000000000000000000000000000000000..043eda87810f65848cc3c8e69bf8a90d6e05e8c2 --- /dev/null +++ b/src/audio/portaudio/pa_dll_switch/loadPA_DLL.cpp @@ -0,0 +1,203 @@ +////////////////////////////////////////////////////////////////////////// + + +HINSTANCE pPaDll; + +/* + the function pointers to the PortAudio DLLs +*/ + +PaError (__cdecl* Pa_Initialize)( void ); + + + +PaError (__cdecl* Pa_Terminate)( void ); + + +long (__cdecl* Pa_GetHostError)( void ); + + +const char* (__cdecl* Pa_GetErrorText)( PaError ); + + +int (__cdecl* Pa_CountDevices)(void); + +PaDeviceID (__cdecl* Pa_GetDefaultInputDeviceID)( void ); + +PaDeviceID (__cdecl* Pa_GetDefaultOutputDeviceID)( void ); + + +const PaDeviceInfo* (__cdecl* Pa_GetDeviceInfo)( PaDeviceID); + + + +PaError (__cdecl* Pa_OpenStream)( + PortAudioStream ** , + PaDeviceID , + int , + PaSampleFormat , + void *, + PaDeviceID , + int , + PaSampleFormat , + void *, + double , + unsigned long , + unsigned long , + unsigned long , + PortAudioCallback *, + void * ); + + + +PaError (__cdecl* Pa_OpenDefaultStream)( PortAudioStream** stream, + int numInputChannels, + int numOutputChannels, + PaSampleFormat sampleFormat, + double sampleRate, + unsigned long framesPerBuffer, + unsigned long numberOfBuffers, + PortAudioCallback *callback, + void *userData ); + + +PaError (__cdecl* Pa_CloseStream)( PortAudioStream* ); + + +PaError (__cdecl* Pa_StartStream)( PortAudioStream *stream ); + +PaError (__cdecl* Pa_StopStream)( PortAudioStream *stream ); + +PaError (__cdecl* Pa_AbortStream)( PortAudioStream *stream ); + +PaError (__cdecl* Pa_StreamActive)( PortAudioStream *stream ); + +PaTimestamp (__cdecl* Pa_StreamTime)( PortAudioStream *stream ); + +double (__cdecl* Pa_GetCPULoad)( PortAudioStream* stream ); + +int (__cdecl* Pa_GetMinNumBuffers)( int framesPerBuffer, double sampleRate ); + +void (__cdecl* Pa_Sleep)( long msec ); + +PaError (__cdecl* Pa_GetSampleSize)( PaSampleFormat format ); + + +////////////////////////////////////////////////////////////////////////// + +... + +ZERROR AudioEngine::DirectXSupport(ZBOOL bSupDX) +{ + if (bSupDX) + if (CheckForDirectXSupport()) + bSupportDirectX = _TRUE; + else + return _NO_SOUND; + else + bSupportDirectX = _FALSE; + return _NO_ERROR; +} + + + +ZBOOL AudioEngine::CheckForDirectXSupport() +{ + HMODULE pTestDXLib; + FARPROC pFunctionality; + + pTestDXLib=LoadLibrary("DSOUND"); + if (pTestDXLib!=NULL) // check if there is a DirectSound + { + pFunctionality = GetProcAddress(pTestDXLib, (char*) 7); + if (pFunctionality!=NULL) + { + FreeLibrary(pTestDXLib); + return _TRUE; + } + else + { + FreeLibrary(pTestDXLib); + return _FALSE; + } + } + else + return _FALSE; +} + + +ZERROR AudioEngine::LoadPALib() +{ +#ifdef _DEBUG + if (bSupportDirectX) + pPaDll = LoadLibrary("PA_DXD"); + else + pPaDll = LoadLibrary("PA_MMED"); +#else + if (bSupportDirectX) + pPaDll = LoadLibrary("PA_DX"); + else + pPaDll = LoadLibrary("PA_MME"); +#endif + if (pPaDll!=NULL) + { + + Pa_Initialize = (int (__cdecl*)(void))GetProcAddress(pPaDll,"Pa_Initialize"); + Pa_Terminate = (int (__cdecl*)(void))GetProcAddress(pPaDll,"Pa_Terminate"); + Pa_GetHostError = (long (__cdecl* )( void )) GetProcAddress(pPaDll,"Pa_GetHostError"); + Pa_GetErrorText = (const char* (__cdecl* )( PaError )) GetProcAddress(pPaDll,"Pa_GetErrorText"); + Pa_CountDevices = (int (__cdecl*)(void))GetProcAddress(pPaDll,"Pa_CountDevices"); + Pa_GetDefaultInputDeviceID = (int (__cdecl*)(void))GetProcAddress(pPaDll,"Pa_GetDefaultInputDeviceID"); + Pa_GetDefaultOutputDeviceID = (int (__cdecl*)(void))GetProcAddress(pPaDll,"Pa_GetDefaultOutputDeviceID"); + Pa_GetDeviceInfo = (const PaDeviceInfo* (__cdecl* )( PaDeviceID)) GetProcAddress(pPaDll,"Pa_GetDeviceInfo"); + Pa_OpenStream = ( PaError (__cdecl* )( + PortAudioStream ** , + PaDeviceID , + int , + PaSampleFormat , + void *, + PaDeviceID , + int , + PaSampleFormat , + void *, + double , + unsigned long , + unsigned long , + unsigned long , + PortAudioCallback *, + void * )) GetProcAddress(pPaDll,"Pa_OpenStream"); + + Pa_OpenDefaultStream = (PaError (__cdecl* )( PortAudioStream** , + int , + int , + PaSampleFormat , + double , + unsigned long , + unsigned long , + PortAudioCallback *, + void * )) GetProcAddress(pPaDll,"Pa_OpenDefaultStream"); + Pa_CloseStream = (PaError (__cdecl* )( PortAudioStream* )) GetProcAddress(pPaDll,"Pa_CloseStream"); + Pa_StartStream = (PaError (__cdecl* )( PortAudioStream* )) GetProcAddress(pPaDll,"Pa_StartStream"); + Pa_StopStream = (PaError (__cdecl* )( PortAudioStream* ))GetProcAddress(pPaDll,"Pa_StopStream"); + Pa_AbortStream = (PaError (__cdecl* )( PortAudioStream* )) GetProcAddress(pPaDll,"Pa_AbortStream"); + Pa_StreamActive = (PaError (__cdecl* )( PortAudioStream* )) GetProcAddress(pPaDll,"Pa_StreamActive"); + Pa_StreamTime = (PaTimestamp (__cdecl* )( PortAudioStream *))GetProcAddress(pPaDll,"Pa_StreamTime"); + Pa_GetCPULoad = (double (__cdecl* )( PortAudioStream* ))GetProcAddress(pPaDll,"Pa_GetCPULoad"); + Pa_GetMinNumBuffers = (int (__cdecl* )( int , double )) GetProcAddress(pPaDll,"Pa_GetMinNumBuffers"); + Pa_Sleep = (void (__cdecl* )( long )) GetProcAddress(pPaDll,"Pa_Sleep"); + Pa_GetSampleSize = (PaError (__cdecl* )( PaSampleFormat )) GetProcAddress(pPaDll,"Pa_GetSampleSize"); + + return _NO_ERROR; + } + else + return _DLL_NOT_FOUND; +} + +ZERROR AudioEngine::UnLoadPALib() +{ + if (pPaDll!=NULL) + FreeLibrary(pPaDll); + return _NO_ERROR; +} + +... diff --git a/src/audio/portaudio/pa_dll_switch/pa_lib.c b/src/audio/portaudio/pa_dll_switch/pa_lib.c new file mode 100644 index 0000000000000000000000000000000000000000..86601592c4e2117777b3a4d74fb3f4d22b26f0f2 --- /dev/null +++ b/src/audio/portaudio/pa_dll_switch/pa_lib.c @@ -0,0 +1,827 @@ +/* + * Portable Audio I/O Library + * Host Independant Layer + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2000 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. + * + * 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. + * + * 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. + * + */ + +/* Modification History: + PLB20010422 - apply Mike Berry's changes for CodeWarrior on PC +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> + +/* PLB20010422 - "memory.h" doesn't work on CodeWarrior for PC. Thanks Mike Berry for the mod. */ +#ifdef _WIN32 +#ifndef __MWERKS__ +#include <memory.h> +#endif /* __MWERKS__ */ +#else /* !_WIN32 */ +#include <memory.h> +#endif /* _WIN32 */ + +#include "portaudio.h" +#include "pa_host.h" +#include "pa_trace.h" + +/* The reason we might NOT want to validate the rate before opening the stream + * is because many DirectSound drivers lie about the rates they actually support. + */ +#define PA_VALIDATE_RATE (0) /* If true validate sample rate against driver info. */ + +/* +O- maybe not allocate past_InputBuffer and past_OutputBuffer if not needed for conversion +*/ + +#ifndef FALSE + #define FALSE (0) + #define TRUE (!FALSE) +#endif + +#define PRINT(x) { printf x; fflush(stdout); } +#define ERR_RPT(x) PRINT(x) +#define DBUG(x) /* PRINT(x) */ +#define DBUGX(x) /* PRINT(x) */ + +static int gInitCount = 0; /* Count number of times Pa_Initialize() called to allow nesting and overlapping. */ + +static PaError Pa_KillStream( PortAudioStream *stream, int abort ); + +/***********************************************************************/ +int PaHost_FindClosestTableEntry( double allowableError, const double *rateTable, int numRates, double frameRate ) +{ + double err, minErr = allowableError; + int i, bestFit = -1; + + for( i=0; i<numRates; i++ ) + { + err = fabs( frameRate - rateTable[i] ); + if( err < minErr ) + { + minErr = err; + bestFit = i; + } + } + return bestFit; +} + +/************************************************************************** +** Make sure sample rate is legal and also convert to enumeration for driver. +*/ +PaError PaHost_ValidateSampleRate( PaDeviceID id, double requestedFrameRate, + double *closestFrameRatePtr ) +{ + long bestRateIndex; + const PaDeviceInfo *pdi; + pdi = Pa_GetDeviceInfo( id ); + if( pdi == NULL ) return paInvalidDeviceId; + + if( pdi->numSampleRates == -1 ) + { + /* Is it out of range? */ + if( (requestedFrameRate < pdi->sampleRates[0]) || + (requestedFrameRate > pdi->sampleRates[1]) ) + { + return paInvalidSampleRate; + } + + *closestFrameRatePtr = requestedFrameRate; + } + else + { + bestRateIndex = PaHost_FindClosestTableEntry( 1.0, pdi->sampleRates, pdi->numSampleRates, requestedFrameRate ); + if( bestRateIndex < 0 ) return paInvalidSampleRate; + *closestFrameRatePtr = pdi->sampleRates[bestRateIndex]; + } + return paNoError; +} + +/*************************************************************************/ +DLL_API PaError Pa_OpenStream( + PortAudioStream** streamPtrPtr, + PaDeviceID inputDeviceID, + int numInputChannels, + PaSampleFormat inputSampleFormat, + void *inputDriverInfo, + PaDeviceID outputDeviceID, + int numOutputChannels, + PaSampleFormat outputSampleFormat, + void *outputDriverInfo, + double sampleRate, + unsigned long framesPerBuffer, + unsigned long numberOfBuffers, + unsigned long streamFlags, + PortAudioCallback *callback, + void *userData ) +{ + internalPortAudioStream *past = NULL; + PaError result = paNoError; + int bitsPerInputSample; + int bitsPerOutputSample; + /* Print passed parameters. */ + DBUG(("Pa_OpenStream( %p, %d, %d, %d, %p, /* input */ \n", + streamPtrPtr, inputDeviceID, numInputChannels, + inputSampleFormat, inputDriverInfo )); + DBUG((" %d, %d, %d, %p, /* output */\n", + outputDeviceID, numOutputChannels, + outputSampleFormat, outputDriverInfo )); + DBUG((" %g, %d, %d, 0x%x, , %p )\n", + sampleRate, framesPerBuffer, numberOfBuffers, + streamFlags, userData )); + + /* Check for parameter errors. */ + if( (streamFlags & ~(paClipOff | paDitherOff)) != 0 ) return paInvalidFlag; + if( streamPtrPtr == NULL ) return paBadStreamPtr; + if( inputDriverInfo != NULL ) return paHostError; /* REVIEW */ + if( outputDriverInfo != NULL ) return paHostError; /* REVIEW */ + if( (inputDeviceID < 0) && ( outputDeviceID < 0) ) return paInvalidDeviceId; + if( (outputDeviceID >= Pa_CountDevices()) || (inputDeviceID >= Pa_CountDevices()) ) return paInvalidDeviceId; + if( (numInputChannels <= 0) && ( numOutputChannels <= 0) ) return paInvalidChannelCount; + +#if SUPPORT_AUDIO_CAPTURE + if( inputDeviceID >= 0 ) + { + PaError size = Pa_GetSampleSize( inputSampleFormat ); + if( size < 0 ) return size; + bitsPerInputSample = 8 * size; + if( (numInputChannels <= 0) ) return paInvalidChannelCount; + } +#else + if( inputDeviceID >= 0 ) + { + return paInvalidChannelCount; + } +#endif /* SUPPORT_AUDIO_CAPTURE */ + else + { + if( numInputChannels > 0 ) return paInvalidChannelCount; + bitsPerInputSample = 0; + } + + if( outputDeviceID >= 0 ) + { + PaError size = Pa_GetSampleSize( outputSampleFormat ); + if( size < 0 ) return size; + bitsPerOutputSample = 8 * size; + if( (numOutputChannels <= 0) ) return paInvalidChannelCount; + } + else + { + if( numOutputChannels > 0 ) return paInvalidChannelCount; + bitsPerOutputSample = 0; + } + + if( callback == NULL ) return paNullCallback; + + /* Allocate and clear stream structure. */ + past = (internalPortAudioStream *) PaHost_AllocateFastMemory( sizeof(internalPortAudioStream) ); + if( past == NULL ) return paInsufficientMemory; + memset( past, 0, sizeof(internalPortAudioStream) ); + AddTraceMessage("Pa_OpenStream: past", (long) past ); + + past->past_Magic = PA_MAGIC; /* Set ID to catch bugs. */ + past->past_FramesPerUserBuffer = framesPerBuffer; + past->past_NumUserBuffers = numberOfBuffers; /* NOTE - PaHost_OpenStream() NMUST CHECK FOR ZERO! */ + past->past_Callback = callback; + past->past_UserData = userData; + past->past_OutputSampleFormat = outputSampleFormat; + past->past_InputSampleFormat = inputSampleFormat; + past->past_OutputDeviceID = outputDeviceID; + past->past_InputDeviceID = inputDeviceID; + past->past_NumInputChannels = numInputChannels; + past->past_NumOutputChannels = numOutputChannels; + past->past_Flags = streamFlags; + + /* Check for absurd sample rates. */ + if( (sampleRate < 1000.0) || (sampleRate > 200000.0) ) + { + result = paInvalidSampleRate; + goto cleanup; + } + + /* Allocate buffers that may be used for format conversion from user to native buffers. */ + if( numInputChannels > 0 ) + { + +#if PA_VALIDATE_RATE + result = PaHost_ValidateSampleRate( inputDeviceID, sampleRate, &past->past_SampleRate ); + if( result < 0 ) + { + goto cleanup; + } +#else + past->past_SampleRate = sampleRate; +#endif + /* Allocate single Input buffer. */ + past->past_InputBufferSize = framesPerBuffer * numInputChannels * ((bitsPerInputSample+7) / 8); + past->past_InputBuffer = PaHost_AllocateFastMemory(past->past_InputBufferSize); + if( past->past_InputBuffer == NULL ) + { + result = paInsufficientMemory; + goto cleanup; + } + } + else + { + past->past_InputBuffer = NULL; + } + + /* Allocate single Output buffer. */ + if( numOutputChannels > 0 ) + { +#if PA_VALIDATE_RATE + result = PaHost_ValidateSampleRate( outputDeviceID, sampleRate, &past->past_SampleRate ); + if( result < 0 ) + { + goto cleanup; + } +#else + past->past_SampleRate = sampleRate; +#endif + past->past_OutputBufferSize = framesPerBuffer * numOutputChannels * ((bitsPerOutputSample+7) / 8); + past->past_OutputBuffer = PaHost_AllocateFastMemory(past->past_OutputBufferSize); + if( past->past_OutputBuffer == NULL ) + { + result = paInsufficientMemory; + goto cleanup; + } + } + else + { + past->past_OutputBuffer = NULL; + } + + result = PaHost_OpenStream( past ); + if( result < 0 ) goto cleanup; + + *streamPtrPtr = (void *) past; + + return result; + +cleanup: + if( past != NULL ) Pa_CloseStream( past ); + *streamPtrPtr = NULL; + return result; +} + + +/*************************************************************************/ +DLL_API PaError Pa_OpenDefaultStream( PortAudioStream** stream, + int numInputChannels, + int numOutputChannels, + PaSampleFormat sampleFormat, + double sampleRate, + unsigned long framesPerBuffer, + unsigned long numberOfBuffers, + PortAudioCallback *callback, + void *userData ) +{ + return Pa_OpenStream( + stream, + ((numInputChannels > 0) ? Pa_GetDefaultInputDeviceID() : paNoDevice), + numInputChannels, sampleFormat, NULL, + ((numOutputChannels > 0) ? Pa_GetDefaultOutputDeviceID() : paNoDevice), + numOutputChannels, sampleFormat, NULL, + sampleRate, framesPerBuffer, numberOfBuffers, paNoFlag, callback, userData ); +} + +/*************************************************************************/ +DLL_API PaError Pa_CloseStream( PortAudioStream* stream) +{ + PaError result; + internalPortAudioStream *past; + + DBUG(("Pa_CloseStream()\n")); + if( stream == NULL ) return paBadStreamPtr; + past = (internalPortAudioStream *) stream; + + Pa_AbortStream( past ); + result = PaHost_CloseStream( past ); + + if( past->past_InputBuffer ) PaHost_FreeFastMemory( past->past_InputBuffer, past->past_InputBufferSize ); + if( past->past_OutputBuffer ) PaHost_FreeFastMemory( past->past_OutputBuffer, past->past_OutputBufferSize ); + PaHost_FreeFastMemory( past, sizeof(internalPortAudioStream) ); + + return result; +} + +/*************************************************************************/ +DLL_API PaError Pa_StartStream( PortAudioStream *stream ) +{ + PaError result = paHostError; + internalPortAudioStream *past; + + if( stream == NULL ) return paBadStreamPtr; + past = (internalPortAudioStream *) stream; + + past->past_FrameCount = 0.0; + + if( past->past_NumInputChannels > 0 ) + { + result = PaHost_StartInput( past ); + DBUG(("Pa_StartStream: PaHost_StartInput returned = 0x%X.\n", result)); + if( result < 0 ) goto error; + } + + if( past->past_NumOutputChannels > 0 ) + { + result = PaHost_StartOutput( past ); + DBUG(("Pa_StartStream: PaHost_StartOutput returned = 0x%X.\n", result)); + if( result < 0 ) goto error; + } + + result = PaHost_StartEngine( past ); + DBUG(("Pa_StartStream: PaHost_StartEngine returned = 0x%X.\n", result)); + if( result < 0 ) goto error; + + return paNoError; + +error: + return result; +} + +/*************************************************************************/ +DLL_API PaError Pa_StopStream( PortAudioStream *stream ) +{ + return Pa_KillStream( stream, 0 ); +} + +/*************************************************************************/ +DLL_API PaError Pa_AbortStream( PortAudioStream *stream ) +{ + return Pa_KillStream( stream, 1 ); +} + +/*************************************************************************/ +static PaError Pa_KillStream( PortAudioStream *stream, int abort ) +{ + PaError result = paNoError; + internalPortAudioStream *past; + + DBUG(("Pa_StopStream().\n")); + if( stream == NULL ) return paBadStreamPtr; + past = (internalPortAudioStream *) stream; + + if( (past->past_NumInputChannels > 0) || (past->past_NumOutputChannels > 0) ) + { + result = PaHost_StopEngine( past, abort ); + DBUG(("Pa_StopStream: PaHost_StopEngine returned = 0x%X.\n", result)); + if( result < 0 ) goto error; + } + + if( past->past_NumInputChannels > 0 ) + { + result = PaHost_StopInput( past, abort ); + DBUG(("Pa_StopStream: PaHost_StopInput returned = 0x%X.\n", result)); + if( result != paNoError ) goto error; + } + + if( past->past_NumOutputChannels > 0 ) + { + result = PaHost_StopOutput( past, abort ); + DBUG(("Pa_StopStream: PaHost_StopOutput returned = 0x%X.\n", result)); + if( result != paNoError ) goto error; + } + +error: + past->past_Usage = 0; + past->past_IfLastExitValid = 0; + + return result; +} + +/*************************************************************************/ +DLL_API PaError Pa_StreamActive( PortAudioStream *stream ) +{ + internalPortAudioStream *past; + if( stream == NULL ) return paBadStreamPtr; + past = (internalPortAudioStream *) stream; + return PaHost_StreamActive( past ); +} + +/*************************************************************************/ +DLL_API const char *Pa_GetErrorText( PaError errnum ) +{ + const char *msg; + + switch(errnum) + { + case paNoError: msg = "Success"; break; + case paHostError: msg = "Host error."; break; + case paInvalidChannelCount: msg = "Invalid number of channels."; break; + case paInvalidSampleRate: msg = "Invalid sample rate."; break; + case paInvalidDeviceId: msg = "Invalid device ID."; break; + case paInvalidFlag: msg = "Invalid flag."; break; + case paSampleFormatNotSupported: msg = "Sample format not supported"; break; + case paBadIODeviceCombination: msg = "Illegal combination of I/O devices."; break; + case paInsufficientMemory: msg = "Insufficient memory."; break; + case paBufferTooBig: msg = "Buffer too big."; break; + case paBufferTooSmall: msg = "Buffer too small."; break; + case paNullCallback: msg = "No callback routine specified."; break; + case paBadStreamPtr: msg = "Invalid stream pointer."; break; + case paTimedOut : msg = "Wait Timed Out."; break; + case paInternalError: msg = "Internal PortAudio Error."; break; + default: msg = "Illegal error number."; break; + } + return msg; +} + +/* + Get CPU Load as a fraction of total CPU time. + A value of 0.5 would imply that PortAudio and the sound generating + callback was consuming roughly 50% of the available CPU time. + The amount may vary depending on CPU load. + This function may be called from the callback function. +*/ +DLL_API double Pa_GetCPULoad( PortAudioStream* stream) +{ + internalPortAudioStream *past; + if( stream == NULL ) return (double) paBadStreamPtr; + past = (internalPortAudioStream *) stream; + return past->past_Usage; +} + +/************************************************************* +** Calculate 2 LSB dither signal with a triangular distribution. +** Ranged properly for adding to a 32 bit integer prior to >>15. +*/ +#define DITHER_BITS (15) +#define DITHER_SCALE (1.0f / ((1<<DITHER_BITS)-1)) +static long Pa_TriangularDither( void ) +{ + static unsigned long previous = 0; + static unsigned long randSeed1 = 22222; + static unsigned long randSeed2 = 5555555; + long current, highPass; + /* Generate two random numbers. */ + randSeed1 = (randSeed1 * 196314165) + 907633515; + randSeed2 = (randSeed2 * 196314165) + 907633515; + /* Generate triangular distribution about 0. */ + current = (((long)randSeed1)>>(32-DITHER_BITS)) + (((long)randSeed2)>>(32-DITHER_BITS)); + /* High pass filter to reduce audibility. */ + highPass = current - previous; + previous = current; + return highPass; +} + +/************************************************************************* +** Called by host code. +** Convert input from Int16, call user code, then convert output +** to Int16 format for native use. +** Assumes host native format is paInt16. +** Returns result from user callback. +*/ +long Pa_CallConvertInt16( internalPortAudioStream *past, + short *nativeInputBuffer, + short *nativeOutputBuffer ) +{ + long temp; + long bytesEmpty = 0; + long bytesFilled = 0; + int userResult; + unsigned int i; + void *inputBuffer = NULL; + void *outputBuffer = NULL; + +#if SUPPORT_AUDIO_CAPTURE + /* Get native data from DirectSound. */ + if( (past->past_NumInputChannels > 0) && (nativeInputBuffer != NULL) ) + { + /* Convert from native format to PA format. */ + unsigned int samplesPerBuffer = past->past_FramesPerUserBuffer * past->past_NumInputChannels; + switch(past->past_InputSampleFormat) + { + + case paFloat32: + { + float *inBufPtr = (float *) past->past_InputBuffer; + inputBuffer = past->past_InputBuffer; + for( i=0; i<samplesPerBuffer; i++ ) + { + inBufPtr[i] = nativeInputBuffer[i] * (1.0f / 32767.0f); + } + break; + } + + case paInt32: + { + /* Convert 16 bit data to 32 bit integers */ + int *inBufPtr = (int *) past->past_InputBuffer; + inputBuffer = past->past_InputBuffer; + for( i=0; i<samplesPerBuffer; i++ ) + { + inBufPtr[i] = nativeInputBuffer[i] << 16; + } + break; + } + + case paInt16: + { + /* Already in correct format so don't copy. */ + inputBuffer = nativeInputBuffer; + break; + } + + case paInt8: + { + /* Convert 16 bit data to 8 bit chars */ + char *inBufPtr = (char *) past->past_InputBuffer; + inputBuffer = past->past_InputBuffer; + if( past->past_Flags & paDitherOff ) + { + for( i=0; i<samplesPerBuffer; i++ ) + { + inBufPtr[i] = (char)(nativeInputBuffer[i] >> 8); + } + } + else + { + for( i=0; i<samplesPerBuffer; i++ ) + { + temp = nativeInputBuffer[i]; + temp += Pa_TriangularDither() >> 7; + temp = ((temp < -0x8000) ? -0x8000 : ((temp > 0x7FFF) ? 0x7FFF : temp)); + inBufPtr[i] = (char)(temp >> 8); + } + } + break; + } + + case paUInt8: + { + /* Convert 16 bit data to 8 bit unsigned chars */ + unsigned char *inBufPtr = (unsigned char *) past->past_InputBuffer; + inputBuffer = past->past_InputBuffer; + if( past->past_Flags & paDitherOff ) + { + for( i=0; i<samplesPerBuffer; i++ ) + { + inBufPtr[i] = ((unsigned char)(nativeInputBuffer[i] >> 8)) + 0x80; + } + } + else + { + /* If you dither then you have to clip because dithering could push the signal out of range! */ + for( i=0; i<samplesPerBuffer; i++ ) + { + temp = nativeInputBuffer[i]; + temp += Pa_TriangularDither() >> 7; + temp = ((temp < -0x8000) ? -0x8000 : ((temp > 0x7FFF) ? 0x7FFF : temp)); + inBufPtr[i] = (unsigned char)(temp + 0x80); + } + } + break; + } + + default: + break; + } + } +#endif /* SUPPORT_AUDIO_CAPTURE */ + + /* Are we doing output time? */ + if( (past->past_NumOutputChannels > 0) && (nativeOutputBuffer != NULL) ) + { + /* May already be in native format so just write directly to native buffer. */ + outputBuffer = (past->past_OutputSampleFormat == paInt16) ? + nativeOutputBuffer : past->past_OutputBuffer; + } + /* + AddTraceMessage("Pa_CallConvertInt16: inputBuffer = ", (int) inputBuffer ); + AddTraceMessage("Pa_CallConvertInt16: outputBuffer = ", (int) outputBuffer ); + */ + /* Call user callback routine. */ + userResult = past->past_Callback( + inputBuffer, + outputBuffer, + past->past_FramesPerUserBuffer, + past->past_FrameCount, + past->past_UserData ); + + past->past_FrameCount += (PaTimestamp) past->past_FramesPerUserBuffer; + + /* Convert to native format if necessary. */ + if( outputBuffer != NULL ) + { + unsigned int samplesPerBuffer = past->past_FramesPerUserBuffer * past->past_NumOutputChannels; + switch(past->past_OutputSampleFormat) + { + case paFloat32: + { + float *outBufPtr = (float *) past->past_OutputBuffer; + if( past->past_Flags & paDitherOff ) + { + if( past->past_Flags & paClipOff ) /* NOTHING */ + { + for( i=0; i<samplesPerBuffer; i++ ) + { + *nativeOutputBuffer++ = (short) (outBufPtr[i] * (32767.0f)); + } + } + else /* CLIP */ + { + for( i=0; i<samplesPerBuffer; i++ ) + { + temp = (long)(outBufPtr[i] * 32767.0f); + *nativeOutputBuffer++ = (short)((temp < -0x8000) ? -0x8000 : ((temp > 0x7FFF) ? 0x7FFF : temp)); + } + } + } + else + { + /* If you dither then you have to clip because dithering could push the signal out of range! */ + for( i=0; i<samplesPerBuffer; i++ ) + { + float dither = Pa_TriangularDither()*DITHER_SCALE; + float dithered = (outBufPtr[i] * (32767.0f)) + dither; + temp = (long) (dithered); + *nativeOutputBuffer++ = (short)((temp < -0x8000) ? -0x8000 : ((temp > 0x7FFF) ? 0x7FFF : temp)); + } + } + break; + } + + case paInt32: + { + int *outBufPtr = (int *) past->past_OutputBuffer; + if( past->past_Flags & paDitherOff ) + { + for( i=0; i<samplesPerBuffer; i++ ) + { + *nativeOutputBuffer++ = (short) (outBufPtr[i] >> 16 ); + } + } + else + { + for( i=0; i<samplesPerBuffer; i++ ) + { + /* Shift one bit down before dithering so that we have room for overflow from add. */ + temp = (outBufPtr[i] >> 1) + Pa_TriangularDither(); + temp = temp >> 15; + *nativeOutputBuffer++ = (short)((temp < -0x8000) ? -0x8000 : ((temp > 0x7FFF) ? 0x7FFF : temp)); + } + } + break; + } + + case paInt8: + { + char *outBufPtr = (char *) past->past_OutputBuffer; + for( i=0; i<samplesPerBuffer; i++ ) + { + *nativeOutputBuffer++ = ((short)outBufPtr[i]) << 8; + } + break; + } + + case paUInt8: + { + unsigned char *outBufPtr = (unsigned char *) past->past_OutputBuffer; + for( i=0; i<samplesPerBuffer; i++ ) + { + *nativeOutputBuffer++ = ((short)(outBufPtr[i] - 0x80)) << 8; + } + break; + } + + default: + break; + } + + } + + return userResult; +} + +/************************************************************************* +** Called by host code. +** Convert input from Float32, call user code, then convert output +** to Float32 format for native use. +** Assumes host native format is Float32. +** Returns result from user callback. +** FIXME - Unimplemented for formats other than paFloat32!!!! +*/ +long Pa_CallConvertFloat32( internalPortAudioStream *past, + float *nativeInputBuffer, + float *nativeOutputBuffer ) +{ + long bytesEmpty = 0; + long bytesFilled = 0; + int userResult; + void *inputBuffer = NULL; + void *outputBuffer = NULL; + + /* Get native data from DirectSound. */ + if( (past->past_NumInputChannels > 0) && (nativeInputBuffer != NULL) ) + { + inputBuffer = nativeInputBuffer; // FIXME + } + + /* Are we doing output time? */ + if( (past->past_NumOutputChannels > 0) && (nativeOutputBuffer != NULL) ) + { + /* May already be in native format so just write directly to native buffer. */ + outputBuffer = (past->past_OutputSampleFormat == paFloat32) ? + nativeOutputBuffer : past->past_OutputBuffer; + } + /* + AddTraceMessage("Pa_CallConvertInt16: inputBuffer = ", (int) inputBuffer ); + AddTraceMessage("Pa_CallConvertInt16: outputBuffer = ", (int) outputBuffer ); + */ + /* Call user callback routine. */ + userResult = past->past_Callback( + inputBuffer, + outputBuffer, + past->past_FramesPerUserBuffer, + past->past_FrameCount, + past->past_UserData ); + + past->past_FrameCount += (PaTimestamp) past->past_FramesPerUserBuffer; + + /* Convert to native format if necessary. */ // FIXME + return userResult; +} + +/*************************************************************************/ +DLL_API PaError Pa_Initialize( void ) +{ + if( gInitCount++ > 0 ) return paNoError; + ResetTraceMessages(); + return PaHost_Init(); +} + +DLL_API PaError Pa_Terminate( void ) +{ + PaError result = paNoError; + + if( gInitCount == 0 ) return paNoError; + else if( --gInitCount == 0 ) + { + result = PaHost_Term(); + DumpTraceMessages(); + } + return result; +} + +/*************************************************************************/ +DLL_API PaError Pa_GetSampleSize( PaSampleFormat format ) +{ + int size; + switch(format ) + { + + case paUInt8: + case paInt8: + size = 1; + break; + + case paInt16: + size = 2; + break; + + case paPackedInt24: + size = 3; + break; + + case paFloat32: + case paInt32: + case paInt24: + size = 4; + break; + + default: + size = paSampleFormatNotSupported; + break; + } + return (PaError) size; +} + + diff --git a/src/audio/portaudio/pa_dll_switch/portaudio.h b/src/audio/portaudio/pa_dll_switch/portaudio.h new file mode 100644 index 0000000000000000000000000000000000000000..9632521ebc44f0787c25530c4c33116200bd87ba --- /dev/null +++ b/src/audio/portaudio/pa_dll_switch/portaudio.h @@ -0,0 +1,439 @@ +#ifndef PORT_AUDIO_H +#define PORT_AUDIO_H + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/* + * PortAudio Portable Real-Time Audio Library + * PortAudio API Header File + * Latest version available at: http://www.audiomulch.com/portaudio/ + * + * 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. + * + * 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. + * + * 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. + * + */ + +// added by zplane.developement in order to generate a DLL + +#if defined(PA_MME_EXPORTS) || defined(PA_DX_EXPORTS) +#define DLL_API __declspec( dllexport ) +#elif defined(_LIB) || defined(_STATIC_LINK) || defined(_STATIC_APP) +#define DLL_API +#else +#define DLL_API __declspec(dllexport) +#endif + + +typedef int PaError; +typedef enum { + paNoError = 0, + + paHostError = -10000, + paInvalidChannelCount, + paInvalidSampleRate, + paInvalidDeviceId, + paInvalidFlag, + paSampleFormatNotSupported, + paBadIODeviceCombination, + paInsufficientMemory, + paBufferTooBig, + paBufferTooSmall, + paNullCallback, + paBadStreamPtr, + paTimedOut, + paInternalError +} PaErrorNum; + +/* + Pa_Initialize() is the library initialisation function - call this before + using the library. +*/ + +DLL_API PaError Pa_Initialize( void ); + +/* + Pa_Terminate() is the library termination function - call this after + using the library. +*/ + +DLL_API PaError Pa_Terminate( void ); + +/* + Return host specific error. + This can be called after receiving a paHostError. +*/ +DLL_API long Pa_GetHostError( void ); + +/* + Translate the error number into a human readable message. +*/ +DLL_API const char *Pa_GetErrorText( PaError errnum ); + +/* + Sample formats + + These are formats used to pass sound data between the callback and the + stream. Each device has a "native" format which may be used when optimum + efficiency or control over conversion is required. + + Formats marked "always available" are supported (emulated) by all devices. + + The floating point representation uses +1.0 and -1.0 as the respective + maximum and minimum. + +*/ + +typedef unsigned long PaSampleFormat; +#define paFloat32 ((PaSampleFormat) (1<<0)) /*always available*/ +#define paInt16 ((PaSampleFormat) (1<<1)) /*always available*/ +#define paInt32 ((PaSampleFormat) (1<<2)) /*always available*/ +#define paInt24 ((PaSampleFormat) (1<<3)) +#define paPackedInt24 ((PaSampleFormat) (1<<4)) +#define paInt8 ((PaSampleFormat) (1<<5)) +#define paUInt8 ((PaSampleFormat) (1<<6)) /* unsigned 8 bit, 128 is "ground" */ +#define paCustomFormat ((PaSampleFormat) (1<<16)) + +/* + Device enumeration mechanism. + + Device ids range from 0 to Pa_CountDevices()-1. + + Devices may support input, output or both. Device 0 is always the "default" + device and should support at least stereo in and out if that is available + on the taget platform _even_ if this involves kludging an input/output + device on platforms that usually separate input from output. Other platform + specific devices are specified by positive device ids. +*/ + +typedef int PaDeviceID; +#define paNoDevice -1 + +typedef struct +{ + int structVersion; + const char *name; + int maxInputChannels; + int maxOutputChannels; + /* Number of discrete rates, or -1 if range supported. */ + int numSampleRates; + /* Array of supported sample rates, or {min,max} if range supported. */ + const double *sampleRates; + PaSampleFormat nativeSampleFormats; +} +PaDeviceInfo; + + +DLL_API int Pa_CountDevices(); +/* + Pa_GetDefaultInputDeviceID(), Pa_GetDefaultOutputDeviceID() + + Return the default device ID or paNoDevice if there is no devices. + The result can be passed to Pa_OpenStream(). + + On the PC, the user can specify a default device by + setting an environment variable. For example, to use device #1. + + set PA_RECOMMENDED_OUTPUT_DEVICE=1 + + The user should first determine the available device ID by using + the supplied application "pa_devs". +*/ +DLL_API PaDeviceID Pa_GetDefaultInputDeviceID( void ); +DLL_API PaDeviceID Pa_GetDefaultOutputDeviceID( void ); + +/* + PaTimestamp is used to represent a continuous sample clock with arbitrary + start time useful for syncronisation. The type is used in the outTime + argument to the callback function and the result of Pa_StreamTime() +*/ + +typedef double PaTimestamp; + +/* + Pa_GetDeviceInfo() returns a pointer to an immutable PaDeviceInfo structure + referring to the device specified by id. + If id is out of range the function returns NULL. + + The returned structure is owned by the PortAudio implementation and must + not be manipulated or freed. The pointer is guaranteed to be valid until + between calls to Pa_Initialize() and Pa_Terminate(). +*/ + +DLL_API const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceID id ); + +/* + PortAudioCallback is implemented by clients of the portable audio api. + + inputBuffer and outputBuffer are arrays of interleaved samples, + the format, packing and number of channels used by the buffers are + determined by parameters to Pa_OpenStream() (see below). + + framesPerBuffer is the number of sample frames to be processed by the callback. + + outTime is the time in samples when the buffer(s) processed by + this callback will begin being played at the audio output. + See also Pa_StreamTime() + + userData is the value of a user supplied pointer passed to Pa_OpenStream() + intended for storing synthesis data etc. + + return value: + The callback can return a nonzero value to stop the stream. This may be + useful in applications such as soundfile players where a specific duration + of output is required. However, it is not necessary to utilise this mechanism + as StopStream() will also terminate the stream. A callback returning a + nonzero value must fill the entire outputBuffer. + + NOTE: None of the other stream functions may be called from within the + callback function except for Pa_GetCPULoad(). + +*/ + +typedef int (PortAudioCallback)( + void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ); + + +/* + Stream flags + + These flags may be supplied (ored together) in the streamFlags argument to + the Pa_OpenStream() function. + + [ suggestions? ] +*/ + +#define paNoFlag (0) +#define paClipOff (1<<0) /* disable defult clipping of out of range samples */ +#define paDitherOff (1<<1) /* disable default dithering */ +#define paPlatformSpecificFlags (0x00010000) +typedef unsigned long PaStreamFlags; + +/* + A single PortAudioStream provides multiple channels of real-time + input and output audio streaming to a client application. + Pointers to PortAudioStream objects are passed between PortAudio functions. +*/ + +typedef void PortAudioStream; +#define PaStream PortAudioStream + +/* + Pa_OpenStream() opens a stream for either input, output or both. + + stream is the address of a PortAudioStream pointer which will receive + a pointer to the newly opened stream. + + inputDevice is the id of the device used for input (see PaDeviceID above.) + inputDevice may be paNoDevice to indicate that an input device is not required. + + numInputChannels is the number of channels of sound to be delivered to the + callback. It can range from 1 to the value of maxInputChannels in the + device input record for the device specified in the inputDevice parameter. + If inputDevice is paNoDevice numInputChannels is ignored. + + inputSampleFormat is the format of inputBuffer provided to the callback + function. inputSampleFormat may be any of the formats described by the + PaSampleFormat enumeration (see above). PortAudio guarantees support for + the sound devices native formats (nativeSampleFormats in the device info + record) and additionally 16 and 32 bit integer and 32 bit floating point + formats. Support for other formats is implementation defined. + + inputDriverInfo is a pointer to an optional driver specific data structure + containing additional information for device setup or stream processing. + inputDriverInfo is never required for correct operation. If not used + inputDriverInfo should be NULL. + + outputDevice is the id of the device used for output (see PaDeviceID above.) + outputDevice may be paNoDevice to indicate that an output device is not required. + + numOutputChannels is the number of channels of sound to be supplied by the + callback. See the definition of numInputChannels above for more details. + + outputSampleFormat is the sample format of the outputBuffer filled by the + callback function. See the definition of inputSampleFormat above for more + details. + + outputDriverInfo is a pointer to an optional driver specific data structure + containing additional information for device setup or stream processing. + outputDriverInfo is never required for correct operation. If not used + outputDriverInfo should be NULL. + + sampleRate is the desired sampleRate for input and output + + framesPerBuffer is the length in sample frames of all internal sample buffers + used for communication with platform specific audio routines. Wherever + possible this corresponds to the framesPerBuffer parameter passed to the + callback function. + + numberOfBuffers is the number of buffers used for multibuffered + communication with the platform specific audio routines. This parameter is + provided only as a guide - and does not imply that an implementation must + use multibuffered i/o when reliable double buffering is available (such as + SndPlayDoubleBuffer() on the Macintosh.) + + streamFlags may contain a combination of flags ORed together. + These flags modify the behavior of the + streaming process. Some flags may only be relevant to certain buffer formats. + + callback is a pointer to a client supplied function that is responsible + for processing and filling input and output buffers (see above for details.) + + userData is a client supplied pointer which is passed to the callback + function. It could for example, contain a pointer to instance data necessary + for processing the audio buffers. + + return value: + Apon success Pa_OpenStream() returns PaNoError and places a pointer to a + valid PortAudioStream in the stream argument. The stream is inactive (stopped). + If a call to Pa_OpenStream() fails a nonzero error code is returned (see + PAError above) and the value of stream is invalid. + +*/ + +DLL_API PaError Pa_OpenStream( PortAudioStream** stream, + PaDeviceID inputDevice, + int numInputChannels, + PaSampleFormat inputSampleFormat, + void *inputDriverInfo, + PaDeviceID outputDevice, + int numOutputChannels, + PaSampleFormat outputSampleFormat, + void *outputDriverInfo, + double sampleRate, + unsigned long framesPerBuffer, + unsigned long numberOfBuffers, + PaStreamFlags streamFlags, + PortAudioCallback *callback, + void *userData ); + + +/* + Pa_OpenDefaultStream() is a simplified version of Pa_OpenStream() that + opens the default input and/or ouput devices. Most parameters have + identical meaning to their Pa_OpenStream() counterparts, with the following + exceptions: + + If either numInputChannels or numOutputChannels is 0 the respective device + is not opened (same as passing paNoDevice in the device arguments to Pa_OpenStream() ) + + sampleFormat applies to both the input and output buffers. +*/ + +DLL_API PaError Pa_OpenDefaultStream( PortAudioStream** stream, + int numInputChannels, + int numOutputChannels, + PaSampleFormat sampleFormat, + double sampleRate, + unsigned long framesPerBuffer, + unsigned long numberOfBuffers, + PortAudioCallback *callback, + void *userData ); + +/* + Pa_CloseStream() closes an audio stream, flushing any pending buffers. +*/ + +DLL_API PaError Pa_CloseStream( PortAudioStream* ); + +/* + Pa_StartStream() and Pa_StopStream() begin and terminate audio processing. + When Pa_StopStream() returns, all pending audio buffers have been played. + Pa_AbortStream() stops playing immediately without waiting for pending + buffers to complete. +*/ + +DLL_API PaError Pa_StartStream( PortAudioStream *stream ); + +DLL_API PaError Pa_StopStream( PortAudioStream *stream ); + +DLL_API PaError Pa_AbortStream( PortAudioStream *stream ); + +/* + Pa_StreamActive() returns one when the stream is playing audio, + zero when not playing, or a negative error number if the + stream is invalid. + The stream is active between calls to Pa_StartStream() and Pa_StopStream(), + but may also become inactive if the callback returns a non-zero value. + In the latter case, the stream is considered inactive after the last + buffer has finished playing. +*/ + +DLL_API PaError Pa_StreamActive( PortAudioStream *stream ); + +/* + Pa_StreamTime() returns the current output time for the stream in samples. + This time may be used as a time reference (for example syncronising audio to + MIDI). +*/ + +DLL_API PaTimestamp Pa_StreamTime( PortAudioStream *stream ); + +/* + The "CPU Load" is a fraction of total CPU time consumed by the + stream's audio processing. + A value of 0.5 would imply that PortAudio and the sound generating + callback was consuming roughly 50% of the available CPU time. + This function may be called from the callback function or the application. +*/ +DLL_API double Pa_GetCPULoad( PortAudioStream* stream ); + +/* + Use Pa_GetMinNumBuffers() to determine minimum number of buffers required for + the current host based on minimum latency. + On the PC, for the DirectSound implementation, latency can be optionally set + by user by setting an environment variable. + For example, to set latency to 200 msec, put: + + set PA_MIN_LATENCY_MSEC=200 + + in the AUTOEXEC.BAT file and reboot. + If the environment variable is not set, then the latency will be determined + based on the OS. Windows NT has higher latency than Win95. +*/ + +DLL_API int Pa_GetMinNumBuffers( int framesPerBuffer, double sampleRate ); + +/* + Sleep for at least 'msec' milliseconds. + You may sleep longer than the requested time so don't rely + on this for accurate musical timing. +*/ +DLL_API void Pa_Sleep( long msec ); + +/* + Return size in bytes of a single sample in a given PaSampleFormat + or paSampleFormatNotSupported. +*/ +DLL_API PaError Pa_GetSampleSize( PaSampleFormat format ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PORT_AUDIO_H */ diff --git a/src/audio/portaudio/pa_jack/pa_jack.c b/src/audio/portaudio/pa_jack/pa_jack.c new file mode 100644 index 0000000000000000000000000000000000000000..c0691d1847bc6a2b482676b3630110615af8e160 --- /dev/null +++ b/src/audio/portaudio/pa_jack/pa_jack.c @@ -0,0 +1,1718 @@ +/* + * $Id$ + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * JACK Implementation by Joshua Haberman + * + * Copyright (c) 2004 Stefan Westerfeld <stefan@space.twc.de> + * Copyright (c) 2004 Arve Knudsen <aknuds-1@broadpark.no> + * Copyright (c) 2002 Joshua Haberman <joshua@haberman.com> + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, 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. + * + * 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. + * + * 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. + */ + +#include <string.h> +#include <regex.h> +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> +#include <sys/types.h> +#include <unistd.h> +#include <errno.h> /* EBUSY */ +#include <signal.h> /* sig_atomic_t */ +#include <math.h> +#include <semaphore.h> + +#include <jack/types.h> +#include <jack/jack.h> + +#include "pa_util.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_process.h" +#include "pa_allocation.h" +#include "pa_cpuload.h" +#include "../pablio/ringbuffer.c" + +static int aErr_; +static PaError paErr_; /* For use with ENSURE_PA */ +static pthread_t mainThread_; +static char *jackErr_ = NULL; + +#define STRINGIZE_HELPER(expr) #expr +#define STRINGIZE(expr) STRINGIZE_HELPER(expr) + +/* Check PaError */ +#define ENSURE_PA(expr) \ + do { \ + if( (paErr_ = (expr)) < paNoError ) \ + { \ + if( (paErr_) == paUnanticipatedHostError && pthread_self() == mainThread_ ) \ + { \ + assert( jackErr_ ); \ + PaUtil_SetLastHostErrorInfo( paJACK, -1, jackErr_ ); \ + } \ + PaUtil_DebugPrint(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \ + result = paErr_; \ + goto error; \ + } \ + } while( 0 ) + +#define UNLESS(expr, code) \ + do { \ + if( (expr) == 0 ) \ + { \ + if( (code) == paUnanticipatedHostError && pthread_self() == mainThread_ ) \ + { \ + assert( jackErr_ ); \ + PaUtil_SetLastHostErrorInfo( paJACK, -1, jackErr_ ); \ + } \ + PaUtil_DebugPrint(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \ + result = (code); \ + goto error; \ + } \ + } while( 0 ) + +#define ASSERT_CALL(expr, success) \ + aErr_ = (expr); \ + assert( aErr_ == success ); + +/* + * Functions that directly map to the PortAudio stream interface + */ + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); +static PaError CloseStream( PaStream* stream ); +static PaError StartStream( PaStream *stream ); +static PaError StopStream( PaStream *stream ); +static PaError AbortStream( PaStream *stream ); +static PaError IsStreamStopped( PaStream *s ); +static PaError IsStreamActive( PaStream *stream ); +/*static PaTime GetStreamInputLatency( PaStream *stream );*/ +/*static PaTime GetStreamOutputLatency( PaStream *stream );*/ +static PaTime GetStreamTime( PaStream *stream ); +static double GetStreamCpuLoad( PaStream* stream ); + + +/* + * Data specific to this API + */ + +struct PaJackStream; + +typedef struct +{ + PaUtilHostApiRepresentation commonHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup *deviceInfoMemory; + + jack_client_t *jack_client; + int jack_buffer_size; + PaHostApiIndex hostApiIndex; + + pthread_mutex_t mtx; + pthread_cond_t cond; + unsigned long inputBase, outputBase; + + /* For dealing with the process thread */ + volatile int xrun; /* Received xrun notification from JACK? */ + struct PaJackStream * volatile toAdd, * volatile toRemove; + struct PaJackStream *processQueue; + volatile sig_atomic_t jackIsDown; +} +PaJackHostApiRepresentation; + +/* PaJackStream - a stream data structure specifically for this implementation */ + +typedef struct PaJackStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilBufferProcessor bufferProcessor; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaJackHostApiRepresentation *hostApi; + + /* our input and output ports */ + jack_port_t **local_input_ports; + jack_port_t **local_output_ports; + + /* the input and output ports of the client we are connecting to */ + jack_port_t **remote_input_ports; + jack_port_t **remote_output_ports; + + int num_incoming_connections; + int num_outgoing_connections; + + jack_client_t *jack_client; + + /* The stream is running if it's still producing samples. + * The stream is active if samples it produced are still being heard. + */ + volatile sig_atomic_t is_running; + volatile sig_atomic_t is_active; + /* Used to signal processing thread that stream should start or stop, respectively */ + volatile sig_atomic_t doStart, doStop, doAbort; + + jack_nframes_t t0; + + PaUtilAllocationGroup *stream_memory; + + /* These are useful in the process callback */ + + int callbackResult; + int isSilenced; + int xrun; + + /* These are useful for the blocking API */ + + int isBlockingStream; + RingBuffer inFIFO; + RingBuffer outFIFO; + volatile sig_atomic_t data_available; + sem_t data_semaphore; + int bytesPerFrame; + int samplesPerFrame; + + struct PaJackStream *next; +} +PaJackStream; + +#define TRUE 1 +#define FALSE 0 + +/* + * Functions specific to this API + */ + +static int JackCallback( jack_nframes_t frames, void *userData ); + + +/* + * + * Implementation + * + */ + +/* ---- blocking emulation layer ---- */ + +/* Allocate buffer. */ +static PaError BlockingInitFIFO( RingBuffer *rbuf, long numFrames, long bytesPerFrame ) +{ + long numBytes = numFrames * bytesPerFrame; + char *buffer = (char *) malloc( numBytes ); + if( buffer == NULL ) return paInsufficientMemory; + memset( buffer, 0, numBytes ); + return (PaError) RingBuffer_Init( rbuf, numBytes, buffer ); +} + +/* Free buffer. */ +static PaError BlockingTermFIFO( RingBuffer *rbuf ) +{ + if( rbuf->buffer ) free( rbuf->buffer ); + rbuf->buffer = NULL; + return paNoError; +} + +static int +BlockingCallback( const void *inputBuffer, + void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + struct PaJackStream *stream = (PaJackStream *)userData; + long numBytes = stream->bytesPerFrame * framesPerBuffer; + + /* This may get called with NULL inputBuffer during initial setup. */ + if( inputBuffer != NULL ) + { + RingBuffer_Write( &stream->inFIFO, inputBuffer, numBytes ); + } + if( outputBuffer != NULL ) + { + int numRead = RingBuffer_Read( &stream->outFIFO, outputBuffer, numBytes ); + /* Zero out remainder of buffer if we run out of data. */ + memset( (char *)outputBuffer + numRead, 0, numBytes - numRead ); + } + + if( !stream->data_available ) + { + stream->data_available = 1; + sem_post( &stream->data_semaphore ); + } + return paContinue; +} + +static PaError +BlockingBegin( PaJackStream *stream, int minimum_buffer_size ) +{ + long doRead = 0; + long doWrite = 0; + PaError result = paNoError; + long numFrames; + + doRead = stream->local_input_ports != NULL; + doWrite = stream->local_output_ports != NULL; + /* <FIXME> */ + stream->samplesPerFrame = 2; + stream->bytesPerFrame = sizeof(float) * stream->samplesPerFrame; + /* </FIXME> */ + numFrames = 32; + while (numFrames < minimum_buffer_size) + numFrames *= 2; + + if( doRead ) + { + ENSURE_PA( BlockingInitFIFO( &stream->inFIFO, numFrames, stream->bytesPerFrame ) ); + } + if( doWrite ) + { + long numBytes; + + ENSURE_PA( BlockingInitFIFO( &stream->outFIFO, numFrames, stream->bytesPerFrame ) ); + + /* Make Write FIFO appear full initially. */ + numBytes = RingBuffer_GetWriteAvailable( &stream->outFIFO ); + RingBuffer_AdvanceWriteIndex( &stream->outFIFO, numBytes ); + } + + stream->data_available = 0; + sem_init( &stream->data_semaphore, 0, 0 ); + +error: + return result; +} + +static void +BlockingEnd( PaJackStream *stream ) +{ + BlockingTermFIFO( &stream->inFIFO ); + BlockingTermFIFO( &stream->outFIFO ); + + sem_destroy( &stream->data_semaphore ); +} + +static PaError BlockingReadStream( PaStream* s, void *data, unsigned long numFrames ) +{ + PaError result = paNoError; + PaJackStream *stream = (PaJackStream *)s; + + long bytesRead; + char *p = (char *) data; + long numBytes = stream->bytesPerFrame * numFrames; + while( numBytes > 0 ) + { + bytesRead = RingBuffer_Read( &stream->inFIFO, p, numBytes ); + numBytes -= bytesRead; + p += bytesRead; + if( numBytes > 0 ) + { + /* see write for an explanation */ + if( stream->data_available ) + stream->data_available = 0; + else + sem_wait( &stream->data_semaphore ); + } + } + + return result; +} + +static PaError BlockingWriteStream( PaStream* s, const void *data, unsigned long numFrames ) +{ + PaError result = paNoError; + PaJackStream *stream = (PaJackStream *)s; + long bytesWritten; + char *p = (char *) data; + long numBytes = stream->bytesPerFrame * numFrames; + while( numBytes > 0 ) + { + bytesWritten = RingBuffer_Write( &stream->outFIFO, p, numBytes ); + numBytes -= bytesWritten; + p += bytesWritten; + if( numBytes > 0 ) + { + /* we use the following algorithm: + * (1) write data + * (2) if some data didn't fit into the ringbuffer, set data_available to 0 + * to indicate to the audio that if space becomes available, we want to know + * (3) retry to write data (because it might be that between (1) and (2) + * new space in the buffer became available) + * (4) if this failed, we are sure that the buffer is really empty and + * we will definitely receive a notification when it becomes available + * thus we can safely sleep + * + * if the algorithm bailed out in step (3) before, it leaks a count of 1 + * on the semaphore; however, it doesn't matter, because if we block in (4), + * we also do it in a loop + */ + if( stream->data_available ) + stream->data_available = 0; + else + sem_wait( &stream->data_semaphore ); + } + } + + return result; +} + +static signed long +BlockingGetStreamReadAvailable( PaStream* s ) +{ + PaJackStream *stream = (PaJackStream *)s; + + int bytesFull = RingBuffer_GetReadAvailable( &stream->inFIFO ); + return bytesFull / stream->bytesPerFrame; +} + +static signed long +BlockingGetStreamWriteAvailable( PaStream* s ) +{ + PaJackStream *stream = (PaJackStream *)s; + + int bytesEmpty = RingBuffer_GetWriteAvailable( &stream->outFIFO ); + return bytesEmpty / stream->bytesPerFrame; +} + +static PaError +BlockingWaitEmpty( PaStream *s ) +{ + PaJackStream *stream = (PaJackStream *)s; + + while( RingBuffer_GetReadAvailable( &stream->outFIFO ) > 0 ) + { + stream->data_available = 0; + sem_wait( &stream->data_semaphore ); + } + return 0; +} + +/* ---- jack driver ---- */ + +/* BuildDeviceList(): + * + * The process of determining a list of PortAudio "devices" from + * JACK's client/port system is fairly involved, so it is separated + * into its own routine. + */ + +static PaError BuildDeviceList( PaJackHostApiRepresentation *jackApi ) +{ + /* Utility macros for the repetitive process of allocating memory */ + + /* ... MALLOC: allocate memory as part of the device list + * allocation group */ +#define MALLOC(size) \ + (PaUtil_GroupAllocateMemory( jackApi->deviceInfoMemory, (size) )) + + /* JACK has no concept of a device. To JACK, there are clients + * which have an arbitrary number of ports. To make this + * intelligible to PortAudio clients, we will group each JACK client + * into a device, and make each port of that client a channel */ + + PaError result = paNoError; + PaUtilHostApiRepresentation *commonApi = &jackApi->commonHostApiRep; + + const char **jack_ports = NULL; + char **client_names = NULL; + char *regex_pattern = alloca( jack_client_name_size() + 3 ); + int port_index, client_index, i; + double globalSampleRate; + regex_t port_regex; + unsigned long numClients = 0, numPorts = 0; + char *tmp_client_name = alloca( jack_client_name_size() ); + + commonApi->info.defaultInputDevice = paNoDevice; + commonApi->info.defaultOutputDevice = paNoDevice; + commonApi->info.deviceCount = 0; + + /* Parse the list of ports, using a regex to grab the client names */ + ASSERT_CALL( regcomp( &port_regex, "^[^:]*", REG_EXTENDED ), 0 ); + + /* since we are rebuilding the list of devices, free all memory + * associated with the previous list */ + PaUtil_FreeAllAllocations( jackApi->deviceInfoMemory ); + + /* We can only retrieve the list of clients indirectly, by first + * asking for a list of all ports, then parsing the port names + * according to the client_name:port_name convention (which is + * enforced by jackd) + * A: If jack_get_ports returns NULL, there's nothing for us to do */ + UNLESS( (jack_ports = jack_get_ports( jackApi->jack_client, "", "", 0 )) && jack_ports[0], paNoError ); + /* Find number of ports */ + while( jack_ports[numPorts] ) + ++numPorts; + /* At least there will be one port per client :) */ + UNLESS( client_names = alloca( numPorts * sizeof (char *) ), paInsufficientMemory ); + + /* Build a list of clients from the list of ports */ + for( numClients = 0, port_index = 0; jack_ports[port_index] != NULL; port_index++ ) + { + int client_seen = FALSE; + regmatch_t match_info; + const char *port = jack_ports[port_index]; + + /* extract the client name from the port name, using a regex + * that parses the clientname:portname syntax */ + UNLESS( !regexec( &port_regex, port, 1, &match_info, 0 ), paInternalError ); + assert(match_info.rm_eo - match_info.rm_so < jack_client_name_size()); + memcpy( tmp_client_name, port + match_info.rm_so, + match_info.rm_eo - match_info.rm_so ); + tmp_client_name[match_info.rm_eo - match_info.rm_so] = '\0'; + + /* do we know about this port's client yet? */ + for( i = 0; i < numClients; i++ ) + { + if( strcmp( tmp_client_name, client_names[i] ) == 0 ) + client_seen = TRUE; + } + + if (client_seen) + continue; /* A: Nothing to see here, move along */ + + UNLESS( client_names[numClients] = (char*)MALLOC(strlen(tmp_client_name) + 1), paInsufficientMemory ); + + /* The alsa_pcm client should go in spot 0. If this + * is the alsa_pcm client AND we are NOT about to put + * it in spot 0 put it in spot 0 and move whatever + * was already in spot 0 to the end. */ + if( strcmp( "alsa_pcm", tmp_client_name ) == 0 && numClients > 0 ) + { + /* alsa_pcm goes in spot 0 */ + strcpy( client_names[ numClients ], client_names[0] ); + strcpy( client_names[0], tmp_client_name ); + } + else + { + /* put the new client at the end of the client list */ + strcpy( client_names[ numClients ], tmp_client_name ); + } + ++numClients; + } + + /* Now we have a list of clients, which will become the list of + * PortAudio devices. */ + + /* there is one global sample rate all clients must conform to */ + + globalSampleRate = jack_get_sample_rate( jackApi->jack_client ); + UNLESS( commonApi->deviceInfos = (PaDeviceInfo**)MALLOC( sizeof(PaDeviceInfo*) * + numClients ), paInsufficientMemory ); + + assert( commonApi->info.deviceCount == 0 ); + + /* Create a PaDeviceInfo structure for every client */ + for( client_index = 0; client_index < numClients; client_index++ ) + { + PaDeviceInfo *curDevInfo; + const char **clientPorts = NULL; + + UNLESS( curDevInfo = (PaDeviceInfo*)MALLOC( sizeof(PaDeviceInfo) ), paInsufficientMemory ); + UNLESS( curDevInfo->name = (char*)MALLOC( strlen(client_names[client_index]) + 1 ), paInsufficientMemory ); + strcpy( (char *)curDevInfo->name, client_names[client_index] ); + + curDevInfo->structVersion = 2; + curDevInfo->hostApi = jackApi->hostApiIndex; + + /* JACK is very inflexible: there is one sample rate the whole + * system must run at, and all clients must speak IEEE float. */ + curDevInfo->defaultSampleRate = globalSampleRate; + + /* To determine how many input and output channels are available, + * we re-query jackd with more specific parameters. */ + + sprintf( regex_pattern, "%s:.*", client_names[client_index] ); + + /* ... what are your output ports (that we could input from)? */ + clientPorts = jack_get_ports( jackApi->jack_client, regex_pattern, + NULL, JackPortIsOutput); + curDevInfo->maxInputChannels = 0; + curDevInfo->defaultLowInputLatency = 0.; + curDevInfo->defaultHighInputLatency = 0.; + if( clientPorts ) + { + jack_port_t *p = jack_port_by_name( jackApi->jack_client, clientPorts[0] ); + curDevInfo->defaultLowInputLatency = curDevInfo->defaultHighInputLatency = + jack_port_get_latency( p ) / globalSampleRate; + free( p ); + + for( i = 0; clientPorts[i] != NULL; i++) + { + /* The number of ports returned is the number of output channels. + * We don't care what they are, we just care how many */ + curDevInfo->maxInputChannels++; + } + free(clientPorts); + } + + /* ... what are your input ports (that we could output to)? */ + clientPorts = jack_get_ports( jackApi->jack_client, regex_pattern, + NULL, JackPortIsInput); + curDevInfo->maxOutputChannels = 0; + curDevInfo->defaultLowOutputLatency = 0.; + curDevInfo->defaultHighOutputLatency = 0.; + if( clientPorts ) + { + jack_port_t *p = jack_port_by_name( jackApi->jack_client, clientPorts[0] ); + curDevInfo->defaultLowOutputLatency = curDevInfo->defaultHighOutputLatency = + jack_port_get_latency( p ) / globalSampleRate; + free( p ); + + for( i = 0; clientPorts[i] != NULL; i++) + { + /* The number of ports returned is the number of input channels. + * We don't care what they are, we just care how many */ + curDevInfo->maxOutputChannels++; + } + free(clientPorts); + } + + /* Add this client to the list of devices */ + commonApi->deviceInfos[client_index] = curDevInfo; + ++commonApi->info.deviceCount; + if( commonApi->info.defaultInputDevice == paNoDevice && curDevInfo->maxInputChannels > 0 ) + commonApi->info.defaultInputDevice = client_index; + if( commonApi->info.defaultOutputDevice == paNoDevice && curDevInfo->maxOutputChannels > 0 ) + commonApi->info.defaultOutputDevice = client_index; + } + +error: + regfree( &port_regex ); + free( jack_ports ); + return result; +} +#undef MALLOC + +static void UpdateSampleRate( PaJackStream *stream, double sampleRate ) +{ + /* XXX: Maybe not the cleanest way of going about this? */ + stream->cpuLoadMeasurer.samplingPeriod = stream->bufferProcessor.samplePeriod = 1. / sampleRate; + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; +} + +static void JackErrorCallback( const char *msg ) +{ + if( pthread_self() == mainThread_ ) + { + assert( msg ); + free( jackErr_ ); + jackErr_ = malloc( strlen( msg ) ); + sprintf( jackErr_, msg ); + } +} + +static void JackOnShutdown( void *arg ) +{ + PaJackHostApiRepresentation *jackApi = (PaJackHostApiRepresentation *)arg; + PaJackStream *stream = jackApi->processQueue; + + PA_DEBUG(( "%s: JACK server is shutting down\n", __FUNCTION__ )); + for( ; stream; stream = stream->next ) + { + stream->is_active = 0; + } + + /* Make sure that the main thread doesn't get stuck waiting on the condition */ + ASSERT_CALL( pthread_mutex_lock( &jackApi->mtx ), 0 ); + jackApi->jackIsDown = 1; + ASSERT_CALL( pthread_cond_signal( &jackApi->cond ), 0 ); + ASSERT_CALL( pthread_mutex_unlock( &jackApi->mtx ), 0 ); + +} + +static int JackSrCb( jack_nframes_t nframes, void *arg ) +{ + PaJackHostApiRepresentation *jackApi = (PaJackHostApiRepresentation *)arg; + double sampleRate = (double)nframes; + PaJackStream *stream = jackApi->processQueue; + + /* Update all streams in process queue */ + PA_DEBUG(( "%s: Acting on change in JACK samplerate: %f\n", __FUNCTION__, sampleRate )); + for( ; stream; stream = stream->next ) + { + if( stream->streamRepresentation.streamInfo.sampleRate != sampleRate ) + { + PA_DEBUG(( "%s: Updating samplerate\n", __FUNCTION__ )); + UpdateSampleRate( stream, sampleRate ); + } + } + + return 0; +} + +static int JackXRunCb(void *arg) { + PaJackHostApiRepresentation *hostApi = (PaJackHostApiRepresentation *)arg; + assert( hostApi ); + hostApi->xrun = TRUE; + PA_DEBUG(( "%s: JACK signalled xrun\n", __FUNCTION__ )); + return 0; +} + +PaError PaJack_Initialize( PaUtilHostApiRepresentation **hostApi, + PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + PaJackHostApiRepresentation *jackHostApi; + int activated = 0; + char *clientName; + int written; + *hostApi = NULL; /* Initialize to NULL */ + + UNLESS( jackHostApi = (PaJackHostApiRepresentation*) + PaUtil_AllocateMemory( sizeof(PaJackHostApiRepresentation) ), paInsufficientMemory ); + jackHostApi->deviceInfoMemory = NULL; + + mainThread_ = pthread_self(); + ASSERT_CALL( pthread_mutex_init( &jackHostApi->mtx, NULL ), 0 ); + 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. */ + + clientName = alloca( jack_client_name_size() ); + written = snprintf( clientName, jack_client_name_size(), "PortAudio-%d", getpid() ); + assert( written < jack_client_name_size() ); + jackHostApi->jack_client = jack_client_new( clientName ); + if( jackHostApi->jack_client == NULL ) + { + /* the V19 development docs say that if an implementation + * detects that it cannot be used, it should return a NULL + * interface and paNoError */ + result = paNoError; + goto error; + } + + UNLESS( jackHostApi->deviceInfoMemory = PaUtil_CreateAllocationGroup(), paInsufficientMemory ); + jackHostApi->hostApiIndex = hostApiIndex; + + *hostApi = &jackHostApi->commonHostApiRep; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paJACK; + (*hostApi)->info.name = "JACK Audio Connection Kit"; + + /* Build a device list by querying the JACK server */ + + ENSURE_PA( BuildDeviceList( jackHostApi ) ); + + /* Register functions */ + + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PaUtil_InitializeStreamInterface( &jackHostApi->callbackStreamInterface, + CloseStream, StartStream, + StopStream, AbortStream, + IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, + PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface( &jackHostApi->blockingStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + BlockingReadStream, BlockingWriteStream, + BlockingGetStreamReadAvailable, BlockingGetStreamWriteAvailable ); + + jackHostApi->inputBase = jackHostApi->outputBase = 0; + jackHostApi->xrun = 0; + jackHostApi->toAdd = jackHostApi->toRemove = NULL; + jackHostApi->processQueue = NULL; + jackHostApi->jackIsDown = 0; + + jack_on_shutdown( jackHostApi->jack_client, JackOnShutdown, jackHostApi ); + jack_set_error_function( JackErrorCallback ); + jackHostApi->jack_buffer_size = jack_get_buffer_size ( jackHostApi->jack_client ); + UNLESS( !jack_set_sample_rate_callback( jackHostApi->jack_client, JackSrCb, jackHostApi ), paUnanticipatedHostError ); + UNLESS( !jack_set_xrun_callback( jackHostApi->jack_client, JackXRunCb, jackHostApi ), paUnanticipatedHostError ); + UNLESS( !jack_set_process_callback( jackHostApi->jack_client, JackCallback, jackHostApi ), paUnanticipatedHostError ); + UNLESS( !jack_activate( jackHostApi->jack_client ), paUnanticipatedHostError ); + activated = 1; + + return result; + +error: + if( activated ) + ASSERT_CALL( jack_deactivate( jackHostApi->jack_client ), 0 ); + + if( jackHostApi ) + { + if( jackHostApi->jack_client ) + ASSERT_CALL( jack_client_close( jackHostApi->jack_client ), 0 ); + + if( jackHostApi->deviceInfoMemory ) + { + PaUtil_FreeAllAllocations( jackHostApi->deviceInfoMemory ); + PaUtil_DestroyAllocationGroup( jackHostApi->deviceInfoMemory ); + } + + PaUtil_FreeMemory( jackHostApi ); + } + return result; +} + + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaJackHostApiRepresentation *jackHostApi = (PaJackHostApiRepresentation*)hostApi; + + /* note: this automatically disconnects all ports, since a deactivated + * client is not allowed to have any ports connected */ + ASSERT_CALL( jack_deactivate( jackHostApi->jack_client ), 0 ); + + ASSERT_CALL( pthread_mutex_destroy( &jackHostApi->mtx ), 0 ); + ASSERT_CALL( pthread_cond_destroy( &jackHostApi->cond ), 0 ); + + ASSERT_CALL( jack_client_close( jackHostApi->jack_client ), 0 ); + + if( jackHostApi->deviceInfoMemory ) + { + PaUtil_FreeAllAllocations( jackHostApi->deviceInfoMemory ); + PaUtil_DestroyAllocationGroup( jackHostApi->deviceInfoMemory ); + } + + PaUtil_FreeMemory( jackHostApi ); + + free( jackErr_ ); +} + +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + int inputChannelCount = 0, outputChannelCount = 0; + PaSampleFormat inputSampleFormat, outputSampleFormat; + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + + /* validate inputStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + inputChannelCount = 0; + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that output device can support inputChannelCount */ + if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + + /* validate outputStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + outputChannelCount = 0; + } + + /* + The following check is not necessary for JACK. + + - if a full duplex stream is requested, check that the combination + of input and output parameters is supported + + + Because the buffer adapter handles conversion between all standard + sample formats, the following checks are only required if paCustomFormat + is implemented, or under some other unusual conditions. + + - check that input device can support inputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + + - check that output device can support outputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + */ + + /* check that the device supports sampleRate */ + +#define ABS(x) ( (x) > 0 ? (x) : -(x) ) + if( ABS(sampleRate - jack_get_sample_rate(((PaJackHostApiRepresentation *) hostApi)->jack_client )) > 1 ) + return paInvalidSampleRate; +#undef ABS + + return paFormatIsSupported; +} + +/* Basic stream initialization */ +static PaError InitializeStream( PaJackStream *stream, PaJackHostApiRepresentation *hostApi, int numInputChannels, + int numOutputChannels ) +{ + PaError result = paNoError; + assert( stream ); + + memset( stream, 0, sizeof (PaJackStream) ); + UNLESS( stream->stream_memory = PaUtil_CreateAllocationGroup(), paInsufficientMemory ); + stream->jack_client = hostApi->jack_client; + stream->hostApi = hostApi; + + if( numInputChannels > 0 ) + { + UNLESS( stream->local_input_ports = + (jack_port_t**) PaUtil_GroupAllocateMemory( stream->stream_memory, sizeof(jack_port_t*) * numInputChannels ), + paInsufficientMemory ); + memset( stream->local_input_ports, 0, sizeof(jack_port_t*) * numInputChannels ); + UNLESS( stream->remote_output_ports = + (jack_port_t**) PaUtil_GroupAllocateMemory( stream->stream_memory, sizeof(jack_port_t*) * numInputChannels ), + paInsufficientMemory ); + memset( stream->remote_output_ports, 0, sizeof(jack_port_t*) * numInputChannels ); + } + if( numOutputChannels > 0 ) + { + UNLESS( stream->local_output_ports = + (jack_port_t**) PaUtil_GroupAllocateMemory( stream->stream_memory, sizeof(jack_port_t*) * numOutputChannels ), + paInsufficientMemory ); + memset( stream->local_output_ports, 0, sizeof(jack_port_t*) * numOutputChannels ); + UNLESS( stream->remote_input_ports = + (jack_port_t**) PaUtil_GroupAllocateMemory( stream->stream_memory, sizeof(jack_port_t*) * numOutputChannels ), + paInsufficientMemory ); + memset( stream->remote_input_ports, 0, sizeof(jack_port_t*) * numOutputChannels ); + } + + stream->num_incoming_connections = numInputChannels; + stream->num_outgoing_connections = numOutputChannels; + +error: + return result; +} + +/*! + * Free resources associated with stream, and eventually stream itself. + * + * Frees allocated memory, and closes opened pcms. + */ +static void CleanUpStream( PaJackStream *stream, int terminateStreamRepresentation, int terminateBufferProcessor ) +{ + int i; + assert( stream ); + + if( stream->isBlockingStream ) + BlockingEnd( stream ); + + for( i = 0; i < stream->num_incoming_connections; ++i ) + { + if( stream->local_input_ports[i] ) + ASSERT_CALL( jack_port_unregister( stream->jack_client, stream->local_input_ports[i] ), 0 ); + free( stream->remote_output_ports[i] ); + } + for( i = 0; i < stream->num_outgoing_connections; ++i ) + { + if( stream->local_output_ports[i] ) + ASSERT_CALL( jack_port_unregister( stream->jack_client, stream->local_output_ports[i] ), 0 ); + free( stream->remote_input_ports[i] ); + } + + if( terminateStreamRepresentation ) + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + if( terminateBufferProcessor ) + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + + if( stream->stream_memory ) + { + PaUtil_FreeAllAllocations( stream->stream_memory ); + PaUtil_DestroyAllocationGroup( stream->stream_memory ); + } + PaUtil_FreeMemory( stream ); +} + +static PaError WaitCondition( PaJackHostApiRepresentation *hostApi ) +{ + PaError result = paNoError; + int err = 0; + PaTime pt = PaUtil_GetTime(); + struct timespec ts; + + ts.tv_sec = (time_t) floor( pt + 1 ); + ts.tv_nsec = (long) ((pt - floor( pt )) * 1000000000); + /* XXX: Best enclose in loop, in case of spurious wakeups? */ + err = pthread_cond_timedwait( &hostApi->cond, &hostApi->mtx, &ts ); + + /* Make sure we didn't time out */ + UNLESS( err != ETIMEDOUT, paTimedOut ); + UNLESS( !err, paInternalError ); + +error: + return result; +} + +static PaError AddStream( PaJackStream *stream ) +{ + PaError result = paNoError; + PaJackHostApiRepresentation *hostApi = stream->hostApi; + /* Add to queue of streams that should be processed */ + ASSERT_CALL( pthread_mutex_lock( &hostApi->mtx ), 0 ); + if( !hostApi->jackIsDown ) + { + hostApi->toAdd = stream; + /* Unlock mutex and await signal from processing thread */ + result = WaitCondition( stream->hostApi ); + } + ASSERT_CALL( pthread_mutex_unlock( &hostApi->mtx ), 0 ); + ENSURE_PA( result ); + + UNLESS( !hostApi->jackIsDown, paDeviceUnavailable ); + +error: + return result; +} + +/* Remove stream from processing queue */ +static PaError RemoveStream( PaJackStream *stream ) +{ + PaError result = paNoError; + PaJackHostApiRepresentation *hostApi = stream->hostApi; + + /* Add to queue over streams that should be processed */ + ASSERT_CALL( pthread_mutex_lock( &hostApi->mtx ), 0 ); + if( !hostApi->jackIsDown ) + { + hostApi->toRemove = stream; + /* Unlock mutex and await signal from processing thread */ + result = WaitCondition( stream->hostApi ); + } + ASSERT_CALL( pthread_mutex_unlock( &hostApi->mtx ), 0 ); + ENSURE_PA( result ); + +error: + return result; +} + +/* Add stream to processing queue */ +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ) +{ + PaError result = paNoError; + PaJackHostApiRepresentation *jackHostApi = (PaJackHostApiRepresentation*)hostApi; + PaJackStream *stream = NULL; + char *port_string = alloca( jack_port_name_size() ); + unsigned long regexSz = jack_client_name_size() + 3; + char *regex_pattern = alloca( regexSz ); + const char **jack_ports = NULL; + /* int jack_max_buffer_size = jack_get_buffer_size( jackHostApi->jack_client ); */ + int i; + int inputChannelCount, outputChannelCount; + const double jackSr = jack_get_sample_rate( jackHostApi->jack_client ); + PaSampleFormat inputSampleFormat = 0, outputSampleFormat = 0; + int bpInitialized = 0, srInitialized = 0; /* Initialized buffer processor and stream representation? */ + unsigned long ofs; + + /* validate platform specific flags */ + if( (streamFlags & paPlatformSpecificFlags) != 0 ) + return paInvalidFlag; /* unexpected platform specific flag */ + if( (streamFlags & paPrimeOutputBuffersUsingStreamCallback) != 0 ) + { + streamFlags &= ~paPrimeOutputBuffersUsingStreamCallback; + /*return paInvalidFlag;*/ /* This implementation does not support buffer priming */ + } + + if( framesPerBuffer != paFramesPerBufferUnspecified ) + { + /* Jack operates with power of two buffers, and we don't support non-integer buffer adaption (yet) */ + /*UNLESS( !(framesPerBuffer & (framesPerBuffer - 1)), paBufferTooBig );*/ /* TODO: Add descriptive error code? */ + } + + /* Preliminary checks */ + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + + /* validate inputStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + inputChannelCount = 0; + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that output device can support inputChannelCount */ + if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + + /* validate outputStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + outputChannelCount = 0; + } + + /* ... check that the sample rate exactly matches the ONE acceptable rate + * A: This rate isn't necessarily constant though? */ + +#define ABS(x) ( (x) > 0 ? (x) : -(x) ) + if( ABS(sampleRate - jackSr) > 1 ) + return paInvalidSampleRate; +#undef ABS + + UNLESS( stream = (PaJackStream*)PaUtil_AllocateMemory( sizeof(PaJackStream) ), paInsufficientMemory ); + ENSURE_PA( InitializeStream( stream, jackHostApi, inputChannelCount, outputChannelCount ) ); + + /* the blocking emulation, if necessary */ + stream->isBlockingStream = !streamCallback; + if( stream->isBlockingStream ) + { + float latency = 0.001; /* 1ms is the absolute minimum we support */ + int minimum_buffer_frames = 0; + + if( inputParameters && inputParameters->suggestedLatency > latency ) + latency = inputParameters->suggestedLatency; + else if( outputParameters && outputParameters->suggestedLatency > latency ) + latency = outputParameters->suggestedLatency; + + /* the latency the user asked for indicates the minimum buffer size in frames */ + minimum_buffer_frames = (int) (latency * jack_get_sample_rate( jackHostApi->jack_client )); + + /* we also need to be able to store at least three full jack buffers to avoid dropouts */ + if( jackHostApi->jack_buffer_size * 3 > minimum_buffer_frames ) + minimum_buffer_frames = jackHostApi->jack_buffer_size * 3; + + /* setup blocking API data structures (FIXME: can fail) */ + BlockingBegin( stream, minimum_buffer_frames ); + + /* install our own callback for the blocking API */ + streamCallback = BlockingCallback; + userData = stream; + + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &jackHostApi->blockingStreamInterface, streamCallback, userData ); + } + else + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &jackHostApi->callbackStreamInterface, streamCallback, userData ); + } + srInitialized = 1; + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, jackSr ); + + /* create the JACK ports. We cannot connect them until audio + * processing begins */ + + /* Register a unique set of ports for this stream + * TODO: Robust allocation of new port names */ + + ofs = jackHostApi->inputBase; + for( i = 0; i < inputChannelCount; i++ ) + { + snprintf( port_string, jack_port_name_size(), "in_%lu", ofs + i ); + UNLESS( stream->local_input_ports[i] = jack_port_register( + jackHostApi->jack_client, port_string, + JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0 ), paInsufficientMemory ); + } + jackHostApi->inputBase += inputChannelCount; + + ofs = jackHostApi->outputBase; + for( i = 0; i < outputChannelCount; i++ ) + { + snprintf( port_string, jack_port_name_size(), "out_%lu", ofs + i ); + UNLESS( stream->local_output_ports[i] = jack_port_register( + jackHostApi->jack_client, port_string, + JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 ), paInsufficientMemory ); + } + jackHostApi->outputBase += outputChannelCount; + + /* look up the jack_port_t's for the remote ports. We could do + * this at stream start time, but doing it here ensures the + * name lookup only happens once. */ + + if( inputChannelCount > 0 ) + { + int err = 0; + + /* ... remote output ports (that we input from) */ + snprintf( regex_pattern, regexSz, "%s:.*", hostApi->deviceInfos[ inputParameters->device ]->name ); + UNLESS( jack_ports = jack_get_ports( jackHostApi->jack_client, regex_pattern, + NULL, JackPortIsOutput ), paUnanticipatedHostError ); + for( i = 0; i < inputChannelCount && jack_ports[i]; i++ ) + { + if( (stream->remote_output_ports[i] = jack_port_by_name( + jackHostApi->jack_client, jack_ports[i] )) == NULL ) + { + err = 1; + break; + } + } + free( jack_ports ); + UNLESS( !err, paInsufficientMemory ); + + /* Fewer ports than expected? */ + UNLESS( i == inputChannelCount, paInternalError ); + } + + if( outputChannelCount > 0 ) + { + int err = 0; + + /* ... remote input ports (that we output to) */ + snprintf( regex_pattern, regexSz, "%s:.*", hostApi->deviceInfos[ outputParameters->device ]->name ); + UNLESS( jack_ports = jack_get_ports( jackHostApi->jack_client, regex_pattern, + NULL, JackPortIsInput ), paUnanticipatedHostError ); + for( i = 0; i < outputChannelCount && jack_ports[i]; i++ ) + { + if( (stream->remote_input_ports[i] = jack_port_by_name( + jackHostApi->jack_client, jack_ports[i] )) == 0 ) + { + err = 1; + break; + } + } + free( jack_ports ); + UNLESS( !err , paInsufficientMemory ); + + /* Fewer ports than expected? */ + UNLESS( i == outputChannelCount, paInternalError ); + } + + ENSURE_PA( PaUtil_InitializeBufferProcessor( + &stream->bufferProcessor, + inputChannelCount, + inputSampleFormat, + paFloat32, /* hostInputSampleFormat */ + outputChannelCount, + outputSampleFormat, + paFloat32, /* hostOutputSampleFormat */ + jackSr, + streamFlags, + framesPerBuffer, + 0, /* Ignored */ + paUtilUnknownHostBufferSize, /* Buffer size may vary on JACK's discretion */ + streamCallback, + userData ) ); + bpInitialized = 1; + + if( stream->num_incoming_connections > 0 ) + stream->streamRepresentation.streamInfo.inputLatency = (jack_port_get_latency( stream->remote_output_ports[0] ) + - jack_get_buffer_size( jackHostApi->jack_client ) /* One buffer is not counted as latency */ + + PaUtil_GetBufferProcessorInputLatency( &stream->bufferProcessor )) / sampleRate; + if( stream->num_outgoing_connections > 0 ) + stream->streamRepresentation.streamInfo.outputLatency = (jack_port_get_latency( stream->remote_input_ports[0] ) + - jack_get_buffer_size( jackHostApi->jack_client ) /* One buffer is not counted as latency */ + + PaUtil_GetBufferProcessorOutputLatency( &stream->bufferProcessor )) / sampleRate; + + stream->streamRepresentation.streamInfo.sampleRate = jackSr; + stream->t0 = jack_frame_time( jackHostApi->jack_client ); /* A: Time should run from Pa_OpenStream */ + + ENSURE_PA( AddStream( stream ) ); /* Add to queue over opened streams */ + + *s = (PaStream*)stream; + + return result; + +error: + if( stream ) + CleanUpStream( stream, srInitialized, bpInitialized ); + + return result; +} + +/* + When CloseStream() is called, the multi-api layer ensures that + the stream has already been stopped or aborted. +*/ +static PaError CloseStream( PaStream* s ) +{ + PaError result = paNoError; + PaJackStream *stream = (PaJackStream*)s; + + /* Remove this stream from the processing queue */ + ENSURE_PA( RemoveStream( stream ) ); + +error: + CleanUpStream( stream, 1, 1 ); + return result; +} + +static PaError RealProcess( PaJackStream *stream, jack_nframes_t frames ) +{ + PaError result = paNoError; + PaStreamCallbackTimeInfo timeInfo = {0,0,0}; + int chn; + int framesProcessed; + const double sr = jack_get_sample_rate( stream->jack_client ); /* Shouldn't change during the process callback */ + PaStreamCallbackFlags cbFlags = 0; + + /* If the user has returned !paContinue from the callback we'll want to flush the internal buffers, + * when these are empty we can finally mark the stream as inactive */ + if( stream->callbackResult != paContinue && + PaUtil_IsBufferProcessorOutputEmpty( &stream->bufferProcessor ) ) + { + stream->is_active = 0; + if( stream->streamRepresentation.streamFinishedCallback ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + PA_DEBUG(( "%s: Callback finished\n", __FUNCTION__ )); + + goto end; + } + + timeInfo.currentTime = (jack_frame_time( stream->jack_client ) - stream->t0) / sr; + if( stream->num_incoming_connections > 0 ) + timeInfo.inputBufferAdcTime = timeInfo.currentTime - jack_port_get_latency( stream->remote_output_ports[0] ) + / sr; + if( stream->num_outgoing_connections > 0 ) + timeInfo.outputBufferDacTime = timeInfo.currentTime + jack_port_get_latency( stream->remote_input_ports[0] ) + / sr; + + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + + if( stream->xrun ) + { + /* XXX: Any way to tell which of these occurred? */ + cbFlags = paOutputUnderflow | paInputOverflow; + stream->xrun = FALSE; + } + PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, + cbFlags ); + + if( stream->num_incoming_connections > 0 ) + PaUtil_SetInputFrameCount( &stream->bufferProcessor, frames ); + if( stream->num_outgoing_connections > 0 ) + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, frames ); + + for( chn = 0; chn < stream->num_incoming_connections; chn++ ) + { + jack_default_audio_sample_t *channel_buf = (jack_default_audio_sample_t*) + jack_port_get_buffer( stream->local_input_ports[chn], + frames ); + + PaUtil_SetNonInterleavedInputChannel( &stream->bufferProcessor, + chn, + channel_buf ); + } + + for( chn = 0; chn < stream->num_outgoing_connections; chn++ ) + { + jack_default_audio_sample_t *channel_buf = (jack_default_audio_sample_t*) + jack_port_get_buffer( stream->local_output_ports[chn], + frames ); + + PaUtil_SetNonInterleavedOutputChannel( &stream->bufferProcessor, + chn, + channel_buf ); + } + + framesProcessed = PaUtil_EndBufferProcessing( &stream->bufferProcessor, + &stream->callbackResult ); + /* We've specified a host buffer size mode where every frame should be consumed by the buffer processor */ + assert( framesProcessed == frames ); + + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed ); + +end: + return result; +} + +/* Alter the processing queue if necessary */ +static PaError UpdateQueue( PaJackHostApiRepresentation *hostApi ) +{ + PaError result = paNoError; + int queueModified = 0; + const double jackSr = jack_get_sample_rate( hostApi->jack_client ); + int err; + + if( (err = pthread_mutex_trylock( &hostApi->mtx )) != 0 ) + { + assert( err == EBUSY ); + return paNoError; + } + + if( hostApi->toAdd ) + { + if( hostApi->processQueue ) + { + PaJackStream *node = hostApi->processQueue; + /* Advance to end of queue */ + while( node->next ) + node = node->next; + + node->next = hostApi->toAdd; + } + else + hostApi->processQueue = (PaJackStream *)hostApi->toAdd; + + /* If necessary, update stream state */ + if( hostApi->toAdd->streamRepresentation.streamInfo.sampleRate != jackSr ) + UpdateSampleRate( hostApi->toAdd, jackSr ); + + hostApi->toAdd = NULL; + queueModified = 1; + } + if( hostApi->toRemove ) + { + int removed = 0; + PaJackStream *node = hostApi->processQueue, *prev = NULL; + assert( hostApi->processQueue ); + + while( node ) + { + if( node == hostApi->toRemove ) + { + if( prev ) + prev->next = node->next; + else + hostApi->processQueue = (PaJackStream *)node->next; + + removed = 1; + break; + } + + prev = node; + node = node->next; + } + UNLESS( removed, paInternalError ); + hostApi->toRemove = NULL; + PA_DEBUG(( "%s: Removed stream from processing queue\n", __FUNCTION__ )); + queueModified = 1; + } + + if( queueModified ) + { + /* Signal that we've done what was asked of us */ + ASSERT_CALL( pthread_cond_signal( &hostApi->cond ), 0 ); + } + +error: + ASSERT_CALL( pthread_mutex_unlock( &hostApi->mtx ), 0 ); + + return result; +} + +static int JackCallback( jack_nframes_t frames, void *userData ) +{ + PaError result = paNoError; + PaJackHostApiRepresentation *hostApi = (PaJackHostApiRepresentation *)userData; + PaJackStream *stream = NULL; + int xrun = hostApi->xrun; + hostApi->xrun = 0; + + assert( hostApi ); + + ENSURE_PA( UpdateQueue( hostApi ) ); + + /* Process each stream */ + stream = hostApi->processQueue; + for( ; stream; stream = stream->next ) + { + if( xrun ) /* Don't override if already set */ + stream->xrun = 1; + + /* See if this stream is to be started */ + if( stream->doStart ) + { + /* If we can't obtain a lock, we'll try next time */ + int err = pthread_mutex_trylock( &stream->hostApi->mtx ); + if( !err ) + { + if( stream->doStart ) /* Could potentially change before obtaining the lock */ + { + stream->is_active = 1; + stream->doStart = 0; + PA_DEBUG(( "%s: Starting stream\n", __FUNCTION__ )); + ASSERT_CALL( pthread_cond_signal( &stream->hostApi->cond ), 0 ); + stream->callbackResult = paContinue; + stream->isSilenced = 0; + } + + ASSERT_CALL( pthread_mutex_unlock( &stream->hostApi->mtx ), 0 ); + } + else + assert( err == EBUSY ); + } + else if( stream->doStop || stream->doAbort ) /* Should we stop/abort stream? */ + { + if( stream->callbackResult == paContinue ) /* Ok, make it stop */ + { + PA_DEBUG(( "%s: Stopping stream\n", __FUNCTION__ )); + stream->callbackResult = stream->doStop ? paComplete : paAbort; + } + } + + if( stream->is_active ) + ENSURE_PA( RealProcess( stream, frames ) ); + /* If we have just entered inactive state, silence output */ + if( !stream->is_active && !stream->isSilenced ) + { + int i; + + /* Silence buffer after entering inactive state */ + PA_DEBUG(( "Silencing the output\n" )); + for( i = 0; i < stream->num_outgoing_connections; ++i ) + { + jack_default_audio_sample_t *buffer = jack_port_get_buffer( stream->local_output_ports[i], frames ); + memset( buffer, 0, sizeof (jack_default_audio_sample_t) * frames ); + } + + stream->isSilenced = 1; + } + + if( stream->doStop || stream->doAbort ) + { + /* See if RealProcess has acted on the request */ + if( !stream->is_active ) /* Ok, signal to the main thread that we've carried out the operation */ + { + /* If we can't obtain a lock, we'll try next time */ + int err = pthread_mutex_trylock( &stream->hostApi->mtx ); + if( !err ) + { + stream->doStop = stream->doAbort = 0; + ASSERT_CALL( pthread_cond_signal( &stream->hostApi->cond ), 0 ); + ASSERT_CALL( pthread_mutex_unlock( &stream->hostApi->mtx ), 0 ); + } + else + assert( err == EBUSY ); + } + } + } + + return 0; +error: + return -1; +} + +static PaError StartStream( PaStream *s ) +{ + PaError result = paNoError; + PaJackStream *stream = (PaJackStream*)s; + int i; + + /* Ready the processor */ + PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); + + /* connect the ports */ + + /* NOTE: I would rather use jack_port_connect which uses jack_port_t's + * instead of port names, but it is not implemented yet. */ + if( stream->num_incoming_connections > 0 ) + { + for( i = 0; i < stream->num_incoming_connections; i++ ) + UNLESS( jack_connect( stream->jack_client, + jack_port_name( stream->remote_output_ports[i] ), + jack_port_name( stream->local_input_ports[i] ) ) == 0, paUnanticipatedHostError ); + } + + if( stream->num_outgoing_connections > 0 ) + { + for( i = 0; i < stream->num_outgoing_connections; i++ ) + UNLESS( jack_connect( stream->jack_client, + jack_port_name( stream->local_output_ports[i] ), + jack_port_name( stream->remote_input_ports[i] ) ) == 0, paUnanticipatedHostError ); + } + + stream->xrun = FALSE; + + /* Enable processing */ + + ASSERT_CALL( pthread_mutex_lock( &stream->hostApi->mtx ), 0 ); + stream->doStart = 1; + + /* Wait for stream to be started */ + result = WaitCondition( stream->hostApi ); + /* + do + { + err = pthread_cond_timedwait( &stream->hostApi->cond, &stream->hostApi->mtx, &ts ); + } while( !stream->is_active && !err ); + */ + if( result != paNoError ) /* Something went wrong, call off the stream start */ + { + stream->doStart = 0; + stream->is_active = 0; /* Cancel any processing */ + } + ASSERT_CALL( pthread_mutex_unlock( &stream->hostApi->mtx ), 0 ); + + ENSURE_PA( result ); + + stream->is_running = TRUE; + PA_DEBUG(( "%s: Stream started\n", __FUNCTION__ )); + +error: + return result; +} + +static PaError RealStop( PaJackStream *stream, int abort ) +{ + PaError result = paNoError; + int i; + + if( stream->isBlockingStream ) + BlockingWaitEmpty ( stream ); + + ASSERT_CALL( pthread_mutex_lock( &stream->hostApi->mtx ), 0 ); + if( abort ) + stream->doAbort = 1; + else + stream->doStop = 1; + + /* Wait for stream to be stopped */ + result = WaitCondition( stream->hostApi ); + ASSERT_CALL( pthread_mutex_unlock( &stream->hostApi->mtx ), 0 ); + ENSURE_PA( result ); + + UNLESS( !stream->is_active, paInternalError ); + + PA_DEBUG(( "%s: Stream stopped\n", __FUNCTION__ )); + +error: + stream->is_running = FALSE; + + /* Disconnect ports belonging to this stream */ + + if( !stream->hostApi->jackIsDown ) /* XXX: Well? */ + { + if( stream->num_incoming_connections > 0 ) + { + for( i = 0; i < stream->num_incoming_connections; i++ ) + UNLESS( !jack_disconnect( stream->jack_client, + jack_port_name( stream->remote_output_ports[i] ), + jack_port_name( stream->local_input_ports[i] ) ), paUnanticipatedHostError ); + } + if( stream->num_outgoing_connections > 0 ) + { + for( i = 0; i < stream->num_outgoing_connections; i++ ) + UNLESS( !jack_disconnect( stream->jack_client, + jack_port_name( stream->local_output_ports[i] ), + jack_port_name( stream->remote_input_ports[i] ) ), paUnanticipatedHostError ); + } + } + + return result; +} + +static PaError StopStream( PaStream *s ) +{ + assert(s); + return RealStop( (PaJackStream *)s, 0 ); +} + +static PaError AbortStream( PaStream *s ) +{ + assert(s); + return RealStop( (PaJackStream *)s, 1 ); +} + +static PaError IsStreamStopped( PaStream *s ) +{ + PaJackStream *stream = (PaJackStream*)s; + return !stream->is_running; +} + + +static PaError IsStreamActive( PaStream *s ) +{ + PaJackStream *stream = (PaJackStream*)s; + return stream->is_active; +} + + +static PaTime GetStreamTime( PaStream *s ) +{ + PaJackStream *stream = (PaJackStream*)s; + + /* A: Is this relevant?? --> TODO: what if we're recording-only? */ + return (jack_frame_time( stream->jack_client ) - stream->t0) / (PaTime)jack_get_sample_rate( stream->jack_client ); +} + + +static double GetStreamCpuLoad( PaStream* s ) +{ + PaJackStream *stream = (PaJackStream*)s; + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); +} diff --git a/src/audio/portaudio/pa_linux_alsa/pa_linux_alsa.c b/src/audio/portaudio/pa_linux_alsa/pa_linux_alsa.c new file mode 100644 index 0000000000000000000000000000000000000000..f12483ddea52366d078e40dd7e256b90b7d269be --- /dev/null +++ b/src/audio/portaudio/pa_linux_alsa/pa_linux_alsa.c @@ -0,0 +1,3272 @@ +/* + * $Id$ + * 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 <joshua@haberman.com> + * Copyright (c) 2005 Arve Knudsen <aknuds-1@broadpark.no> + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, 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. + * + * 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. + * + * 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. + */ + +#define ALSA_PCM_NEW_HW_PARAMS_API +#define ALSA_PCM_NEW_SW_PARAMS_API +#include <alsa/asoundlib.h> +#undef ALSA_PCM_NEW_HW_PARAMS_API +#undef ALSA_PCM_NEW_SW_PARAMS_API + +#include <sys/poll.h> +#include <string.h> /* strlen() */ +#include <limits.h> +#include <math.h> +#include <pthread.h> +#include <signal.h> +#include <time.h> +#include <sys/mman.h> +#include <signal.h> /* For sig_atomic_t */ + +#include "portaudio.h" +#include "pa_util.h" +#include "../pa_unix/pa_unix_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" + +#include "pa_linux_alsa.h" + +/* Check return value of ALSA function, and map it to PaError */ +#define ENSURE_(expr, code) \ + do { \ + if( UNLIKELY( (aErr_ = (expr)) < 0 ) ) \ + { \ + /* PaUtil_SetLastHostErrorInfo should only be used in the main thread */ \ + if( (code) == paUnanticipatedHostError && pthread_self() != callbackThread_ ) \ + { \ + PaUtil_SetLastHostErrorInfo( paALSA, aErr_, snd_strerror( aErr_ ) ); \ + } \ + PaUtil_DebugPrint(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \ + result = (code); \ + goto error; \ + } \ + } while( 0 ); + +#define ASSERT_CALL_(expr, success) \ + aErr_ = (expr); \ + assert( aErr_ == success ); + +static int aErr_; /* Used with ENSURE_ */ +static pthread_t callbackThread_; + +typedef enum +{ + StreamDirection_In, + StreamDirection_Out +} StreamDirection; + +/* Threading utility struct */ +typedef struct PaAlsaThreading +{ + pthread_t watchdogThread; + pthread_t callbackThread; + int watchdogRunning; + int rtSched; + int rtPrio; + int useWatchdog; + unsigned long throttledSleepTime; + volatile PaTime callbackTime; + volatile PaTime callbackCpuTime; + PaUtilCpuLoadMeasurer *cpuLoadMeasurer; +} PaAlsaThreading; + +typedef struct +{ + PaSampleFormat hostSampleFormat; + unsigned long framesPerBuffer; + int numUserChannels, numHostChannels; + int userInterleaved, hostInterleaved; + + snd_pcm_t *pcm; + snd_pcm_uframes_t bufferSize; + snd_pcm_format_t nativeFormat; + unsigned int nfds; + int ready; /* Marked ready from poll */ + void **userBuffers; + snd_pcm_uframes_t offset; + StreamDirection streamDir; + + snd_pcm_channel_area_t *channelAreas; /* Needed for channel adaption */ +} PaAlsaStreamComponent; + +/* Implementation specific stream structure */ +typedef struct PaAlsaStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + PaAlsaThreading threading; + + unsigned long framesPerUserBuffer; + + int primeBuffers; + int callbackMode; /* bool: are we running in callback mode? */ + int pcmsSynced; /* Have we successfully synced pcms */ + + /* the callback thread uses these to poll the sound device(s), waiting + * for data to be ready/available */ + struct pollfd *pfds; + int pollTimeout; + + /* Used in communication between threads */ + volatile sig_atomic_t callback_finished; /* bool: are we in the "callback finished" state? */ + volatile sig_atomic_t callbackAbort; /* Drop frames? */ + volatile sig_atomic_t callbackStop; /* Signal a stop */ + volatile sig_atomic_t isActive; /* Is stream in active state? (Between StartStream and StopStream || !paContinue) */ + pthread_mutex_t stateMtx; /* Used to synchronize access to stream state */ + pthread_mutex_t startMtx; /* Used to synchronize stream start in callback mode */ + pthread_cond_t startCond; /* Wait untill audio is started in callback thread */ + + int neverDropInput; + + PaTime underrun; + PaTime overrun; + + PaAlsaStreamComponent capture, playback; +} +PaAlsaStream; + +/* PaAlsaHostApiRepresentation - host api datastructure specific to this implementation */ + +typedef struct PaAlsaHostApiRepresentation +{ + PaUtilHostApiRepresentation commonHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup *allocations; + + PaHostApiIndex hostApiIndex; +} +PaAlsaHostApiRepresentation; + +typedef struct PaAlsaDeviceInfo +{ + PaDeviceInfo commonDeviceInfo; + char *alsaName; + int isPlug; + int minInputChannels; + int minOutputChannels; +} +PaAlsaDeviceInfo; + +/* Threading utilities */ + +static void InitializeThreading( PaAlsaThreading *th, PaUtilCpuLoadMeasurer *clm ) +{ + th->watchdogRunning = 0; + th->rtSched = 0; + th->callbackTime = 0; + th->callbackCpuTime = 0; + th->useWatchdog = 1; + th->throttledSleepTime = 0; + th->cpuLoadMeasurer = clm; + + th->rtPrio = (sched_get_priority_max( SCHED_FIFO ) - sched_get_priority_min( SCHED_FIFO )) / 2 + + sched_get_priority_min( SCHED_FIFO ); +} + +static PaError KillCallbackThread( PaAlsaThreading *th, int wait, PaError *exitResult, PaError *watchdogExitResult ) +{ + PaError result = paNoError; + void *pret; + + if( exitResult ) + *exitResult = paNoError; + if( watchdogExitResult ) + *watchdogExitResult = paNoError; + + if( th->watchdogRunning ) + { + pthread_cancel( th->watchdogThread ); + ASSERT_CALL_( pthread_join( th->watchdogThread, &pret ), 0 ); + + if( pret && pret != PTHREAD_CANCELED ) + { + if( watchdogExitResult ) + *watchdogExitResult = *(PaError *) pret; + free( pret ); + } + } + + /* Only kill the thread if it isn't in the process of stopping (flushing adaptation buffers) */ + /* TODO: Make join time out */ + if( !wait ) + pthread_cancel( th->callbackThread ); /* XXX: Safe to call this if the thread has exited on its own? */ + ASSERT_CALL_( pthread_join( th->callbackThread, &pret ), 0 ); + + if( pret && pret != PTHREAD_CANCELED ) + { + if( exitResult ) + *exitResult = *(PaError *) pret; + free( pret ); + } + + return result; +} + +static void OnWatchdogExit( void *userData ) +{ + PaAlsaThreading *th = (PaAlsaThreading *) userData; + struct sched_param spm = { 0 }; + assert( th ); + + ASSERT_CALL_( pthread_setschedparam( th->callbackThread, SCHED_OTHER, &spm ), 0 ); /* Lower before exiting */ + PA_DEBUG(( "Watchdog exiting\n" )); +} + +static PaError BoostPriority( PaAlsaThreading *th ) +{ + PaError result = paNoError; + struct sched_param spm = { 0 }; + spm.sched_priority = th->rtPrio; + + assert( th ); + + if( pthread_setschedparam( th->callbackThread, SCHED_FIFO, &spm ) != 0 ) + { + PA_UNLESS( errno == EPERM, paInternalError ); /* Lack permission to raise priority */ + PA_DEBUG(( "Failed bumping priority\n" )); + result = 0; + } + else + result = 1; /* Success */ +error: + return result; +} + +static void *WatchdogFunc( void *userData ) +{ + PaError result = paNoError, *pres = NULL; + int err; + PaAlsaThreading *th = (PaAlsaThreading *) userData; + unsigned intervalMsec = 500; + const PaTime maxSeconds = 3.; /* Max seconds between callbacks */ + PaTime timeThen = PaUtil_GetTime(), timeNow, timeElapsed, cpuTimeThen, cpuTimeNow, cpuTimeElapsed; + double cpuLoad, avgCpuLoad = 0.; + int throttled = 0; + + assert( th ); + + pthread_cleanup_push( &OnWatchdogExit, th ); /* Execute OnWatchdogExit when exiting */ + + /* Boost priority of callback thread */ + PA_ENSURE( result = BoostPriority( th ) ); + if( !result ) + { + pthread_exit( NULL ); /* Boost failed, might as well exit */ + } + + cpuTimeThen = th->callbackCpuTime; + { + int policy; + struct sched_param spm = { 0 }; + pthread_getschedparam( pthread_self(), &policy, &spm ); + PA_DEBUG(( "%s: Watchdog priority is %d\n", __FUNCTION__, spm.sched_priority )); + } + + while( 1 ) + { + double lowpassCoeff = 0.9, lowpassCoeff1 = 0.99999 - lowpassCoeff; + + /* Test before and after in case whatever underlying sleep call isn't interrupted by pthread_cancel */ + pthread_testcancel(); + Pa_Sleep( intervalMsec ); + pthread_testcancel(); + + if( PaUtil_GetTime() - th->callbackTime > maxSeconds ) + { + PA_DEBUG(( "Watchdog: Terminating callback thread\n" )); + /* Tell thread to terminate */ + err = pthread_kill( th->callbackThread, SIGKILL ); + pthread_exit( NULL ); + } + + PA_DEBUG(( "%s: PortAudio reports CPU load: %g\n", __FUNCTION__, PaUtil_GetCpuLoad( th->cpuLoadMeasurer ) )); + + /* Check if we should throttle, or unthrottle :P */ + cpuTimeNow = th->callbackCpuTime; + cpuTimeElapsed = cpuTimeNow - cpuTimeThen; + cpuTimeThen = cpuTimeNow; + + timeNow = PaUtil_GetTime(); + timeElapsed = timeNow - timeThen; + timeThen = timeNow; + cpuLoad = cpuTimeElapsed / timeElapsed; + avgCpuLoad = avgCpuLoad * lowpassCoeff + cpuLoad * lowpassCoeff1; + /* + if( throttled ) + PA_DEBUG(( "Watchdog: CPU load: %g, %g\n", avgCpuLoad, cpuTimeElapsed )); + */ + if( PaUtil_GetCpuLoad( th->cpuLoadMeasurer ) > .925 ) + { + static int policy; + static struct sched_param spm = { 0 }; + static const struct sched_param defaultSpm = { 0 }; + PA_DEBUG(( "%s: Throttling audio thread, priority %d\n", __FUNCTION__, spm.sched_priority )); + + pthread_getschedparam( th->callbackThread, &policy, &spm ); + if( !pthread_setschedparam( th->callbackThread, SCHED_OTHER, &defaultSpm ) ) + { + throttled = 1; + } + else + PA_DEBUG(( "Watchdog: Couldn't lower priority of audio thread: %s\n", strerror( errno ) )); + + /* Give other processes a go, before raising priority again */ + PA_DEBUG(( "%s: Watchdog sleeping for %lu msecs before unthrottling\n", __FUNCTION__, th->throttledSleepTime )); + Pa_Sleep( th->throttledSleepTime ); + + /* Reset callback priority */ + if( pthread_setschedparam( th->callbackThread, SCHED_FIFO, &spm ) != 0 ) + { + PA_DEBUG(( "%s: Couldn't raise priority of audio thread: %s\n", __FUNCTION__, strerror( errno ) )); + } + + if( PaUtil_GetCpuLoad( th->cpuLoadMeasurer ) >= .99 ) + intervalMsec = 50; + else + intervalMsec = 100; + + /* + lowpassCoeff = .97; + lowpassCoeff1 = .99999 - lowpassCoeff; + */ + } + else if( throttled && avgCpuLoad < .8 ) + { + intervalMsec = 500; + throttled = 0; + + /* + lowpassCoeff = .9; + lowpassCoeff1 = .99999 - lowpassCoeff; + */ + } + } + + pthread_cleanup_pop( 1 ); /* Execute cleanup on exit */ + +error: + /* Shouldn't get here in the normal case */ + + /* Pass on error code */ + pres = malloc( sizeof (PaError) ); + *pres = result; + + pthread_exit( pres ); +} + +static PaError CreateCallbackThread( PaAlsaThreading *th, void *(*callbackThreadFunc)( void * ), PaStream *s ) +{ + PaError result = paNoError; + pthread_attr_t attr; + int started = 0; + +#if defined _POSIX_MEMLOCK && (_POSIX_MEMLOCK != -1) + if( th->rtSched ) + { + if( mlockall( MCL_CURRENT | MCL_FUTURE ) < 0 ) + { + int savedErrno = errno; /* In case errno gets overwritten */ + assert( savedErrno != EINVAL ); /* Most likely a programmer error */ + PA_UNLESS( (savedErrno == EPERM), paInternalError ); + PA_DEBUG(( "%s: Failed locking memory\n", __FUNCTION__ )); + } + else + PA_DEBUG(( "%s: Successfully locked memory\n", __FUNCTION__ )); + } +#endif + + PA_UNLESS( !pthread_attr_init( &attr ), paInternalError ); + /* Priority relative to other processes */ + PA_UNLESS( !pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM ), paInternalError ); + + PA_UNLESS( !pthread_create( &th->callbackThread, &attr, callbackThreadFunc, s ), paInternalError ); + started = 1; + + if( th->rtSched ) + { + if( th->useWatchdog ) + { + int err; + struct sched_param wdSpm = { 0 }; + /* Launch watchdog, watchdog sets callback thread priority */ + int prio = PA_MIN( th->rtPrio + 4, sched_get_priority_max( SCHED_FIFO ) ); + wdSpm.sched_priority = prio; + + PA_UNLESS( !pthread_attr_init( &attr ), paInternalError ); + PA_UNLESS( !pthread_attr_setinheritsched( &attr, PTHREAD_EXPLICIT_SCHED ), paInternalError ); + PA_UNLESS( !pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM ), paInternalError ); + PA_UNLESS( !pthread_attr_setschedpolicy( &attr, SCHED_FIFO ), paInternalError ); + PA_UNLESS( !pthread_attr_setschedparam( &attr, &wdSpm ), paInternalError ); + if( (err = pthread_create( &th->watchdogThread, &attr, &WatchdogFunc, th )) ) + { + PA_UNLESS( err == EPERM, paInternalError ); + /* Permission error, go on without realtime privileges */ + PA_DEBUG(( "Failed bumping priority\n" )); + } + else + { + int policy; + th->watchdogRunning = 1; + ASSERT_CALL_( pthread_getschedparam( th->watchdogThread, &policy, &wdSpm ), 0 ); + /* Check if priority is right, policy could potentially differ from SCHED_FIFO (but that's alright) */ + if( wdSpm.sched_priority != prio ) + { + PA_DEBUG(( "Watchdog priority not set correctly (%d)\n", wdSpm.sched_priority )); + PA_ENSURE( paInternalError ); + } + } + } + else + PA_ENSURE( BoostPriority( th ) ); + } + +end: + return result; +error: + if( started ) + KillCallbackThread( th, 0, NULL, NULL ); + + goto end; +} + +static void CallbackUpdate( PaAlsaThreading *th ) +{ + th->callbackTime = PaUtil_GetTime(); + th->callbackCpuTime = PaUtil_GetCpuLoad( th->cpuLoadMeasurer ); +} + +/* prototypes for functions declared in this file */ + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *callback, + void *userData ); +static PaError CloseStream( PaStream* stream ); +static PaError StartStream( PaStream *stream ); +static PaError StopStream( PaStream *stream ); +static PaError AbortStream( PaStream *stream ); +static PaError IsStreamStopped( PaStream *s ); +static PaError IsStreamActive( PaStream *stream ); +static PaTime GetStreamTime( PaStream *stream ); +static double GetStreamCpuLoad( PaStream* stream ); +static PaError BuildDeviceList( PaAlsaHostApiRepresentation *hostApi ); +static int SetApproximateSampleRate( snd_pcm_t *pcm, snd_pcm_hw_params_t *hwParams, double sampleRate ); +static int GetExactSampleRate( snd_pcm_hw_params_t *hwParams, double *sampleRate ); + +/* Callback prototypes */ +static void *CallbackThreadFunc( void *userData ); + +/* Blocking prototypes */ +static signed long GetStreamReadAvailable( PaStream* s ); +static signed long GetStreamWriteAvailable( PaStream* s ); +static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames ); +static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames ); + + +static const PaAlsaDeviceInfo *GetDeviceInfo( const PaUtilHostApiRepresentation *hostApi, int device ) +{ + return (const PaAlsaDeviceInfo *)hostApi->deviceInfos[device]; +} + +PaError PaAlsa_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + PaAlsaHostApiRepresentation *alsaHostApi = NULL; + + PA_UNLESS( alsaHostApi = (PaAlsaHostApiRepresentation*) PaUtil_AllocateMemory( + sizeof(PaAlsaHostApiRepresentation) ), paInsufficientMemory ); + PA_UNLESS( alsaHostApi->allocations = PaUtil_CreateAllocationGroup(), paInsufficientMemory ); + alsaHostApi->hostApiIndex = hostApiIndex; + + *hostApi = (PaUtilHostApiRepresentation*)alsaHostApi; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paALSA; + (*hostApi)->info.name = "ALSA"; + + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PA_ENSURE( BuildDeviceList( alsaHostApi ) ); + + PaUtil_InitializeStreamInterface( &alsaHostApi->callbackStreamInterface, + CloseStream, StartStream, + StopStream, AbortStream, + IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, + PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface( &alsaHostApi->blockingStreamInterface, + CloseStream, StartStream, + StopStream, AbortStream, + IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, + GetStreamReadAvailable, + GetStreamWriteAvailable ); + + return result; + +error: + if( alsaHostApi ) + { + if( alsaHostApi->allocations ) + { + PaUtil_FreeAllAllocations( alsaHostApi->allocations ); + PaUtil_DestroyAllocationGroup( alsaHostApi->allocations ); + } + + PaUtil_FreeMemory( alsaHostApi ); + } + + return result; +} + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaAlsaHostApiRepresentation *alsaHostApi = (PaAlsaHostApiRepresentation*)hostApi; + + assert( hostApi ); + + if( alsaHostApi->allocations ) + { + PaUtil_FreeAllAllocations( alsaHostApi->allocations ); + PaUtil_DestroyAllocationGroup( alsaHostApi->allocations ); + } + + PaUtil_FreeMemory( alsaHostApi ); + snd_config_update_free_global(); +} + +/*! Determine max channels and default latencies. + * + * This function provides functionality to grope an opened (might be opened for capture or playback) pcm device for + * traits like max channels, suitable default latencies and default sample rate. Upon error, max channels is set to zero, + * and a suitable result returned. The device is closed before returning. + */ +static PaError GropeDevice( snd_pcm_t *pcm, int *minChannels, int *maxChannels, double *defaultLowLatency, + double *defaultHighLatency, double *defaultSampleRate, int isPlug ) +{ + PaError result = paNoError; + snd_pcm_hw_params_t *hwParams; + snd_pcm_uframes_t lowLatency = 512, highLatency = 2048; + unsigned int minChans, maxChans; + double defaultSr = *defaultSampleRate; + + assert( pcm ); + + ENSURE_( snd_pcm_nonblock( pcm, 0 ), paUnanticipatedHostError ); + + snd_pcm_hw_params_alloca( &hwParams ); + snd_pcm_hw_params_any( pcm, hwParams ); + + if( defaultSr >= 0 ) + { + /* Could be that the device opened in one mode supports samplerates that the other mode wont have, + * so try again .. */ + if( SetApproximateSampleRate( pcm, hwParams, defaultSr ) < 0 ) + { + defaultSr = -1.; + PA_DEBUG(( "%s: Original default samplerate failed, trying again ..\n", __FUNCTION__ )); + } + } + + if( defaultSr < 0. ) /* Default sample rate not set */ + { + unsigned int sampleRate = 44100; /* Will contain approximate rate returned by alsa-lib */ + ENSURE_( snd_pcm_hw_params_set_rate_near( pcm, hwParams, &sampleRate, NULL ), paUnanticipatedHostError ); + ENSURE_( GetExactSampleRate( hwParams, &defaultSr ), paUnanticipatedHostError ); + } + + ENSURE_( snd_pcm_hw_params_get_channels_min( hwParams, &minChans ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_get_channels_max( hwParams, &maxChans ), paUnanticipatedHostError ); + assert( maxChans <= INT_MAX ); + assert( maxChans > 0 ); /* Weird linking issue could cause wrong version of ALSA symbols to be called, + resulting in zeroed values */ + + /* XXX: Limit to sensible number (ALSA plugins accept a crazy amount of channels)? */ + if( isPlug && maxChans > 128 ) + { + maxChans = 128; + PA_DEBUG(( "%s: Limiting number of plugin channels to %u\n", __FUNCTION__, maxChans )); + } + + /* TWEAKME: + * + * Giving values for default min and max latency is not + * straightforward. Here are our objectives: + * + * * for low latency, we want to give the lowest value + * that will work reliably. This varies based on the + * sound card, kernel, CPU, etc. I think it is better + * to give sub-optimal latency than to give a number + * too low and cause dropouts. My conservative + * estimate at this point is to base it on 4096-sample + * latency at 44.1 kHz, which gives a latency of 23ms. + * * for high latency we want to give a large enough + * value that dropouts are basically impossible. This + * doesn't really require as much tweaking, since + * providing too large a number will just cause us to + * select the nearest setting that will work at stream + * config time. + */ + ENSURE_( snd_pcm_hw_params_set_buffer_size_near( pcm, hwParams, &lowLatency ), paUnanticipatedHostError ); + + /* Have to reset hwParams, to set new buffer size */ + ENSURE_( snd_pcm_hw_params_any( pcm, hwParams ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_set_buffer_size_near( pcm, hwParams, &highLatency ), paUnanticipatedHostError ); + + *minChannels = (int)minChans; + *maxChannels = (int)maxChans; + *defaultSampleRate = defaultSr; + *defaultLowLatency = (double) lowLatency / *defaultSampleRate; + *defaultHighLatency = (double) highLatency / *defaultSampleRate; + +end: + snd_pcm_close( pcm ); + return result; + +error: + goto end; +} + +/* Initialize device info with invalid values (maxInputChannels and maxOutputChannels are set to zero since these indicate + * wether input/output is available) */ +static void InitializeDeviceInfo( PaDeviceInfo *deviceInfo ) +{ + deviceInfo->structVersion = -1; + deviceInfo->name = NULL; + deviceInfo->hostApi = -1; + deviceInfo->maxInputChannels = 0; + deviceInfo->maxOutputChannels = 0; + deviceInfo->defaultLowInputLatency = -1.; + deviceInfo->defaultLowOutputLatency = -1.; + deviceInfo->defaultHighInputLatency = -1.; + deviceInfo->defaultHighOutputLatency = -1.; + deviceInfo->defaultSampleRate = -1.; +} + +/* Helper struct */ +typedef struct +{ + char *alsaName; + char *name; + int isPlug; + int hasPlayback; + int hasCapture; +} DeviceNames; + +static PaError PaAlsa_StrDup( PaAlsaHostApiRepresentation *alsaApi, + char **dst, + const char *src) +{ + PaError result = paNoError; + int len = strlen( src ) + 1; + + /* PA_DEBUG(("PaStrDup %s %d\n", src, len)); */ + + PA_UNLESS( *dst = (char *)PaUtil_GroupAllocateMemory( alsaApi->allocations, len ), + paInsufficientMemory ); + strncpy( *dst, src, len ); + +error: + return result; +} + +/* Disregard standard plugins + * XXX: Might want to make the "default" plugin available, if we can make it work + */ +static int IgnorePlugin( const char *pluginId ) +{ +#define numIgnored 10 + static const char *ignoredPlugins[numIgnored] = {"hw", "plughw", "plug", "default", "dsnoop", "dmix", "tee", + "file", "null", "shm"}; + int i; + + for( i = 0; i < numIgnored; ++i ) + { + if( !strcmp( pluginId, ignoredPlugins[i] ) ) + { + return 1; + } + } + + return 0; +} + +/* Build PaDeviceInfo list, ignore devices for which we cannot determine capabilities (possibly busy, sigh) */ +static PaError BuildDeviceList( PaAlsaHostApiRepresentation *alsaApi ) +{ + PaUtilHostApiRepresentation *commonApi = &alsaApi->commonHostApiRep; + PaAlsaDeviceInfo *deviceInfoArray; + int cardIdx = -1, devIdx = 0; + snd_ctl_card_info_t *cardInfo; + PaError result = paNoError; + size_t numDeviceNames = 0, maxDeviceNames = 1, i; + DeviceNames *deviceNames = NULL; + snd_config_t *topNode = NULL; + snd_pcm_info_t *pcmInfo; + int res; + int blocking = SND_PCM_NONBLOCK; + char alsaCardName[50]; + if( getenv( "PA_ALSA_INITIALIZE_BLOCK" ) && atoi( getenv( "PA_ALSA_INITIALIZE_BLOCK" ) ) ) + blocking = 0; + + /* These two will be set to the first working input and output device, respectively */ + commonApi->info.defaultInputDevice = paNoDevice; + commonApi->info.defaultOutputDevice = paNoDevice; + + /* count the devices by enumerating all the card numbers */ + + /* snd_card_next() modifies the integer passed to it to be: + * the index of the first card if the parameter is -1 + * the index of the next card if the parameter is the index of a card + * -1 if there are no more cards + * + * The function itself returns 0 if it succeeded. */ + cardIdx = -1; + snd_ctl_card_info_alloca( &cardInfo ); + snd_pcm_info_alloca( &pcmInfo ); + while( snd_card_next( &cardIdx ) == 0 && cardIdx >= 0 ) + { + char *cardName; + int devIdx = -1; + snd_ctl_t *ctl; + char buf[50]; + + snprintf( alsaCardName, sizeof (alsaCardName), "hw:%d", cardIdx ); + + /* Acquire name of card */ + if( snd_ctl_open( &ctl, alsaCardName, 0 ) < 0 ) + continue; /* Unable to open card :( */ + snd_ctl_card_info( ctl, cardInfo ); + + PA_ENSURE( PaAlsa_StrDup( alsaApi, &cardName, snd_ctl_card_info_get_name( cardInfo )) ); + + while( snd_ctl_pcm_next_device( ctl, &devIdx ) == 0 && devIdx >= 0 ) + { + char *alsaDeviceName, *deviceName; + size_t len; + int hasPlayback = 0, hasCapture = 0; + snprintf( buf, sizeof (buf), "%s:%d,%d", "hw", cardIdx, devIdx ); + + /* Obtain info about this particular device */ + snd_pcm_info_set_device( pcmInfo, devIdx ); + snd_pcm_info_set_subdevice( pcmInfo, 0 ); + snd_pcm_info_set_stream( pcmInfo, SND_PCM_STREAM_CAPTURE ); + if( snd_ctl_pcm_info( ctl, pcmInfo ) >= 0 ) + hasCapture = 1; + + snd_pcm_info_set_stream( pcmInfo, SND_PCM_STREAM_PLAYBACK ); + if( snd_ctl_pcm_info( ctl, pcmInfo ) >= 0 ) + hasPlayback = 1; + + if( !hasPlayback && !hasCapture ) + { + continue; /* Error */ + } + + /* The length of the string written by snprintf plus terminating 0 */ + len = snprintf( NULL, 0, "%s: %s (%s)", cardName, snd_pcm_info_get_name( pcmInfo ), buf ) + 1; + PA_UNLESS( deviceName = (char *)PaUtil_GroupAllocateMemory( alsaApi->allocations, len ), + paInsufficientMemory ); + snprintf( deviceName, len, "%s: %s (%s)", cardName, + snd_pcm_info_get_name( pcmInfo ), buf ); + + ++numDeviceNames; + if( !deviceNames || numDeviceNames > maxDeviceNames ) + { + maxDeviceNames *= 2; + PA_UNLESS( deviceNames = (DeviceNames *) realloc( deviceNames, maxDeviceNames * sizeof (DeviceNames) ), + paInsufficientMemory ); + } + + PA_ENSURE( PaAlsa_StrDup( alsaApi, &alsaDeviceName, buf ) ); + + deviceNames[ numDeviceNames - 1 ].alsaName = alsaDeviceName; + deviceNames[ numDeviceNames - 1 ].name = deviceName; + deviceNames[ numDeviceNames - 1 ].isPlug = 0; + deviceNames[ numDeviceNames - 1 ].hasPlayback = hasPlayback; + deviceNames[ numDeviceNames - 1 ].hasCapture = hasCapture; + } + snd_ctl_close( ctl ); + } + + /* Iterate over plugin devices */ + snd_config_update(); + if( (res = snd_config_search( snd_config, "pcm", &topNode )) >= 0 ) + { + snd_config_iterator_t i, next; + + snd_config_for_each( i, next, topNode ) + { + const char *tpStr = NULL, *idStr = NULL; + char *alsaDeviceName, *deviceName; + snd_config_t *n = snd_config_iterator_entry( i ), *tp = NULL; + if( snd_config_get_type( n ) != SND_CONFIG_TYPE_COMPOUND ) + continue; + + ENSURE_( snd_config_search( n, "type", &tp ), paUnanticipatedHostError ); + ENSURE_( snd_config_get_string( tp, &tpStr ), paUnanticipatedHostError ); + + ENSURE_( snd_config_get_id( n, &idStr ), paUnanticipatedHostError ); + if( IgnorePlugin( idStr ) ) + { + PA_DEBUG(( "%s: Ignoring ALSA plugin device %s of type %s\n", __FUNCTION__, idStr, tpStr )); + continue; + } + + PA_DEBUG(( "%s: Found plugin %s of type %s\n", __FUNCTION__, idStr, tpStr )); + + PA_UNLESS( alsaDeviceName = (char*)PaUtil_GroupAllocateMemory( alsaApi->allocations, + strlen(idStr) + 6 ), paInsufficientMemory ); + strcpy( alsaDeviceName, idStr ); + PA_UNLESS( deviceName = (char*)PaUtil_GroupAllocateMemory( alsaApi->allocations, + strlen(idStr) + 1 ), paInsufficientMemory ); + strcpy( deviceName, idStr ); + + ++numDeviceNames; + if( !deviceNames || numDeviceNames > maxDeviceNames ) + { + maxDeviceNames *= 2; + PA_UNLESS( deviceNames = (DeviceNames *) realloc( deviceNames, maxDeviceNames * sizeof (DeviceNames) ), + paInsufficientMemory ); + } + + deviceNames[numDeviceNames - 1].alsaName = alsaDeviceName; + deviceNames[numDeviceNames - 1].name = deviceName; + deviceNames[numDeviceNames - 1].isPlug = 1; + deviceNames[numDeviceNames - 1].hasPlayback = 1; + deviceNames[numDeviceNames - 1].hasCapture = 1; + } + } + else + PA_DEBUG(( "%s: Iterating over ALSA plugins failed: %s\n", __FUNCTION__, snd_strerror( res ) )); + + /* allocate deviceInfo memory based on the number of devices */ + PA_UNLESS( commonApi->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + alsaApi->allocations, sizeof(PaDeviceInfo*) * (numDeviceNames) ), paInsufficientMemory ); + + /* allocate all device info structs in a contiguous block */ + PA_UNLESS( deviceInfoArray = (PaAlsaDeviceInfo*)PaUtil_GroupAllocateMemory( + alsaApi->allocations, sizeof(PaAlsaDeviceInfo) * numDeviceNames ), paInsufficientMemory ); + + /* Loop over list of cards, filling in info, if a device is deemed unavailable (can't get name), + * it's ignored. + */ + /* while( snd_card_next( &cardIdx ) == 0 && cardIdx >= 0 ) */ + for( i = 0, devIdx = 0; i < numDeviceNames; ++i ) + { + snd_pcm_t *pcm; + PaAlsaDeviceInfo *deviceInfo = &deviceInfoArray[devIdx]; + PaDeviceInfo *commonDeviceInfo = &deviceInfo->commonDeviceInfo; + + /* Zero fields */ + InitializeDeviceInfo( commonDeviceInfo ); + + /* to determine device capabilities, we must open the device and query the + * hardware parameter configuration space */ + + /* Query capture */ + if( deviceNames[i].hasCapture && + snd_pcm_open( &pcm, deviceNames[i].alsaName, SND_PCM_STREAM_CAPTURE, blocking ) >= 0 ) + { + if( GropeDevice( pcm, &deviceInfo->minInputChannels, &commonDeviceInfo->maxInputChannels, + &commonDeviceInfo->defaultLowInputLatency, &commonDeviceInfo->defaultHighInputLatency, + &commonDeviceInfo->defaultSampleRate, deviceNames[i].isPlug ) != paNoError ) + continue; /* Error */ + } + + /* Query playback */ + if( deviceNames[i].hasPlayback && + snd_pcm_open( &pcm, deviceNames[i].alsaName, SND_PCM_STREAM_PLAYBACK, blocking ) >= 0 ) + { + if( GropeDevice( pcm, &deviceInfo->minOutputChannels, &commonDeviceInfo->maxOutputChannels, + &commonDeviceInfo->defaultLowOutputLatency, &commonDeviceInfo->defaultHighOutputLatency, + &commonDeviceInfo->defaultSampleRate, deviceNames[i].isPlug ) != paNoError ) + continue; /* Error */ + } + + commonDeviceInfo->structVersion = 2; + commonDeviceInfo->hostApi = alsaApi->hostApiIndex; + commonDeviceInfo->name = deviceNames[i].name; + deviceInfo->alsaName = deviceNames[i].alsaName; + deviceInfo->isPlug = deviceNames[i].isPlug; + + /* A: Storing pointer to PaAlsaDeviceInfo object as pointer to PaDeviceInfo object. + * Should now be safe to add device info, unless the device supports neither capture nor playback + */ + if( commonDeviceInfo->maxInputChannels > 0 || commonDeviceInfo->maxOutputChannels > 0 ) + { + if( commonApi->info.defaultInputDevice == paNoDevice && commonDeviceInfo->maxInputChannels > 0 ) + commonApi->info.defaultInputDevice = devIdx; + if( commonApi->info.defaultOutputDevice == paNoDevice && commonDeviceInfo->maxOutputChannels > 0 ) + commonApi->info.defaultOutputDevice = devIdx; + + commonApi->deviceInfos[devIdx++] = (PaDeviceInfo *) deviceInfo; + } + } + free( deviceNames ); + + commonApi->info.deviceCount = devIdx; /* Number of successfully queried devices */ + +end: + return result; + +error: + goto end; /* No particular action */ +} + +/* Check against known device capabilities */ +static PaError ValidateParameters( const PaStreamParameters *parameters, PaUtilHostApiRepresentation *hostApi, StreamDirection mode ) +{ + PaError result = paNoError; + int maxChans; + const PaAlsaDeviceInfo *deviceInfo = NULL; + assert( parameters ); + + if( parameters->device != paUseHostApiSpecificDeviceSpecification ) + { + assert( parameters->device < hostApi->info.deviceCount ); + PA_UNLESS( parameters->hostApiSpecificStreamInfo == NULL, paBadIODeviceCombination ); + deviceInfo = GetDeviceInfo( hostApi, parameters->device ); + } + else + { + const PaAlsaStreamInfo *streamInfo = parameters->hostApiSpecificStreamInfo; + + PA_UNLESS( parameters->device == paUseHostApiSpecificDeviceSpecification, paInvalidDevice ); + PA_UNLESS( streamInfo->size == sizeof (PaAlsaStreamInfo) && streamInfo->version == 1, + paIncompatibleHostApiSpecificStreamInfo ); + + return paNoError; /* Skip further checking */ + } + + assert( deviceInfo ); + assert( parameters->hostApiSpecificStreamInfo == NULL ); + maxChans = (StreamDirection_In == mode ? deviceInfo->commonDeviceInfo.maxInputChannels : + deviceInfo->commonDeviceInfo.maxOutputChannels); + PA_UNLESS( parameters->channelCount <= maxChans, paInvalidChannelCount ); + +error: + return result; +} + +/* Given an open stream, what sample formats are available? */ + +static PaSampleFormat GetAvailableFormats( snd_pcm_t *pcm ) +{ + PaSampleFormat available = 0; + snd_pcm_hw_params_t *hwParams; + snd_pcm_hw_params_alloca( &hwParams ); + + snd_pcm_hw_params_any( pcm, hwParams ); + + if( snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_FLOAT ) >= 0) + available |= paFloat32; + + if( snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S32 ) >= 0) + available |= paInt32; + + if( snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S24 ) >= 0) + available |= paInt24; + + if( snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S16 ) >= 0) + available |= paInt16; + + if( snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_U8 ) >= 0) + available |= paUInt8; + + if( snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S8 ) >= 0) + available |= paInt8; + + return available; +} + +static snd_pcm_format_t Pa2AlsaFormat( PaSampleFormat paFormat ) +{ + switch( paFormat ) + { + case paFloat32: + return SND_PCM_FORMAT_FLOAT; + + case paInt16: + return SND_PCM_FORMAT_S16; + + case paInt24: + return SND_PCM_FORMAT_S24; + + case paInt32: + return SND_PCM_FORMAT_S32; + + case paInt8: + return SND_PCM_FORMAT_S8; + + case paUInt8: + return SND_PCM_FORMAT_U8; + + default: + return SND_PCM_FORMAT_UNKNOWN; + } +} + +/** Open an ALSA pcm handle. + * + * The device to be open can be specified in a custom PaAlsaStreamInfo struct, or it will be a device number. In case of a + * device number, it maybe specified through an env variable (PA_ALSA_PLUGHW) that we should open the corresponding plugin + * device. + */ +static PaError AlsaOpen( const PaUtilHostApiRepresentation *hostApi, const PaStreamParameters *params, StreamDirection + streamDir, snd_pcm_t **pcm ) +{ + PaError result = paNoError; + int ret; + const char *deviceName = alloca( 50 ); + const PaAlsaDeviceInfo *deviceInfo = NULL; + PaAlsaStreamInfo *streamInfo = (PaAlsaStreamInfo *)params->hostApiSpecificStreamInfo; + + if( !streamInfo ) + { + int usePlug = 0; + deviceInfo = GetDeviceInfo( hostApi, params->device ); + + /* If device name starts with hw: and PA_ALSA_PLUGHW is 1, we open the plughw device instead */ + if( !strncmp( "hw:", deviceInfo->alsaName, 3 ) && getenv( "PA_ALSA_PLUGHW" ) ) + usePlug = atoi( getenv( "PA_ALSA_PLUGHW" ) ); + if( usePlug ) + snprintf( (char *) deviceName, 50, "plug%s", deviceInfo->alsaName ); + else + deviceName = deviceInfo->alsaName; + } + else + deviceName = streamInfo->deviceString; + + if( (ret = snd_pcm_open( pcm, deviceName, streamDir == StreamDirection_In ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK, + SND_PCM_NONBLOCK )) < 0 ) + { + *pcm = NULL; /* Not to be closed */ + ENSURE_( ret, ret == -EBUSY ? paDeviceUnavailable : paBadIODeviceCombination ); + } + ENSURE_( snd_pcm_nonblock( *pcm, 0 ), paUnanticipatedHostError ); + +end: + return result; + +error: + goto end; +} + +static PaError TestParameters( const PaUtilHostApiRepresentation *hostApi, const PaStreamParameters *parameters, + double sampleRate, StreamDirection streamDir ) +{ + PaError result = paNoError; + snd_pcm_t *pcm = NULL; + PaSampleFormat availableFormats; + /* We are able to adapt to a number of channels less than what the device supports */ + unsigned int numHostChannels; + PaSampleFormat hostFormat; + snd_pcm_hw_params_t *hwParams; + snd_pcm_hw_params_alloca( &hwParams ); + + if( !parameters->hostApiSpecificStreamInfo ) + { + const PaAlsaDeviceInfo *devInfo = GetDeviceInfo( hostApi, parameters->device ); + numHostChannels = PA_MAX( parameters->channelCount, StreamDirection_In == streamDir ? + devInfo->minInputChannels : devInfo->minOutputChannels ); + } + else + numHostChannels = parameters->channelCount; + + PA_ENSURE( AlsaOpen( hostApi, parameters, streamDir, &pcm ) ); + + snd_pcm_hw_params_any( pcm, hwParams ); + + if( SetApproximateSampleRate( pcm, hwParams, sampleRate ) < 0 ) + { + result = paInvalidSampleRate; + goto error; + } + + if( snd_pcm_hw_params_set_channels( pcm, hwParams, numHostChannels ) < 0 ) + { + result = paInvalidChannelCount; + goto error; + } + + /* See if we can find a best possible match */ + availableFormats = GetAvailableFormats( pcm ); + PA_ENSURE( hostFormat = PaUtil_SelectClosestAvailableFormat( availableFormats, parameters->sampleFormat ) ); + ENSURE_( snd_pcm_hw_params_set_format( pcm, hwParams, Pa2AlsaFormat( hostFormat ) ), paUnanticipatedHostError ); + + ENSURE_( snd_pcm_hw_params( pcm, hwParams ), paUnanticipatedHostError ); + +end: + if( pcm ) + snd_pcm_close( pcm ); + return result; + +error: + goto end; +} + +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + int inputChannelCount = 0, outputChannelCount = 0; + PaSampleFormat inputSampleFormat, outputSampleFormat; + PaError result = paFormatIsSupported; + + if( inputParameters ) + { + PA_ENSURE( ValidateParameters( inputParameters, hostApi, StreamDirection_In ) ); + + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + } + + if( outputParameters ) + { + PA_ENSURE( ValidateParameters( outputParameters, hostApi, StreamDirection_Out ) ); + + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + } + + if( inputChannelCount ) + { + if( (result = TestParameters( hostApi, inputParameters, sampleRate, StreamDirection_In )) + != paNoError ) + goto error; + } + if ( outputChannelCount ) + { + if( (result = TestParameters( hostApi, outputParameters, sampleRate, StreamDirection_Out )) + != paNoError ) + goto error; + } + + return paFormatIsSupported; + +error: + return result; +} + +static PaError PaAlsaStreamComponent_Initialize( PaAlsaStreamComponent *self, PaAlsaHostApiRepresentation *alsaApi, + const PaStreamParameters *params, StreamDirection streamDir, int callbackMode ) +{ + PaError result = paNoError; + PaSampleFormat userSampleFormat = params->sampleFormat, hostSampleFormat; + assert( params->channelCount > 0 ); + + /* Make sure things have an initial value */ + memset( self, 0, sizeof (PaAlsaStreamComponent) ); + + if( NULL == params->hostApiSpecificStreamInfo ) + { + const PaAlsaDeviceInfo *devInfo = GetDeviceInfo( &alsaApi->commonHostApiRep, params->device ); + self->numHostChannels = PA_MAX( params->channelCount, StreamDirection_In == streamDir ? devInfo->minInputChannels + : devInfo->minOutputChannels ); + } + else + { + /* We're blissfully unaware of the minimum channelCount */ + self->numHostChannels = params->channelCount; + } + + PA_ENSURE( AlsaOpen( &alsaApi->commonHostApiRep, params, streamDir, &self->pcm ) ); + self->nfds = snd_pcm_poll_descriptors_count( self->pcm ); + hostSampleFormat = PaUtil_SelectClosestAvailableFormat( GetAvailableFormats( self->pcm ), userSampleFormat ); + + self->hostSampleFormat = hostSampleFormat; + self->nativeFormat = Pa2AlsaFormat( hostSampleFormat ); + self->hostInterleaved = self->userInterleaved = !(userSampleFormat & paNonInterleaved); + self->numUserChannels = params->channelCount; + self->streamDir = streamDir; + + if( !callbackMode && !self->userInterleaved ) + { + /* Pre-allocate non-interleaved user provided buffers */ + PA_UNLESS( self->userBuffers = PaUtil_AllocateMemory( sizeof (void *) * self->numUserChannels ), + paInsufficientMemory ); + } + +error: + return result; +} + +static void PaAlsaStreamComponent_Terminate( PaAlsaStreamComponent *self ) +{ + snd_pcm_close( self->pcm ); + if( self->userBuffers ) + PaUtil_FreeMemory( self->userBuffers ); +} + +/** Configure the associated ALSA pcm. + * + */ +static PaError PaAlsaStreamComponent_Configure( PaAlsaStreamComponent *self, const PaStreamParameters *params, unsigned long + framesPerHostBuffer, int primeBuffers, int callbackMode, double *sampleRate, PaTime *returnedLatency ) +{ + /* + int numPeriods; + + if( getenv("PA_NUMPERIODS") != NULL ) + numPeriods = atoi( getenv("PA_NUMPERIODS") ); + else + numPeriods = ( (latency * sampleRate) / *framesPerBuffer ) + 1; + + PA_DEBUG(( "latency: %f, rate: %f, framesPerBuffer: %d\n", latency, sampleRate, *framesPerBuffer )); + if( numPeriods <= 1 ) + numPeriods = 2; + */ + + /* Configuration consists of setting all of ALSA's parameters. + * These parameters come in two flavors: hardware parameters + * and software paramters. Hardware parameters will affect + * the way the device is initialized, software parameters + * affect the way ALSA interacts with me, the user-level client. + */ + + snd_pcm_hw_params_t *hwParams; + snd_pcm_sw_params_t *swParams; + PaError result = paNoError; + snd_pcm_access_t accessMode, alternateAccessMode; + unsigned int numPeriods, minPeriods = 2; + int dir = 0; + snd_pcm_t *pcm = self->pcm; + PaTime latency = params->suggestedLatency; + double sr = *sampleRate; + *returnedLatency = -1.; + + snd_pcm_hw_params_alloca( &hwParams ); + snd_pcm_sw_params_alloca( &swParams ); + + self->framesPerBuffer = framesPerHostBuffer; + + /* ... fill up the configuration space with all possibile + * combinations of parameters this device will accept */ + ENSURE_( snd_pcm_hw_params_any( pcm, hwParams ), paUnanticipatedHostError ); + + ENSURE_( snd_pcm_hw_params_set_periods_integer( pcm, hwParams ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_set_period_size_integer( pcm, hwParams ), paUnanticipatedHostError ); + + if( self->userInterleaved ) + { + accessMode = SND_PCM_ACCESS_MMAP_INTERLEAVED; + alternateAccessMode = SND_PCM_ACCESS_MMAP_NONINTERLEAVED; + } + else + { + accessMode = SND_PCM_ACCESS_MMAP_NONINTERLEAVED; + alternateAccessMode = SND_PCM_ACCESS_MMAP_INTERLEAVED; + } + + /* If requested access mode fails, try alternate mode */ + if( snd_pcm_hw_params_set_access( pcm, hwParams, accessMode ) < 0 ) + { + ENSURE_( snd_pcm_hw_params_set_access( pcm, hwParams, alternateAccessMode ), paUnanticipatedHostError ); + /* Flip mode */ + self->hostInterleaved = !self->userInterleaved; + } + + ENSURE_( snd_pcm_hw_params_set_format( pcm, hwParams, self->nativeFormat ), paUnanticipatedHostError ); + + ENSURE_( SetApproximateSampleRate( pcm, hwParams, sr ), paInvalidSampleRate ); + ENSURE_( GetExactSampleRate( hwParams, &sr ), paUnanticipatedHostError ); + /* reject if there's no sample rate within 1% of the one requested */ + if( (fabs( *sampleRate - sr ) / *sampleRate) > 0.01 ) + { + PA_DEBUG(("%s: Wanted %f, closest sample rate was %d\n", __FUNCTION__, sampleRate, sr )); + PA_ENSURE( paInvalidSampleRate ); + } + + ENSURE_( snd_pcm_hw_params_set_channels( pcm, hwParams, self->numHostChannels ), paInvalidChannelCount ); + + /* I think there should be at least 2 periods (even though ALSA doesn't appear to enforce this) */ + dir = 0; + ENSURE_( snd_pcm_hw_params_set_periods_min( pcm, hwParams, &minPeriods, &dir ), paUnanticipatedHostError ); + dir = 0; + ENSURE_( snd_pcm_hw_params_set_period_size_near( pcm, hwParams, &self->framesPerBuffer, &dir ), paUnanticipatedHostError ); + + /* Find an acceptable number of periods */ + numPeriods = (latency * sr) / self->framesPerBuffer + 1; + dir = 0; + ENSURE_( snd_pcm_hw_params_set_periods_near( pcm, hwParams, &numPeriods, &dir ), paUnanticipatedHostError ); + /* Minimum of periods should already be 2 */ + PA_UNLESS( numPeriods >= 2, paInternalError ); + + /* Set the parameters! */ + ENSURE_( snd_pcm_hw_params( pcm, hwParams ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_get_buffer_size( hwParams, &self->bufferSize ), paUnanticipatedHostError ); + + /* Latency in seconds, one period is not counted as latency */ + latency = (numPeriods - 1) * self->framesPerBuffer / sr; + + /* Now software parameters... */ + ENSURE_( snd_pcm_sw_params_current( pcm, swParams ), paUnanticipatedHostError ); + + ENSURE_( snd_pcm_sw_params_set_start_threshold( pcm, swParams, self->framesPerBuffer ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_sw_params_set_stop_threshold( pcm, swParams, self->bufferSize ), paUnanticipatedHostError ); + + /* Silence buffer in the case of underrun */ + if( !primeBuffers ) /* XXX: Make sense? */ + { + snd_pcm_uframes_t boundary; + ENSURE_( snd_pcm_sw_params_get_boundary( swParams, &boundary ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_sw_params_set_silence_threshold( pcm, swParams, 0 ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_sw_params_set_silence_size( pcm, swParams, boundary ), paUnanticipatedHostError ); + } + + ENSURE_( snd_pcm_sw_params_set_avail_min( pcm, swParams, self->framesPerBuffer ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_sw_params_set_xfer_align( pcm, swParams, 1 ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_sw_params_set_tstamp_mode( pcm, swParams, SND_PCM_TSTAMP_MMAP ), paUnanticipatedHostError ); + + /* Set the parameters! */ + ENSURE_( snd_pcm_sw_params( pcm, swParams ), paUnanticipatedHostError ); + + *sampleRate = sr; + *returnedLatency = latency; + +end: + return result; + +error: + goto end; /* No particular action */ +} + +static PaError PaAlsaStream_Initialize( PaAlsaStream *self, PaAlsaHostApiRepresentation *alsaApi, const PaStreamParameters *inParams, + const PaStreamParameters *outParams, double sampleRate, unsigned long framesPerUserBuffer, PaStreamCallback callback, + PaStreamFlags streamFlags, void *userData ) +{ + PaError result = paNoError; + assert( self ); + + memset( self, 0, sizeof (PaAlsaStream) ); + + if( NULL != callback ) + { + PaUtil_InitializeStreamRepresentation( &self->streamRepresentation, + &alsaApi->callbackStreamInterface, + callback, userData ); + self->callbackMode = 1; + } + else + { + PaUtil_InitializeStreamRepresentation( &self->streamRepresentation, + &alsaApi->blockingStreamInterface, + NULL, userData ); + } + + self->framesPerUserBuffer = framesPerUserBuffer; + self->neverDropInput = streamFlags & paNeverDropInput; + /* XXX: Ignore paPrimeOutputBuffersUsingStreamCallback untill buffer priming is fully supported in pa_process.c */ + /* + if( outParams & streamFlags & paPrimeOutputBuffersUsingStreamCallback ) + self->primeBuffers = 1; + */ + memset( &self->capture, 0, sizeof (PaAlsaStreamComponent) ); + memset( &self->playback, 0, sizeof (PaAlsaStreamComponent) ); + if( inParams ) + PA_ENSURE( PaAlsaStreamComponent_Initialize( &self->capture, alsaApi, inParams, StreamDirection_In, NULL != callback ) ); + if( outParams ) + PA_ENSURE( PaAlsaStreamComponent_Initialize( &self->playback, alsaApi, outParams, StreamDirection_Out, NULL != callback ) ); + + assert( self->capture.nfds || self->playback.nfds ); + + PA_UNLESS( self->pfds = (struct pollfd*)PaUtil_AllocateMemory( (self->capture.nfds + + self->playback.nfds) * sizeof (struct pollfd) ), paInsufficientMemory ); + + PaUtil_InitializeCpuLoadMeasurer( &self->cpuLoadMeasurer, sampleRate ); + InitializeThreading( &self->threading, &self->cpuLoadMeasurer ); + ASSERT_CALL_( pthread_mutex_init( &self->stateMtx, NULL ), 0 ); + ASSERT_CALL_( pthread_mutex_init( &self->startMtx, NULL ), 0 ); + ASSERT_CALL_( pthread_cond_init( &self->startCond, NULL ), 0 ); + +error: + return result; +} + +/** Free resources associated with stream, and eventually stream itself. + * + * Frees allocated memory, and terminates individual StreamComponents. + */ +static void PaAlsaStream_Terminate( PaAlsaStream *self ) +{ + assert( self ); + + if( self->capture.pcm ) + { + PaAlsaStreamComponent_Terminate( &self->capture ); + } + if( self->playback.pcm ) + { + PaAlsaStreamComponent_Terminate( &self->playback ); + } + + PaUtil_FreeMemory( self->pfds ); + ASSERT_CALL_( pthread_mutex_destroy( &self->stateMtx ), 0 ); + ASSERT_CALL_( pthread_mutex_destroy( &self->startMtx ), 0 ); + ASSERT_CALL_( pthread_cond_destroy( &self->startCond ), 0 ); + + PaUtil_FreeMemory( self ); +} + +/** Calculate polling timeout + * + * @param frames Time to wait + * @return Polling timeout in milliseconds + */ +static int CalculatePollTimeout( const PaAlsaStream *stream, unsigned long frames ) +{ + assert( stream->streamRepresentation.streamInfo.sampleRate > 0.0 ); + /* Period in msecs, rounded up */ + return (int)ceil( 1000 * frames / stream->streamRepresentation.streamInfo.sampleRate ); +} + +/** Set up ALSA stream parameters. + * + */ +static PaError PaAlsaStream_Configure( PaAlsaStream *self, const PaStreamParameters *inParams, const PaStreamParameters + *outParams, double sampleRate, unsigned long framesPerHostBuffer, double *inputLatency, double *outputLatency, + unsigned long *maxHostBufferSize ) +{ + PaError result = paNoError; + double realSr = sampleRate; + + if( self->capture.pcm ) + PA_ENSURE( PaAlsaStreamComponent_Configure( &self->capture, inParams, framesPerHostBuffer, self->primeBuffers, + self->callbackMode, &realSr, inputLatency ) ); + if( self->playback.pcm ) + PA_ENSURE( PaAlsaStreamComponent_Configure( &self->playback, outParams, framesPerHostBuffer, self->primeBuffers, + self->callbackMode, &realSr, outputLatency ) ); + + /* Should be exact now */ + self->streamRepresentation.streamInfo.sampleRate = realSr; + + /* this will cause the two streams to automatically start/stop/prepare in sync. + * We only need to execute these operations on one of the pair. + * A: We don't want to do this on a blocking stream. + */ + if( self->callbackMode && self->capture.pcm && self->playback.pcm ) + { + int err = snd_pcm_link( self->capture.pcm, self->playback.pcm ); + if( err >= 0 ) + self->pcmsSynced = 1; + else + PA_DEBUG(( "%s: Unable to sync pcms: %s\n", __FUNCTION__, snd_strerror( err ) )); + } + + /* Frames per host buffer for the stream is set as a compromise between the two directions */ + framesPerHostBuffer = PA_MIN( self->capture.pcm ? self->capture.framesPerBuffer : ULONG_MAX, + self->playback.pcm ? self->playback.framesPerBuffer : ULONG_MAX ); + self->pollTimeout = CalculatePollTimeout( self, framesPerHostBuffer ); /* Period in msecs, rounded up */ + + *maxHostBufferSize = PA_MAX( self->capture.pcm ? self->capture.bufferSize : 0, + self->playback.pcm ? self->playback.bufferSize : 0 ); + + /* Time before watchdog unthrottles realtime thread == 1/4 of period time in msecs */ + self->threading.throttledSleepTime = (unsigned long) (framesPerHostBuffer / sampleRate / 4 * 1000); + + if( self->callbackMode ) + { + /* If the user expects a certain number of frames per callback we will either have to rely on block adaption + * (framesPerHostBuffer is not an integer multiple of framesPerBuffer) or we can simply align the number + * of host buffer frames with what the user specified */ + if( self->framesPerUserBuffer != paFramesPerBufferUnspecified ) + { + /* self->alignFrames = 1; */ + + /* Unless the ratio between number of host and user buffer frames is an integer we will have to rely + * on block adaption */ + /* + if( framesPerHostBuffer % framesPerBuffer != 0 || (self->capture.pcm && self->playback.pcm && + self->capture.framesPerBuffer != self->playback.framesPerBuffer) ) + self->useBlockAdaption = 1; + else + self->alignFrames = 1; + */ + } + } + +error: + return result; +} + +/* We need to determine how many frames per host buffer to use. Our + * goals are to provide the best possible performance, but also to + * most closely honor the requested latency settings. Therefore this + * decision is based on: + * + * - the period sizes that playback and/or capture support. The + * host buffer size has to be one of these. + * - the number of periods that playback and/or capture support. + * + * We want to make period_size*(num_periods-1) to be as close as possible + * to latency*rate for both playback and capture. + * + * This is one of those blocks of code that will just take a lot of + * refinement to be any good. + * + * In the full-duplex case it is possible that the routine was unable + * to find a number of frames per buffer acceptable to both devices + * TODO: Implement an algorithm to find the value closest to acceptance + * by both devices, to minimize difference between period sizes? + */ +static PaError DetermineFramesPerBuffer( const PaAlsaStream *stream, double sampleRate, const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, unsigned long *determinedFrames, const PaUtilHostApiRepresentation *hostApi ) +{ + PaError result = paNoError; + unsigned long framesPerBuffer = 0; + int numHostInputChannels = 0, numHostOutputChannels = 0; + + /* XXX: Clean this up */ + if( stream->capture.pcm ) + { + const PaAlsaDeviceInfo *devInfo = GetDeviceInfo( hostApi, inputParameters->device ); + numHostInputChannels = PA_MAX( inputParameters->channelCount, devInfo->minInputChannels ); + } + if( stream->playback.pcm ) + { + const PaAlsaDeviceInfo *devInfo = GetDeviceInfo( hostApi, outputParameters->device ); + numHostOutputChannels = PA_MAX( outputParameters->channelCount, devInfo->minOutputChannels ); + } + + if( stream->capture.pcm && stream->playback.pcm ) + { + snd_pcm_uframes_t desiredLatency, e; + snd_pcm_uframes_t minPeriodSize, minPlayback, minCapture, maxPeriodSize, maxPlayback, maxCapture, + optimalPeriodSize, periodSize; + int dir = 0; + unsigned int minPeriods = 2; + + snd_pcm_t *pcm; + snd_pcm_hw_params_t *hwParamsPlayback, *hwParamsCapture; + + snd_pcm_hw_params_alloca( &hwParamsPlayback ); + snd_pcm_hw_params_alloca( &hwParamsCapture ); + + /* Come up with a common desired latency */ + pcm = stream->playback.pcm; + snd_pcm_hw_params_any( pcm, hwParamsPlayback ); + ENSURE_( SetApproximateSampleRate( pcm, hwParamsPlayback, sampleRate ), paInvalidSampleRate ); + ENSURE_( snd_pcm_hw_params_set_channels( pcm, hwParamsPlayback, numHostOutputChannels ), + paBadIODeviceCombination ); + + ENSURE_( snd_pcm_hw_params_set_period_size_integer( pcm, hwParamsPlayback ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_set_periods_integer( pcm, hwParamsPlayback ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_set_periods_min( pcm, hwParamsPlayback, &minPeriods, &dir ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_get_period_size_min( hwParamsPlayback, &minPlayback, &dir ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_get_period_size_max( hwParamsPlayback, &maxPlayback, &dir ), paUnanticipatedHostError ); + + pcm = stream->capture.pcm; + ENSURE_( snd_pcm_hw_params_any( pcm, hwParamsCapture ), paUnanticipatedHostError ); + ENSURE_( SetApproximateSampleRate( pcm, hwParamsCapture, sampleRate ), paBadIODeviceCombination ); + ENSURE_( snd_pcm_hw_params_set_channels( pcm, hwParamsCapture, numHostInputChannels ), + paBadIODeviceCombination ); + + ENSURE_( snd_pcm_hw_params_set_period_size_integer( pcm, hwParamsCapture ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_set_periods_integer( pcm, hwParamsCapture ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_set_periods_min( pcm, hwParamsCapture, &minPeriods, &dir ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_get_period_size_min( hwParamsCapture, &minCapture, &dir ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_get_period_size_max( hwParamsCapture, &maxCapture, &dir ), paUnanticipatedHostError ); + + minPeriodSize = PA_MAX( minPlayback, minCapture ); + maxPeriodSize = PA_MIN( maxPlayback, maxCapture ); + + desiredLatency = (snd_pcm_uframes_t) (PA_MIN( outputParameters->suggestedLatency, inputParameters->suggestedLatency ) + * sampleRate); + /* Clamp desiredLatency */ + { + snd_pcm_uframes_t tmp, maxBufferSize = ULONG_MAX; + ENSURE_( snd_pcm_hw_params_get_buffer_size_max( hwParamsPlayback, &maxBufferSize ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_get_buffer_size_max( hwParamsCapture, &tmp ), paUnanticipatedHostError ); + maxBufferSize = PA_MIN( maxBufferSize, tmp ); + + desiredLatency = PA_MIN( desiredLatency, maxBufferSize ); + } + + /* Find the closest power of 2 */ + e = ilogb( minPeriodSize ); + if( minPeriodSize & (minPeriodSize - 1) ) + e += 1; + periodSize = (snd_pcm_uframes_t) pow( 2, e ); + + while( periodSize <= maxPeriodSize ) + { + if( snd_pcm_hw_params_test_period_size( stream->playback.pcm, hwParamsPlayback, periodSize, 0 ) >= 0 && + snd_pcm_hw_params_test_period_size( stream->capture.pcm, hwParamsCapture, periodSize, 0 ) >= 0 ) + break; /* Ok! */ + + periodSize *= 2; + } + + /* 4 periods considered optimal */ + optimalPeriodSize = PA_MAX( desiredLatency / 4, minPeriodSize ); + optimalPeriodSize = PA_MIN( optimalPeriodSize, maxPeriodSize ); + + /* Find the closest power of 2 */ + e = ilogb( optimalPeriodSize ); + if( optimalPeriodSize & (optimalPeriodSize - 1) ) + e += 1; + optimalPeriodSize = (snd_pcm_uframes_t) pow( 2, e ); + + while( optimalPeriodSize >= periodSize ) + { + pcm = stream->playback.pcm; + if( snd_pcm_hw_params_test_period_size( pcm, hwParamsPlayback, optimalPeriodSize, 0 ) < 0 ) + continue; + + pcm = stream->capture.pcm; + if( snd_pcm_hw_params_test_period_size( pcm, hwParamsCapture, optimalPeriodSize, 0 ) >= 0 ) + break; + + optimalPeriodSize /= 2; + } + + if( optimalPeriodSize > periodSize ) + periodSize = optimalPeriodSize; + + if( periodSize <= maxPeriodSize ) + { + /* Looks good */ + framesPerBuffer = periodSize; + } + else + { + /* Unable to find a common period size, oh well */ + optimalPeriodSize = PA_MAX( desiredLatency / 4, minPeriodSize ); + optimalPeriodSize = PA_MIN( optimalPeriodSize, maxPeriodSize ); + + /* ConfigureStream should find individual period sizes acceptable for each device */ + framesPerBuffer = optimalPeriodSize; + /* PA_ENSURE( paBadIODeviceCombination ); */ + } + } + else /* half-duplex is a slightly simpler case */ + { + unsigned long bufferSize, channels; + snd_pcm_t *pcm; + snd_pcm_hw_params_t *hwParams; + + snd_pcm_hw_params_alloca( &hwParams ); + + if( stream->capture.pcm ) + { + pcm = stream->capture.pcm; + bufferSize = inputParameters->suggestedLatency * sampleRate; + channels = numHostInputChannels; + } + else + { + pcm = stream->playback.pcm; + bufferSize = outputParameters->suggestedLatency * sampleRate; + channels = numHostOutputChannels; + } + + ENSURE_( snd_pcm_hw_params_any( pcm, hwParams ), paUnanticipatedHostError ); + ENSURE_( SetApproximateSampleRate( pcm, hwParams, sampleRate ), paInvalidSampleRate ); + ENSURE_( snd_pcm_hw_params_set_channels( pcm, hwParams, channels ), paBadIODeviceCombination ); + + ENSURE_( snd_pcm_hw_params_set_period_size_integer( pcm, hwParams ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_set_periods_integer( pcm, hwParams ), paUnanticipatedHostError ); + + /* Using 5 as a base number of periods, we try to approximate the suggested latency (+1 period), + finding a combination of period/buffer size which best fits these constraints */ + framesPerBuffer = bufferSize / 4; + bufferSize += framesPerBuffer; /* One period doesn't count as latency */ + ENSURE_( snd_pcm_hw_params_set_buffer_size_near( pcm, hwParams, &bufferSize ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_set_period_size_near( pcm, hwParams, &framesPerBuffer, NULL ), paUnanticipatedHostError ); + } + + PA_UNLESS( framesPerBuffer != 0, paInternalError ); + *determinedFrames = framesPerBuffer; + +error: + return result; +} + +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *callback, + void *userData ) +{ + PaError result = paNoError; + PaAlsaHostApiRepresentation *alsaHostApi = (PaAlsaHostApiRepresentation*)hostApi; + PaAlsaStream *stream = NULL; + PaSampleFormat hostInputSampleFormat = 0, hostOutputSampleFormat = 0; + PaSampleFormat inputSampleFormat = 0, outputSampleFormat = 0; + int numInputChannels = 0, numOutputChannels = 0; + PaTime inputLatency, outputLatency; + unsigned long framesPerHostBuffer; + PaUtilHostBufferSizeMode hostBufferSizeMode = paUtilBoundedHostBufferSize; + unsigned long maxHostBufferSize; /* Upper bound of host buffer size */ + + if( (streamFlags & paPlatformSpecificFlags) != 0 ) + return paInvalidFlag; + + if( inputParameters ) + { + PA_ENSURE( ValidateParameters( inputParameters, hostApi, StreamDirection_In ) ); + + numInputChannels = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + } + if( outputParameters ) + { + PA_ENSURE( ValidateParameters( outputParameters, hostApi, StreamDirection_Out ) ); + + numOutputChannels = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + } + + /* XXX: Why do we support this anyway? */ + if( framesPerBuffer == paFramesPerBufferUnspecified && getenv( "PA_ALSA_PERIODSIZE" ) != NULL ) + { + PA_DEBUG(( "%s: Getting framesPerBuffer from environment\n", __FUNCTION__ )); + framesPerBuffer = atoi( getenv("PA_ALSA_PERIODSIZE") ); + } + framesPerHostBuffer = framesPerBuffer; + + PA_UNLESS( stream = (PaAlsaStream*)PaUtil_AllocateMemory( sizeof(PaAlsaStream) ), paInsufficientMemory ); + PA_ENSURE( PaAlsaStream_Initialize( stream, alsaHostApi, inputParameters, outputParameters, sampleRate, + framesPerBuffer, callback, streamFlags, userData ) ); + + /* If the number of frames per buffer is unspecified, we have to come up with + * one. This is both a blessing and a curse: a blessing because we can optimize + * the number to best meet the requirements, but a curse because that's really + * hard to do well. For this reason we also support an interface where the user + * specifies these by setting environment variables. */ + if( framesPerBuffer == paFramesPerBufferUnspecified ) + { + PA_ENSURE( DetermineFramesPerBuffer( stream, sampleRate, inputParameters, outputParameters, &framesPerHostBuffer, + hostApi ) ); + } + + PA_ENSURE( PaAlsaStream_Configure( stream, inputParameters, outputParameters, sampleRate, framesPerHostBuffer, + &inputLatency, &outputLatency, &maxHostBufferSize ) ); + hostInputSampleFormat = stream->capture.hostSampleFormat; + hostOutputSampleFormat = stream->playback.hostSampleFormat; + + if( framesPerHostBuffer != framesPerBuffer ) + { + PA_DEBUG(( "%s: Number of frames per user and host buffer differs\n", __FUNCTION__ )); + } + + PA_ENSURE( PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, + numInputChannels, inputSampleFormat, hostInputSampleFormat, + numOutputChannels, outputSampleFormat, hostOutputSampleFormat, + sampleRate, streamFlags, framesPerBuffer, maxHostBufferSize, + hostBufferSizeMode, callback, userData ) ); + + /* Ok, buffer processor is initialized, now we can deduce it's latency */ + if( numInputChannels > 0 ) + stream->streamRepresentation.streamInfo.inputLatency = inputLatency + PaUtil_GetBufferProcessorInputLatency( + &stream->bufferProcessor ); + if( numOutputChannels > 0 ) + stream->streamRepresentation.streamInfo.outputLatency = outputLatency + PaUtil_GetBufferProcessorOutputLatency( + &stream->bufferProcessor ); + + *s = (PaStream*)stream; + + return result; + +error: + if( stream ) + PaAlsaStream_Terminate( stream ); + + return result; +} + +static PaError CloseStream( PaStream* s ) +{ + PaError result = paNoError; + PaAlsaStream *stream = (PaAlsaStream*)s; + + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + + PaAlsaStream_Terminate( stream ); + + return result; +} + +static void SilenceBuffer( PaAlsaStream *stream ) +{ + const snd_pcm_channel_area_t *areas; + snd_pcm_uframes_t frames = (snd_pcm_uframes_t)snd_pcm_avail_update( stream->playback.pcm ), offset; + + snd_pcm_mmap_begin( stream->playback.pcm, &areas, &offset, &frames ); + snd_pcm_areas_silence( areas, offset, stream->playback.numHostChannels, frames, stream->playback.nativeFormat ); + snd_pcm_mmap_commit( stream->playback.pcm, offset, frames ); +} + +/** Start/prepare pcm(s) for streaming. + * + * Depending on wether the stream is in callback or blocking mode, we will respectively start or simply + * prepare the playback pcm. If the buffer has _not_ been primed, we will in callback mode prepare and + * silence the buffer before starting playback. In blocking mode we simply prepare, as the playback will + * be started automatically as the user writes to output. + * + * The capture pcm, however, will simply be prepared and started. + * + * PaAlsaStream::startMtx makes sure access is synchronized (useful in callback mode) + */ +static PaError AlsaStart( PaAlsaStream *stream, int priming ) +{ + PaError result = paNoError; + + if( stream->playback.pcm ) + { + if( stream->callbackMode ) + { + if( !priming ) + { + /* Buffer isn't primed, so prepare and silence */ + ENSURE_( snd_pcm_prepare( stream->playback.pcm ), paUnanticipatedHostError ); + SilenceBuffer( stream ); + } + ENSURE_( snd_pcm_start( stream->playback.pcm ), paUnanticipatedHostError ); + } + else + ENSURE_( snd_pcm_prepare( stream->playback.pcm ), paUnanticipatedHostError ); + } + if( stream->capture.pcm && !stream->pcmsSynced ) + { + ENSURE_( snd_pcm_prepare( stream->capture.pcm ), paUnanticipatedHostError ); + /* For a blocking stream we want to start capture as well, since nothing will happen otherwise */ + ENSURE_( snd_pcm_start( stream->capture.pcm ), paUnanticipatedHostError ); + } + +end: + return result; +error: + goto end; +} + +/** Utility function for determining if pcms are in running state. + * + */ +static int IsRunning( PaAlsaStream *stream ) +{ + int result = 0; + + ASSERT_CALL_( pthread_mutex_lock( &stream->stateMtx ), 0 ); /* Synchronize access to pcm state */ + if( stream->capture.pcm ) + { + snd_pcm_state_t capture_state = snd_pcm_state( stream->capture.pcm ); + + if( capture_state == SND_PCM_STATE_RUNNING || capture_state == SND_PCM_STATE_XRUN + || capture_state == SND_PCM_STATE_DRAINING ) + { + result = 1; + goto end; + } + } + + if( stream->playback.pcm ) + { + snd_pcm_state_t playback_state = snd_pcm_state( stream->playback.pcm ); + + if( playback_state == SND_PCM_STATE_RUNNING || playback_state == SND_PCM_STATE_XRUN + || playback_state == SND_PCM_STATE_DRAINING ) + { + result = 1; + goto end; + } + } + +end: + ASSERT_CALL_( pthread_mutex_unlock( &stream->stateMtx ), 0 ); + + return result; +} + +static PaError StartStream( PaStream *s ) +{ + PaError result = paNoError; + PaAlsaStream *stream = (PaAlsaStream*)s; + int streamStarted = 0; /* So we can know wether we need to take the stream down */ + + /* Ready the processor */ + PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); + + /* Set now, so we can test for activity further down */ + stream->isActive = 1; + + if( stream->callbackMode ) + { + int res = 0; + PaTime pt = PaUtil_GetTime(); + struct timespec ts; + + PA_ENSURE( CreateCallbackThread( &stream->threading, &CallbackThreadFunc, stream ) ); + streamStarted = 1; + + /* Wait for stream to be started */ + ts.tv_sec = (time_t) floor( pt + 1 ); + ts.tv_nsec = (long) ((pt - floor( pt )) * 1000000000); + + /* Since we'll be holding a lock on the startMtx (when not waiting on the condition), IsRunning won't be checking + * stream state at the same time as the callback thread affects it. We also check IsStreamActive, in the unlikely + * case the callback thread exits in the meantime (the stream will be considered inactive after the thread exits) */ + ASSERT_CALL_( pthread_mutex_lock( &stream->startMtx ), 0 ); + + /* Due to possible spurious wakeups, we enclose in a loop */ + while( !IsRunning( stream ) && IsStreamActive( s ) && !res ) + { + res = pthread_cond_timedwait( &stream->startCond, &stream->startMtx, &ts ); + } + ASSERT_CALL_( pthread_mutex_unlock( &stream->startMtx ), 0 ); + + PA_UNLESS( !res || res == ETIMEDOUT, paInternalError ); + PA_DEBUG(( "%s: Waited for %g seconds for stream to start\n", __FUNCTION__, PaUtil_GetTime() - pt )); + + if( res == ETIMEDOUT ) + { + PA_ENSURE( paTimedOut ); + } + } + else + { + PA_ENSURE( AlsaStart( stream, 0 ) ); + streamStarted = 1; + } + +end: + return result; +error: + if( streamStarted ) + AbortStream( stream ); + stream->isActive = 0; + + goto end; +} + +static PaError AlsaStop( PaAlsaStream *stream, int abort ) +{ + PaError result = paNoError; + + if( abort ) + { + if( stream->playback.pcm ) + ENSURE_( snd_pcm_drop( stream->playback.pcm ), paUnanticipatedHostError ); + if( stream->capture.pcm && !stream->pcmsSynced ) + ENSURE_( snd_pcm_drop( stream->capture.pcm ), paUnanticipatedHostError ); + + PA_DEBUG(( "Dropped frames\n" )); + } + else + { + if( stream->playback.pcm ) + ENSURE_( snd_pcm_drain( stream->playback.pcm ), paUnanticipatedHostError ); + if( stream->capture.pcm && !stream->pcmsSynced ) + ENSURE_( snd_pcm_drain( stream->capture.pcm ), paUnanticipatedHostError ); + } + +end: + return result; +error: + goto end; +} + +/** Stop or abort stream. + * + * If a stream is in callback mode we will have to inspect wether the background thread has + * finished, or we will have to take it out. In either case we join the thread before + * returning. In blocking mode, we simply tell ALSA to stop abruptly (abort) or finish + * buffers (drain) + * + * Stream will be considered inactive (!PaAlsaStream::isActive) after a call to this function + */ +static PaError RealStop( PaAlsaStream *stream, int abort ) +{ + PaError result = paNoError; + + /* First deal with the callback thread, cancelling and/or joining + * it if necessary + */ + if( stream->callbackMode ) + { + PaError threadRes, watchdogRes; + stream->callbackAbort = abort; + + if( !abort ) + { + PA_DEBUG(( "Stopping callback\n" )); + stream->callbackStop = 1; + } + PA_ENSURE( KillCallbackThread( &stream->threading, !abort, &threadRes, &watchdogRes ) ); + if( threadRes != paNoError ) + PA_DEBUG(( "Callback thread returned: %d\n", threadRes )); + if( watchdogRes != paNoError ) + PA_DEBUG(( "Watchdog thread returned: %d\n", watchdogRes )); + + stream->callbackStop = 0; /* The deed is done */ + stream->callback_finished = 0; + } + else + { + PA_ENSURE( AlsaStop( stream, abort ) ); + } + + stream->isActive = 0; + +end: + return result; + +error: + goto end; +} + +static PaError StopStream( PaStream *s ) +{ + return RealStop( (PaAlsaStream *) s, 0 ); +} + +static PaError AbortStream( PaStream *s ) +{ + return RealStop( (PaAlsaStream * ) s, 1 ); +} + +/** The stream is considered stopped before StartStream, or AFTER a call to Abort/StopStream (callback + * returning !paContinue is not considered) + * + */ +static PaError IsStreamStopped( PaStream *s ) +{ + PaAlsaStream *stream = (PaAlsaStream *)s; + + /* callback_finished indicates we need to join callback thread (ie. in Abort/StopStream) */ + return !IsStreamActive( s ) && !stream->callback_finished; +} + +static PaError IsStreamActive( PaStream *s ) +{ + PaAlsaStream *stream = (PaAlsaStream*)s; + return stream->isActive; +} + +static PaTime GetStreamTime( PaStream *s ) +{ + PaAlsaStream *stream = (PaAlsaStream*)s; + + snd_timestamp_t timestamp; + snd_pcm_status_t *status; + snd_pcm_status_alloca( &status ); + + /* TODO: what if we have both? does it really matter? */ + + /* TODO: if running in callback mode, this will mean + * libasound routines are being called from multiple threads. + * need to verify that libasound is thread-safe. */ + + if( stream->capture.pcm ) + { + snd_pcm_status( stream->capture.pcm, status ); + } + else if( stream->playback.pcm ) + { + snd_pcm_status( stream->playback.pcm, status ); + } + + snd_pcm_status_get_tstamp( status, ×tamp ); + return timestamp.tv_sec + (PaTime)timestamp.tv_usec / 1000000.0; +} + +static double GetStreamCpuLoad( PaStream* s ) +{ + PaAlsaStream *stream = (PaAlsaStream*)s; + + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); +} + +static int SetApproximateSampleRate( snd_pcm_t *pcm, snd_pcm_hw_params_t *hwParams, double sampleRate ) +{ + unsigned long approx = (unsigned long) sampleRate; + int dir = 0; + double fraction = sampleRate - approx; + + assert( pcm && hwParams ); + + if( fraction > 0.0 ) + { + if( fraction > 0.5 ) + { + ++approx; + dir = -1; + } + else + dir = 1; + } + + return snd_pcm_hw_params_set_rate( pcm, hwParams, approx, dir ); +} + +/* Return exact sample rate in param sampleRate */ +static int GetExactSampleRate( snd_pcm_hw_params_t *hwParams, double *sampleRate ) +{ + unsigned int num, den; + int err; + + assert( hwParams ); + + err = snd_pcm_hw_params_get_rate_numden( hwParams, &num, &den ); + *sampleRate = (double) num / den; + + return err; +} + +/* Utility functions for blocking/callback interfaces */ + +/* Atomic restart of stream (we don't want the intermediate state visible) */ +static PaError AlsaRestart( PaAlsaStream *stream ) +{ + PaError result = paNoError; + + ASSERT_CALL_( pthread_mutex_lock( &stream->stateMtx ), 0 ); + PA_ENSURE( AlsaStop( stream, 0 ) ); + PA_ENSURE( AlsaStart( stream, 0 ) ); + + PA_DEBUG(( "%s: Restarted audio\n", __FUNCTION__ )); + +error: + ASSERT_CALL_( pthread_mutex_unlock( &stream->stateMtx ), 0 ); + return result; +} + +/** Recover from xrun state. + * + */ +static PaError PaAlsaStream_HandleXrun( PaAlsaStream *self ) +{ + PaError result = paNoError; + snd_pcm_status_t *st; + PaTime now = PaUtil_GetTime(); + snd_timestamp_t t; + + snd_pcm_status_alloca( &st ); + + if( self->playback.pcm ) + { + snd_pcm_status( self->playback.pcm, st ); + if( snd_pcm_status_get_state( st ) == SND_PCM_STATE_XRUN ) + { + snd_pcm_status_get_trigger_tstamp( st, &t ); + self->underrun = now * 1000 - ((PaTime) t.tv_sec * 1000 + (PaTime) t.tv_usec / 1000); + } + } + if( self->capture.pcm ) + { + snd_pcm_status( self->capture.pcm, st ); + if( snd_pcm_status_get_state( st ) == SND_PCM_STATE_XRUN ) + { + snd_pcm_status_get_trigger_tstamp( st, &t ); + self->overrun = now * 1000 - ((PaTime) t.tv_sec * 1000 + (PaTime) t.tv_usec / 1000); + } + } + + PA_ENSURE( AlsaRestart( self ) ); + +end: + return result; +error: + goto end; +} + +/** Decide if we should continue polling for specified direction, eventually adjust the poll timeout. + * + */ +static PaError ContinuePoll( const PaAlsaStream *stream, StreamDirection streamDir, int *pollTimeout, int *continuePoll ) +{ + PaError result = paNoError; + snd_pcm_sframes_t delay, margin; + int err; + const PaAlsaStreamComponent *component = NULL, *otherComponent = NULL; + + *continuePoll = 1; + + if( StreamDirection_In == streamDir ) + { + component = &stream->capture; + otherComponent = &stream->playback; + } + else + { + component = &stream->playback; + otherComponent = &stream->capture; + } + + /* ALSA docs say that negative delay should indicate xrun, but in my experience snd_pcm_delay returns -EPIPE */ + if( (err = snd_pcm_delay( otherComponent->pcm, &delay )) < 0 ) + { + if( err == -EPIPE ) + { + /* Xrun */ + *continuePoll = 0; + goto error; + } + + ENSURE_( err, paUnanticipatedHostError ); + } + + if( StreamDirection_Out == streamDir ) + { + /* Number of eligible frames before capture overrun */ + delay = otherComponent->bufferSize - delay; + } + margin = delay - otherComponent->framesPerBuffer / 2; + + if( margin < 0 ) + { + PA_DEBUG(( "%s: Stopping poll for %s\n", __FUNCTION__, StreamDirection_In == streamDir ? "capture" : "playback" )); + *continuePoll = 0; + } + else if( margin < otherComponent->framesPerBuffer ) + { + *pollTimeout = CalculatePollTimeout( stream, margin ); + PA_DEBUG(( "%s: Trying to poll again for %s frames, pollTimeout: %d\n", + __FUNCTION__, StreamDirection_In == streamDir ? "capture" : "playback", *pollTimeout )); + } + +error: + return result; +} + +/* Callback interface */ + +static void OnExit( void *data ) +{ + PaAlsaStream *stream = (PaAlsaStream *) data; + + assert( data ); + + PaUtil_ResetCpuLoadMeasurer( &stream->cpuLoadMeasurer ); + + stream->callback_finished = 1; /* Let the outside world know stream was stopped in callback */ + AlsaStop( stream, stream->callbackAbort ); + stream->callbackAbort = 0; /* Clear state */ + + PA_DEBUG(( "OnExit: Stoppage\n" )); + + /* Eventually notify user all buffers have played */ + if( stream->streamRepresentation.streamFinishedCallback ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + stream->isActive = 0; +} + +static void CalculateTimeInfo( PaAlsaStream *stream, PaStreamCallbackTimeInfo *timeInfo ) +{ + snd_pcm_status_t *capture_status, *playback_status; + snd_timestamp_t capture_timestamp, playback_timestamp; + PaTime capture_time = 0., playback_time = 0.; + + snd_pcm_status_alloca( &capture_status ); + snd_pcm_status_alloca( &playback_status ); + + if( stream->capture.pcm ) + { + snd_pcm_sframes_t capture_delay; + + snd_pcm_status( stream->capture.pcm, capture_status ); + snd_pcm_status_get_tstamp( capture_status, &capture_timestamp ); + + capture_time = capture_timestamp.tv_sec + + ((PaTime)capture_timestamp.tv_usec / 1000000.0); + timeInfo->currentTime = capture_time; + + capture_delay = snd_pcm_status_get_delay( capture_status ); + timeInfo->inputBufferAdcTime = timeInfo->currentTime - + (PaTime)capture_delay / stream->streamRepresentation.streamInfo.sampleRate; + } + if( stream->playback.pcm ) + { + snd_pcm_sframes_t playback_delay; + + snd_pcm_status( stream->playback.pcm, playback_status ); + snd_pcm_status_get_tstamp( playback_status, &playback_timestamp ); + + playback_time = playback_timestamp.tv_sec + + ((PaTime)playback_timestamp.tv_usec / 1000000.0); + + if( stream->capture.pcm ) /* Full duplex */ + { + /* Hmm, we have both a playback and a capture timestamp. + * Hopefully they are the same... */ + if( fabs( capture_time - playback_time ) > 0.01 ) + PA_DEBUG(("Capture time and playback time differ by %f\n", fabs(capture_time-playback_time))); + } + else + timeInfo->currentTime = playback_time; + + playback_delay = snd_pcm_status_get_delay( playback_status ); + timeInfo->outputBufferDacTime = timeInfo->currentTime + + (PaTime)playback_delay / stream->streamRepresentation.streamInfo.sampleRate; + } +} + +/** Called after buffer processing is finished. + * + * A number of mmapped frames is committed, it is possible that an xrun has occurred in the meantime. + * + * @param numFrames The number of frames that has been processed + * @param xrun Return whether an xrun has occurred + */ +static PaError PaAlsaStreamComponent_EndProcessing( PaAlsaStreamComponent *self, unsigned long numFrames, int *xrun ) +{ + PaError result = paNoError; + int res; + + /* @concern FullDuplex It is possible that only one direction is marked ready after polling, and processed + * afterwards + */ + if( !self->ready ) + goto end; + + res = snd_pcm_mmap_commit( self->pcm, self->offset, numFrames ); + if( res == -EPIPE || res == -ESTRPIPE ) + { + *xrun = 1; + } + else + { + ENSURE_( res, paUnanticipatedHostError ); + } + +end: +error: + return result; +} + +/* Extract buffer from channel area */ +static unsigned char *ExtractAddress( const snd_pcm_channel_area_t *area, snd_pcm_uframes_t offset ) +{ + return (unsigned char *) area->addr + (area->first + offset * area->step) / 8; +} + +/** Do necessary adaption between user and host channels. + * + @concern ChannelAdaption Adapting between user and host channels can involve silencing unused channels and + duplicating mono information if host outputs come in pairs. + */ +static PaError PaAlsaStreamComponent_DoChannelAdaption( PaAlsaStreamComponent *self, PaUtilBufferProcessor *bp, int numFrames ) +{ + PaError result = paNoError; + unsigned char *p; + int i; + int unusedChans = self->numHostChannels - self->numUserChannels; + unsigned char *src, *dst; + int convertMono = (self->numHostChannels % 2) == 0 && (self->numUserChannels % 2) != 0; + + assert( StreamDirection_Out == self->streamDir ); + + if( self->hostInterleaved ) + { + int swidth = snd_pcm_format_size( self->nativeFormat, 1 ); + unsigned char *buffer = ExtractAddress( self->channelAreas, self->offset ); + + /* Start after the last user channel */ + p = buffer + self->numUserChannels * swidth; + + if( convertMono ) + { + /* Convert the last user channel into stereo pair */ + src = buffer + (self->numUserChannels - 1) * swidth; + for( i = 0; i < numFrames; ++i ) + { + dst = src + swidth; + memcpy( dst, src, swidth ); + src += self->numHostChannels * swidth; + } + + /* Don't touch the channel we just wrote to */ + p += swidth; + --unusedChans; + } + + if( unusedChans > 0 ) + { + /* Silence unused output channels */ + for( i = 0; i < numFrames; ++i ) + { + memset( p, 0, swidth * unusedChans ); + p += self->numHostChannels * swidth; + } + } + } + else + { + /* We extract the last user channel */ + if( convertMono ) + { + ENSURE_( snd_pcm_area_copy( self->channelAreas + self->numUserChannels, self->offset, self->channelAreas + + (self->numUserChannels - 1), self->offset, numFrames, self->nativeFormat ), paUnanticipatedHostError ); + --unusedChans; + } + if( unusedChans > 0 ) + { + snd_pcm_areas_silence( self->channelAreas + (self->numHostChannels - unusedChans), self->offset, unusedChans, numFrames, + self->nativeFormat ); + } + } + +error: + return result; +} + +static PaError PaAlsaStream_EndProcessing( PaAlsaStream *self, unsigned long numFrames, int *xrunOccurred ) +{ + PaError result = paNoError; + int xrun = 0; + + if( self->capture.pcm ) + { + PA_ENSURE( PaAlsaStreamComponent_EndProcessing( &self->capture, numFrames, &xrun ) ); + } + if( self->playback.pcm ) + { + if( self->playback.numHostChannels > self->playback.numUserChannels ) + PA_ENSURE( PaAlsaStreamComponent_DoChannelAdaption( &self->playback, &self->bufferProcessor, numFrames ) ); + PA_ENSURE( PaAlsaStreamComponent_EndProcessing( &self->playback, numFrames, &xrun ) ); + } + +error: + *xrunOccurred = xrun; + return result; +} + +/** Update the number of available frames. + * + */ +static PaError PaAlsaStreamComponent_GetAvailableFrames( PaAlsaStreamComponent *self, unsigned long *numFrames, int *xrunOccurred ) +{ + PaError result = paNoError; + snd_pcm_sframes_t framesAvail = snd_pcm_avail_update( self->pcm ); + *xrunOccurred = 0; + + if( -EPIPE == framesAvail ) + { + *xrunOccurred = 1; + framesAvail = 0; + } + else + ENSURE_( framesAvail, paUnanticipatedHostError ); + + *numFrames = framesAvail; + +error: + return result; +} + +/** Fill in pollfd objects. + */ +static PaError PaAlsaStreamComponent_BeginPolling( PaAlsaStreamComponent *self, struct pollfd *pfds ) +{ + PaError result = paNoError; + int ret = snd_pcm_poll_descriptors( self->pcm, pfds, self->nfds ); + assert( ret == self->nfds ); + + self->ready = 0; + + return result; +} + +/** Examine results from poll(). + * + * @param pfds pollfds to inspect + * @param shouldPoll Should we continue to poll + * @param xrun Has an xrun occurred + */ +static PaError PaAlsaStreamComponent_EndPolling( PaAlsaStreamComponent *self, struct pollfd *pfds, int *shouldPoll, int *xrun ) +{ + PaError result = paNoError; + unsigned short revents; + + ENSURE_( snd_pcm_poll_descriptors_revents( self->pcm, pfds, self->nfds, &revents ), paUnanticipatedHostError ); + if( revents != 0 ) + { + if( revents & POLLERR ) + { + *xrun = 1; + } + else + self->ready = 1; + + *shouldPoll = 0; + } + +error: + return result; +} + +/** Return the number of available frames for this stream. + * + * @concern FullDuplex The minimum available for the two directions is calculated, it might be desirable to ignore + * one direction however (not marked ready from poll), so this is controlled by queryCapture and queryPlayback. + * + * @param queryCapture Check available for capture + * @param queryPlayback Check available for playback + * @param available The returned number of frames + * @param xrunOccurred Return whether an xrun has occurred + */ +static PaError PaAlsaStream_GetAvailableFrames( PaAlsaStream *self, int queryCapture, int queryPlayback, unsigned long + *available, int *xrunOccurred ) +{ + PaError result = paNoError; + unsigned long captureFrames, playbackFrames; + *xrunOccurred = 0; + + assert( queryCapture || queryPlayback ); + + if( queryCapture ) + { + assert( self->capture.pcm ); + PA_ENSURE( PaAlsaStreamComponent_GetAvailableFrames( &self->capture, &captureFrames, xrunOccurred ) ); + if( *xrunOccurred ) + goto end; + } + if( queryPlayback ) + { + assert( self->playback.pcm ); + PA_ENSURE( PaAlsaStreamComponent_GetAvailableFrames( &self->playback, &playbackFrames, xrunOccurred ) ); + if( *xrunOccurred ) + goto end; + } + + if( queryCapture && queryPlayback ) + { + *available = PA_MIN( captureFrames, playbackFrames ); + } + else if( queryCapture ) + { + *available = captureFrames; + } + else + { + *available = playbackFrames; + } + +end: +error: + return result; +} + +/** Wait for and report available buffer space from ALSA. + * + * Unless ALSA reports a minimum of frames available for I/O, we poll the ALSA filedescriptors for more. + * Both of these operations can uncover xrun conditions. + * + * @concern Xruns Both polling and querying available frames can report an xrun condition. + * + * @param framesAvail Return the number of available frames + * @param xrunOccurred Return whether an xrun has occurred + */ +static PaError PaAlsaStream_WaitForFrames( PaAlsaStream *self, unsigned long *framesAvail, int *xrunOccurred ) +{ + PaError result = paNoError; + int pollPlayback = self->playback.pcm != NULL, pollCapture = self->capture.pcm != NULL; + int pollTimeout = self->pollTimeout; + int xrun = 0; + + assert( self ); + assert( framesAvail ); + + if( !self->callbackMode ) + { + /* In blocking mode we will only wait if necessary */ + PA_ENSURE( PaAlsaStream_GetAvailableFrames( self, self->capture.pcm != NULL, self->playback.pcm != NULL, + framesAvail, &xrun ) ); + if( xrun ) + { + goto end; + } + + if( *framesAvail > 0 ) + { + /* Mark pcms ready from poll */ + if( self->capture.pcm ) + self->capture.ready = 1; + if( self->playback.pcm ) + self->playback.ready = 1; + + goto end; + } + } + + while( pollPlayback || pollCapture ) + { + int totalFds = 0; + struct pollfd *capturePfds = NULL, *playbackPfds = NULL; + + pthread_testcancel(); + + if( pollCapture ) + { + capturePfds = self->pfds; + PA_ENSURE( PaAlsaStreamComponent_BeginPolling( &self->capture, capturePfds ) ); + totalFds += self->capture.nfds; + } + if( pollPlayback ) + { + playbackPfds = self->pfds + (self->capture.pcm ? self->capture.nfds : 0); + PA_ENSURE( PaAlsaStreamComponent_BeginPolling( &self->playback, playbackPfds ) ); + totalFds += self->playback.nfds; + } + + if( poll( self->pfds, totalFds, pollTimeout ) < 0 ) + { + /* XXX: Depend on preprocessor condition? */ + if( errno == EINTR ) { /* gdb */ + continue; + } + + /* TODO: Add macro for checking system calls */ + PA_ENSURE( paInternalError ); + } + + /* check the return status of our pfds */ + if( pollCapture ) + { + PA_ENSURE( PaAlsaStreamComponent_EndPolling( &self->capture, capturePfds, &pollCapture, &xrun ) ); + } + if( pollPlayback ) + { + PA_ENSURE( PaAlsaStreamComponent_EndPolling( &self->playback, playbackPfds, &pollPlayback, &xrun ) ); + } + if( xrun ) + { + break; + } + + /* @concern FullDuplex If only one of two pcms is ready we may want to compromise between the two. + * If there is less than half a period's worth of samples left of frames in the other pcm's buffer we will + * stop polling. + */ + if( self->capture.pcm && self->playback.pcm ) + { + if( pollCapture && !pollPlayback ) + { + PA_ENSURE( ContinuePoll( self, StreamDirection_In, &pollTimeout, &pollCapture ) ); + } + else if( pollPlayback && !pollCapture ) + { + PA_ENSURE( ContinuePoll( self, StreamDirection_Out, &pollTimeout, &pollPlayback ) ); + } + } + } + + if( !xrun ) + { + /* Get the number of available frames for the pcms that are marked ready. + * @concern FullDuplex If only one direction is marked ready (from poll), the number of frames available for + * the other direction is returned. This under the assumption that input is dropped earlier if paNeverDropInput + * is not specified. + */ + int captureReady = self->capture.pcm ? self->capture.ready : 0, + playbackReady = self->playback.pcm ? self->playback.ready : 0; + PA_ENSURE( PaAlsaStream_GetAvailableFrames( self, captureReady, playbackReady, framesAvail, &xrun ) ); + + if( self->capture.pcm && self->playback.pcm ) + { + if( !self->playback.ready && !self->neverDropInput ) + { + /* TODO: Drop input */ + } + } + } + +end: +error: + if( xrun ) + { + /* Recover from the xrun state */ + PA_ENSURE( PaAlsaStream_HandleXrun( self ) ); + *framesAvail = 0; + } + *xrunOccurred = xrun; + + return result; +} + +/** Register per-channel ALSA buffer information with buffer processor. + * + * Mmapped buffer space is acquired from ALSA, and registered with the buffer processor. Differences between the + * number of host and user channels is taken into account. + * + * @param numFrames On entrance the number of requested frames, on exit the number of contiguously accessible frames. + */ +static PaError PaAlsaStreamComponent_RegisterChannels( PaAlsaStreamComponent *self, PaUtilBufferProcessor *bp, + unsigned long *numFrames, int *xrun ) +{ + PaError result = paNoError; + const snd_pcm_channel_area_t *areas, *area; + void (*setChannel)(PaUtilBufferProcessor *, unsigned int, void *, unsigned int) = + StreamDirection_In == self->streamDir ? PaUtil_SetInputChannel : PaUtil_SetOutputChannel; + unsigned char *buffer, *p; + int i; + unsigned long framesAvail; + + /* This _must_ be called before mmap_begin */ + PA_ENSURE( PaAlsaStreamComponent_GetAvailableFrames( self, &framesAvail, xrun ) ); + if( *xrun ) + { + *numFrames = 0; + goto end; + } + + ENSURE_( snd_pcm_mmap_begin( self->pcm, &areas, &self->offset, numFrames ), paUnanticipatedHostError ); + + if( self->hostInterleaved ) + { + int swidth = snd_pcm_format_size( self->nativeFormat, 1 ); + + p = buffer = ExtractAddress( areas, self->offset ); + for( i = 0; i < self->numUserChannels; ++i ) + { + /* We're setting the channels up to userChannels, but the stride will be hostChannels samples */ + setChannel( bp, i, p, self->numHostChannels ); + p += swidth; + } + } + else + { + for( i = 0; i < self->numUserChannels; ++i ) + { + area = areas + i; + buffer = ExtractAddress( area, self->offset ); + setChannel( bp, i, buffer, 1 ); + } + } + + /* @concern ChannelAdaption Buffer address is recorded so we can do some channel adaption later */ + self->channelAreas = (snd_pcm_channel_area_t *)areas; + +end: +error: + return result; +} + +/** Initiate buffer processing. + * + * ALSA buffers are registered with the PA buffer processor and the buffer size (in frames) set. + * + * @concern FullDuplex If both directions are being processed, the minimum amount of frames for the two directions is + * calculated. + * + * @param numFrames On entrance the number of available frames, on exit the number of received frames + * @param xrunOccurred Return whether an xrun has occurred + */ +static PaError PaAlsaStream_SetUpBuffers( PaAlsaStream *self, unsigned long *numFrames, int *xrunOccurred ) +{ + PaError result = paNoError; + unsigned long captureFrames = ULONG_MAX, playbackFrames = ULONG_MAX, commonFrames = 0; + int xrun = 0; + + /* Extract per-channel ALSA buffer pointers and register them with the buffer processor. + * It is possible that a direction is not marked ready however, because it is out of sync with the other. + */ + if( self->capture.pcm && self->capture.ready ) + { + captureFrames = *numFrames; + PA_ENSURE( PaAlsaStreamComponent_RegisterChannels( &self->capture, &self->bufferProcessor, &captureFrames, + &xrun ) ); + } + if( self->playback.pcm && self->playback.ready ) + { + playbackFrames = *numFrames; + PA_ENSURE( PaAlsaStreamComponent_RegisterChannels( &self->playback, &self->bufferProcessor, &playbackFrames, + &xrun ) ); + } + if( xrun ) + { + /* Nothing more to do */ + assert( 0 == commonFrames ); + goto end; + } + + commonFrames = PA_MIN( captureFrames, playbackFrames ); + assert( commonFrames <= *numFrames ); + + /* Inform PortAudio of the number of frames we got. + * @concern FullDuplex We might be experiencing underflow in either end; if its an input underflow, we go on + * with output. If its output underflow however, depending on the paNeverDropInput flag, we may want to simply + * discard the excess input or call the callback with paOutputOverflow flagged. + */ + if( self->capture.pcm ) + { + if( self->capture.ready ) + { + PaUtil_SetInputFrameCount( &self->bufferProcessor, commonFrames ); + } + else + { + /* We have input underflow */ + PaUtil_SetNoInput( &self->bufferProcessor ); + } + } + if( self->playback.pcm ) + { + if( self->playback.ready ) + { + PaUtil_SetOutputFrameCount( &self->bufferProcessor, commonFrames ); + } + else + { + /* We have output underflow, but keeping input data (paNeverDropInput) */ + /* assert( self->neverDropInput ); */ + PaUtil_SetNoOutput( &self->bufferProcessor ); + } + } + +end: + *numFrames = commonFrames; +error: + if( xrun ) + { + PA_ENSURE( PaAlsaStream_HandleXrun( self ) ); + *numFrames = 0; + } + *xrunOccurred = xrun; + + return result; +} + +/** Callback thread's function. + * + * Roughly, the workflow can be described in the following way: The number of available frames that can be processed + * directly is obtained from ALSA, we then request as much directly accessible memory as possible within this amount + * from ALSA. The buffer memory is registered with the PA buffer processor and processing is carried out with + * PaUtil_EndBufferProcessing. Finally, the number of processed frames is reported to ALSA. The processing can + * happen in several iterations untill we have consumed the known number of available frames (or an xrun is detected). + */ +static void *CallbackThreadFunc( void *userData ) +{ + PaError result = paNoError, *pres = NULL; + PaAlsaStream *stream = (PaAlsaStream*) userData; + PaStreamCallbackTimeInfo timeInfo = {0, 0, 0}; + snd_pcm_sframes_t startThreshold = 0; + int callbackResult = paContinue; + PaStreamCallbackFlags cbFlags = 0; /* We might want to keep state across iterations */ + int streamStarted = 0; + + assert( stream ); + + callbackThread_ = pthread_self(); + /* Execute OnExit when exiting */ + pthread_cleanup_push( &OnExit, stream ); + + /* Not implemented */ + assert( !stream->primeBuffers ); + + /* @concern StreamStart If the output is being primed the output pcm needs to be prepared, otherwise the + * stream is started immediately. The latter involves signaling the waiting main thread. + */ + if( stream->primeBuffers ) + { + snd_pcm_sframes_t avail; + + if( stream->playback.pcm ) + ENSURE_( snd_pcm_prepare( stream->playback.pcm ), paUnanticipatedHostError ); + if( stream->capture.pcm && !stream->pcmsSynced ) + ENSURE_( snd_pcm_prepare( stream->capture.pcm ), paUnanticipatedHostError ); + + /* We can't be certain that the whole ring buffer is available for priming, but there should be + * at least one period */ + avail = snd_pcm_avail_update( stream->playback.pcm ); + startThreshold = avail - (avail % stream->playback.framesPerBuffer); + assert( startThreshold >= stream->playback.framesPerBuffer ); + } + else + { + ASSERT_CALL_( pthread_mutex_lock( &stream->startMtx ), 0 ); + PA_ENSURE( AlsaStart( stream, 0 ) ); /* Buffer will be zeroed */ + ASSERT_CALL_( pthread_cond_signal( &stream->startCond ), 0 ); + ASSERT_CALL_( pthread_mutex_unlock( &stream->startMtx ), 0 ); + + streamStarted = 1; + } + + while( 1 ) + { + unsigned long framesAvail, framesGot; + int xrun = 0; + + pthread_testcancel(); + + /* @concern StreamStop if the main thread has requested a stop and the stream has not been effectively + * stopped we signal this condition by modifying callbackResult (we'll want to flush buffered output). + */ + if( stream->callbackStop && paContinue == callbackResult ) + { + PA_DEBUG(( "Setting callbackResult to paComplete\n" )); + callbackResult = paComplete; + } + + if( paContinue != callbackResult ) + { + stream->callbackAbort = (paAbort == callbackResult); + if( stream->callbackAbort || + /** @concern BlockAdaption Go on if adaption buffers are empty */ + PaUtil_IsBufferProcessorOutputEmpty( &stream->bufferProcessor ) ) + goto end; + + PA_DEBUG(( "%s: Flushing buffer processor\n", __FUNCTION__ )); + /* There is still buffered output that needs to be processed */ + } + + /* Wait for data to become available, this comes down to polling the ALSA file descriptors untill we have + * a number of available frames. + */ + PA_ENSURE( PaAlsaStream_WaitForFrames( stream, &framesAvail, &xrun ) ); + if( xrun ) + { + assert( 0 == framesAvail ); + continue; + + /* XXX: Report xruns to the user? A situation is conceivable where the callback is never invoked due + * to constant xruns, it might be desirable to notify the user of this. + */ + } + + /* Consume buffer space. Once we have a number of frames available for consumption we must retrieve the + * mmapped buffers from ALSA, this is contiguously accessible memory however, so we may receive smaller + * portions at a time than is available as a whole. Therefore we should be prepared to process several + * chunks successively. The buffers are passed to the PA buffer processor. + */ + while( framesAvail > 0 ) + { + xrun = 0; + + pthread_testcancel(); + + /** @concern Xruns Under/overflows are to be reported to the callback */ + if( stream->underrun > 0.0 ) + { + cbFlags |= paOutputUnderflow; + stream->underrun = 0.0; + } + if( stream->overrun > 0.0 ) + { + cbFlags |= paInputOverflow; + stream->overrun = 0.0; + } + if( stream->capture.pcm && stream->playback.pcm ) + { + /** @concern FullDuplex It's possible that only one direction is being processed to avoid an + * under- or overflow, this should be reported correspondingly */ + if( !stream->capture.ready ) + { + cbFlags |= paInputUnderflow; + PA_DEBUG(( "%s: Input underflow\n", __FUNCTION__ )); + } + else if( !stream->playback.ready ) + { + cbFlags |= paOutputOverflow; + PA_DEBUG(( "%s: Output overflow\n", __FUNCTION__ )); + } + } + + CallbackUpdate( &stream->threading ); + CalculateTimeInfo( stream, &timeInfo ); + PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, cbFlags ); + cbFlags = 0; + + /* CPU load measurement should include processing activivity external to the stream callback */ + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + + framesGot = framesAvail; + PA_ENSURE( PaAlsaStream_SetUpBuffers( stream, &framesGot, &xrun ) ); + framesAvail -= framesGot; + + if( framesGot > 0 ) + { + assert( !xrun ); + + PaUtil_EndBufferProcessing( &stream->bufferProcessor, &callbackResult ); + PA_ENSURE( PaAlsaStream_EndProcessing( stream, framesGot, &xrun ) ); + } + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesGot ); + + if( framesGot == 0 ) + { + if( !xrun ) + PA_DEBUG(( "%s: Received less frames than reported from ALSA!\n", __FUNCTION__ )); + + /* Go back to polling for more frames */ + break; + + } + + if( paContinue != callbackResult ) + break; + } + } + + /* Match pthread_cleanup_push */ + pthread_cleanup_pop( 1 ); + +end: + pthread_exit( pres ); + +error: + /* Pass on error code */ + pres = malloc( sizeof (PaError) ); + *pres = result; + + goto end; +} + +/* Blocking interface */ + +static PaError ReadStream( PaStream* s, void *buffer, unsigned long frames ) +{ + PaError result = paNoError; + PaAlsaStream *stream = (PaAlsaStream*)s; + unsigned long framesGot, framesAvail; + void *userBuffer; + snd_pcm_t *save = stream->playback.pcm; + + assert( stream ); + + PA_UNLESS( stream->capture.pcm, paCanNotReadFromAnOutputOnlyStream ); + + /* Disregard playback */ + stream->playback.pcm = NULL; + + if( stream->overrun > 0. ) + { + result = paInputOverflowed; + stream->overrun = 0.0; + } + + if( stream->capture.userInterleaved ) + userBuffer = buffer; + else + { + /* Copy channels into local array */ + userBuffer = stream->capture.userBuffers; + memcpy( userBuffer, buffer, sizeof (void *) * stream->capture.numUserChannels ); + } + + /* Start stream if in prepared state */ + if( snd_pcm_state( stream->capture.pcm ) == SND_PCM_STATE_PREPARED ) + { + ENSURE_( snd_pcm_start( stream->capture.pcm ), paUnanticipatedHostError ); + } + + while( frames > 0 ) + { + int xrun = 0; + PA_ENSURE( PaAlsaStream_WaitForFrames( stream, &framesAvail, &xrun ) ); + framesGot = PA_MIN( framesAvail, frames ); + + PA_ENSURE( PaAlsaStream_SetUpBuffers( stream, &framesGot, &xrun ) ); + if( framesGot > 0 ) + { + framesGot = PaUtil_CopyInput( &stream->bufferProcessor, &userBuffer, framesGot ); + PA_ENSURE( PaAlsaStream_EndProcessing( stream, framesGot, &xrun ) ); + frames -= framesGot; + } + } + +end: + stream->playback.pcm = save; + return result; +error: + goto end; +} + +static PaError WriteStream( PaStream* s, const void *buffer, unsigned long frames ) +{ + PaError result = paNoError; + signed long err; + PaAlsaStream *stream = (PaAlsaStream*)s; + snd_pcm_uframes_t framesGot, framesAvail; + const void *userBuffer; + snd_pcm_t *save = stream->capture.pcm; + + assert( stream ); + + PA_UNLESS( stream->playback.pcm, paCanNotWriteToAnInputOnlyStream ); + + /* Disregard capture */ + stream->capture.pcm = NULL; + + if( stream->underrun > 0. ) + { + result = paOutputUnderflowed; + stream->underrun = 0.0; + } + + if( stream->playback.userInterleaved ) + userBuffer = buffer; + else /* Copy channels into local array */ + { + userBuffer = stream->playback.userBuffers; + memcpy( (void *)userBuffer, buffer, sizeof (void *) * stream->playback.numUserChannels ); + } + + while( frames > 0 ) + { + int xrun = 0; + snd_pcm_uframes_t hwAvail; + + PA_ENSURE( PaAlsaStream_WaitForFrames( stream, &framesAvail, &xrun ) ); + framesGot = PA_MIN( framesAvail, frames ); + + PA_ENSURE( PaAlsaStream_SetUpBuffers( stream, &framesGot, &xrun ) ); + if( framesGot > 0 ) + { + framesGot = PaUtil_CopyOutput( &stream->bufferProcessor, &userBuffer, framesGot ); + PA_ENSURE( PaAlsaStream_EndProcessing( stream, framesGot, &xrun ) ); + frames -= framesGot; + } + + /* Frames residing in buffer */ + PA_ENSURE( err = GetStreamWriteAvailable( stream ) ); + framesAvail = err; + hwAvail = stream->playback.bufferSize - framesAvail; + + /* Start stream after one period of samples worth */ + if( snd_pcm_state( stream->playback.pcm ) == SND_PCM_STATE_PREPARED && + hwAvail >= stream->playback.framesPerBuffer ) + { + ENSURE_( snd_pcm_start( stream->playback.pcm ), paUnanticipatedHostError ); + } + } + +end: + stream->capture.pcm = save; + return result; +error: + goto end; +} + +/* Return frames available for reading. In the event of an overflow, the capture pcm will be restarted */ +static signed long GetStreamReadAvailable( PaStream* s ) +{ + PaError result = paNoError; + PaAlsaStream *stream = (PaAlsaStream*)s; + unsigned long avail; + int xrun; + + PA_ENSURE( PaAlsaStreamComponent_GetAvailableFrames( &stream->capture, &avail, &xrun ) ); + if( xrun ) + { + PA_ENSURE( PaAlsaStream_HandleXrun( stream ) ); + PA_ENSURE( PaAlsaStreamComponent_GetAvailableFrames( &stream->capture, &avail, &xrun ) ); + if( xrun ) + PA_ENSURE( paInputOverflowed ); + } + + return (signed long)avail; + +error: + return result; +} + +static signed long GetStreamWriteAvailable( PaStream* s ) +{ + PaError result = paNoError; + PaAlsaStream *stream = (PaAlsaStream*)s; + unsigned long avail; + int xrun; + + PA_ENSURE( PaAlsaStreamComponent_GetAvailableFrames( &stream->playback, &avail, &xrun ) ); + if( xrun ) + { + snd_pcm_sframes_t savail; + + PA_ENSURE( PaAlsaStream_HandleXrun( stream ) ); + savail = snd_pcm_avail_update( stream->playback.pcm ); + + /* savail should not contain -EPIPE now, since PaAlsaStream_HandleXrun will only prepare the pcm */ + ENSURE_( savail, paUnanticipatedHostError ); + + avail = (unsigned long) savail; + } + + return (signed long)avail; + +error: + return result; +} + +/* Extensions */ + +/* Initialize host api specific structure */ +void PaAlsa_InitializeStreamInfo( PaAlsaStreamInfo *info ) +{ + info->size = sizeof (PaAlsaStreamInfo); + info->hostApiType = paALSA; + info->version = 1; + info->deviceString = NULL; +} + +void PaAlsa_EnableRealtimeScheduling( PaStream *s, int enable ) +{ + PaAlsaStream *stream = (PaAlsaStream *) s; + stream->threading.rtSched = enable; +} + +void PaAlsa_EnableWatchdog( PaStream *s, int enable ) +{ + PaAlsaStream *stream = (PaAlsaStream *) s; + stream->threading.useWatchdog = enable; +} diff --git a/src/audio/portaudio/pa_linux_alsa/pa_linux_alsa.h b/src/audio/portaudio/pa_linux_alsa/pa_linux_alsa.h new file mode 100644 index 0000000000000000000000000000000000000000..b9d43c9291c20c5088b654bc83566821a0969409 --- /dev/null +++ b/src/audio/portaudio/pa_linux_alsa/pa_linux_alsa.h @@ -0,0 +1,64 @@ +#ifndef PA_LINUX_ALSA_H +#define PA_LINUX_ALSA_H + +/* + * $Id$ + * PortAudio Portable Real-Time Audio Library + * ALSA-specific extensions + * + * 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. + * + * 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. + * + * 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. + * + */ + +/** @file + * ALSA-specific PortAudio API extension header file. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct PaAlsaStreamInfo +{ + unsigned long size; + PaHostApiTypeId hostApiType; + unsigned long version; + + const char *deviceString; +} +PaAlsaStreamInfo; + +void PaAlsa_InitializeStreamInfo( PaAlsaStreamInfo *info ); + +void PaAlsa_EnableRealtimeScheduling( PaStream *s, int enable ); + +void PaAlsa_EnableWatchdog( PaStream *s, int enable ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/audio/portaudio/pa_mac/pa_mac_hostapis.c b/src/audio/portaudio/pa_mac/pa_mac_hostapis.c new file mode 100644 index 0000000000000000000000000000000000000000..f8306c302f4c5a669eb64e419ded5a3a91440c7d --- /dev/null +++ b/src/audio/portaudio/pa_mac/pa_mac_hostapis.c @@ -0,0 +1,79 @@ +/* + * $Id$ + * 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 + * + * 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. + * + * 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. + * + * 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. + */ + +/** @file +Mac OS host API initialization function table. +*/ + + +#include "pa_hostapi.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + PaError PaSkeleton_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + PaError PaMacCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + PaError PaMacSm_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + PaError PaJack_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + PaError PaMacAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +PaUtilHostApiInitializer *paHostApiInitializers[] = +{ +#ifdef PA_USE_COREAUDIO + PaMacCore_Initialize, +#endif + +#ifdef PA_USE_SM + PaMacSm_Initialize, +#endif + +#ifdef PA_USE_JACK + PaJack_Initialize, +#endif + +#ifdef PA_USE_ASIO + PaMacAsio_Initialize, +#endif + + PaSkeleton_Initialize, /* just for testing */ + + 0 /* NULL terminated array */ +}; + + +int paDefaultHostApiIndex = 0; diff --git a/src/audio/portaudio/pa_mac_core/notes.txt b/src/audio/portaudio/pa_mac_core/notes.txt new file mode 100644 index 0000000000000000000000000000000000000000..c79b90e66dc61da13b25b55a9ecaa19647bdb65c --- /dev/null +++ b/src/audio/portaudio/pa_mac_core/notes.txt @@ -0,0 +1,34 @@ +Notes on Core Audio Implementation of PortAudio + +by Phil Burk and Darren Gibbs + +Document last updated March 20, 2002 + +WHAT WORKS + +Output with very low latency, <10 msec. +Half duplex input or output. +Full duplex on the same CoreAudio device. +The paFLoat32, paInt16, paInt8, paUInt8 sample formats. +Pa_GetCPULoad() +Pa_StreamTime() + +KNOWN BUGS OR LIMITATIONS + +We do not yet support simultaneous input and output on different +devices. Note that some CoreAudio devices like the Roland UH30 look +like one device but are actually two different CoreAudio devices. The +BuiltIn audio is typically one CoreAudio device. + +Mono doesn't work. + +DEVICE MAPPING + +CoreAudio devices can support both input and output. But the sample +rates supported may be different. So we have map one or two PortAudio +device to each CoreAudio device depending on whether it supports +input, output or both. + +When we query devices, we first get a list of CoreAudio devices. Then +we scan the list and add a PortAudio device for each CoreAudio device +that supports input. Then we make a scan for output devices. diff --git a/src/audio/portaudio/pa_mac_core/pa_mac_core.c b/src/audio/portaudio/pa_mac_core/pa_mac_core.c new file mode 100644 index 0000000000000000000000000000000000000000..9db047ebb20af18b3fa98ad17760e08a8a421f44 --- /dev/null +++ b/src/audio/portaudio/pa_mac_core/pa_mac_core.c @@ -0,0 +1,892 @@ +/* + * $Id$ + * pa_mac_core.c + * Implementation of PortAudio for Mac OS X CoreAudio + * + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * + * Authors: Ross Bencina and Phil Burk + * 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. + * + * 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. + * + * 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. + * + */ + +#include <CoreAudio/CoreAudio.h> +#include <AudioToolbox/AudioToolbox.h> +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include <assert.h> + +#include "portaudio.h" +#include "pa_trace.h" +#include "pa_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" + +// ===== constants ===== + +// ===== structs ===== +#pragma mark structs + +// PaMacCoreHostApiRepresentation - host api datastructure specific to this implementation +typedef struct PaMacCore_HAR +{ + PaUtilHostApiRepresentation inheritedHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup *allocations; + AudioDeviceID *macCoreDeviceIds; +} +PaMacCoreHostApiRepresentation; + +typedef struct PaMacCore_DI +{ + PaDeviceInfo inheritedDeviceInfo; +} +PaMacCoreDeviceInfo; + +// PaMacCoreStream - a stream data structure specifically for this implementation +typedef struct PaMacCore_S +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + + int primeStreamUsingCallback; + + AudioDeviceID inputDevice; + AudioDeviceID outputDevice; + + // Processing thread management -------------- +// HANDLE abortEvent; +// HANDLE processingThread; +// DWORD processingThreadId; + + char throttleProcessingThreadOnOverload; // 0 -> don't throtte, non-0 -> throttle + int processingThreadPriority; + int highThreadPriority; + int throttledThreadPriority; + unsigned long throttledSleepMsecs; + + int isStopped; + volatile int isActive; + volatile int stopProcessing; // stop thread once existing buffers have been returned + volatile int abortProcessing; // stop thread immediately + +// DWORD allBuffersDurationMs; // used to calculate timeouts +} +PaMacCoreStream; + +// Data needed by the CoreAudio callback functions +typedef struct PaMacCore_CD +{ + PaMacCoreStream *stream; + PaStreamCallback *callback; + void *userData; + PaUtilConverter *inputConverter; + PaUtilConverter *outputConverter; + void *inputBuffer; + void *outputBuffer; + int inputChannelCount; + int outputChannelCount; + PaSampleFormat inputSampleFormat; + PaSampleFormat outputSampleFormat; + PaUtilTriangularDitherGenerator *ditherGenerator; +} +PaMacClientData; + +// ===== CoreAudio-PortAudio bridge functions ===== +#pragma mark CoreAudio-PortAudio bridge functions + +// Maps CoreAudio OSStatus codes to PortAudio PaError codes +static PaError conv_err(OSStatus error) +{ + PaError result; + + switch (error) { + case kAudioHardwareNoError: + result = paNoError; break; + case kAudioHardwareNotRunningError: + result = paInternalError; break; + case kAudioHardwareUnspecifiedError: + result = paInternalError; break; + case kAudioHardwareUnknownPropertyError: + result = paInternalError; break; + case kAudioHardwareBadPropertySizeError: + result = paInternalError; break; + case kAudioHardwareIllegalOperationError: + result = paInternalError; break; + case kAudioHardwareBadDeviceError: + result = paInvalidDevice; break; + case kAudioHardwareBadStreamError: + result = paBadStreamPtr; break; + case kAudioHardwareUnsupportedOperationError: + result = paInternalError; break; + case kAudioDeviceUnsupportedFormatError: + result = paSampleFormatNotSupported; break; + case kAudioDevicePermissionsError: + result = paDeviceUnavailable; break; + default: + result = paInternalError; + } + + return result; +} + +static AudioStreamBasicDescription *InitializeStreamDescription(const PaStreamParameters *parameters, double sampleRate) +{ + struct AudioStreamBasicDescription *streamDescription = PaUtil_AllocateMemory(sizeof(AudioStreamBasicDescription)); + streamDescription->mSampleRate = sampleRate; + streamDescription->mFormatID = kAudioFormatLinearPCM; + streamDescription->mFormatFlags = 0; + streamDescription->mFramesPerPacket = 1; + + if (parameters->sampleFormat & paNonInterleaved) { + streamDescription->mFormatFlags |= kLinearPCMFormatFlagIsNonInterleaved; + streamDescription->mChannelsPerFrame = 1; + streamDescription->mBytesPerFrame = Pa_GetSampleSize(parameters->sampleFormat); + streamDescription->mBytesPerPacket = Pa_GetSampleSize(parameters->sampleFormat); + } + else { + streamDescription->mChannelsPerFrame = parameters->channelCount; + } + + streamDescription->mBytesPerFrame = Pa_GetSampleSize(parameters->sampleFormat) * streamDescription->mChannelsPerFrame; + streamDescription->mBytesPerPacket = streamDescription->mBytesPerFrame * streamDescription->mFramesPerPacket; + + if (parameters->sampleFormat & paFloat32) { + streamDescription->mFormatFlags |= kLinearPCMFormatFlagIsFloat; + streamDescription->mBitsPerChannel = 32; + } + else if (parameters->sampleFormat & paInt32) { + streamDescription->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; + streamDescription->mBitsPerChannel = 32; + } + else if (parameters->sampleFormat & paInt24) { + streamDescription->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; + streamDescription->mBitsPerChannel = 24; + } + else if (parameters->sampleFormat & paInt16) { + streamDescription->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; + streamDescription->mBitsPerChannel = 16; + } + else if (parameters->sampleFormat & paInt8) { + streamDescription->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; + streamDescription->mBitsPerChannel = 8; + } + else if (parameters->sampleFormat & paInt32) { + streamDescription->mBitsPerChannel = 8; + } + + return streamDescription; +} + +static PaStreamCallbackTimeInfo *InitializeTimeInfo(const AudioTimeStamp* now, const AudioTimeStamp* inputTime, const AudioTimeStamp* outputTime) +{ + PaStreamCallbackTimeInfo *timeInfo = PaUtil_AllocateMemory(sizeof(PaStreamCallbackTimeInfo)); + + timeInfo->inputBufferAdcTime = inputTime->mSampleTime; + timeInfo->currentTime = now->mSampleTime; + timeInfo->outputBufferDacTime = outputTime->mSampleTime; + + return timeInfo; +} + +// ===== support functions ===== +#pragma mark support functions + +static void CleanUp(PaMacCoreHostApiRepresentation *macCoreHostApi) +{ + if( macCoreHostApi->allocations ) + { + PaUtil_FreeAllAllocations( macCoreHostApi->allocations ); + PaUtil_DestroyAllocationGroup( macCoreHostApi->allocations ); + } + + PaUtil_FreeMemory( macCoreHostApi ); +} + +static PaError GetChannelInfo(PaDeviceInfo *deviceInfo, AudioDeviceID macCoreDeviceId, int isInput) +{ + UInt32 propSize; + PaError err = paNoError; + UInt32 i; + int numChannels = 0; + AudioBufferList *buflist; + + err = conv_err(AudioDeviceGetPropertyInfo(macCoreDeviceId, 0, isInput, kAudioDevicePropertyStreamConfiguration, &propSize, NULL)); + buflist = PaUtil_AllocateMemory(propSize); + err = conv_err(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertyStreamConfiguration, &propSize, buflist)); + if (!err) { + for (i = 0; i < buflist->mNumberBuffers; ++i) { + numChannels += buflist->mBuffers[i].mNumberChannels; + } + int frameLatency; + propSize = sizeof(UInt32); + err = conv_err(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertyLatency, &propSize, &frameLatency)); + if (!err) { + double secondLatency = frameLatency / deviceInfo->defaultSampleRate; + if (isInput) { + deviceInfo->maxInputChannels = numChannels; + deviceInfo->defaultLowInputLatency = secondLatency; + deviceInfo->defaultHighInputLatency = secondLatency; + } + else { + deviceInfo->maxOutputChannels = numChannels; + deviceInfo->defaultLowOutputLatency = secondLatency; + deviceInfo->defaultHighOutputLatency = secondLatency; + } + } + } + PaUtil_FreeMemory(buflist); + + return err; +} + +static PaError InitializeDeviceInfo(PaMacCoreDeviceInfo *macCoreDeviceInfo, AudioDeviceID macCoreDeviceId, PaHostApiIndex hostApiIndex ) +{ + PaDeviceInfo *deviceInfo = &macCoreDeviceInfo->inheritedDeviceInfo; + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; + + PaError err = paNoError; + UInt32 propSize; + + err = conv_err(AudioDeviceGetPropertyInfo(macCoreDeviceId, 0, 0, kAudioDevicePropertyDeviceName, &propSize, NULL)); + // FIXME: this allocation should be part of the allocations group + char *name = PaUtil_AllocateMemory(propSize); + err = conv_err(AudioDeviceGetProperty(macCoreDeviceId, 0, 0, kAudioDevicePropertyDeviceName, &propSize, name)); + if (!err) { + deviceInfo->name = name; + } + + Float64 sampleRate; + propSize = sizeof(Float64); + err = conv_err(AudioDeviceGetProperty(macCoreDeviceId, 0, 0, kAudioDevicePropertyNominalSampleRate, &propSize, &sampleRate)); + if (!err) { + deviceInfo->defaultSampleRate = sampleRate; + } + + + // Get channel info + err = GetChannelInfo(deviceInfo, macCoreDeviceId, 1); + err = GetChannelInfo(deviceInfo, macCoreDeviceId, 0); + + return err; +} + +static PaError InitializeDeviceInfos( PaMacCoreHostApiRepresentation *macCoreHostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + PaUtilHostApiRepresentation *hostApi; + PaMacCoreDeviceInfo *deviceInfoArray; + + // initialise device counts and default devices under the assumption that there are no devices. These values are incremented below if and when devices are successfully initialized. + hostApi = &macCoreHostApi->inheritedHostApiRep; + hostApi->info.deviceCount = 0; + hostApi->info.defaultInputDevice = paNoDevice; + hostApi->info.defaultOutputDevice = paNoDevice; + + UInt32 propsize; + AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &propsize, NULL); + int numDevices = propsize / sizeof(AudioDeviceID); + hostApi->info.deviceCount = numDevices; + if (numDevices > 0) { + hostApi->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + macCoreHostApi->allocations, sizeof(PaDeviceInfo*) * numDevices ); + if( !hostApi->deviceInfos ) + { + return paInsufficientMemory; + } + + // allocate all device info structs in a contiguous block + deviceInfoArray = (PaMacCoreDeviceInfo*)PaUtil_GroupAllocateMemory( + macCoreHostApi->allocations, sizeof(PaMacCoreDeviceInfo) * numDevices ); + if( !deviceInfoArray ) + { + return paInsufficientMemory; + } + + macCoreHostApi->macCoreDeviceIds = PaUtil_GroupAllocateMemory(macCoreHostApi->allocations, propsize); + AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &propsize, macCoreHostApi->macCoreDeviceIds); + + AudioDeviceID defaultInputDevice, defaultOutputDevice; + propsize = sizeof(AudioDeviceID); + AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, &propsize, &defaultInputDevice); + AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &propsize, &defaultOutputDevice); + + UInt32 i; + for (i = 0; i < numDevices; ++i) { + if (macCoreHostApi->macCoreDeviceIds[i] == defaultInputDevice) { + hostApi->info.defaultInputDevice = i; + } + if (macCoreHostApi->macCoreDeviceIds[i] == defaultOutputDevice) { + hostApi->info.defaultOutputDevice = i; + } + InitializeDeviceInfo(&deviceInfoArray[i], macCoreHostApi->macCoreDeviceIds[i], hostApiIndex); + hostApi->deviceInfos[i] = &(deviceInfoArray[i].inheritedDeviceInfo); + } + } + + return result; +} + +static OSStatus CheckFormat(AudioDeviceID macCoreDeviceId, const PaStreamParameters *parameters, double sampleRate, int isInput) +{ + UInt32 propSize = sizeof(AudioStreamBasicDescription); + AudioStreamBasicDescription *streamDescription = PaUtil_AllocateMemory(propSize); + + streamDescription->mSampleRate = sampleRate; + streamDescription->mFormatID = 0; + streamDescription->mFormatFlags = 0; + streamDescription->mBytesPerPacket = 0; + streamDescription->mFramesPerPacket = 0; + streamDescription->mBytesPerFrame = 0; + streamDescription->mChannelsPerFrame = 0; + streamDescription->mBitsPerChannel = 0; + streamDescription->mReserved = 0; + + OSStatus result = AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertyStreamFormatSupported, &propSize, streamDescription); + PaUtil_FreeMemory(streamDescription); + return result; +} + +static OSStatus CopyInputData(PaMacClientData* destination, const AudioBufferList *source, unsigned long frameCount) +{ + int frameSpacing, channelSpacing; + if (destination->inputSampleFormat & paNonInterleaved) { + frameSpacing = 1; + channelSpacing = destination->inputChannelCount; + } + else { + frameSpacing = destination->inputChannelCount; + channelSpacing = 1; + } + + AudioBuffer const *inputBuffer = &source->mBuffers[0]; + void *coreAudioBuffer = inputBuffer->mData; + void *portAudioBuffer = destination->inputBuffer; + UInt32 i, streamNumber, streamChannel; + for (i = streamNumber = streamChannel = 0; i < destination->inputChannelCount; ++i, ++streamChannel) { + if (streamChannel >= inputBuffer->mNumberChannels) { + ++streamNumber; + inputBuffer = &source->mBuffers[streamNumber]; + coreAudioBuffer = inputBuffer->mData; + streamChannel = 0; + } + destination->inputConverter(portAudioBuffer, frameSpacing, coreAudioBuffer, inputBuffer->mNumberChannels, frameCount, destination->ditherGenerator); + coreAudioBuffer += sizeof(Float32); + portAudioBuffer += Pa_GetSampleSize(destination->inputSampleFormat) * channelSpacing; + } +} + +static OSStatus CopyOutputData(AudioBufferList* destination, PaMacClientData *source, unsigned long frameCount) +{ + int frameSpacing, channelSpacing; + if (source->outputSampleFormat & paNonInterleaved) { + frameSpacing = 1; + channelSpacing = source->outputChannelCount; + } + else { + frameSpacing = source->outputChannelCount; + channelSpacing = 1; + } + + AudioBuffer *outputBuffer = &destination->mBuffers[0]; + void *coreAudioBuffer = outputBuffer->mData; + void *portAudioBuffer = source->outputBuffer; + UInt32 i, streamNumber, streamChannel; + for (i = streamNumber = streamChannel = 0; i < source->outputChannelCount; ++i, ++streamChannel) { + if (streamChannel >= outputBuffer->mNumberChannels) { + ++streamNumber; + outputBuffer = &destination->mBuffers[streamNumber]; + coreAudioBuffer = outputBuffer->mData; + streamChannel = 0; + } + source->outputConverter(coreAudioBuffer, outputBuffer->mNumberChannels, portAudioBuffer, frameSpacing, frameCount, NULL); + coreAudioBuffer += sizeof(Float32); + portAudioBuffer += Pa_GetSampleSize(source->outputSampleFormat) * channelSpacing; + } +} + +static OSStatus AudioIOProc( AudioDeviceID inDevice, + const AudioTimeStamp* inNow, + const AudioBufferList* inInputData, + const AudioTimeStamp* inInputTime, + AudioBufferList* outOutputData, + const AudioTimeStamp* inOutputTime, + void* inClientData) +{ + PaMacClientData *clientData = (PaMacClientData *)inClientData; + PaStreamCallbackTimeInfo *timeInfo = InitializeTimeInfo(inNow, inInputTime, inOutputTime); + + PaUtil_BeginCpuLoadMeasurement( &clientData->stream->cpuLoadMeasurer ); + + AudioBuffer *outputBuffer = &outOutputData->mBuffers[0]; + unsigned long frameCount = outputBuffer->mDataByteSize / (outputBuffer->mNumberChannels * sizeof(Float32)); + + if (clientData->inputBuffer) { + CopyInputData(clientData, inInputData, frameCount); + } + PaStreamCallbackResult result = clientData->callback(clientData->inputBuffer, clientData->outputBuffer, frameCount, timeInfo, paNoFlag, clientData->userData); + if (clientData->outputBuffer) { + CopyOutputData(outOutputData, clientData, frameCount); + } + + PaUtil_EndCpuLoadMeasurement( &clientData->stream->cpuLoadMeasurer, frameCount ); + + if (result == paComplete || result == paAbort) { + Pa_StopStream(clientData->stream); + } +} + +// This is not for input-only streams, this is for streams where the input device is different from the output device +// TODO: This needs to store the output data in a buffer, to be written to the device the next time AudioOutputProc is called +static OSStatus AudioInputProc( AudioDeviceID inDevice, + const AudioTimeStamp* inNow, + const AudioBufferList* inInputData, + const AudioTimeStamp* inInputTime, + AudioBufferList* outOutputData, + const AudioTimeStamp* inOutputTime, + void* inClientData) +{ + PaMacClientData *clientData = (PaMacClientData *)inClientData; + PaStreamCallbackTimeInfo *timeInfo = InitializeTimeInfo(inNow, inInputTime, inOutputTime); + + PaUtil_BeginCpuLoadMeasurement( &clientData->stream->cpuLoadMeasurer ); + + AudioBuffer const *inputBuffer = &inInputData->mBuffers[0]; + unsigned long frameCount = inputBuffer->mDataByteSize / (inputBuffer->mNumberChannels * sizeof(Float32)); + + CopyInputData(clientData, inInputData, frameCount); + clientData->callback(clientData->inputBuffer, NULL, frameCount, timeInfo, paNoFlag, clientData->userData); + + PaUtil_EndCpuLoadMeasurement( &clientData->stream->cpuLoadMeasurer, frameCount ); +} + +// This is not for output-only streams, this is for streams where the input device is different from the output device +static OSStatus AudioOutputProc( AudioDeviceID inDevice, + const AudioTimeStamp* inNow, + const AudioBufferList* inInputData, + const AudioTimeStamp* inInputTime, + AudioBufferList* outOutputData, + const AudioTimeStamp* inOutputTime, + void* inClientData) +{ + PaMacClientData *clientData = (PaMacClientData *)inClientData; + PaStreamCallbackTimeInfo *timeInfo = InitializeTimeInfo(inNow, inInputTime, inOutputTime); + + PaUtil_BeginCpuLoadMeasurement( &clientData->stream->cpuLoadMeasurer ); + + AudioBuffer *outputBuffer = &outOutputData->mBuffers[0]; + unsigned long frameCount = outputBuffer->mDataByteSize / (outputBuffer->mNumberChannels * sizeof(Float32)); + + clientData->callback(NULL, clientData->outputBuffer, frameCount, timeInfo, paNoFlag, clientData->userData); + + CopyOutputData(outOutputData, clientData, frameCount); + + PaUtil_EndCpuLoadMeasurement( &clientData->stream->cpuLoadMeasurer, frameCount ); +} + +static PaError SetSampleRate(AudioDeviceID device, double sampleRate, int isInput) +{ + PaError result = paNoError; + + double actualSampleRate; + UInt32 propSize = sizeof(double); + result = conv_err(AudioDeviceSetProperty(device, NULL, 0, isInput, kAudioDevicePropertyNominalSampleRate, propSize, &sampleRate)); + + result = conv_err(AudioDeviceGetProperty(device, 0, isInput, kAudioDevicePropertyNominalSampleRate, &propSize, &actualSampleRate)); + + if (result == paNoError && actualSampleRate != sampleRate) { + result = paInvalidSampleRate; + } + + return result; +} + +static PaError SetFramesPerBuffer(AudioDeviceID device, unsigned long framesPerBuffer, int isInput) +{ + PaError result = paNoError; + UInt32 preferredFramesPerBuffer = framesPerBuffer; + // while (preferredFramesPerBuffer > UINT32_MAX) { + // preferredFramesPerBuffer /= 2; + // } + + UInt32 actualFramesPerBuffer; + UInt32 propSize = sizeof(UInt32); + result = conv_err(AudioDeviceSetProperty(device, NULL, 0, isInput, kAudioDevicePropertyBufferFrameSize, propSize, &preferredFramesPerBuffer)); + + result = conv_err(AudioDeviceGetProperty(device, 0, isInput, kAudioDevicePropertyBufferFrameSize, &propSize, &actualFramesPerBuffer)); + + if (result != paNoError) { + // do nothing + } + else if (actualFramesPerBuffer > framesPerBuffer) { + result = paBufferTooSmall; + } + else if (actualFramesPerBuffer < framesPerBuffer) { + result = paBufferTooBig; + } + + return result; +} + +static PaError SetUpUnidirectionalStream(AudioDeviceID device, double sampleRate, unsigned long framesPerBuffer, int isInput) +{ + PaError err = paNoError; + err = SetSampleRate(device, sampleRate, isInput); + if( err == paNoError ) + err = SetFramesPerBuffer(device, framesPerBuffer, isInput); + return err; +} + +// ===== PortAudio functions ===== +#pragma mark PortAudio functions + +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + + PaError PaMacCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + +#ifdef __cplusplus +} +#endif // __cplusplus + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaMacCoreHostApiRepresentation *macCoreHostApi = (PaMacCoreHostApiRepresentation*)hostApi; + + CleanUp(macCoreHostApi); +} + +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + PaMacCoreHostApiRepresentation *macCoreHostApi = (PaMacCoreHostApiRepresentation*)hostApi; + PaDeviceInfo *deviceInfo; + + PaError result = paNoError; + if (inputParameters) { + deviceInfo = macCoreHostApi->inheritedHostApiRep.deviceInfos[inputParameters->device]; + if (inputParameters->channelCount > deviceInfo->maxInputChannels) + result = paInvalidChannelCount; + else if (CheckFormat(macCoreHostApi->macCoreDeviceIds[inputParameters->device], inputParameters, sampleRate, 1) != kAudioHardwareNoError) { + result = paInvalidSampleRate; + } + } + if (outputParameters && result == paNoError) { + deviceInfo = macCoreHostApi->inheritedHostApiRep.deviceInfos[outputParameters->device]; + if (outputParameters->channelCount > deviceInfo->maxOutputChannels) + result = paInvalidChannelCount; + else if (CheckFormat(macCoreHostApi->macCoreDeviceIds[outputParameters->device], outputParameters, sampleRate, 0) != kAudioHardwareNoError) { + result = paInvalidSampleRate; + } + } + + return result; +} + +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ) +{ + PaError err = paNoError; + PaMacCoreHostApiRepresentation *macCoreHostApi = (PaMacCoreHostApiRepresentation *)hostApi; + PaMacCoreStream *stream = PaUtil_AllocateMemory(sizeof(PaMacCoreStream)); + stream->isActive = 0; + stream->isStopped = 1; + stream->inputDevice = kAudioDeviceUnknown; + stream->outputDevice = kAudioDeviceUnknown; + + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + ( (streamCallback) + ? &macCoreHostApi->callbackStreamInterface + : &macCoreHostApi->blockingStreamInterface ), + streamCallback, userData ); + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); + + *s = (PaStream*)stream; + PaMacClientData *clientData = PaUtil_AllocateMemory(sizeof(PaMacClientData)); + clientData->stream = stream; + clientData->callback = streamCallback; + clientData->userData = userData; + clientData->inputBuffer = 0; + clientData->outputBuffer = 0; + clientData->ditherGenerator = PaUtil_AllocateMemory(sizeof(PaUtilTriangularDitherGenerator)); + PaUtil_InitializeTriangularDitherState(clientData->ditherGenerator); + + if (inputParameters != NULL) { + stream->inputDevice = macCoreHostApi->macCoreDeviceIds[inputParameters->device]; + clientData->inputConverter = PaUtil_SelectConverter(paFloat32, inputParameters->sampleFormat, streamFlags); + clientData->inputBuffer = PaUtil_AllocateMemory(Pa_GetSampleSize(inputParameters->sampleFormat) * framesPerBuffer * inputParameters->channelCount); + clientData->inputChannelCount = inputParameters->channelCount; + clientData->inputSampleFormat = inputParameters->sampleFormat; + err = SetUpUnidirectionalStream(stream->inputDevice, sampleRate, framesPerBuffer, 1); + } + + if (err == paNoError && outputParameters != NULL) { + stream->outputDevice = macCoreHostApi->macCoreDeviceIds[outputParameters->device]; + clientData->outputConverter = PaUtil_SelectConverter(outputParameters->sampleFormat, paFloat32, streamFlags); + clientData->outputBuffer = PaUtil_AllocateMemory(Pa_GetSampleSize(outputParameters->sampleFormat) * framesPerBuffer * outputParameters->channelCount); + clientData->outputChannelCount = outputParameters->channelCount; + clientData->outputSampleFormat = outputParameters->sampleFormat; + err = SetUpUnidirectionalStream(stream->outputDevice, sampleRate, framesPerBuffer, 0); + } + + if (inputParameters == NULL || outputParameters == NULL || stream->inputDevice == stream->outputDevice) { + AudioDeviceID device = (inputParameters == NULL) ? stream->outputDevice : stream->inputDevice; + + AudioDeviceAddIOProc(device, AudioIOProc, clientData); + } + else { + // using different devices for input and output + AudioDeviceAddIOProc(stream->inputDevice, AudioInputProc, clientData); + AudioDeviceAddIOProc(stream->outputDevice, AudioOutputProc, clientData); + } + + return err; +} + +// Note: When CloseStream() is called, the multi-api layer ensures that the stream has already been stopped or aborted. +static PaError CloseStream( PaStream* s ) +{ + PaError err = paNoError; + PaMacCoreStream *stream = (PaMacCoreStream*)s; + + PaUtil_ResetCpuLoadMeasurer( &stream->cpuLoadMeasurer ); + + if (stream->inputDevice != kAudioDeviceUnknown) { + if (stream->outputDevice == kAudioDeviceUnknown || stream->outputDevice == stream->inputDevice) { + err = conv_err(AudioDeviceRemoveIOProc(stream->inputDevice, AudioIOProc)); + } + else { + err = conv_err(AudioDeviceRemoveIOProc(stream->inputDevice, AudioInputProc)); + err = conv_err(AudioDeviceRemoveIOProc(stream->outputDevice, AudioOutputProc)); + } + } + else { + err = conv_err(AudioDeviceRemoveIOProc(stream->outputDevice, AudioIOProc)); + } + + return err; +} + + +static PaError StartStream( PaStream *s ) +{ + PaError err = paNoError; + PaMacCoreStream *stream = (PaMacCoreStream*)s; + + if (stream->inputDevice != kAudioDeviceUnknown) { + if (stream->outputDevice == kAudioDeviceUnknown || stream->outputDevice == stream->inputDevice) { + err = conv_err(AudioDeviceStart(stream->inputDevice, AudioIOProc)); + } + else { + err = conv_err(AudioDeviceStart(stream->inputDevice, AudioInputProc)); + err = conv_err(AudioDeviceStart(stream->outputDevice, AudioOutputProc)); + } + } + else { + err = conv_err(AudioDeviceStart(stream->outputDevice, AudioIOProc)); + } + + stream->isActive = 1; + stream->isStopped = 0; + return err; +} + +static PaError AbortStream( PaStream *s ) +{ + PaError err = paNoError; + PaMacCoreStream *stream = (PaMacCoreStream*)s; + + if (stream->inputDevice != kAudioDeviceUnknown) { + if (stream->outputDevice == kAudioDeviceUnknown || stream->outputDevice == stream->inputDevice) { + err = conv_err(AudioDeviceStop(stream->inputDevice, AudioIOProc)); + } + else { + err = conv_err(AudioDeviceStop(stream->inputDevice, AudioInputProc)); + err = conv_err(AudioDeviceStop(stream->outputDevice, AudioOutputProc)); + } + } + else { + err = conv_err(AudioDeviceStop(stream->outputDevice, AudioIOProc)); + } + + stream->isActive = 0; + stream->isStopped = 1; + return err; +} + +static PaError StopStream( PaStream *s ) +{ + // TODO: this should be nicer than abort + return AbortStream(s); +} + +static PaError IsStreamStopped( PaStream *s ) +{ + PaMacCoreStream *stream = (PaMacCoreStream*)s; + + return stream->isStopped; +} + + +static PaError IsStreamActive( PaStream *s ) +{ + PaMacCoreStream *stream = (PaMacCoreStream*)s; + + return stream->isActive; +} + + +static PaTime GetStreamTime( PaStream *s ) +{ + OSStatus err; + PaTime result; + PaMacCoreStream *stream = (PaMacCoreStream*)s; + + AudioTimeStamp *timeStamp = PaUtil_AllocateMemory(sizeof(AudioTimeStamp)); + if (stream->inputDevice != kAudioDeviceUnknown) { + err = AudioDeviceGetCurrentTime(stream->inputDevice, timeStamp); + } + else { + err = AudioDeviceGetCurrentTime(stream->outputDevice, timeStamp); + } + + result = err ? 0 : timeStamp->mSampleTime; + PaUtil_FreeMemory(timeStamp); + + return result; +} + + +static double GetStreamCpuLoad( PaStream* s ) +{ + PaMacCoreStream *stream = (PaMacCoreStream*)s; + + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); +} + + +// As separate stream interfaces are used for blocking and callback streams, the following functions can be guaranteed to only be called for blocking streams. + +static PaError ReadStream( PaStream* s, + void *buffer, + unsigned long frames ) +{ + return paInternalError; +} + + +static PaError WriteStream( PaStream* s, + const void *buffer, + unsigned long frames ) +{ + return paInternalError; +} + + +static signed long GetStreamReadAvailable( PaStream* s ) +{ + return paInternalError; +} + + +static signed long GetStreamWriteAvailable( PaStream* s ) +{ + return paInternalError; +} + +// HostAPI-specific initialization function +PaError PaMacCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + PaMacCoreHostApiRepresentation *macCoreHostApi = (PaMacCoreHostApiRepresentation *)PaUtil_AllocateMemory( sizeof(PaMacCoreHostApiRepresentation) ); + if( !macCoreHostApi ) + { + result = paInsufficientMemory; + goto error; + } + + macCoreHostApi->allocations = PaUtil_CreateAllocationGroup(); + if( !macCoreHostApi->allocations ) + { + result = paInsufficientMemory; + goto error; + } + + *hostApi = &macCoreHostApi->inheritedHostApiRep; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paCoreAudio; + (*hostApi)->info.name = "CoreAudio"; + + result = InitializeDeviceInfos(macCoreHostApi, hostApiIndex); + if (result != paNoError) { + goto error; + } + + // Set up the proper callbacks to this HostApi's functions + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PaUtil_InitializeStreamInterface( &macCoreHostApi->callbackStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface( &macCoreHostApi->blockingStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable ); + + return result; + +error: + if( macCoreHostApi ) { + CleanUp(macCoreHostApi); + } + + return result; +} \ No newline at end of file diff --git a/src/audio/portaudio/pa_mac_sm/pa_mac_sm.c b/src/audio/portaudio/pa_mac_sm/pa_mac_sm.c new file mode 100644 index 0000000000000000000000000000000000000000..7a8e03009d25cf823c729c795708a21f3be9090b --- /dev/null +++ b/src/audio/portaudio/pa_mac_sm/pa_mac_sm.c @@ -0,0 +1,1656 @@ +/* + * $Id$ + * Portable Audio I/O Library for Macintosh + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2000 Phil Burk + * + * Special thanks to Chris Rolfe for his many helpful suggestions, bug fixes, + * and code contributions. + * Thanks also to Tue Haste Andersen, Alberto Ricci, Nico Wald, + * Roelf Toxopeus and Tom Erbe for testing the code and making + * numerous suggestions. + * + * 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. + * + * 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. + * + * 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. + */ +/* Modification History + PLB20010415 - ScanInputDevices was setting sDefaultOutputDeviceID instead of sDefaultInputDeviceID + PLB20010415 - Device Scan was crashing for anything other than siBadSoundInDevice, but some Macs may return other errors! + PLB20010420 - Fix TIMEOUT in record mode. + PLB20010420 - Change CARBON_COMPATIBLE to TARGET_API_MAC_CARBON + PLB20010907 - Pass unused event to WaitNextEvent to prevent Mac OSX crash. Thanks Dominic Mazzoni. + PLB20010908 - Use requested number of input channels. Thanks Dominic Mazzoni. + PLB20011009 - Use NewSndCallBackUPP() for CARBON + PLB20020417 - I used to call Pa_GetMinNumBuffers() which doesn't take into account the + variable minFramesPerHostBuffer. Now I call PaMac_GetMinNumBuffers() which will + give lower latency when virtual memory is turned off. + Thanks Kristoffer Jensen and Georgios Marentakis for spotting this bug. + PLB20020423 - Use new method to calculate CPU load similar to other ports. Based on num frames calculated. + Fixed Pa_StreamTime(). Now estimates how many frames have played based on MicroSecond timer. + Added PA_MAX_USAGE_ALLOWED to prevent Mac form hanging when CPU load approaches 100%. + PLB20020424 - Fixed return value in Pa_StreamTime +*/ + +/* +COMPATIBILITY +This Macintosh implementation is designed for use with Mac OS 7, 8 and +9 on PowerMacs, and OS X if compiled with CARBON + +OUTPUT +A circular array of CmpSoundHeaders is used as a queue. For low latency situations +there will only be two small buffers used. For higher latency, more and larger buffers +may be used. +To play the sound we use SndDoCommand() with bufferCmd. Each buffer is followed +by a callbackCmd which informs us when the buffer has been processsed. + +INPUT +The SndInput Manager SPBRecord call is used for sound input. If only +input is used, then the PA user callback is called from the Input completion proc. +For full-duplex, or output only operation, the PA callback is called from the +HostBuffer output completion proc. In that case, input sound is passed to the +callback by a simple FIFO. + +TODO: +O- Add support for native sample data formats other than int16. +O- Review buffer sizing. Should it be based on result of siDeviceBufferInfo query? +O- Determine default devices somehow. +*/ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <memory.h> +#include <math.h> + +/* Mac specific includes */ +#include "OSUtils.h" +#include <MacTypes.h> +#include <Math64.h> +#include <Errors.h> +#include <Sound.h> +#include <SoundInput.h> +#include <SoundComponents.h> +#include <Devices.h> +#include <DateTimeUtils.h> +#include <Timer.h> +#include <Gestalt.h> + +#include "portaudio.h" +#include "pa_host.h" +#include "pa_trace.h" + +#ifndef FALSE + #define FALSE (0) + #define TRUE (!FALSE) +#endif + +/* #define TARGET_API_MAC_CARBON (1) */ + +/* + * Define maximum CPU load that will be allowed. User callback will + * be skipped if load exceeds this limit. This is to prevent the Mac + * from hanging when the CPU is hogged by the sound thread. + * On my PowerBook G3, the mac hung when I used 94% of CPU ( usage = 0.94 ). + */ +#define PA_MAX_USAGE_ALLOWED (0.92) + +/* Debugging output macros. */ +#define PRINT(x) { printf x; fflush(stdout); } +#define ERR_RPT(x) PRINT(x) +#define DBUG(x) /* PRINT(x) /**/ +#define DBUGX(x) /* PRINT(x) /**/ + +#define MAC_PHYSICAL_FRAMES_PER_BUFFER (512) /* Minimum number of stereo frames per SoundManager double buffer. */ +#define MAC_VIRTUAL_FRAMES_PER_BUFFER (4096) /* Need this many when using Virtual Memory for recording. */ +#define PA_MIN_NUM_HOST_BUFFERS (2) +#define PA_MAX_NUM_HOST_BUFFERS (16) /* Do not exceed!! */ +#define PA_MAX_DEVICE_INFO (32) + +/* Conversions for 16.16 fixed point code. */ +#define DoubleToUnsignedFixed(x) ((UnsignedFixed) ((x) * 65536.0)) +#define UnsignedFixedToDouble(fx) (((double)(fx)) * (1.0/(1<<16))) + +/************************************************************************************/ +/****************** Structures ******************************************************/ +/************************************************************************************/ +/* Use for passing buffers from input callback to output callback for processing. */ +typedef struct MultiBuffer +{ + char *buffers[PA_MAX_NUM_HOST_BUFFERS]; + int numBuffers; + int nextWrite; + int nextRead; +} +MultiBuffer; + +/* Define structure to contain all Macintosh specific data. */ +typedef struct PaHostSoundControl +{ + UInt64 pahsc_EntryCount; + double pahsc_InverseMicrosPerHostBuffer; /* 1/Microseconds of real-time audio per user buffer. */ + + /* Use char instead of Boolean for atomic operation. */ + volatile char pahsc_IsRecording; /* Recording in progress. Set by foreground. Cleared by background. */ + volatile char pahsc_StopRecording; /* Signal sent to background. */ + volatile char pahsc_IfInsideCallback; + /* Input */ + SPB pahsc_InputParams; + SICompletionUPP pahsc_InputCompletionProc; + MultiBuffer pahsc_InputMultiBuffer; + int32 pahsc_BytesPerInputHostBuffer; + int32 pahsc_InputRefNum; + /* Output */ + CmpSoundHeader pahsc_SoundHeaders[PA_MAX_NUM_HOST_BUFFERS]; + int32 pahsc_BytesPerOutputHostBuffer; + SndChannelPtr pahsc_Channel; + SndCallBackUPP pahsc_OutputCompletionProc; + int32 pahsc_NumOutsQueued; + int32 pahsc_NumOutsPlayed; + PaTimestamp pahsc_NumFramesDone; + UInt64 pahsc_WhenFramesDoneIncremented; + /* Init Time -------------- */ + int32 pahsc_NumHostBuffers; + int32 pahsc_FramesPerHostBuffer; + int32 pahsc_UserBuffersPerHostBuffer; + int32 pahsc_MinFramesPerHostBuffer; /* Can vary depending on virtual memory usage. */ +} +PaHostSoundControl; + +/* Mac specific device information. */ +typedef struct internalPortAudioDevice +{ + long pad_DeviceRefNum; + long pad_DeviceBufferSize; + Component pad_Identifier; + PaDeviceInfo pad_Info; +} +internalPortAudioDevice; + +/************************************************************************************/ +/****************** Data ************************************************************/ +/************************************************************************************/ +static int sNumDevices = 0; +static internalPortAudioDevice sDevices[PA_MAX_DEVICE_INFO] = { 0 }; +static int32 sPaHostError = 0; +static int sDefaultOutputDeviceID; +static int sDefaultInputDeviceID; + +/************************************************************************************/ +/****************** Prototypes ******************************************************/ +/************************************************************************************/ +static PaError PaMac_TimeSlice( internalPortAudioStream *past, int16 *macOutputBufPtr ); +static PaError PaMac_CallUserLoop( internalPortAudioStream *past, int16 *outPtr ); +static PaError PaMac_RecordNext( internalPortAudioStream *past ); +static void PaMac_StartLoadCalculation( internalPortAudioStream *past ); +static int PaMac_GetMinNumBuffers( int minFramesPerHostBuffer, int framesPerBuffer, double sampleRate ); +static double *PaMac_GetSampleRatesFromHandle ( int numRates, Handle h ); +static PaError PaMac_ScanInputDevices( void ); +static PaError PaMac_ScanOutputDevices( void ); +static PaError PaMac_QueryOutputDeviceInfo( Component identifier, internalPortAudioDevice *ipad ); +static PaError PaMac_QueryInputDeviceInfo( Str255 deviceName, internalPortAudioDevice *ipad ); +static void PaMac_InitSoundHeader( internalPortAudioStream *past, CmpSoundHeader *sndHeader ); +static void PaMac_EndLoadCalculation( internalPortAudioStream *past ); +static void PaMac_PlayNext ( internalPortAudioStream *past, int index ); +static long PaMac_FillNextOutputBuffer( internalPortAudioStream *past, int index ); +static pascal void PaMac_InputCompletionProc(SPBPtr recParams); +static pascal void PaMac_OutputCompletionProc (SndChannelPtr theChannel, SndCommand * theCmd); +static PaError PaMac_BackgroundManager( internalPortAudioStream *past, int index ); +long PaHost_GetTotalBufferFrames( internalPortAudioStream *past ); +static int Mac_IsVirtualMemoryOn( void ); +static void PToCString(unsigned char* inString, char* outString); +char *MultiBuffer_GetNextWriteBuffer( MultiBuffer *mbuf ); +char *MultiBuffer_GetNextReadBuffer( MultiBuffer *mbuf ); +int MultiBuffer_GetNextReadIndex( MultiBuffer *mbuf ); +int MultiBuffer_GetNextWriteIndex( MultiBuffer *mbuf ); +int MultiBuffer_IsWriteable( MultiBuffer *mbuf ); +int MultiBuffer_IsReadable( MultiBuffer *mbuf ); +void MultiBuffer_AdvanceReadIndex( MultiBuffer *mbuf ); +void MultiBuffer_AdvanceWriteIndex( MultiBuffer *mbuf ); + +/************************************************************************* +** Simple FIFO index control for multiple buffers. +** Read and Write indices range from 0 to 2N-1. +** This allows us to distinguish between full and empty. +*/ +char *MultiBuffer_GetNextWriteBuffer( MultiBuffer *mbuf ) +{ + return mbuf->buffers[mbuf->nextWrite % mbuf->numBuffers]; +} +char *MultiBuffer_GetNextReadBuffer( MultiBuffer *mbuf ) +{ + return mbuf->buffers[mbuf->nextRead % mbuf->numBuffers]; +} +int MultiBuffer_GetNextReadIndex( MultiBuffer *mbuf ) +{ + return mbuf->nextRead % mbuf->numBuffers; +} +int MultiBuffer_GetNextWriteIndex( MultiBuffer *mbuf ) +{ + return mbuf->nextWrite % mbuf->numBuffers; +} + +int MultiBuffer_IsWriteable( MultiBuffer *mbuf ) +{ + int bufsFull = mbuf->nextWrite - mbuf->nextRead; + if( bufsFull < 0 ) bufsFull += (2 * mbuf->numBuffers); + return (bufsFull < mbuf->numBuffers); +} +int MultiBuffer_IsReadable( MultiBuffer *mbuf ) +{ + int bufsFull = mbuf->nextWrite - mbuf->nextRead; + if( bufsFull < 0 ) bufsFull += (2 * mbuf->numBuffers); + return (bufsFull > 0); +} +void MultiBuffer_AdvanceReadIndex( MultiBuffer *mbuf ) +{ + int temp = mbuf->nextRead + 1; + mbuf->nextRead = (temp >= (2 * mbuf->numBuffers)) ? 0 : temp; +} +void MultiBuffer_AdvanceWriteIndex( MultiBuffer *mbuf ) +{ + int temp = mbuf->nextWrite + 1; + mbuf->nextWrite = (temp >= (2 * mbuf->numBuffers)) ? 0 : temp; +} + +/************************************************************************* +** String Utility by Chris Rolfe +*/ +static void PToCString(unsigned char* inString, char* outString) +{ + long i; + for(i=0; i<inString[0]; i++) /* convert Pascal to C string */ + outString[i] = inString[i+1]; + outString[i]=0; +} + +/*************************************************************************/ +PaError PaHost_Term( void ) +{ + int i; + PaDeviceInfo *dev; + double *rates; + /* Free any allocated sample rate arrays. */ + for( i=0; i<sNumDevices; i++ ) + { + dev = &sDevices[i].pad_Info; + rates = (double *) dev->sampleRates; + if( (rates != NULL) ) free( rates ); /* MEM_011 */ + dev->sampleRates = NULL; + if( dev->name != NULL ) free( (void *) dev->name ); /* MEM_010 */ + dev->name = NULL; + } + sNumDevices = 0; + return paNoError; +} + +/************************************************************************* + PaHost_Init() is the library initialization function - call this before + using the library. +*/ +PaError PaHost_Init( void ) +{ + PaError err; + NumVersionVariant version; + + version.parts = SndSoundManagerVersion(); + DBUG(("SndSoundManagerVersion = 0x%x\n", version.whole)); + + /* Have we already initialized the device info? */ + err = (PaError) Pa_CountDevices(); + if( err < 0 ) return err; + else return paNoError; +} + +/************************************************************************* + PaMac_ScanOutputDevices() queries the properties of all output devices. +*/ +static PaError PaMac_ScanOutputDevices( void ) +{ + PaError err; + Component identifier=0; + ComponentDescription criteria = { kSoundOutputDeviceType, 0, 0, 0, 0 }; + long numComponents, i; + + /* Search the system linked list for output components */ + numComponents = CountComponents (&criteria); + identifier = 0; + sDefaultOutputDeviceID = sNumDevices; /* FIXME - query somehow */ + for (i = 0; i < numComponents; i++) + { + /* passing nil returns first matching component. */ + identifier = FindNextComponent( identifier, &criteria); + sDevices[sNumDevices].pad_Identifier = identifier; + + /* Set up for default OUTPUT devices. */ + err = PaMac_QueryOutputDeviceInfo( identifier, &sDevices[sNumDevices] ); + if( err < 0 ) return err; + else sNumDevices++; + + } + + return paNoError; +} + +/************************************************************************* + PaMac_ScanInputDevices() queries the properties of all input devices. +*/ +static PaError PaMac_ScanInputDevices( void ) +{ + Str255 deviceName; + int count; + Handle iconHandle; + PaError err; + OSErr oserr; + count = 1; + sDefaultInputDeviceID = sNumDevices; /* FIXME - query somehow */ /* PLB20010415 - was setting sDefaultOutputDeviceID */ + while(true) + { + /* Thanks Chris Rolfe and Alberto Ricci for this trick. */ + oserr = SPBGetIndexedDevice(count++, deviceName, &iconHandle); + DBUG(("PaMac_ScanInputDevices: SPBGetIndexedDevice returned %d\n", oserr )); +#if 1 + /* PLB20010415 - was giving error for anything other than siBadSoundInDevice, but some Macs may return other errors! */ + if(oserr != noErr) break; /* Some type of error is expected when count > devices */ +#else + if(oserr == siBadSoundInDevice) + { /* it's expected when count > devices */ + oserr = noErr; + break; + } + if(oserr != noErr) + { + ERR_RPT(("ERROR: SPBGetIndexedDevice(%d,,) returned %d\n", count-1, oserr )); + sPaHostError = oserr; + return paHostError; + } +#endif + DisposeHandle(iconHandle); /* Don't need the icon */ + + err = PaMac_QueryInputDeviceInfo( deviceName, &sDevices[sNumDevices] ); + DBUG(("PaMac_ScanInputDevices: PaMac_QueryInputDeviceInfo returned %d\n", err )); + if( err < 0 ) return err; + else if( err == 1 ) sNumDevices++; + } + + return paNoError; +} + +/* Sample rate info returned by using siSampleRateAvailable selector in SPBGetDeviceInfo() */ +/* Thanks to Chris Rolfe for help with this query. */ +#pragma options align=mac68k +typedef struct +{ + int16 numRates; + UnsignedFixed (**rates)[]; /* Handle created by SPBGetDeviceInfo */ +} +SRateInfo; +#pragma options align=reset + +/************************************************************************* +** PaMac_QueryOutputDeviceInfo() +** Query information about a named output device. +** Clears contents of ipad and writes info based on queries. +** Return one if OK, +** zero if device cannot be used, +** or negative error. +*/ +static PaError PaMac_QueryOutputDeviceInfo( Component identifier, internalPortAudioDevice *ipad ) +{ + int len; + OSErr err; + PaDeviceInfo *dev = &ipad->pad_Info; + SRateInfo srinfo = {0}; + int numRates; + ComponentDescription tempD; + Handle nameH=nil, infoH=nil, iconH=nil; + + memset( ipad, 0, sizeof(internalPortAudioDevice) ); + + dev->structVersion = 1; + dev->maxInputChannels = 0; + dev->maxOutputChannels = 2; + dev->nativeSampleFormats = paInt16; /* FIXME - query to see if 24 or 32 bit data can be handled. */ + + /* Get sample rates supported. */ + err = GetSoundOutputInfo(identifier, siSampleRateAvailable, (Ptr) &srinfo); + if(err != noErr) + { + ERR_RPT(("Error in PaMac_QueryOutputDeviceInfo: GetSoundOutputInfo siSampleRateAvailable returned %d\n", err )); + goto error; + } + numRates = srinfo.numRates; + DBUG(("PaMac_QueryOutputDeviceInfo: srinfo.numRates = 0x%x\n", srinfo.numRates )); + if( numRates == 0 ) + { + dev->numSampleRates = -1; + numRates = 2; + } + else + { + dev->numSampleRates = numRates; + } + dev->sampleRates = PaMac_GetSampleRatesFromHandle( numRates, (Handle) srinfo.rates ); + /* SPBGetDeviceInfo created the handle, but it's OUR job to release it. */ + DisposeHandle((Handle) srinfo.rates); + + /* Device name */ + /* we pass an existing handle for the component name; + we don't care about the info (type, subtype, etc.) or icon, so set them to nil */ + infoH = nil; + iconH = nil; + nameH = NewHandle(0); + if(nameH == nil) return paInsufficientMemory; + err = GetComponentInfo(identifier, &tempD, nameH, infoH, iconH); + if (err) + { + ERR_RPT(("Error in PaMac_QueryOutputDeviceInfo: GetComponentInfo returned %d\n", err )); + goto error; + } + len = (*nameH)[0] + 1; + dev->name = (char *) malloc(len); /* MEM_010 */ + if( dev->name == NULL ) + { + DisposeHandle(nameH); + return paInsufficientMemory; + } + else + { + PToCString((unsigned char *)(*nameH), (char *) dev->name); + DisposeHandle(nameH); + } + + DBUG(("PaMac_QueryOutputDeviceInfo: dev->name = %s\n", dev->name )); + return paNoError; + +error: + sPaHostError = err; + return paHostError; + +} + +/************************************************************************* +** PaMac_QueryInputDeviceInfo() +** Query information about a named input device. +** Clears contents of ipad and writes info based on queries. +** Return one if OK, +** zero if device cannot be used, +** or negative error. +*/ +static PaError PaMac_QueryInputDeviceInfo( Str255 deviceName, internalPortAudioDevice *ipad ) +{ + PaError result = paNoError; + int len; + OSErr err; + long mRefNum = 0; + long tempL; + int16 tempS; + Fixed tempF; + PaDeviceInfo *dev = &ipad->pad_Info; + SRateInfo srinfo = {0}; + int numRates; + + memset( ipad, 0, sizeof(internalPortAudioDevice) ); + dev->maxOutputChannels = 0; + + /* Open device based on name. If device is in use, it may not be able to open in write mode. */ + err = SPBOpenDevice( deviceName, siWritePermission, &mRefNum); + if (err) + { + /* If device is in use, it may not be able to open in write mode so try read mode. */ + err = SPBOpenDevice( deviceName, siReadPermission, &mRefNum); + if (err) + { + ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBOpenDevice returned %d\n", err )); + sPaHostError = err; + return paHostError; + } + } + + /* Define macros for printing out device info. */ +#define PrintDeviceInfo(selector,var) \ + err = SPBGetDeviceInfo(mRefNum, selector, (Ptr) &var); \ + if (err) { \ + DBUG(("query %s failed\n", #selector )); \ + }\ + else { \ + DBUG(("query %s = 0x%x\n", #selector, var )); \ + } + + PrintDeviceInfo( siContinuous, tempS ); + PrintDeviceInfo( siAsync, tempS ); + PrintDeviceInfo( siNumberChannels, tempS ); + PrintDeviceInfo( siSampleSize, tempS ); + PrintDeviceInfo( siSampleRate, tempF ); + PrintDeviceInfo( siChannelAvailable, tempS ); + PrintDeviceInfo( siActiveChannels, tempL ); + PrintDeviceInfo( siDeviceBufferInfo, tempL ); + + err = SPBGetDeviceInfo(mRefNum, siActiveChannels, (Ptr) &tempL); + if (err == 0) DBUG(("%s = 0x%x\n", "siActiveChannels", tempL )); + /* Can we use this device? */ + err = SPBGetDeviceInfo(mRefNum, siAsync, (Ptr) &tempS); + if (err) + { + ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBGetDeviceInfo siAsync returned %d\n", err )); + goto error; + } + if( tempS == 0 ) goto useless; /* Does not support async recording so forget about it. */ + + err = SPBGetDeviceInfo(mRefNum, siChannelAvailable, (Ptr) &tempS); + if (err) + { + ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBGetDeviceInfo siChannelAvailable returned %d\n", err )); + goto error; + } + dev->maxInputChannels = tempS; + + /* Get sample rates supported. */ + err = SPBGetDeviceInfo(mRefNum, siSampleRateAvailable, (Ptr) &srinfo); + if (err) + { + ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBGetDeviceInfo siSampleRateAvailable returned %d\n", err )); + goto error; + } + + numRates = srinfo.numRates; + DBUG(("numRates = 0x%x\n", numRates )); + if( numRates == 0 ) + { + dev->numSampleRates = -1; + numRates = 2; + } + else + { + dev->numSampleRates = numRates; + } + dev->sampleRates = PaMac_GetSampleRatesFromHandle( numRates, (Handle) srinfo.rates ); + /* SPBGetDeviceInfo created the handle, but it's OUR job to release it. */ + DisposeHandle((Handle) srinfo.rates); + + /* Get size of device buffer. */ + err = SPBGetDeviceInfo(mRefNum, siDeviceBufferInfo, (Ptr) &tempL); + if (err) + { + ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBGetDeviceInfo siDeviceBufferInfo returned %d\n", err )); + goto error; + } + ipad->pad_DeviceBufferSize = tempL; + DBUG(("siDeviceBufferInfo = %d\n", tempL )); + + /* Set format based on sample size. */ + err = SPBGetDeviceInfo(mRefNum, siSampleSize, (Ptr) &tempS); + if (err) + { + ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBGetDeviceInfo siSampleSize returned %d\n", err )); + goto error; + } + switch( tempS ) + { + case 0x0020: + dev->nativeSampleFormats = paInt32; /* FIXME - warning, code probably won't support this! */ + break; + case 0x0010: + default: /* FIXME - What about other formats? */ + dev->nativeSampleFormats = paInt16; + break; + } + DBUG(("nativeSampleFormats = %d\n", dev->nativeSampleFormats )); + + /* Device name */ + len = deviceName[0] + 1; /* Get length of Pascal string */ + dev->name = (char *) malloc(len); /* MEM_010 */ + if( dev->name == NULL ) + { + result = paInsufficientMemory; + goto cleanup; + } + PToCString(deviceName, (char *) dev->name); + DBUG(("deviceName = %s\n", dev->name )); + result = (PaError) 1; + /* All done so close up device. */ +cleanup: + if( mRefNum ) SPBCloseDevice(mRefNum); + return result; + +error: + if( mRefNum ) SPBCloseDevice(mRefNum); + sPaHostError = err; + return paHostError; + +useless: + if( mRefNum ) SPBCloseDevice(mRefNum); + return (PaError) 0; +} + +/************************************************************************* +** Allocate a double array and fill it with listed sample rates. +*/ +static double * PaMac_GetSampleRatesFromHandle ( int numRates, Handle h ) +{ + OSErr err = noErr; + SInt8 hState; + int i; + UnsignedFixed *fixedRates; + double *rates = (double *) malloc( numRates * sizeof(double) ); /* MEM_011 */ + if( rates == NULL ) return NULL; + /* Save and restore handle state as suggested by TechNote at: + http://developer.apple.com/technotes/tn/tn1122.html + */ + hState = HGetState (h); + if (!(err = MemError ())) + { + HLock (h); + if (!(err = MemError ( ))) + { + fixedRates = (UInt32 *) *h; + for( i=0; i<numRates; i++ ) + { + rates[i] = UnsignedFixedToDouble(fixedRates[i]); + } + + HSetState (h,hState); + err = MemError ( ); + } + } + if( err ) + { + free( rates ); + ERR_RPT(("Error in PaMac_GetSampleRatesFromHandle = %d\n", err )); + } + return rates; +} + +/*************************************************************************/ +int Pa_CountDevices() +{ + PaError err; + DBUG(("Pa_CountDevices()\n")); + /* If no devices, go find some. */ + if( sNumDevices <= 0 ) + { + err = PaMac_ScanOutputDevices(); + if( err != paNoError ) goto error; + err = PaMac_ScanInputDevices(); + if( err != paNoError ) goto error; + } + return sNumDevices; + +error: + PaHost_Term(); + DBUG(("Pa_CountDevices: returns %d\n", err )); + return err; + +} + +/*************************************************************************/ +const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceID id ) +{ + if( (id < 0) || ( id >= Pa_CountDevices()) ) return NULL; + return &sDevices[id].pad_Info; +} +/*************************************************************************/ +PaDeviceID Pa_GetDefaultInputDeviceID( void ) +{ + return sDefaultInputDeviceID; +} + +/*************************************************************************/ +PaDeviceID Pa_GetDefaultOutputDeviceID( void ) +{ + return sDefaultOutputDeviceID; +} + +/********************************* BEGIN CPU UTILIZATION MEASUREMENT ****/ +static void PaMac_StartLoadCalculation( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + UnsignedWide widePad; + if( pahsc == NULL ) return; + /* Query system timer for usage analysis and to prevent overuse of CPU. */ + Microseconds( &widePad ); + pahsc->pahsc_EntryCount = UnsignedWideToUInt64( widePad ); +} + +/****************************************************************************** +** Measure fractional CPU load based on real-time it took to calculate +** buffers worth of output. +*/ +/**************************************************************************/ +static void PaMac_EndLoadCalculation( internalPortAudioStream *past ) +{ + UnsignedWide widePad; + UInt64 currentCount; + long usecsElapsed; + double newUsage; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return; + + /* Measure CPU utilization during this callback. Note that this calculation + ** assumes that we had the processor the whole time. + */ +#define LOWPASS_COEFFICIENT_0 (0.95) +#define LOWPASS_COEFFICIENT_1 (0.99999 - LOWPASS_COEFFICIENT_0) + Microseconds( &widePad ); + currentCount = UnsignedWideToUInt64( widePad ); + + usecsElapsed = (long) U64Subtract(currentCount, pahsc->pahsc_EntryCount); + + /* Use inverse because it is faster than the divide. */ + newUsage = usecsElapsed * pahsc->pahsc_InverseMicrosPerHostBuffer; + + past->past_Usage = (LOWPASS_COEFFICIENT_0 * past->past_Usage) + + (LOWPASS_COEFFICIENT_1 * newUsage); + +} + +/*********************************************************************** +** Called by Pa_StartStream() +*/ +PaError PaHost_StartInput( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + pahsc->pahsc_IsRecording = 0; + pahsc->pahsc_StopRecording = 0; + pahsc->pahsc_InputMultiBuffer.nextWrite = 0; + pahsc->pahsc_InputMultiBuffer.nextRead = 0; + return PaMac_RecordNext( past ); +} + +/*********************************************************************** +** Called by Pa_StopStream(). +** May be called during error recovery or cleanup code +** so protect against NULL pointers. +*/ +PaError PaHost_StopInput( internalPortAudioStream *past, int abort ) +{ + int32 timeOutMsec; + PaError result = paNoError; + OSErr err = 0; + long mRefNum; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; + + (void) abort; + + mRefNum = pahsc->pahsc_InputRefNum; + + DBUG(("PaHost_StopInput: mRefNum = %d\n", mRefNum )); + if( mRefNum ) + { + DBUG(("PaHost_StopInput: pahsc_IsRecording = %d\n", pahsc->pahsc_IsRecording )); + if( pahsc->pahsc_IsRecording ) + { + /* PLB20010420 - Fix TIMEOUT in record mode. */ + pahsc->pahsc_StopRecording = 1; /* Request that we stop recording. */ + err = SPBStopRecording(mRefNum); + DBUG(("PaHost_StopInput: then pahsc_IsRecording = %d\n", pahsc->pahsc_IsRecording )); + + /* Calculate timeOut longer than longest time it could take to play one buffer. */ + timeOutMsec = (int32) ((1500.0 * pahsc->pahsc_FramesPerHostBuffer) / past->past_SampleRate); + /* Keep querying sound channel until it is no longer busy playing. */ + while( !err && pahsc->pahsc_IsRecording && (timeOutMsec > 0)) + { + Pa_Sleep(20); + timeOutMsec -= 20; + } + if( timeOutMsec <= 0 ) + { + ERR_RPT(("PaHost_StopInput: timed out!\n")); + return paTimedOut; + } + } + } + if( err ) + { + sPaHostError = err; + result = paHostError; + } + + DBUG(("PaHost_StopInput: finished.\n", mRefNum )); + return result; +} + +/***********************************************************************/ +static void PaMac_InitSoundHeader( internalPortAudioStream *past, CmpSoundHeader *sndHeader ) +{ + sndHeader->numChannels = past->past_NumOutputChannels; + sndHeader->sampleRate = DoubleToUnsignedFixed(past->past_SampleRate); + sndHeader->loopStart = 0; + sndHeader->loopEnd = 0; + sndHeader->encode = cmpSH; + sndHeader->baseFrequency = kMiddleC; + sndHeader->markerChunk = nil; + sndHeader->futureUse2 = nil; + sndHeader->stateVars = nil; + sndHeader->leftOverSamples = nil; + sndHeader->compressionID = 0; + sndHeader->packetSize = 0; + sndHeader->snthID = 0; + sndHeader->sampleSize = 8 * sizeof(int16); // FIXME - might be 24 or 32 bits some day; + sndHeader->sampleArea[0] = 0; + sndHeader->format = kSoundNotCompressed; +} + +static void SetFramesDone( PaHostSoundControl *pahsc, PaTimestamp framesDone ) +{ + UnsignedWide now; + Microseconds( &now ); + pahsc->pahsc_NumFramesDone = framesDone; + pahsc->pahsc_WhenFramesDoneIncremented = UnsignedWideToUInt64( now ); +} + +/***********************************************************************/ +PaError PaHost_StartOutput( internalPortAudioStream *past ) +{ + SndCommand pauseCommand; + SndCommand resumeCommand; + int i; + OSErr error; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paInternalError; + if( pahsc->pahsc_Channel == NULL ) return paInternalError; + + past->past_StopSoon = 0; + past->past_IsActive = 1; + pahsc->pahsc_NumOutsQueued = 0; + pahsc->pahsc_NumOutsPlayed = 0; + + SetFramesDone( pahsc, 0.0 ); + + /* Pause channel so it does not do back ground processing while we are still filling the queue. */ + pauseCommand.cmd = pauseCmd; + pauseCommand.param1 = pauseCommand.param2 = 0; + error = SndDoCommand (pahsc->pahsc_Channel, &pauseCommand, true); + if (noErr != error) goto exit; + + /* Queue all of the buffers so we start off full. */ + for (i = 0; i<pahsc->pahsc_NumHostBuffers; i++) + { + PaMac_PlayNext( past, i ); + } + + /* Resume channel now that the queue is full. */ + resumeCommand.cmd = resumeCmd; + resumeCommand.param1 = resumeCommand.param2 = 0; + error = SndDoImmediate( pahsc->pahsc_Channel, &resumeCommand ); + if (noErr != error) goto exit; + + return paNoError; +exit: + past->past_IsActive = 0; + sPaHostError = error; + ERR_RPT(("Error in PaHost_StartOutput: SndDoCommand returned %d\n", error )); + return paHostError; +} + +/*******************************************************************/ +long PaHost_GetTotalBufferFrames( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + return (long) (pahsc->pahsc_NumHostBuffers * pahsc->pahsc_FramesPerHostBuffer); +} + +/*********************************************************************** +** Called by Pa_StopStream(). +** May be called during error recovery or cleanup code +** so protect against NULL pointers. +*/ +PaError PaHost_StopOutput( internalPortAudioStream *past, int abort ) +{ + int32 timeOutMsec; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; + if( pahsc->pahsc_Channel == NULL ) return paNoError; + + DBUG(("PaHost_StopOutput()\n")); + if( past->past_IsActive == 0 ) return paNoError; + + /* Set flags for callback function to see. */ + if( abort ) past->past_StopNow = 1; + past->past_StopSoon = 1; + /* Calculate timeOut longer than longest time it could take to play all buffers. */ + timeOutMsec = (int32) ((1500.0 * PaHost_GetTotalBufferFrames( past )) / past->past_SampleRate); + /* Keep querying sound channel until it is no longer busy playing. */ + while( past->past_IsActive && (timeOutMsec > 0)) + { + Pa_Sleep(20); + timeOutMsec -= 20; + } + if( timeOutMsec <= 0 ) + { + ERR_RPT(("PaHost_StopOutput: timed out!\n")); + return paTimedOut; + } + else return paNoError; +} + +/***********************************************************************/ +PaError PaHost_StartEngine( internalPortAudioStream *past ) +{ + (void) past; /* Prevent unused variable warnings. */ + return paNoError; +} + +/***********************************************************************/ +PaError PaHost_StopEngine( internalPortAudioStream *past, int abort ) +{ + (void) past; /* Prevent unused variable warnings. */ + (void) abort; /* Prevent unused variable warnings. */ + return paNoError; +} +/***********************************************************************/ +PaError PaHost_StreamActive( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + return (PaError) ( past->past_IsActive + pahsc->pahsc_IsRecording ); +} +int Mac_IsVirtualMemoryOn( void ) +{ + long attr; + OSErr result = Gestalt( gestaltVMAttr, &attr ); + DBUG(("gestaltVMAttr : 0x%x\n", attr )); + return ((attr >> gestaltVMHasPagingControl ) & 1); +} + +/******************************************************************* +* Determine number of host Buffers +* and how many User Buffers we can put into each host buffer. +*/ +static void PaHost_CalcNumHostBuffers( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + int32 minNumBuffers; + int32 minFramesPerHostBuffer; + int32 minTotalFrames; + int32 userBuffersPerHostBuffer; + int32 framesPerHostBuffer; + int32 numHostBuffers; + + minFramesPerHostBuffer = pahsc->pahsc_MinFramesPerHostBuffer; + minFramesPerHostBuffer = (minFramesPerHostBuffer + 7) & ~7; + DBUG(("PaHost_CalcNumHostBuffers: minFramesPerHostBuffer = %d\n", minFramesPerHostBuffer )); + + /* Determine number of user buffers based on minimum latency. */ + /* PLB20020417 I used to call Pa_GetMinNumBuffers() which doesn't take into account the + ** variable minFramesPerHostBuffer. Now I call PaMac_GetMinNumBuffers() which will + ** gove lower latency when virtual memory is turned off. */ + /* minNumBuffers = Pa_GetMinNumBuffers( past->past_FramesPerUserBuffer, past->past_SampleRate ); WRONG */ + minNumBuffers = PaMac_GetMinNumBuffers( minFramesPerHostBuffer, past->past_FramesPerUserBuffer, past->past_SampleRate ); + + past->past_NumUserBuffers = ( minNumBuffers > past->past_NumUserBuffers ) ? minNumBuffers : past->past_NumUserBuffers; + DBUG(("PaHost_CalcNumHostBuffers: min past_NumUserBuffers = %d\n", past->past_NumUserBuffers )); + minTotalFrames = past->past_NumUserBuffers * past->past_FramesPerUserBuffer; + + /* We cannot make the buffers too small because they may not get serviced quickly enough. */ + if( (int32) past->past_FramesPerUserBuffer < minFramesPerHostBuffer ) + { + userBuffersPerHostBuffer = + (minFramesPerHostBuffer + past->past_FramesPerUserBuffer - 1) / + past->past_FramesPerUserBuffer; + } + else + { + userBuffersPerHostBuffer = 1; + } + framesPerHostBuffer = past->past_FramesPerUserBuffer * userBuffersPerHostBuffer; + + /* Calculate number of host buffers needed. Round up to cover minTotalFrames. */ + numHostBuffers = (minTotalFrames + framesPerHostBuffer - 1) / framesPerHostBuffer; + /* Make sure we have enough host buffers. */ + if( numHostBuffers < PA_MIN_NUM_HOST_BUFFERS) + { + numHostBuffers = PA_MIN_NUM_HOST_BUFFERS; + } + else + { + /* If we have too many host buffers, try to put more user buffers in a host buffer. */ + while(numHostBuffers > PA_MAX_NUM_HOST_BUFFERS) + { + userBuffersPerHostBuffer += 1; + framesPerHostBuffer = past->past_FramesPerUserBuffer * userBuffersPerHostBuffer; + numHostBuffers = (minTotalFrames + framesPerHostBuffer - 1) / framesPerHostBuffer; + } + } + + pahsc->pahsc_UserBuffersPerHostBuffer = userBuffersPerHostBuffer; + pahsc->pahsc_FramesPerHostBuffer = framesPerHostBuffer; + pahsc->pahsc_NumHostBuffers = numHostBuffers; + DBUG(("PaHost_CalcNumHostBuffers: pahsc_UserBuffersPerHostBuffer = %d\n", pahsc->pahsc_UserBuffersPerHostBuffer )); + DBUG(("PaHost_CalcNumHostBuffers: pahsc_NumHostBuffers = %d\n", pahsc->pahsc_NumHostBuffers )); + DBUG(("PaHost_CalcNumHostBuffers: pahsc_FramesPerHostBuffer = %d\n", pahsc->pahsc_FramesPerHostBuffer )); + DBUG(("PaHost_CalcNumHostBuffers: past_NumUserBuffers = %d\n", past->past_NumUserBuffers )); +} + +/*******************************************************************/ +PaError PaHost_OpenStream( internalPortAudioStream *past ) +{ + OSErr err; + PaError result = paHostError; + PaHostSoundControl *pahsc; + int i; + /* Allocate and initialize host data. */ + pahsc = (PaHostSoundControl *) PaHost_AllocateFastMemory(sizeof(PaHostSoundControl)); + if( pahsc == NULL ) + { + return paInsufficientMemory; + } + past->past_DeviceData = (void *) pahsc; + + /* If recording, and virtual memory is turned on, then use bigger buffers to prevent glitches. */ + if( (past->past_NumInputChannels > 0) && Mac_IsVirtualMemoryOn() ) + { + pahsc->pahsc_MinFramesPerHostBuffer = MAC_VIRTUAL_FRAMES_PER_BUFFER; + } + else + { + pahsc->pahsc_MinFramesPerHostBuffer = MAC_PHYSICAL_FRAMES_PER_BUFFER; + } + + PaHost_CalcNumHostBuffers( past ); + + /* Setup constants for CPU load measurement. */ + pahsc->pahsc_InverseMicrosPerHostBuffer = past->past_SampleRate / (1000000.0 * pahsc->pahsc_FramesPerHostBuffer); + + /* ------------------ OUTPUT */ + if( past->past_NumOutputChannels > 0 ) + { + /* Create sound channel to which we can send commands. */ + pahsc->pahsc_Channel = 0L; + err = SndNewChannel(&pahsc->pahsc_Channel, sampledSynth, 0, nil); /* FIXME - use kUseOptionalOutputDevice if not default. */ + if(err != 0) + { + ERR_RPT(("Error in PaHost_OpenStream: SndNewChannel returned 0x%x\n", err )); + goto error; + } + + /* Install our callback function pointer straight into the sound channel structure */ + /* Use new CARBON name for callback procedure. */ +#if TARGET_API_MAC_CARBON + pahsc->pahsc_OutputCompletionProc = NewSndCallBackUPP(PaMac_OutputCompletionProc); +#else + pahsc->pahsc_OutputCompletionProc = NewSndCallBackProc(PaMac_OutputCompletionProc); +#endif + + pahsc->pahsc_Channel->callBack = pahsc->pahsc_OutputCompletionProc; + + pahsc->pahsc_BytesPerOutputHostBuffer = pahsc->pahsc_FramesPerHostBuffer * past->past_NumOutputChannels * sizeof(int16); + for (i = 0; i<pahsc->pahsc_NumHostBuffers; i++) + { + char *buf = (char *)PaHost_AllocateFastMemory(pahsc->pahsc_BytesPerOutputHostBuffer); + if (buf == NULL) + { + ERR_RPT(("Error in PaHost_OpenStream: could not allocate output buffer. Size = \n", pahsc->pahsc_BytesPerOutputHostBuffer )); + goto memerror; + } + + PaMac_InitSoundHeader( past, &pahsc->pahsc_SoundHeaders[i] ); + pahsc->pahsc_SoundHeaders[i].samplePtr = buf; + pahsc->pahsc_SoundHeaders[i].numFrames = (unsigned long) pahsc->pahsc_FramesPerHostBuffer; + + } + } +#ifdef SUPPORT_AUDIO_CAPTURE + /* ------------------ INPUT */ + /* Use double buffer scheme that matches output. */ + if( past->past_NumInputChannels > 0 ) + { + int16 tempS; + long tempL; + Fixed tempF; + long mRefNum; + unsigned char noname = 0; /* FIXME - use real device names. */ +#if TARGET_API_MAC_CARBON + pahsc->pahsc_InputCompletionProc = NewSICompletionUPP((SICompletionProcPtr)PaMac_InputCompletionProc); +#else + pahsc->pahsc_InputCompletionProc = NewSICompletionProc((ProcPtr)PaMac_InputCompletionProc); +#endif + pahsc->pahsc_BytesPerInputHostBuffer = pahsc->pahsc_FramesPerHostBuffer * past->past_NumInputChannels * sizeof(int16); + for (i = 0; i<pahsc->pahsc_NumHostBuffers; i++) + { + char *buf = (char *) PaHost_AllocateFastMemory(pahsc->pahsc_BytesPerInputHostBuffer); + if ( buf == NULL ) + { + ERR_RPT(("PaHost_OpenStream: could not allocate input buffer. Size = \n", pahsc->pahsc_BytesPerInputHostBuffer )); + goto memerror; + } + pahsc->pahsc_InputMultiBuffer.buffers[i] = buf; + } + pahsc->pahsc_InputMultiBuffer.numBuffers = pahsc->pahsc_NumHostBuffers; + + err = SPBOpenDevice( (const unsigned char *) &noname, siWritePermission, &mRefNum); /* FIXME - use name so we get selected device */ + // FIXME err = SPBOpenDevice( (const unsigned char *) sDevices[past->past_InputDeviceID].pad_Info.name, siWritePermission, &mRefNum); + if (err) goto error; + pahsc->pahsc_InputRefNum = mRefNum; + DBUG(("PaHost_OpenStream: mRefNum = %d\n", mRefNum )); + + /* Set input device characteristics. */ + tempS = 1; + err = SPBSetDeviceInfo(mRefNum, siContinuous, (Ptr) &tempS); + if (err) + { + ERR_RPT(("Error in PaHost_OpenStream: SPBSetDeviceInfo siContinuous returned %d\n", err )); + goto error; + } + + tempL = 0x03; + err = SPBSetDeviceInfo(mRefNum, siActiveChannels, (Ptr) &tempL); + if (err) + { + DBUG(("PaHost_OpenStream: setting siActiveChannels returned 0x%x. Error ignored.\n", err )); + } + + /* PLB20010908 - Use requested number of input channels. Thanks Dominic Mazzoni. */ + tempS = past->past_NumInputChannels; + err = SPBSetDeviceInfo(mRefNum, siNumberChannels, (Ptr) &tempS); + if (err) + { + ERR_RPT(("Error in PaHost_OpenStream: SPBSetDeviceInfo siNumberChannels returned %d\n", err )); + goto error; + } + + tempF = ((unsigned long)past->past_SampleRate) << 16; + err = SPBSetDeviceInfo(mRefNum, siSampleRate, (Ptr) &tempF); + if (err) + { + ERR_RPT(("Error in PaHost_OpenStream: SPBSetDeviceInfo siSampleRate returned %d\n", err )); + goto error; + } + + /* Setup record-parameter block */ + pahsc->pahsc_InputParams.inRefNum = mRefNum; + pahsc->pahsc_InputParams.milliseconds = 0; // not used + pahsc->pahsc_InputParams.completionRoutine = pahsc->pahsc_InputCompletionProc; + pahsc->pahsc_InputParams.interruptRoutine = 0; + pahsc->pahsc_InputParams.userLong = (long) past; + pahsc->pahsc_InputParams.unused1 = 0; + } +#endif /* SUPPORT_AUDIO_CAPTURE */ + DBUG(("PaHost_OpenStream: complete.\n")); + return paNoError; + +error: + PaHost_CloseStream( past ); + ERR_RPT(("PaHost_OpenStream: sPaHostError = 0x%x.\n", err )); + sPaHostError = err; + return paHostError; + +memerror: + PaHost_CloseStream( past ); + return paInsufficientMemory; +} + +/*********************************************************************** +** Called by Pa_CloseStream(). +** May be called during error recovery or cleanup code +** so protect against NULL pointers. +*/ +PaError PaHost_CloseStream( internalPortAudioStream *past ) +{ + PaError result = paNoError; + OSErr err = 0; + int i; + PaHostSoundControl *pahsc; + + DBUG(("PaHost_CloseStream( 0x%x )\n", past )); + + if( past == NULL ) return paBadStreamPtr; + + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; + + if( past->past_NumOutputChannels > 0 ) + { + /* TRUE means flush now instead of waiting for quietCmd to be processed. */ + if( pahsc->pahsc_Channel != NULL ) SndDisposeChannel(pahsc->pahsc_Channel, TRUE); + { + for (i = 0; i<pahsc->pahsc_NumHostBuffers; i++) + { + Ptr p = (Ptr) pahsc->pahsc_SoundHeaders[i].samplePtr; + if( p != NULL ) PaHost_FreeFastMemory( p, pahsc->pahsc_BytesPerOutputHostBuffer ); + } + } + } + + if( past->past_NumInputChannels > 0 ) + { + if( pahsc->pahsc_InputRefNum ) + { + err = SPBCloseDevice(pahsc->pahsc_InputRefNum); + pahsc->pahsc_InputRefNum = 0; + if( err ) + { + sPaHostError = err; + result = paHostError; + } + } + { + for (i = 0; i<pahsc->pahsc_InputMultiBuffer.numBuffers; i++) + { + Ptr p = (Ptr) pahsc->pahsc_InputMultiBuffer.buffers[i]; + if( p != NULL ) PaHost_FreeFastMemory( p, pahsc->pahsc_BytesPerInputHostBuffer ); + } + } + } + + past->past_DeviceData = NULL; + PaHost_FreeFastMemory( pahsc, sizeof(PaHostSoundControl) ); + + DBUG(("PaHost_CloseStream: complete.\n", past )); + return result; +} +/*************************************************************************/ +int Pa_GetMinNumBuffers( int framesPerUserBuffer, double sampleRate ) +{ +/* We use the MAC_VIRTUAL_FRAMES_PER_BUFFER because we might be recording. +** This routine doesn't have enough information to determine the best value +** and is being depracated. */ + return PaMac_GetMinNumBuffers( MAC_VIRTUAL_FRAMES_PER_BUFFER, framesPerUserBuffer, sampleRate ); +} +/*************************************************************************/ +static int PaMac_GetMinNumBuffers( int minFramesPerHostBuffer, int framesPerUserBuffer, double sampleRate ) +{ + int minUserPerHost = ( minFramesPerHostBuffer + framesPerUserBuffer - 1) / framesPerUserBuffer; + int numBufs = PA_MIN_NUM_HOST_BUFFERS * minUserPerHost; + if( numBufs < PA_MIN_NUM_HOST_BUFFERS ) numBufs = PA_MIN_NUM_HOST_BUFFERS; + (void) sampleRate; + return numBufs; +} + +/*************************************************************************/ +void Pa_Sleep( int32 msec ) +{ + EventRecord event; + int32 sleepTime, endTime; + /* Convert to ticks. Round up so we sleep a MINIMUM of msec time. */ + sleepTime = ((msec * 60) + 999) / 1000; + if( sleepTime < 1 ) sleepTime = 1; + endTime = TickCount() + sleepTime; + do + { + DBUGX(("Sleep for %d ticks.\n", sleepTime )); + /* Use WaitNextEvent() to sleep without getting events. */ + /* PLB20010907 - Pass unused event to WaitNextEvent instead of NULL to prevent + * Mac OSX crash. Thanks Dominic Mazzoni. */ + WaitNextEvent( 0, &event, sleepTime, NULL ); + sleepTime = endTime - TickCount(); + } + while( sleepTime > 0 ); +} +/*************************************************************************/ +int32 Pa_GetHostError( void ) +{ + int32 err = sPaHostError; + sPaHostError = 0; + return err; +} + +/************************************************************************* + * Allocate memory that can be accessed in real-time. + * This may need to be held in physical memory so that it is not + * paged to virtual memory. + * This call MUST be balanced with a call to PaHost_FreeFastMemory(). + */ +void *PaHost_AllocateFastMemory( long numBytes ) +{ + void *addr = NewPtrClear( numBytes ); + if( (addr == NULL) || (MemError () != 0) ) return NULL; + +#if (TARGET_API_MAC_CARBON == 0) + if( HoldMemory( addr, numBytes ) != noErr ) + { + DisposePtr( (Ptr) addr ); + return NULL; + } +#endif + return addr; +} + +/************************************************************************* + * Free memory that could be accessed in real-time. + * This call MUST be balanced with a call to PaHost_AllocateFastMemory(). + */ +void PaHost_FreeFastMemory( void *addr, long numBytes ) +{ + if( addr == NULL ) return; +#if TARGET_API_MAC_CARBON + (void) numBytes; +#else + UnholdMemory( addr, numBytes ); +#endif + DisposePtr( (Ptr) addr ); +} + +/*************************************************************************/ +PaTimestamp Pa_StreamTime( PortAudioStream *stream ) +{ + PaTimestamp framesDone1; + PaTimestamp framesDone2; + UInt64 whenIncremented; + UnsignedWide now; + UInt64 now64; + long microsElapsed; + long framesElapsed; + + PaHostSoundControl *pahsc; + internalPortAudioStream *past = (internalPortAudioStream *) stream; + if( past == NULL ) return paBadStreamPtr; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + +/* Capture information from audio thread. + * We have to be careful that we don't get interrupted in the middle. + * So we grab the pahsc_NumFramesDone twice and make sure it didn't change. + */ + do + { + framesDone1 = pahsc->pahsc_NumFramesDone; + whenIncremented = pahsc->pahsc_WhenFramesDoneIncremented; + framesDone2 = pahsc->pahsc_NumFramesDone; + } while( framesDone1 != framesDone2 ); + + /* Calculate how many microseconds have elapsed and convert to frames. */ + Microseconds( &now ); + now64 = UnsignedWideToUInt64( now ); + microsElapsed = U64Subtract( now64, whenIncremented ); + framesElapsed = microsElapsed * past->past_SampleRate * 0.000001; + + return framesDone1 + framesElapsed; +} + +/************************************************************************** +** Callback for Input, SPBRecord() +*/ +int gRecordCounter = 0; +int gPlayCounter = 0; +pascal void PaMac_InputCompletionProc(SPBPtr recParams) +{ + PaError result = paNoError; + int finished = 1; + internalPortAudioStream *past; + PaHostSoundControl *pahsc; + + gRecordCounter += 1; /* debug hack to see if engine running */ + + /* Get our PA data from Mac structure. */ + past = (internalPortAudioStream *) recParams->userLong; + if( past == NULL ) return; + + if( past->past_Magic != PA_MAGIC ) + { + AddTraceMessage("PaMac_InputCompletionProc: bad MAGIC, past", (long) past ); + AddTraceMessage("PaMac_InputCompletionProc: bad MAGIC, magic", (long) past->past_Magic ); + goto error; + } + pahsc = (PaHostSoundControl *) past->past_DeviceData; + past->past_NumCallbacks += 1; + + /* Have we been asked to stop recording? */ + if( (recParams->error == abortErr) || pahsc->pahsc_StopRecording ) goto error; + + /* If there are no output channels, then we need to call the user callback function from here. + * Otherwise we will call the user code during the output completion routine. + */ + if(past->past_NumOutputChannels == 0) + { + SetFramesDone( pahsc, + pahsc->pahsc_NumFramesDone + pahsc->pahsc_FramesPerHostBuffer ); + result = PaMac_CallUserLoop( past, NULL ); + } + + /* Did user code ask us to stop? If not, issue another recording request. */ + if( (result == paNoError) && (pahsc->pahsc_StopRecording == 0) ) + { + result = PaMac_RecordNext( past ); + if( result != paNoError ) pahsc->pahsc_IsRecording = 0; + } + else goto error; + + return; + +error: + pahsc->pahsc_IsRecording = 0; + pahsc->pahsc_StopRecording = 0; + return; +} + +/*********************************************************************** +** Called by either input or output completion proc. +** Grabs input data if any present, and calls PA conversion code, +** that in turn calls user code. +*/ +static PaError PaMac_CallUserLoop( internalPortAudioStream *past, int16 *outPtr ) +{ + PaError result = paNoError; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + int16 *inPtr = NULL; + int i; + + + /* Advance read index for sound input FIFO here, independantly of record/write process. */ + if(past->past_NumInputChannels > 0) + { + if( MultiBuffer_IsReadable( &pahsc->pahsc_InputMultiBuffer ) ) + { + inPtr = (int16 *) MultiBuffer_GetNextReadBuffer( &pahsc->pahsc_InputMultiBuffer ); + MultiBuffer_AdvanceReadIndex( &pahsc->pahsc_InputMultiBuffer ); + } + } + + /* Call user code enough times to fill buffer. */ + if( (inPtr != NULL) || (outPtr != NULL) ) + { + PaMac_StartLoadCalculation( past ); /* CPU usage */ + +#ifdef PA_MAX_USAGE_ALLOWED + /* If CPU usage exceeds limit, skip user callback to prevent hanging CPU. */ + if( past->past_Usage > PA_MAX_USAGE_ALLOWED ) + { + past->past_FrameCount += (PaTimestamp) pahsc->pahsc_FramesPerHostBuffer; + } + else +#endif + { + + for( i=0; i<pahsc->pahsc_UserBuffersPerHostBuffer; i++ ) + { + result = (PaError) Pa_CallConvertInt16( past, inPtr, outPtr ); + if( result != 0) + { + /* Recording might be in another process, so tell it to stop with a flag. */ + pahsc->pahsc_StopRecording = pahsc->pahsc_IsRecording; + break; + } + /* Advance sample pointers. */ + if(inPtr != NULL) inPtr += past->past_FramesPerUserBuffer * past->past_NumInputChannels; + if(outPtr != NULL) outPtr += past->past_FramesPerUserBuffer * past->past_NumOutputChannels; + } + } + + PaMac_EndLoadCalculation( past ); + } + return result; +} + +/*********************************************************************** +** Setup next recording buffer in FIFO and issue recording request to Snd Input Manager. +*/ +static PaError PaMac_RecordNext( internalPortAudioStream *past ) +{ + PaError result = paNoError; + OSErr err; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + /* Get pointer to next buffer to record into. */ + pahsc->pahsc_InputParams.bufferPtr = MultiBuffer_GetNextWriteBuffer( &pahsc->pahsc_InputMultiBuffer ); + + /* Advance write index if there is room. Otherwise keep writing same buffer. */ + if( MultiBuffer_IsWriteable( &pahsc->pahsc_InputMultiBuffer ) ) + { + MultiBuffer_AdvanceWriteIndex( &pahsc->pahsc_InputMultiBuffer ); + } + + AddTraceMessage("PaMac_RecordNext: bufferPtr", (long) pahsc->pahsc_InputParams.bufferPtr ); + AddTraceMessage("PaMac_RecordNext: nextWrite", pahsc->pahsc_InputMultiBuffer.nextWrite ); + + /* Setup parameters and issue an asynchronous recording request. */ + pahsc->pahsc_InputParams.bufferLength = pahsc->pahsc_BytesPerInputHostBuffer; + pahsc->pahsc_InputParams.count = pahsc->pahsc_BytesPerInputHostBuffer; + err = SPBRecord(&pahsc->pahsc_InputParams, true); + if( err ) + { + AddTraceMessage("PaMac_RecordNext: SPBRecord error ", err ); + sPaHostError = err; + result = paHostError; + } + else + { + pahsc->pahsc_IsRecording = 1; + } + return result; +} + +/************************************************************************** +** Callback for Output Playback() +** Return negative error, 0 to continue, 1 to stop. +*/ +long PaMac_FillNextOutputBuffer( internalPortAudioStream *past, int index ) +{ + PaHostSoundControl *pahsc; + long result = 0; + int finished = 1; + char *outPtr; + + gPlayCounter += 1; /* debug hack */ + + past->past_NumCallbacks += 1; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return -1; + /* Are we nested?! */ + if( pahsc->pahsc_IfInsideCallback ) return 0; + pahsc->pahsc_IfInsideCallback = 1; + /* Get pointer to buffer to fill. */ + outPtr = pahsc->pahsc_SoundHeaders[index].samplePtr; + /* Combine with any sound input, and call user callback. */ + result = PaMac_CallUserLoop( past, (int16 *) outPtr ); + + pahsc->pahsc_IfInsideCallback = 0; + return result; +} + +/************************************************************************************* +** Called by SoundManager when ready for another buffer. +*/ +static pascal void PaMac_OutputCompletionProc (SndChannelPtr theChannel, SndCommand * theCallBackCmd) +{ + internalPortAudioStream *past; + PaHostSoundControl *pahsc; + (void) theChannel; + (void) theCallBackCmd; + + /* Get our data from Mac structure. */ + past = (internalPortAudioStream *) theCallBackCmd->param2; + if( past == NULL ) return; + + pahsc = (PaHostSoundControl *) past->past_DeviceData; + pahsc->pahsc_NumOutsPlayed += 1; + + SetFramesDone( pahsc, + pahsc->pahsc_NumFramesDone + pahsc->pahsc_FramesPerHostBuffer ); + + PaMac_BackgroundManager( past, theCallBackCmd->param1 ); +} + +/*******************************************************************/ +static PaError PaMac_BackgroundManager( internalPortAudioStream *past, int index ) +{ + PaError result = paNoError; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + /* Has someone asked us to abort by calling Pa_AbortStream()? */ + if( past->past_StopNow ) + { + SndCommand command; + /* Clear the queue of any pending commands. */ + command.cmd = flushCmd; + command.param1 = command.param2 = 0; + SndDoImmediate( pahsc->pahsc_Channel, &command ); + /* Then stop currently playing buffer, if any. */ + command.cmd = quietCmd; + SndDoImmediate( pahsc->pahsc_Channel, &command ); + past->past_IsActive = 0; + } + /* Has someone asked us to stop by calling Pa_StopStream() + * OR has a user callback returned '1' to indicate finished. + */ + else if( past->past_StopSoon ) + { + if( (pahsc->pahsc_NumOutsQueued - pahsc->pahsc_NumOutsPlayed) <= 0 ) + { + past->past_IsActive = 0; /* We're finally done. */ + } + } + else + { + PaMac_PlayNext( past, index ); + } + return result; +} + +/************************************************************************************* +** Fill next buffer with sound and queue it for playback. +*/ +static void PaMac_PlayNext ( internalPortAudioStream *past, int index ) +{ + OSErr error; + long result; + SndCommand playCmd; + SndCommand callbackCmd; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + + /* If this was the last buffer, or abort requested, then just be done. */ + if ( past->past_StopSoon ) goto done; + + /* Load buffer with sound. */ + result = PaMac_FillNextOutputBuffer ( past, index ); + if( result > 0 ) past->past_StopSoon = 1; /* Stop generating audio but wait until buffers play. */ + else if( result < 0 ) goto done; + + /* Play the next buffer. */ + playCmd.cmd = bufferCmd; + playCmd.param1 = 0; + playCmd.param2 = (long) &pahsc->pahsc_SoundHeaders[ index ]; + error = SndDoCommand (pahsc->pahsc_Channel, &playCmd, true ); + if( error != noErr ) goto gotError; + + /* Ask for a callback when it is done. */ + callbackCmd.cmd = callBackCmd; + callbackCmd.param1 = index; + callbackCmd.param2 = (long)past; + error = SndDoCommand (pahsc->pahsc_Channel, &callbackCmd, true ); + if( error != noErr ) goto gotError; + pahsc->pahsc_NumOutsQueued += 1; + + return; + +gotError: + sPaHostError = error; +done: + return; +} diff --git a/src/audio/portaudio/pa_sgi/pa_sgi.c b/src/audio/portaudio/pa_sgi/pa_sgi.c new file mode 100644 index 0000000000000000000000000000000000000000..8b742d1ccd63e95b0873907bef1083f091e395e1 --- /dev/null +++ b/src/audio/portaudio/pa_sgi/pa_sgi.c @@ -0,0 +1,1417 @@ +/* + * $Id$ + * PortAudio Portable Real-Time Audio Library. + * Latest Version at: http://www.portaudio.com. + * Silicon Graphics (SGI) IRIX implementation by Pieter Suurmond. + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, 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. + * + * 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. + * + * 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. + * + */ +/** @file + @brief SGI IRIX AL implementation (according to V19 API version 2.0). + + @note This file started as a copy of pa_skeleton.c (v 1.1.2.35 2003/09/20), it + has nothing to do with the old V18 pa_sgi version: this implementation uses the + newer IRIX AL calls and uses pthreads instead of sproc. + + On IRIX, one may type './configure' followed by 'gmake' from the portaudio root + directory to build the static and shared libraries, as well as all the tests. + + On IRIX 6.5, using 'make' instead of 'gmake' may cause Makefile to fail. (This + happens on my machine: make does not understand syntax with 2 colons on a line, + like this: + $(TESTS): bin/%: [snip] + + Maybe this is due to an old make version(?), my only solution is: use gmake. + Anyway, all the tests compile well now, with GCC 3.3, as well as with MIPSpro 7.2.1. + Tested: + - paqa_devs ok, but at a certain point digital i/o fails: + TestAdvance: INPUT, device = 2, rate = 32000, numChannels = 1, format = 1 + Possibly, this is an illegal sr or number of channels for digital i/o. + - paqa_errs 13 of the tests run ok, but 5 of them give weird results. + + patest1 ok. + + patest_buffer ok. + + patest_callbackstop ok. + - patest_clip ok, but hear no difference between dithering turned OFF and ON. + + patest_hang ok. + + patest_latency ok. + + patest_leftright ok. + + patest_maxsines ok. + + patest_many ok. + + patest_multi_sine ok. + + patest_pink ok. + + patest_prime ok. + - patest_read_record ok, but playback stops a little earlier than 5 seconds it seems(?). + + patest_record ok. + + patest_ringmix ok. + + patest_saw ok. + + patest_sine ok. + + patest_sine8 ok. + - patest_sine_formats ok, FLOAT32 + INT16 + INT18 are OK, but UINT8 IS NOT OK! + + patest_sine_time ok. + + patest_start_stop ok, but under/overflow errors of course in the AL queue monitor. + + patest_stop ok. + - patest_sync ok? + + patest_toomanysines ok. + - patest_underflow ok? (stopping after SleepTime = 91: err=Stream is stopped) + - patest_wire ok. + + patest_write_sine ok. + + pa_devs ok. + Ok on an Indy, in both stereo and quadrophonic mode. + + pa_fuzz ok. + + pa_minlat ok. + + Worked on (or checked) proposals: + + 003: Improve Latency Specification OK, but not 100% sure: plus or minus 1 host buffer? + 004 OK: Allow Callbacks to Accept Variable Number of Frames per Buffer. + Simply using a fixed host buffer size. Very roughly implemented now, the adaption + to limited-requested latencies and samplerate may be improved. At least this + implementation chooses its own internal host buffer size (no coredumps any longer). + 005 OK: Blocking Read/Write Interface. + 006: Non-interleaved buffers seems OK? Covered by the buffer-processor and such?.... + 009 OK: Host error reporting should now be. + 010 OK: State Machine and State Querying Functions. + 011 OK: Renaming done. + 014 Implementation Style Guidelines (sorry, my braces are not ANSI style). + 015 OK: Callback Timestamps (During priming, though, these are still null!). + 016 OK: Use Structs for Pa_OpenStream() Parameters. + 019: Notify Client When All Buffers Have Played (Ok, covered by the buffer processor?) + 020 OK: Allow Callback to prime output stream (StartStream() should do the priming) + Should be tested more thoroughly for full duplex streams. (patest_prime seems ok). + + + @todo Underrun or overflow flags at some more places. + + @todo Callback Timestamps during priming. + + @todo Improve adaption to number of channels, samplerate and such when inventing + some frames per host buffer (when client requests 0). + + @todo Make a complete new version to support 'sproc'-applications. + Or could we manage that with some clever if-defs? + It must be clear which version we use (especially when using pa as lib!): + an irix-sproc() version or pthread version. + + @todo In Makefile.in: 'make clean' does not remove lib/libportaudio.so.0.0.19. + + Note: Even when mono-output is requested, with ALv7, the audio library opens + a outputs stereo. One can observe this in SGI's 'Audio Queue Monitor'. +*/ + +#include <string.h> /* For strlen() but also for strerror()! */ +#include <stdio.h> /* printf() */ +#include <math.h> /* fabs() */ + +#include <dmedia/audio.h> /* IRIX AL (audio library). Link with -laudio. */ +#include <dmedia/dmedia.h> /* IRIX DL (digital media library), solely for */ + /* function dmGetUST(). Link with -ldmedia. */ +#include <errno.h> /* To catch 'oserror' after AL-calls. */ +#include <pthread.h> /* POSIX threads. */ +#include <unistd.h> + +#include "pa_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" + + /* Uncomment for diagnostics: */ +#define DBUG(x) /*{ printf x; fflush(stdout); }*/ + + +/* prototypes for functions declared in this file */ + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +PaError PaSGI_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *ipp, + const PaStreamParameters *opp, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); +static PaError CloseStream( PaStream* stream ); +static PaError StartStream( PaStream *stream ); +static PaError StopStream( PaStream *stream ); +static PaError AbortStream( PaStream *stream ); +static PaError IsStreamStopped( PaStream *s ); +static PaError IsStreamActive( PaStream *stream ); +static PaTime GetStreamTime( PaStream *stream ); +static double GetStreamCpuLoad( PaStream* stream ); +static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames ); +static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames ); +static signed long GetStreamReadAvailable( PaStream* stream ); +static signed long GetStreamWriteAvailable( PaStream* stream ); + + +/* + Apparently, we must use macros for reporting unanticipated host errors. + Only in case we return paUnanticipatedHostError from an Portaudio call, + we have to call one of the following three macro's. + (Constant paAL is defined in pa_common/portaudio.h. See also proposal 009.) + + After an AL error, use this to translate the AL error code to human text: +*/ +#define PA_SGI_SET_LAST_AL_ERROR() \ + {\ + int ee = oserror();\ + PaUtil_SetLastHostErrorInfo(paAL, ee, alGetErrorString(ee));\ + } +/* + But after a generic IRIX error, let strerror() translate the error code from + the operating system and use this (strerror() gives the same as perror()): +*/ +#define PA_SGI_SET_LAST_IRIX_ERROR() \ + {\ + int ee = oserror();\ + PaUtil_SetLastHostErrorInfo(paAL, ee, strerror(ee));\ + } + +/* GOT RID OF calling PaUtil_SetLastHostErrorInfo() with 0 as error number. +- Weird samplerate difference became: paInvalidSampleRate. +- Failing to set AL queue size became: paInternalError + (Because I cannot decide between paBufferTooBig and paBufferTooSmall + because it may even the 'default AL queue size that failed... Or + should we introduce another error-code like 'paInvalidQueueSize'?... NO) +*/ + +/* PaSGIHostApiRepresentation - host api datastructure specific to this implementation */ + +typedef struct +{ + PaUtilHostApiRepresentation inheritedHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + PaUtilAllocationGroup* allocations; + /* implementation specific data goes here. */ + ALvalue* sgiDeviceIDs; /* Array of AL resource device numbers. */ + /* PaHostApiIndex hostApiIndex; Hu? As in the linux and oss files? */ +} +PaSGIHostApiRepresentation; + +/* + Initialises sgiDeviceIDs array. +*/ +PaError PaSGI_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + int e, i, deviceCount, def_in, def_out; + PaSGIHostApiRepresentation* SGIHostApi; + PaDeviceInfo* deviceInfoArray; + static const short numParams = 4; /* Array with name, samplerate, channels */ + ALpv y[numParams]; /* and type. */ + static const short maxDevNameChars = 32; /* Including the terminating null char. */ + char devName[maxDevNameChars]; /* Too lazy for dynamic alloc. */ + + /* DBUG(("PaSGI_Initialize() started.\n")); */ + SGIHostApi = (PaSGIHostApiRepresentation*)PaUtil_AllocateMemory(sizeof(PaSGIHostApiRepresentation)); + if( !SGIHostApi ) + { result = paInsufficientMemory; goto cleanup; } + SGIHostApi->allocations = PaUtil_CreateAllocationGroup(); + if( !SGIHostApi->allocations ) + { result = paInsufficientMemory; goto cleanup; } + *hostApi = &SGIHostApi->inheritedHostApiRep; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paAL; /* IRIX AL type id, was paInDevelopment. */ + (*hostApi)->info.name = "SGI IRIX AL"; + (*hostApi)->info.defaultInputDevice = paNoDevice; /* Set later. */ + (*hostApi)->info.defaultOutputDevice = paNoDevice; /* Set later. */ + (*hostApi)->info.deviceCount = 0; /* We 'll increment in the loop below. */ + + /* Determine the total number of input and output devices (thanks to Gary Scavone). */ + deviceCount = alQueryValues(AL_SYSTEM, AL_DEVICES, 0, 0, 0, 0); + if (deviceCount < 0) /* Returns -1 in case of failure. */ + { + DBUG(("Failed to count devices: alQueryValues()=%d; %s.\n", + deviceCount, alGetErrorString(oserror()))); + result = paDeviceUnavailable; /* Is this an appropriate error return code? */ + goto cleanup; + } + if (deviceCount > 0) + { + (*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + SGIHostApi->allocations, sizeof(PaDeviceInfo*) * deviceCount); + if (!(*hostApi)->deviceInfos) + { result = paInsufficientMemory; goto cleanup; } + + /* Allocate all device info structs in a contiguous block. */ + deviceInfoArray = (PaDeviceInfo*)PaUtil_GroupAllocateMemory( + SGIHostApi->allocations, sizeof(PaDeviceInfo) * deviceCount); + if (!deviceInfoArray) + { result = paInsufficientMemory; goto cleanup; } + /* Store all AL device IDs in an array. */ + SGIHostApi->sgiDeviceIDs = (ALvalue*)PaUtil_GroupAllocateMemory(SGIHostApi->allocations, + deviceCount * sizeof(ALvalue)); + if (!SGIHostApi->sgiDeviceIDs) + { result = paInsufficientMemory; goto cleanup; } + /* Same query again, but now store all IDs in array sgiDeviceIDs (still using no qualifiers).*/ + e = alQueryValues(AL_SYSTEM, AL_DEVICES, SGIHostApi->sgiDeviceIDs, deviceCount, 0, 0); + if (e != deviceCount) + { + if (e < 0) /* Sure an AL error really occurred. */ + { PA_SGI_SET_LAST_AL_ERROR() result = paUnanticipatedHostError; } + else /* Seems we lost some devices. */ + { DBUG(("Number of devices suddenly changed!\n")); result = paDeviceUnavailable; } + goto cleanup; + } + y[0].param = AL_DEFAULT_INPUT; + y[1].param = AL_DEFAULT_OUTPUT; + e = alGetParams(AL_SYSTEM, y, 2); /* Get params global to the AL system. */ + if (e != 2) + { + if (e < 0) + { + PA_SGI_SET_LAST_AL_ERROR() /* Calls oserror() and alGetErrorString(). */ + result = paUnanticipatedHostError; /* Sure an AL error really occurred. */ + } + else + { + DBUG(("Default input and/or output could not be found!\n")); + result = paDeviceUnavailable; /* FIX: What if only in or out are available? */ + } + goto cleanup; + } + def_in = y[0].value.i; /* Remember both AL devices for a while. */ + def_out = y[1].value.i; + y[0].param = AL_NAME; + y[0].value.ptr = devName; + y[0].sizeIn = maxDevNameChars; /* Including terminating null char. */ + y[1].param = AL_RATE; + y[2].param = AL_CHANNELS; + y[3].param = AL_TYPE; /* Subtype of AL_INPUT_DEVICE_TYPE or AL_OUTPUT_DEVICE_TYPE? */ + for (i=0; i < deviceCount; ++i) /* Fill allocated deviceInfo structs. */ + { + PaDeviceInfo *deviceInfo = &deviceInfoArray[i]; + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; /* Retrieve name, samplerate, channels and type. */ + e = alGetParams(SGIHostApi->sgiDeviceIDs[i].i, y, numParams); + if (e != numParams) + { + if (e < 0) /* Calls oserror() and alGetErrorString(). */ + { PA_SGI_SET_LAST_AL_ERROR() result = paUnanticipatedHostError; } + else + { DBUG(("alGetParams() could not get all params!\n")); result = paInternalError; } + goto cleanup; + } + deviceInfo->name = (char*)PaUtil_GroupAllocateMemory(SGIHostApi->allocations, strlen(devName) + 1); + if (!deviceInfo->name) + { result = paInsufficientMemory; goto cleanup; } + strcpy((char*)deviceInfo->name, devName); + + /* Determine whether the received number of channels belongs to input or output device. */ + if (alIsSubtype(AL_INPUT_DEVICE_TYPE, y[3].value.i)) + { + deviceInfo->maxInputChannels = y[2].value.i; + deviceInfo->maxOutputChannels = 0; + } + else if (alIsSubtype(AL_OUTPUT_DEVICE_TYPE, y[3].value.i)) + { + deviceInfo->maxInputChannels = 0; + deviceInfo->maxOutputChannels = y[2].value.i; + } + else /* Should never occur. */ + { + DBUG(("AL device is neither input nor output!\n")); + result = paInternalError; + goto cleanup; + } + + /* Determine if this device is the default (in or out). If so, assign. */ + if (def_in == SGIHostApi->sgiDeviceIDs[i].i) + { + if ((*hostApi)->info.defaultInputDevice != paNoDevice) + { + DBUG(("Default input already assigned!\n")); + result = paInternalError; + goto cleanup; + } + (*hostApi)->info.defaultInputDevice = i; + /* DBUG(("Default input assigned to pa device %d (%s).\n", i, deviceInfo->name)); */ + } + else if (def_out == SGIHostApi->sgiDeviceIDs[i].i) + { + if ((*hostApi)->info.defaultOutputDevice != paNoDevice) + { + DBUG(("Default output already assigned!\n")); + result = paInternalError; + goto cleanup; + } + (*hostApi)->info.defaultOutputDevice = i; + /* DBUG(("Default output assigned to pa device %d (%s).\n", i, deviceInfo->name)); */ + } + /*---------------------------------------------- Default latencies set to 'reasonable' values. */ + deviceInfo->defaultLowInputLatency = 0.050; /* 50 milliseconds seems ok. */ + deviceInfo->defaultLowOutputLatency = 0.050; /* These are ALSO ABSOLUTE MINIMA in OpenStream(). */ + deviceInfo->defaultHighInputLatency = 0.500; /* 500 milliseconds a reasonable value? */ + deviceInfo->defaultHighOutputLatency = 0.500; /* Ten times these are ABSOLUTE MAX in OpenStream()). */ + + deviceInfo->defaultSampleRate = alFixedToDouble(y[1].value.ll); /* Read current sr. */ + (*hostApi)->deviceInfos[i] = deviceInfo; + ++(*hostApi)->info.deviceCount; + } + } + /* What if (deviceCount==0)? */ + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PaUtil_InitializeStreamInterface(&SGIHostApi->callbackStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface(&SGIHostApi->blockingStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable ); +cleanup: + if (result != paNoError) + { + if (SGIHostApi) + { + if (SGIHostApi->allocations) + { + PaUtil_FreeAllAllocations(SGIHostApi->allocations); + PaUtil_DestroyAllocationGroup(SGIHostApi->allocations); + } + PaUtil_FreeMemory(SGIHostApi); + } + } + return result; +} + + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaSGIHostApiRepresentation *SGIHostApi = (PaSGIHostApiRepresentation*)hostApi; + + /* Clean up any resources not handled by the allocation group. */ + if( SGIHostApi->allocations ) + { + PaUtil_FreeAllAllocations( SGIHostApi->allocations ); + PaUtil_DestroyAllocationGroup( SGIHostApi->allocations ); + } + PaUtil_FreeMemory( SGIHostApi ); +} + +/* + Check if samplerate is supported for this output device. Called once + or twice by function IsFormatSupported() and one time by OpenStream(). + When paUnanticipatedHostError is returned, the caller does NOT have + to call PA_SGI_SET_LAST_AL_ERROR() or such. +*/ +static PaError sr_supported(int al_device, double sr) +{ + int e; + PaError result; + ALparamInfo pinfo; + long long lsr; /* 64 bit fixed point internal AL samplerate. */ + + if (alGetParamInfo(al_device, AL_RATE, &pinfo)) + { + e = oserror(); + DBUG(("alGetParamInfo(AL_RATE) failed: %s.\n", alGetErrorString(e))); + if (e == AL_BAD_RESOURCE) + result = paInvalidDevice; + else + { + PA_SGI_SET_LAST_AL_ERROR() /* Sure an AL error occured. */ + result = paUnanticipatedHostError; + } + } + else + { + lsr = alDoubleToFixed(sr); /* Within the range? */ + if ((pinfo.min.ll <= lsr) && (lsr <= pinfo.max.ll)) + result = paFormatIsSupported; + else + result = paInvalidSampleRate; + } + /* DBUG(("sr_supported()=%d.\n", result)); */ + return result; +} + + +/* + See common/portaudio.h (suggestedLatency field is ignored). +*/ +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + PaSGIHostApiRepresentation* SGIHostApi = (PaSGIHostApiRepresentation*)hostApi; + int inputChannelCount, outputChannelCount, result; + PaSampleFormat inputSampleFormat, outputSampleFormat; + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + /* Unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification. */ + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + /* Check that input device can support inputChannelCount. */ + if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + /* Validate inputStreamInfo. */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + /* Check if samplerate is supported for this input device. */ + result = sr_supported(SGIHostApi->sgiDeviceIDs[inputParameters->device].i, sampleRate); + if (result != paFormatIsSupported) /* PA_SGI_SET_LAST_AL_ERROR() may already be called. */ + return result; + } + else + { + inputChannelCount = 0; + } + if( outputParameters ) /* As with input above. */ + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + /* Check if samplerate is supported for this output device. */ + result = sr_supported(SGIHostApi->sgiDeviceIDs[outputParameters->device].i, sampleRate); + if (result != paFormatIsSupported) + return result; + } + else + { + outputChannelCount = 0; + } + /* IMPLEMENT ME: + Because the buffer adapter handles conversion between all standard + sample formats, the following checks are only required if paCustomFormat + is implemented, or under some other unusual conditions. + + - check that input device can support inputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + + - check that output device can support outputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + */ + /* suppress unused variable warnings */ + (void) inputSampleFormat; + (void) outputSampleFormat; + return paFormatIsSupported; +} + +/** Auxilary struct, embedded twice in the struct below, for inputs and outputs. */ +typedef struct PaSGIhostPortBuffer +{ + /** NULL means IRIX AL port closed. */ + ALport port; + /** NULL means memory not allocated. */ + void* buffer; +} + PaSGIhostPortBuffer; + +/** Stream data structure specifically for this IRIX AL implementation. */ +typedef struct PaSGIStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + unsigned long framesPerHostCallback; + /** Allocated host buffers and AL ports. */ + PaSGIhostPortBuffer hostPortBuffIn, + hostPortBuffOut; + /** Copy of stream flags given to OpenStream(). */ + PaStreamFlags streamFlags; + /** Stream state may be 0 or 1 or 2, but never 3. */ + unsigned char state; + /** Requests to stop or abort may come from the parent, + or from the child itself (user callback result). */ + unsigned char stopAbort; + pthread_t thread; +} + PaSGIStream; + +/** Stream can be in only one of the following three states: stopped (1), active (2), or + callback finshed (0). To prevent 'state 3' from occurring, Setting and testing of the + state bits is done atomically. +*/ +#define PA_SGI_STREAM_FLAG_FINISHED_ (0) /* After callback finished or cancelled queued buffers. */ +#define PA_SGI_STREAM_FLAG_STOPPED_ (1) /* Set by OpenStream(), StopStream() and AbortStream(). */ +#define PA_SGI_STREAM_FLAG_ACTIVE_ (2) /* Set by StartStream. Reset by OpenStream(), */ + /* StopStream() and AbortStream(). */ + +/** Stop requests, via the 'stopAbort' field can be either 1, meaning 'stop' or 2, meaning 'abort'. + When both occur at the same time, 'abort' takes precedence, even after a first 'stop'. +*/ +#define PA_SGI_REQ_CONT_ (0) /* Reset by OpenStream(), StopStream and AbortStream. */ +#define PA_SGI_REQ_STOP_ (1) /* Set by StopStream(). */ +#define PA_SGI_REQ_ABORT_ (2) /* Set by AbortStream(). */ + + +/** Called by OpenStream() once or twice. First, the number of channels, sampleformat, and + queue size are configured. The configuration is then bound to the specified AL device. + Then an AL port is opened. Finally, the samplerate of the device is altered (or at least + set again). + + After successful return, actual latency is written in *latency, and actual samplerate + in *samplerate. + + @param pa_params may be NULL and pa_params->channelCount may also be null, in both + cases the function immediately returns. + @return paNoError if configuration was skipped or if it succeeded. +*/ +static PaError set_sgi_device(ALvalue* sgiDeviceIDs, /* Array built by PaSGI_Initialize(). */ + const PaStreamParameters* pa_params, /* read device and channels. */ + double* latency, /* Read and write in seconds. */ + + PaSampleFormat pasfmt, /* Don't read from pa_params!. */ + char* direction, /* "r" or "w". */ + char* name, + long framesPerHostBuffer, + double* samplerate, /* Also writes back here. */ + PaSGIhostPortBuffer* hostPortBuff) /* Receive pointers here. */ +{ + int bytesPerFrame, sgiDevice, alErr, d, dd, iq_size, default_iq_size; + ALpv pvs[2]; + ALconfig alc = NULL; + PaError result = paNoError; + + if (!pa_params) + goto cleanup; /* Not errors, just not full duplex, skip all. */ + if (!pa_params->channelCount) + goto cleanup; + alc = alNewConfig(); /* Create default config. This defaults to stereo, 16-bit integer data. */ + if (!alc) /* Call alFreeConfig() later, when done with it. */ + { result = paInsufficientMemory; goto cleanup; } + /*----------------------- CONFIGURE NUMBER OF CHANNELS: ---------------------------*/ + if (alSetChannels(alc, pa_params->channelCount)) /* Returns 0 on success. */ + { + if (oserror() == AL_BAD_CHANNELS) + result = paInvalidChannelCount; + else + { + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + } + goto cleanup; + } + bytesPerFrame = pa_params->channelCount; /* Is multiplied by width below. */ + /*----------------------- CONFIGURE SAMPLE FORMAT: --------------------------------*/ + if (pasfmt == paFloat32) + { + if (alSetSampFmt(alc, AL_SAMPFMT_FLOAT)) + { + if (oserror() == AL_BAD_SAMPFMT) + result = paSampleFormatNotSupported; + else + { + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + } + goto cleanup; + } + bytesPerFrame *= 4; /* No need to set width for floats. */ + } + else + { + if (alSetSampFmt(alc, AL_SAMPFMT_TWOSCOMP)) + { + if (oserror() == AL_BAD_SAMPFMT) + result = paSampleFormatNotSupported; + else + { + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + } + goto cleanup; + } + if (pasfmt == paInt8) + { + if (alSetWidth(alc, AL_SAMPLE_8)) + { + if (oserror() == AL_BAD_WIDTH) + result = paSampleFormatNotSupported; + else + { + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + } + goto cleanup; + } + /* bytesPerFrame *= 1; */ + } + else if (pasfmt == paInt16) + { + if (alSetWidth(alc, AL_SAMPLE_16)) + { + if (oserror() == AL_BAD_WIDTH) + result = paSampleFormatNotSupported; + else + { + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + } + goto cleanup; + } + bytesPerFrame *= 2; + } + else if (pasfmt == paInt24) + { + if (alSetWidth(alc, AL_SAMPLE_24)) + { + if (oserror() == AL_BAD_WIDTH) + result = paSampleFormatNotSupported; + else + { + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + } + goto cleanup; + } + bytesPerFrame *= 3; /* OR 4 ???????! */ + } + else return paSampleFormatNotSupported; + } + /*----------------------- SET INTERNAL AL QUEUE SIZE: -------------------------------*/ + /* The AL API doesn't provide a means for querying minimum and maximum buffer sizes. + So, if the requested size fails, try again with a value that is closer to the AL's + default queue size. In this implementation, 'Portaudio latency' corresponds to + the AL queue size minus one buffersize: + AL queue size - framesPerHostBuffer + PA latency = ----------------------------------- + sample rate */ + default_iq_size = alGetQueueSize(alc); + if (default_iq_size < 0) /* So let's first get that 'default size'. */ + { /* Default internal queue size could not be determined. */ + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + goto cleanup; + } + /* AL buffer becomes somewhat bigger than the suggested latency, notice this is */ + /* based on requsted samplerate, not in the actual rate, which is measured later. */ + /* Do NOT read pa_params->suggestedLatency, but use the limited *latency param! */ + + iq_size = (int)(0.5 + ((*latency) * (*samplerate))) + (int)framesPerHostBuffer; + /* The AL buffer becomes somewhat */ + /* bigger than the suggested latency. */ + if (iq_size < (framesPerHostBuffer << 1)) /* Make sure the minimum is twice */ + { /* framesPerHostBuffer. */ + DBUG(("Setting minimum queue size.\n")); + iq_size = (framesPerHostBuffer << 1); + } + d = iq_size - default_iq_size; /* Determine whether we'll decrease */ + while (alSetQueueSize(alc, iq_size)) /* or increase after failing. */ + { /* Size in sample frames. */ + if (oserror() != AL_BAD_QSIZE) /* Stop at AL_BAD_CONFIG. */ + { + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + goto cleanup; + } + dd = iq_size - default_iq_size; /* Stop when even the default size failed */ + if (((d >= 0) && (dd <= 0)) || /* (dd=0) or when difference flipped sign. */ + ((d <= 0) && (dd >= 0)) || + (iq_size <= framesPerHostBuffer)) /* Also guarentee that framesPerHostBuffer */ + { /* can be subtracted (res>0) after return. */ + DBUG(("Could not set AL queue size to %d sample frames!\n", iq_size)); + result = paInternalError; /* FIX: PROBABLY AN INAPROPRIATE ERROR CODE HERE. */ + goto cleanup; /* As inapropriate as paUnanticipatedHostError was? */ + } + DBUG(("Failed to set internal queue size to %d frames, ", iq_size)); + if (d > 0) + iq_size -= framesPerHostBuffer; /* Try lesser multiple. */ + else + iq_size += framesPerHostBuffer; /* Try larger multiple. */ + DBUG(("trying %d frames now...\n", iq_size)); + } + /* Note: Actual latency is written back to *latency after meausuring actual (not + the requested) samplerate. See below. + */ + /*----------------------- ALLOCATE HOST BUFFER: --------------------------------------*/ + hostPortBuff->buffer = PaUtil_AllocateMemory((long)bytesPerFrame * framesPerHostBuffer); + if (!hostPortBuff->buffer) /* Caller is responsible for cleanup+close after failures! */ + { result = paInsufficientMemory; goto cleanup; } + /*----------------------- BIND CONFIGURATION TO DEVICE: ------------------------------*/ + sgiDevice = sgiDeviceIDs[pa_params->device].i; + if (alSetDevice(alc, sgiDevice)) /* Try to switch the hardware. */ + { + if (oserror() == AL_BAD_DEVICE) + result = paInvalidDevice; + else + { + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + } + goto cleanup; + } + /*----------------------- OPEN PORT: ----------------------------------------------*/ + hostPortBuff->port = alOpenPort(name, direction, alc); /* Caller is responsible */ + if (!hostPortBuff->port) /* for closing after fail. */ + { + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + goto cleanup; + } /* Maybe set SR earlier? */ + /*----------------------- SET SAMPLERATE: -----------------------------------------*/ + pvs[0].param = AL_MASTER_CLOCK; /* Attempt to set a crystal-based sample- */ + pvs[0].value.i = AL_CRYSTAL_MCLK_TYPE; /* rate on input or output device. */ + pvs[1].param = AL_RATE; + pvs[1].value.ll = alDoubleToFixed(*samplerate); + if (2 != alSetParams(sgiDevice, pvs, 2)) + { + DBUG(("alSetParams() failed to set samplerate to %.4f Hz!\n", *samplerate)); + result = paInvalidSampleRate; + goto cleanup; + } + /*----------------------- GET ACTUAL SAMPLERATE: ---------------------------*/ + alErr = alGetParams(sgiDevice, &pvs[1], 1); /* SEE WHAT WE REALY SET IT TO. */ + if (alErr != 1) /* And return that to caller. */ + { + DBUG(("alGetParams() failed to read samplerate!\n")); + result = paInvalidSampleRate; + goto cleanup; + } + *samplerate = alFixedToDouble(pvs[1].value.ll); /* Between 1 Hz and 1 MHz. */ + if ((*samplerate < 1.0) || (*samplerate > 1000000.0)) + { + DBUG(("alFixedToDouble() resulted a weird samplerate: %.6f Hz!\n", *samplerate)); + result = paInvalidSampleRate; + goto cleanup; + } + /*----------------------- CALC ACTUAL LATENCY (based on actual SR): -----------------------*/ + *latency = (iq_size - framesPerHostBuffer) / (*samplerate); /* FIX: SURE > 0!???? */ +cleanup: + if (alc) + alFreeConfig(alc); /* We no longer need configuration. */ + return result; +} + +/** + Called by OpenStream() if it fails and by CloseStream. Only used here, in this file. + Fields MUST be set to NULL or to a valid value, prior to call. +*/ +static void streamCleanupAndClose(PaSGIStream* stream) +{ + if (stream->hostPortBuffIn.port) alClosePort(stream->hostPortBuffIn.port); /* Close AL ports. */ + if (stream->hostPortBuffIn.buffer) PaUtil_FreeMemory(stream->hostPortBuffIn.buffer); /* Release buffers. */ + if (stream->hostPortBuffOut.port) alClosePort(stream->hostPortBuffOut.port); + if (stream->hostPortBuffOut.buffer) PaUtil_FreeMemory(stream->hostPortBuffOut.buffer); +} + + +/* See pa_hostapi.h for a list of validity guarantees made about OpenStream parameters. */ +static PaError OpenStream(struct PaUtilHostApiRepresentation* hostApi, + PaStream** s, + const PaStreamParameters* ipp, + const PaStreamParameters* opp, + double sampleRate, /* Common to both i and o. */ + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback* streamCallback, + void* userData) +{ + PaError result = paNoError; + PaSGIHostApiRepresentation* SGIHostApi = (PaSGIHostApiRepresentation*)hostApi; + PaSGIStream* stream = 0; + unsigned long framesPerHostBuffer; /* Not necessarily the same as framesPerBuffer. */ + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat, + hostInputSampleFormat, hostOutputSampleFormat; + double sr_in, sr_out, + latency_in, latency_out; + static const PaSampleFormat irixFormats = (paInt8 | paInt16 | paInt24 | paFloat32); + /* Constant used by PaUtil_SelectClosestAvailableFormat(). Because IRIX AL does not + provide a way to query for possible formats for a given device, interface or port, + just add together the formats we know that are supported in general by IRIX AL + (at the end of the year 2003): AL_SAMPFMT_TWOSCOMP with AL_SAMPLE_8(=paInt8), + AL_SAMPLE_16(=paInt16) or AL_SAMPLE_24(=paInt24); AL_SAMPFMT_FLOAT(=paFloat32); + AL_SAMPFMT_DOUBLE(=paFloat64); IRIX misses unsigned 8 and 32 bit signed ints. + */ + DBUG(("OpenStream() started.\n")); + if (ipp) + { + inputChannelCount = ipp->channelCount; + inputSampleFormat = ipp->sampleFormat; + /* Unless alternate device specification is supported, reject the use of paUseHostApiSpecificDeviceSpecification. */ + if (ipp->device == paUseHostApiSpecificDeviceSpecification) /* DEVICE CHOOSEN BY CLIENT. */ + return paInvalidDevice; + /* Check that input device can support inputChannelCount. */ + if (inputChannelCount > hostApi->deviceInfos[ipp->device]->maxInputChannels) + return paInvalidChannelCount; + /* Validate inputStreamInfo. */ + if (ipp->hostApiSpecificStreamInfo) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + hostInputSampleFormat = PaUtil_SelectClosestAvailableFormat(irixFormats, inputSampleFormat); + /* Check if samplerate is supported for this input device. */ + result = sr_supported(SGIHostApi->sgiDeviceIDs[ipp->device].i, sampleRate); + if (result != paFormatIsSupported) /* PA_SGI_SET_LAST_AL_ERROR() may already be called. */ + return result; + /* Validate input latency. Use defaults if necessary. */ + if (ipp->suggestedLatency < hostApi->deviceInfos[ipp->device]->defaultLowInputLatency) + latency_in = hostApi->deviceInfos[ipp->device]->defaultLowInputLatency; /* Low = minimum. */ + else if (ipp->suggestedLatency > 10.0 * hostApi->deviceInfos[ipp->device]->defaultHighInputLatency) + latency_in = 10.0 * hostApi->deviceInfos[ipp->device]->defaultHighInputLatency; /* 10*High = max. */ + else + latency_in = ipp->suggestedLatency; + } + else + { + inputChannelCount = 0; + inputSampleFormat = hostInputSampleFormat = paInt16; /* Surpress 'uninitialised var' warnings. */ + latency_in = 0.0; /* Necessary? */ + } + if (opp) + { + outputChannelCount = opp->channelCount; + outputSampleFormat = opp->sampleFormat; + if (opp->device == paUseHostApiSpecificDeviceSpecification) /* Like input (above). */ + return paInvalidDevice; + if (outputChannelCount > hostApi->deviceInfos[opp->device]->maxOutputChannels) + return paInvalidChannelCount; + if (opp->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + hostOutputSampleFormat = PaUtil_SelectClosestAvailableFormat(irixFormats, outputSampleFormat); + /* Check if samplerate is supported for this output device. */ + result = sr_supported(SGIHostApi->sgiDeviceIDs[opp->device].i, sampleRate); + if (result != paFormatIsSupported) + return result; + /* Validate output latency. Use defaults if necessary. */ + if (opp->suggestedLatency < hostApi->deviceInfos[opp->device]->defaultLowOutputLatency) + latency_out = hostApi->deviceInfos[opp->device]->defaultLowOutputLatency; /* Low = minimum. */ + else if (opp->suggestedLatency > 10.0 * hostApi->deviceInfos[opp->device]->defaultHighOutputLatency) + latency_out = 10.0 * hostApi->deviceInfos[opp->device]->defaultHighOutputLatency; /* 10*High = max. */ + else + latency_out = opp->suggestedLatency; + } + else + { + outputChannelCount = 0; + outputSampleFormat = hostOutputSampleFormat = paInt16; /* Surpress 'uninitialised var' warning. */ + latency_out = 0.0; + } + /* Sure that ipp and opp will never be both NULL. */ + + if( (streamFlags & paPlatformSpecificFlags) != 0 ) /* Validate platform specific flags. */ + return paInvalidFlag; /* Unexpected platform specific flag. */ + + stream = (PaSGIStream*)PaUtil_AllocateMemory( sizeof(PaSGIStream) ); + if (!stream) + { result = paInsufficientMemory; goto cleanup; } + + stream->hostPortBuffIn.port = (ALport)NULL; /* Ports closed. */ + stream->hostPortBuffIn.buffer = NULL; /* No buffers yet. */ + stream->hostPortBuffOut.port = (ALport)NULL; + stream->hostPortBuffOut.buffer = NULL; + + if (streamCallback) + PaUtil_InitializeStreamRepresentation(&stream->streamRepresentation, + &SGIHostApi->callbackStreamInterface, streamCallback, userData); + else + PaUtil_InitializeStreamRepresentation(&stream->streamRepresentation, + &SGIHostApi->blockingStreamInterface, streamCallback, userData); + /* (NULL.) */ + if (framesPerBuffer == paFramesPerBufferUnspecified) /* Proposal 004. */ + { /* Keep framesPerBuffer zero but come up with some fixed host buffer size. */ + double lowest_lat = 0.0; /* 0.0 to surpress uninit warning, we're sure it will end up higher. */ + if (ipp) + lowest_lat = latency_in; /* Sure > 0.0! */ + if (opp && (latency_out < lowest_lat)) + lowest_lat = latency_out; + /* So that queue size becomes approximately 5 times framesPerHostBuffer: */ + framesPerHostBuffer = (unsigned long)((lowest_lat * sampleRate) / 4.0); + /* But always limit: */ + if (framesPerHostBuffer < 64L) + framesPerHostBuffer = 64L; + else if (framesPerHostBuffer > 32768L) + framesPerHostBuffer = 32768L; + DBUG(("Decided to use a fixed host buffer size of %ld frames.\n", framesPerHostBuffer)); + } + else + framesPerHostBuffer = framesPerBuffer; /* Then just take the requested amount. No buffer-adaption yet? */ + + sr_in = sr_out = sampleRate; + /*-------------------------------------------------------------------------------------------*/ + result = set_sgi_device(SGIHostApi->sgiDeviceIDs, /* Needed by alSetDevice and other functs. */ + ipp, /* Reads channelCount, device but NOT latency. */ + &latency_in, /* Read limited requested latency but also WRITE actual. */ + hostInputSampleFormat, /* For alSetSampFmt and alSetWidth. */ + "r", /* "r" for reading from input port. */ + "portaudio in", /* Name string. */ + framesPerHostBuffer, /* As calculated or as requested by the client. */ + &sr_in, /* Receive actual s.rate after setting it. */ + &stream->hostPortBuffIn); /* Receives ALport and input host buffer. */ + if (result != paNoError) goto cleanup; + /*-------------------------------------------------------------------------------------------*/ + result = set_sgi_device(SGIHostApi->sgiDeviceIDs, + opp, + &latency_out, + hostOutputSampleFormat, + "w", /* "w" for writing. */ + "portaudio out", + framesPerHostBuffer, + &sr_out, + &stream->hostPortBuffOut); + if (result != paNoError) goto cleanup; + /*------------------------------------------------------------------------------------------*/ + if (fabs(sr_in - sr_out) > 0.001) /* Make sure both are the 'same'. */ + { + DBUG(("Weird samplerate difference between input and output!\n")); + result = paInvalidSampleRate; /* Could not come up with a better error code. */ + goto cleanup; + } /* sr_in '==' sr_out. */ + sampleRate = sr_in; /* Following fields set to estimated or actual values: */ + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + stream->streamRepresentation.streamInfo.inputLatency = latency_in; /* 0.0 if output only. */ + stream->streamRepresentation.streamInfo.outputLatency = latency_out; /* 0.0 if input only. */ + + PaUtil_InitializeCpuLoadMeasurer(&stream->cpuLoadMeasurer, sampleRate); + result = PaUtil_InitializeBufferProcessor(&stream->bufferProcessor, + inputChannelCount, inputSampleFormat, hostInputSampleFormat, + outputChannelCount, outputSampleFormat, hostOutputSampleFormat, + sampleRate, streamFlags, + framesPerBuffer, /* As requested by OpenStream(), may be zero! */ + framesPerHostBuffer, /* Use fixed number of frames per host buffer */ + paUtilFixedHostBufferSize, /* to keep things simple. See pa_common/pa_ */ + streamCallback, userData); /* process.h for more hostbuffersize options. */ + if (result != paNoError) + goto cleanup; + + stream->framesPerHostCallback = framesPerHostBuffer; + stream->streamFlags = streamFlags; /* Remember priming request. */ + stream->state = PA_SGI_STREAM_FLAG_STOPPED_; /* After opening, the stream */ + stream->stopAbort = PA_SGI_REQ_CONT_; /* is in the stopped state. */ + *s = (PaStream*)stream; /* Pass object to caller. */ +cleanup: + if (result != paNoError) /* Always set result when jumping to cleanup after failure. */ + { + if (stream) + { + streamCleanupAndClose(stream); /* Frees i/o buffers and closes AL ports. */ + PaUtil_FreeMemory(stream); + } + } + return result; +} + +/** POSIX thread that performs the actual i/o and calls the client's callback, + spawned by StartStream(). +*/ +static void* PaSGIpthread(void *userData) +{ + PaSGIStream* stream = (PaSGIStream*)userData; + int callbackResult = paContinue; + double nanosec_per_frame; + PaStreamCallbackTimeInfo timeInfo = { 0, 0, 0 }; + + stream->state = PA_SGI_STREAM_FLAG_ACTIVE_; /* Parent thread also sets active flag, but we + make no assumption about who does this first. */ + nanosec_per_frame = 1000000000.0 / stream->streamRepresentation.streamInfo.sampleRate; + /*----------------------------------------------- OUTPUT PRIMING: -----------------------------*/ + if (stream->hostPortBuffOut.port) /* Somewhat less than AL queue size so the next */ + { /* output buffer will (probably) not block. */ + unsigned long frames_to_prime = (long)(0.5 + + (stream->streamRepresentation.streamInfo.outputLatency + * stream->streamRepresentation.streamInfo.sampleRate)); + if (stream->streamFlags & paPrimeOutputBuffersUsingStreamCallback) + { + PaStreamCallbackFlags cbflags = paPrimingOutput; + if (stream->hostPortBuffIn.port) /* Only set this flag in case of full duplex. */ + cbflags |= paInputUnderflow; + DBUG(("Prime with client's callback: < %ld frames.\n", frames_to_prime)); + while (frames_to_prime >= stream->framesPerHostCallback) /* We will not do less (yet). */ + { /* TODO: Timestamps and CPU load */ + PaUtil_BeginBufferProcessing(&stream->bufferProcessor, /* measurement during priming. */ + &timeInfo, + cbflags); /* Pass underflow + priming flags. */ + if (stream->hostPortBuffIn.port) /* Does that provide client's call- */ + PaUtil_SetNoInput(&stream->bufferProcessor); /* back with silent inputbuffers? */ + + PaUtil_SetOutputFrameCount(&stream->bufferProcessor, 0); /* 0=take host buffer size. */ + PaUtil_SetInterleavedOutputChannels(&stream->bufferProcessor, 0, + stream->hostPortBuffOut.buffer, 0); + callbackResult = paContinue; /* Call the client's callback. */ + frames_to_prime -= PaUtil_EndBufferProcessing(&stream->bufferProcessor, &callbackResult); + if (callbackResult == paAbort) + { /* What should we do in other cases */ + stream->stopAbort = PA_SGI_REQ_ABORT_; /* where (callbackResult!=paContinue). */ + break; /* Don't even output the samples just returned (also skip following while). */ + } + else /* Write interleaved samples to SGI device. */ + alWriteFrames(stream->hostPortBuffOut.port, stream->hostPortBuffOut.buffer, + stream->framesPerHostCallback); + } + } + else /* Prime with silence. */ + { + DBUG(("Prime with silence: %ld frames.\n", frames_to_prime)); + alZeroFrames(stream->hostPortBuffOut.port, frames_to_prime); + } + } + /*------------------------------------------------------ I/O: ---------------------------------*/ + while (!stream->stopAbort) /* Exit loop immediately when 'stop' or 'abort' are raised. */ + { + unsigned long framesProcessed; + stamp_t fn, t, fnd, td; /* Unsigned 64 bit. */ + + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + /* IMPLEMENT ME: - handle buffer slips. */ + if (stream->hostPortBuffIn.port) + { + /* Get device sample frame number associated with next audio sample frame + we're going to read from this port. */ + alGetFrameNumber(stream->hostPortBuffIn.port, &fn); + /* Get some recent pair of (frame number, time) from the audio device to + which our port is connected. time is 'UST' which is given in nanoseconds + and shared with the other audio devices and with other media. */ + alGetFrameTime(stream->hostPortBuffIn.port, &fnd, &td); + /* Calculate UST associated with fn, the next sample frame we're going to read or + write. Because this is signed arithmetic, code works for both inputs and outputs. */ + t = td + (stamp_t) ((double)(fn - fnd) * nanosec_per_frame); + /* If port is not in underflow or overflow state, we can alReadFrames() or + alWriteFrames() here and know that t is the time associated with the first + sample frame of the buffer we read or write. */ + timeInfo.inputBufferAdcTime = ((PaTime)t) / 1000000000.0; + /* Read interleaved samples from AL port (I think it will block only the first time). */ + alReadFrames(stream->hostPortBuffIn.port, stream->hostPortBuffIn.buffer, + stream->framesPerHostCallback); + } + if (stream->hostPortBuffOut.port) + { + alGetFrameNumber(stream->hostPortBuffOut.port, &fn); + alGetFrameTime(stream->hostPortBuffOut.port, &fnd, &td); + t = td + (stamp_t) ((double)(fn - fnd) * nanosec_per_frame); + timeInfo.outputBufferDacTime = ((PaTime)t) / 1000000000.0; + } + dmGetUST((unsigned long long*)(&t)); /* Receive time in nanoseconds in t. */ + timeInfo.currentTime = ((PaTime)t) / 1000000000.0; + + /* If you need to byte swap or shift inputBuffer to convert it into a pa format, do it here. */ + PaUtil_BeginBufferProcessing(&stream->bufferProcessor, + &timeInfo, + 0 /* IMPLEMENT ME: pass underflow/overflow flags when necessary */); + + if (stream->hostPortBuffIn.port) /* Equivalent to (inputChannelCount > 0) */ + { /* We are sure about the amount to transfer (PaUtil_Set before alRead). */ + PaUtil_SetInputFrameCount(&stream->bufferProcessor, 0 /* 0 means take host buffer size */); + PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, + 0, /* first channel of inputBuffer is channel 0 */ + stream->hostPortBuffIn.buffer, + 0 ); /* 0 - use inputChannelCount passed to init buffer processor */ + } + if (stream->hostPortBuffOut.port) + { + PaUtil_SetOutputFrameCount(&stream->bufferProcessor, 0 /* 0 means take host buffer size */); + PaUtil_SetInterleavedOutputChannels(&stream->bufferProcessor, + 0, /* first channel of outputBuffer is channel 0 */ + stream->hostPortBuffOut.buffer, + 0 ); /* 0 - use outputChannelCount passed to init buffer processor */ + } + /* + You must pass a valid value of callback result to PaUtil_EndBufferProcessing() + in general you would pass paContinue for normal operation, and + paComplete to drain the buffer processor's internal output buffer. + You can check whether the buffer processor's output buffer is empty + using PaUtil_IsBufferProcessorOuputEmpty( bufferProcessor ) + */ + callbackResult = paContinue; /* Whoops, lost this somewhere, back again in v 1.2.2.16! */ + framesProcessed = PaUtil_EndBufferProcessing(&stream->bufferProcessor, &callbackResult); + /* If you need to byte swap or shift outputBuffer to convert it to host format, do it here. */ + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed ); + + if (callbackResult != paContinue) + { /* Once finished, call the finished callback. */ + DBUG(("SGI callbackResult = %d.\n", callbackResult)); + if (stream->streamRepresentation.streamFinishedCallback) + stream->streamRepresentation.streamFinishedCallback(stream->streamRepresentation.userData); + if (callbackResult == paAbort) + { + stream->stopAbort = PA_SGI_REQ_ABORT_; + break; /* Don't play the last buffer returned. */ + } + else /* paComplete or some other non-zero value. */ + stream->stopAbort = PA_SGI_REQ_STOP_; + } + if (stream->hostPortBuffOut.port) /* Write interleaved samples to SGI device */ + alWriteFrames(stream->hostPortBuffOut.port, /* (like unix_oss, AFTER checking callback result). */ + stream->hostPortBuffOut.buffer, stream->framesPerHostCallback); + } + if (stream->hostPortBuffOut.port) /* Drain output buffer(s), as long as we don't see an 'abort' request. */ + { + while ((!(stream->stopAbort & PA_SGI_REQ_ABORT_)) && /* Assume _STOP_ is set (or meant). */ + (alGetFilled(stream->hostPortBuffOut.port) > 1)) /* In case of ABORT we quickly leave (again). */ + ; /* Don't provide any new (not even silent) samples, but let an underrun [almost] occur. */ + } + if (callbackResult != paContinue) + stream->state = PA_SGI_STREAM_FLAG_FINISHED_; + return NULL; +} + + +/* + When CloseStream() is called, the multi-api layer ensures + that the stream has already been stopped or aborted. +*/ +static PaError CloseStream(PaStream* s) +{ + PaError result = paNoError; + PaSGIStream* stream = (PaSGIStream*)s; + + DBUG(("SGI CloseStream() started.\n")); + streamCleanupAndClose(stream); /* Releases i/o buffers and closes AL ports. */ + PaUtil_TerminateBufferProcessor(&stream->bufferProcessor); + PaUtil_TerminateStreamRepresentation(&stream->streamRepresentation); + PaUtil_FreeMemory(stream); + return result; +} + + +static PaError StartStream(PaStream *s) +{ + PaError result = paNoError; + PaSGIStream* stream = (PaSGIStream*)s; + + DBUG(("StartStream() started.\n")); + PaUtil_ResetBufferProcessor(&stream->bufferProcessor); /* See pa_common/pa_process.h. */ + if (stream->bufferProcessor.streamCallback) + { /* only when callback is used */ + if (pthread_create(&stream->thread, + NULL, /* pthread_attr_t * attr */ + PaSGIpthread, /* Function to spawn. */ + (void*)stream)) /* Pass stream as arg. */ + { + PA_SGI_SET_LAST_IRIX_ERROR() /* Let's hope oserror() tells something useful. */ + result = paUnanticipatedHostError; + } + else + stream->state = PA_SGI_STREAM_FLAG_ACTIVE_; + } /* Set active before returning from this function. */ + else + stream->state = PA_SGI_STREAM_FLAG_ACTIVE_; /* Apparently, setting active for blocking i/o is */ + return result; /* necessary (for patest_write_sine for example). */ +} + + +static PaError StopStream( PaStream *s ) +{ + PaError result = paNoError; + PaSGIStream* stream = (PaSGIStream*)s; + + if (stream->bufferProcessor.streamCallback) /* Only for callback streams. */ + { + stream->stopAbort = PA_SGI_REQ_STOP_; /* Signal and wait for the thread to drain outputs. */ + if (pthread_join(stream->thread, NULL)) /* When succesful, stream->state */ + { /* is still ACTIVE, or FINISHED. */ + PA_SGI_SET_LAST_IRIX_ERROR() + result = paUnanticipatedHostError; + } + else /* Transition from ACTIVE or FINISHED to STOPPED. */ + stream->state = PA_SGI_STREAM_FLAG_STOPPED_; + stream->stopAbort = PA_SGI_REQ_CONT_; /* For possible next start. */ + } +/* else + stream->state = PA_SGI_STREAM_FLAG_STOPPED_; Is this necessary for blocking i/o? */ + return result; +} + + +static PaError AbortStream( PaStream *s ) +{ + PaError result = paNoError; + PaSGIStream *stream = (PaSGIStream*)s; + + if (stream->bufferProcessor.streamCallback) /* Only for callback streams. */ + { + stream->stopAbort = PA_SGI_REQ_ABORT_; + if (pthread_join(stream->thread, NULL)) + { + PA_SGI_SET_LAST_IRIX_ERROR() + result = paUnanticipatedHostError; + } + else /* Transition from ACTIVE or FINISHED to STOPPED. */ + stream->state = PA_SGI_STREAM_FLAG_STOPPED_; + stream->stopAbort = PA_SGI_REQ_CONT_; /* For possible next start. */ + } +/* else + stream->state = PA_SGI_STREAM_FLAG_STOPPED_; Is this necessary for blocking i/o? */ + return result; +} + + +static PaError IsStreamStopped( PaStream *s ) /* Not just the opposite of IsStreamActive(): */ +{ /* in the 'callback finished' state, it */ + /* returns zero instead of nonzero. */ + if (((PaSGIStream*)s)->state & PA_SGI_STREAM_FLAG_STOPPED_) + return 1; + return 0; +} + + +static PaError IsStreamActive( PaStream *s ) +{ + if (((PaSGIStream*)s)->state & PA_SGI_STREAM_FLAG_ACTIVE_) + return 1; + return 0; +} + + +static PaTime GetStreamTime( PaStream *s ) +{ + stamp_t t; + + (void) s; /* Suppress unused argument warning. */ + dmGetUST((unsigned long long*)(&t)); /* Receive time in nanoseconds in t. */ + return (PaTime)t / 1000000000.0; +} + + +static double GetStreamCpuLoad( PaStream* s ) +{ + PaSGIStream *stream = (PaSGIStream*)s; + + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); +} + + +/* + As separate stream interfaces are used for blocking and callback + streams, the following functions can be guaranteed to only be called + for blocking streams. +*/ + +static PaError ReadStream( PaStream* s, + void *buffer, + unsigned long frames ) +{ + PaSGIStream* stream = (PaSGIStream*)s; + int n; + +printf("stream->framesPerHostCallback=%ld.\n", stream->framesPerHostCallback); +fflush(stdout); + + while (frames) + { + if (frames > stream->framesPerHostCallback) n = stream->framesPerHostCallback; + else n = frames; + /* Read interleaved samples from SGI device. */ + alReadFrames(stream->hostPortBuffIn.port, /* Port already opened by OpenStream(). */ + stream->hostPortBuffIn.buffer, n); /* Already allocated by OpenStream(). */ + /* alReadFrames() always returns 0. */ + PaUtil_SetInputFrameCount(&stream->bufferProcessor, 0); /* 0 means take host buffer size */ + PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, + 0, /* first channel of inputBuffer is channel 0 */ + stream->hostPortBuffIn.buffer, + 0 ); /* 0 means use inputChannelCount passed at init. */ + /* Copy samples from host input channels set up by the PaUtil_SetInterleavedInputChannels + to a user supplied buffer. */ +printf("frames=%ld, buffer=%ld\n", frames, (long)buffer); +fflush(stdout); + PaUtil_CopyInput(&stream->bufferProcessor, &buffer, n); + frames -= n; + } +printf("DONE: frames=%ld, buffer=%ld\n", frames, (long)buffer); + return paNoError; +} + + +static PaError WriteStream( PaStream* s, + const void *buffer, + unsigned long frames ) +{ + PaSGIStream* stream = (PaSGIStream*)s; + unsigned long n; + while (frames) + { + PaUtil_SetOutputFrameCount(&stream->bufferProcessor, 0); /* 0 means take host buffer size */ + PaUtil_SetInterleavedOutputChannels(&stream->bufferProcessor, + 0, /* first channel of inputBuffer is channel 0 */ + stream->hostPortBuffOut.buffer, + 0 ); /* 0 means use inputChannelCount passed at init. */ + /* Copy samples from user supplied buffer to host input channels set up by + PaUtil_SetInterleavedOutputChannels. Copies the minimum of the number of user frames + (specified by the frameCount parameter) and the number of host frames (specified in + a previous call to SetOutputFrameCount()). */ + n = PaUtil_CopyOutput(&stream->bufferProcessor, &buffer, frames); + /* Write interleaved samples to SGI device. */ + alWriteFrames(stream->hostPortBuffOut.port, stream->hostPortBuffOut.buffer, n); + frames -= n; /* alWriteFrames always returns 0. */ + } + return paNoError; +} + + +static signed long GetStreamReadAvailable( PaStream* s ) +{ + return (signed long)alGetFilled(((PaSGIStream*)s)->hostPortBuffIn.port); +} + + +static signed long GetStreamWriteAvailable( PaStream* s ) +{ + return (signed long)alGetFillable(((PaSGIStream*)s)->hostPortBuffOut.port); +} + + +/* CVS reminder: + To download the 'v19-devel' branch from portaudio's CVS server for the first time, type: + cvs -d:pserver:anonymous@www.portaudio.com:/home/cvs checkout -r v19-devel portaudio + Then 'cd' to the 'portaudio' directory that should have been created. + To commit changes: + cvs -d:pserver:pieter@www.portaudio.com:/home/cvs login + cvs -d:pserver:pieter@www.portaudio.com:/home/cvs commit -m 'blabla.' -r v19-devel pa_sgi/pa_sgi.c + cvs -d:pserver:pieter@www.portaudio.com:/home/cvs logout + To see if someone else worked on something: + cvs -d:pserver:anonymous@www.portaudio.com:/home/cvs update -r v19-devel + To get an older revision of a certain file (without sticky business): + cvs -d:pserver:anonymous@www.portaudio.com:/home/cvs update -p -r 1.1.1.1.2.4 pa_tests/patest1.c >pa_tests/patest1.c-OLD + To see logs: + cvs -d:pserver:anonymous@www.portaudio.com:/home/cvs log pa_common/pa_skeleton.c +*/ diff --git a/src/audio/portaudio/pa_tests/README.txt b/src/audio/portaudio/pa_tests/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..f3a0b822643ab8fc48a48ecdf1041c55ea05dd37 --- /dev/null +++ b/src/audio/portaudio/pa_tests/README.txt @@ -0,0 +1,60 @@ +This directory contains various programs to test PortAudio. The files +named patest_* are tests, the files named debug_* are just scratch +files that may or may not work. + +All following tests are up to date with the V19 API. They should all compile +(without any warnings on GCC 3.3). Note that this does not necissarily mean that +the tests pass, just that they compile. + + x- paqa_devs.c + x- paqa_errs.c (needs reviewing) + x- patest1.c + x- patest_buffer.c + x- patest_callbackstop.c + x- patest_clip.c (last test fails, dither doesn't currently force clip in V19) + x- patest_dither.c + x- patest_hang.c + x- patest_latency.c + x- patest_leftright.c + x- patest_longsine.c + x- patest_many.c + x- patest_maxsines.c + o- patest_mono.c + x- patest_multi_sine.c + x- patest_pink.c + x- patest_prime.c + x- patest_read_record.c + x- patest_record.c + x- patest_ringmix.c + x- patest_saw.c + x- patest_sine.c + x- patest_sine8.c + x- patest_sine_formats.c + x- patest_sine_time.c + x- patest_start_stop.c + x- patest_stop.c + x- patest_sync.c + x- patest_toomanysines.c + o- patest_two_rates.c + x- patest_underflow.c + x- patest_wire.c + x- patest_write_sine.c + x- pa_devs.c + x- pa_fuzz.c + x- pa_minlat.c + +The debug_ files are still in V18 format and may need some V19 adaption. +Feel free to fix them, most simply require adjusting to the new API. + +o- pa_tests/debug_convert.c +o- pa_tests/debug_dither_calc.c +o- pa_tests/debug_dual.c +o- pa_tests/debug_multi_in.c +o- pa_tests/debug_multi_out.c +o- pa_tests/debug_record.c +o- pa_tests/debug_record_reuse.c +o- pa_tests/debug_sine.c +o- pa_tests/debug_sine_amp.c +o- pa_tests/debug_sine_formats.c +o- pa_tests/debug_srate.c +o- pa_tests/debug_test1.c diff --git a/src/audio/portaudio/pa_tests/debug_convert.c b/src/audio/portaudio/pa_tests/debug_convert.c new file mode 100644 index 0000000000000000000000000000000000000000..a17b556e4816deb61f91a02088bfa8591f07243c --- /dev/null +++ b/src/audio/portaudio/pa_tests/debug_convert.c @@ -0,0 +1,131 @@ +/* + * $Id$ + * Convert tagged values. + * + * Author: Phil Burk <philburk@softsynth.com> + * + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" +#define OUTPUT_DEVICE (Pa_GetDefaultOutputDeviceID()) +//#define OUTPUT_DEVICE (11) +#define NUM_SECONDS (8) +#define SLEEP_DUR (800) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (256) + +#define NUM_BUFFERS (0) + +typedef struct +{ + unsigned int framesToGo; +} +paTestData; +/* 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 patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + short *out = (short*)outputBuffer; + int i; + int finished = 0; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + if( data->framesToGo < framesPerBuffer ) finished = 1; + + for( i=0; i<framesPerBuffer; i++ ) + { + *out++ = 0x0000 + i; /* left */ + *out++ = 0x1000 + i; /* right */ + } + return finished; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream; + PaError err; + paTestData data; + int i; + int totalSamps; + printf("PortAudio Test: output debug values\n" ); + data.framesToGo = totalSamps = NUM_SECONDS * SAMPLE_RATE; /* Play for a few seconds. */ + printf("totalSamps = %d\n", totalSamps ); + err = Pa_Initialize(); + if( err != paNoError ) goto error; + printf("PortAudio Test: output device = %d\n", OUTPUT_DEVICE ); + + err = Pa_OpenStream( + &stream, + paNoDevice, + 0, /* no input */ + paFloat32, /* 32 bit floating point input */ + NULL, + OUTPUT_DEVICE, + 2, /* stereo output */ + paInt16, /* 32 bit floating point output */ + NULL, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + NUM_BUFFERS, /* number of buffers, if zero then use default minimum */ + paClipOff|paDitherOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + printf("Is callback being called?\n"); + for( i=0; i<((NUM_SECONDS+1)*1000); i+=SLEEP_DUR ) + { + printf("data.framesToGo = %d\n", data.framesToGo ); fflush(stdout); + Pa_Sleep( SLEEP_DUR ); + } + /* Stop sound until ENTER hit. */ + printf("Call Pa_StopStream()\n"); + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/debug_dither_calc.c b/src/audio/portaudio/pa_tests/debug_dither_calc.c new file mode 100644 index 0000000000000000000000000000000000000000..1b24760996341085d42f816a035b656484e4a84c --- /dev/null +++ b/src/audio/portaudio/pa_tests/debug_dither_calc.c @@ -0,0 +1,55 @@ +/* + * $Id$ + * Test Dither calculations. + * + * Author: Phil Burk http://www.softsynth.com + * + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" +#include "pa_host.h" + +/*******************************************************************/ +int main(void); +int main(void) +{ + long max,min; + int i; + + for( i=0; i<10000; i++ ) + { + long dither = PaConvert_TriangularDither(); + // printf("dither = 0x%08X\n", dither ); + if( dither < min ) min = dither; + else if( dither > max ) max = dither; + } + printf("min = 0x%08X = %d, max = 0x%08X = %d\n", min, min, max, max ); +} diff --git a/src/audio/portaudio/pa_tests/debug_dual.c b/src/audio/portaudio/pa_tests/debug_dual.c new file mode 100644 index 0000000000000000000000000000000000000000..35ba032d0129d0169e12e97065dfea2612db3467 --- /dev/null +++ b/src/audio/portaudio/pa_tests/debug_dual.c @@ -0,0 +1,183 @@ +/* + * $Id$ + * debug_dual.c + * Try to open TWO streams on separate cards. + * Play a sine sweep using the Portable Audio api for several seconds. + * Hacked test for debugging PA. + * + * Author: Phil Burk <philburk@softsynth.com> + * + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" +#define DEV_ID_1 (13) +#define DEV_ID_2 (15) +#define NUM_SECONDS (8) +#define SLEEP_DUR (800) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (256) +#if 0 +#define MIN_LATENCY_MSEC (200) +#define NUM_BUFFERS ((MIN_LATENCY_MSEC * SAMPLE_RATE) / (FRAMES_PER_BUFFER * 1000)) +#else +#define NUM_BUFFERS (0) +#endif +#define MIN_FREQ (100.0f) +#define MAX_FREQ (4000.0f) +#define FREQ_SCALAR (1.00002f) +#define CalcPhaseIncrement(freq) (freq/SAMPLE_RATE) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (400) +typedef struct +{ + float sine[TABLE_SIZE + 1]; // add one for guard point for interpolation + float phase_increment; + float left_phase; + float right_phase; +} +paTestData; +/* Convert phase between and 1.0 to sine value + * using linear interpolation. + */ +float LookupSine( paTestData *data, float phase ); +float LookupSine( paTestData *data, float phase ) +{ + float fIndex = phase*TABLE_SIZE; + int index = (int) fIndex; + float fract = fIndex - index; + float lo = data->sine[index]; + float hi = data->sine[index+1]; + float val = lo + fract*(hi-lo); + return val; +} +/* 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 patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned long i; + int finished = 0; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + + for( i=0; i<framesPerBuffer; i++ ) + { + *out++ = LookupSine(data, data->left_phase); /* left */ + *out++ = LookupSine(data, data->right_phase); /* right */ + data->left_phase += data->phase_increment; + if( data->left_phase >= 1.0f ) data->left_phase -= 1.0f; + data->right_phase += (data->phase_increment * 1.5f); /* fifth above */ + if( data->right_phase >= 1.0f ) data->right_phase -= 1.0f; + /* sweep frequency then start over. */ + data->phase_increment *= FREQ_SCALAR; + if( data->phase_increment > CalcPhaseIncrement(MAX_FREQ) ) data->phase_increment = CalcPhaseIncrement(MIN_FREQ); + } + return 0; +} + +PaError TestStart( PortAudioStream **streamPtr, PaDeviceID devID, + paTestData *data ); +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream1, *stream2; + PaError err; + paTestData DATA1, DATA2; + printf("PortAudio Test: DUAL sine sweep. ask for %d buffers\n", NUM_BUFFERS ); + err = Pa_Initialize(); + if( err != paNoError ) goto error; + err = TestStart( &stream1, DEV_ID_1, &DATA1 ); + if( err != paNoError ) goto error; + err = TestStart( &stream2, DEV_ID_2, &DATA2 ); + if( err != paNoError ) goto error; + printf("Hit ENTER\n"); + getchar(); + err = Pa_StopStream( stream1 ); + if( err != paNoError ) goto error; + err = Pa_StopStream( stream2 ); + if( err != paNoError ) goto error; + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} +PaError TestStart( PortAudioStream **streamPtr, PaDeviceID devID, paTestData *data ) +{ + PortAudioStream *stream; + PaError err; + int i; + /* initialise sinusoidal wavetable */ + for( i=0; i<TABLE_SIZE; i++ ) + { + data->sine[i] = (float) sin( ((double)i/(double)TABLE_SIZE) * M_PI * 2. ); + } + data->sine[TABLE_SIZE] = data->sine[0]; // set guard point + data->left_phase = data->right_phase = 0.0; + data->phase_increment = CalcPhaseIncrement(MIN_FREQ); + printf("PortAudio Test: output device = %d\n", devID ); + err = Pa_OpenStream( + &stream, + paNoDevice, + 0, /* no input */ + paFloat32, /* 32 bit floating point input */ + NULL, + devID, + 2, /* stereo output */ + paFloat32, /* 32 bit floating point output */ + NULL, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + NUM_BUFFERS, /* number of buffers, if zero then use default minimum */ + paClipOff|paDitherOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + data ); + if( err != paNoError ) goto error; + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + *streamPtr = stream; + return 0; +error: + return err; +} diff --git a/src/audio/portaudio/pa_tests/debug_multi_in.c b/src/audio/portaudio/pa_tests/debug_multi_in.c new file mode 100644 index 0000000000000000000000000000000000000000..def729c1b4649db31e0420a05b55f53a9fba4edc --- /dev/null +++ b/src/audio/portaudio/pa_tests/debug_multi_in.c @@ -0,0 +1,179 @@ +/* + * $Id$ + * debug_multi_in.c + * Pass output from each of multiple channels + * to a stereo output using the Portable Audio api. + * Hacked test for debugging PA. + * + * Author: Phil Burk http://www.softsynth.com + * + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include <string.h> +#include "portaudio.h" +//#define INPUT_DEVICE_NAME ("EWS88 MT Interleaved Rec") +#define OUTPUT_DEVICE (Pa_GetDefaultOutputDeviceID()) +//#define OUTPUT_DEVICE (18) +#define SAMPLE_RATE (22050) +#define FRAMES_PER_BUFFER (256) +#define MIN_LATENCY_MSEC (400) +#define NUM_BUFFERS ((MIN_LATENCY_MSEC * SAMPLE_RATE) / (FRAMES_PER_BUFFER * 1000)) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +typedef struct +{ + int liveChannel; + int numChannels; +} +paTestData; +/* 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 patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + float *in = (float*)inputBuffer; + int i; + int finished = 0; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + if( in == NULL ) return 0; + for( i=0; i<(int)framesPerBuffer; i++ ) + { + /* Copy one channel of input to output. */ + *out++ = in[data->liveChannel]; + *out++ = in[data->liveChannel]; + in += data->numChannels; + } + return 0; +} +/*******************************************************************/ +int PaFindDeviceByName( const char *name ) +{ + int i; + int numDevices; + const PaDeviceInfo *pdi; + int len = strlen( name ); + PaDeviceID result = paNoDevice; + numDevices = Pa_CountDevices(); + for( i=0; i<numDevices; i++ ) + { + pdi = Pa_GetDeviceInfo( i ); + if( strncmp( name, pdi->name, len ) == 0 ) + { + result = i; + break; + } + } + return result; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream; + PaError err; + paTestData data; + int i; + PaDeviceID inputDevice; + const PaDeviceInfo *pdi; + printf("PortAudio Test: input signal from each channel. %d buffers\n", NUM_BUFFERS ); + data.liveChannel = 0; + err = Pa_Initialize(); + if( err != paNoError ) goto error; +#ifdef INPUT_DEVICE_NAME + printf("Try to use device: %s\n", INPUT_DEVICE_NAME ); + inputDevice = PaFindDeviceByName(INPUT_DEVICE_NAME); + if( inputDevice == paNoDevice ) + { + printf("Could not find %s. Using default instead.\n", INPUT_DEVICE_NAME ); + inputDevice = Pa_GetDefaultInputDeviceID(); + } +#else + printf("Using default input device.\n"); + inputDevice = Pa_GetDefaultInputDeviceID(); +#endif + pdi = Pa_GetDeviceInfo( inputDevice ); + if( pdi == NULL ) + { + printf("Could not get device info!\n"); + goto error; + } + data.numChannels = pdi->maxInputChannels; + printf("Input Device name is %s\n", pdi->name ); + printf("Input Device has %d channels.\n", pdi->maxInputChannels); + err = Pa_OpenStream( + &stream, + inputDevice, + pdi->maxInputChannels, + paFloat32, /* 32 bit floating point input */ + NULL, + OUTPUT_DEVICE, + 2, + paFloat32, /* 32 bit floating point output */ + NULL, + SAMPLE_RATE, + FRAMES_PER_BUFFER, /* frames per buffer */ + NUM_BUFFERS, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + data.liveChannel = 0; + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + for( i=0; i<data.numChannels; i++ ) + { + data.liveChannel = i; + printf("Channel %d being sent to output. Hit ENTER for next channel.", i ); + fflush(stdout); + getchar(); + } + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( stream ); + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/debug_multi_out.c b/src/audio/portaudio/pa_tests/debug_multi_out.c new file mode 100644 index 0000000000000000000000000000000000000000..d230833203d1e67faac6a0eee6b7c66f4e429311 --- /dev/null +++ b/src/audio/portaudio/pa_tests/debug_multi_out.c @@ -0,0 +1,144 @@ +/* + * $Id$ + * debug_multi_out.c + * Play a different sine wave on each channels, + * using the Portable Audio api. + * + * Author: Phil Burk http://www.softsynth.com + * + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" + +#define OUTPUT_DEVICE (Pa_GetDefaultOutputDeviceID()) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (256) +#define FREQ_INCR (300.0 / SAMPLE_RATE) +#define MAX_CHANNELS (64) + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +typedef struct +{ + int numChannels; + double phases[MAX_CHANNELS]; +} +paTestData; + +/* 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 patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + int frameIndex, channelIndex; + int finished = 0; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + for( frameIndex=0; frameIndex<(int)framesPerBuffer; frameIndex++ ) + { + for( channelIndex=0; channelIndex<data->numChannels; channelIndex++ ) + { + /* Output sine wave on every channel. */ + *out++ = (float) sin(data->phases[channelIndex]); + + /* Play each channel at a higher frequency. */ + data->phases[channelIndex] += FREQ_INCR * (4 + channelIndex); + if( data->phases[channelIndex] >= (2.0 * M_PI) ) data->phases[channelIndex] -= (2.0 * M_PI); + } + } + + return 0; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream; + PaError err; + const PaDeviceInfo *pdi; + paTestData data = {0}; + printf("PortAudio Test: output sine wave on each channel.\n" ); + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + pdi = Pa_GetDeviceInfo( OUTPUT_DEVICE ); + data.numChannels = pdi->maxOutputChannels; + if( data.numChannels > MAX_CHANNELS ) data.numChannels = MAX_CHANNELS; + printf("Number of Channels = %d\n", data.numChannels ); + + err = Pa_OpenStream( + &stream, + paNoDevice, /* default input device */ + 0, /* no input */ + paFloat32, /* 32 bit floating point input */ + NULL, + OUTPUT_DEVICE, + data.numChannels, + paFloat32, /* 32 bit floating point output */ + NULL, + SAMPLE_RATE, + FRAMES_PER_BUFFER, /* frames per buffer */ + 0, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Hit ENTER to stop sound.\n"); + fflush(stdout); + getchar(); + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + Pa_CloseStream( stream ); + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/debug_record.c b/src/audio/portaudio/pa_tests/debug_record.c new file mode 100644 index 0000000000000000000000000000000000000000..57b11f581fc7d42f1c4e2839c5f3524f9521a151 --- /dev/null +++ b/src/audio/portaudio/pa_tests/debug_record.c @@ -0,0 +1,339 @@ +/* + * $Id$ + * debug_record.c + * Record input into an array. + * Save array to a file. + * Based on patest_record.c but with various ugly debug hacks thrown in. + * + * Author: Phil Burk http://www.softsynth.com + * + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <memory.h> +#include "portaudio.h" +#define SAMPLE_RATE (22050) +#define NUM_SECONDS (10) +#define SLEEP_DUR_MSEC (200) +#define FRAMES_PER_BUFFER (1<<10) +#define NUM_REC_BUFS (0) + +#if 1 +#define PA_SAMPLE_TYPE paFloat32 +typedef float SAMPLE; +#else +#define PA_SAMPLE_TYPE paInt16 +typedef short SAMPLE; +#endif + +typedef struct +{ + long frameIndex; /* Index into sample array. */ + long maxFrameIndex; + long samplesPerFrame; + long numSamples; + SAMPLE *recordedSamples; +} +paTestData; +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be 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 recordCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + SAMPLE *rptr = (SAMPLE*)inputBuffer; + SAMPLE *wptr = &data->recordedSamples[data->frameIndex * data->samplesPerFrame]; + long framesToCalc; + unsigned long i; + int finished; + unsigned long framesLeft = data->maxFrameIndex - data->frameIndex; + + (void) outputBuffer; /* Prevent unused variable warnings. */ + (void) outTime; + + if( framesLeft < framesPerBuffer ) + { + framesToCalc = framesLeft; + finished = 1; + } + else + { + framesToCalc = framesPerBuffer; + finished = 0; + } + if( inputBuffer == NULL ) + { + for( i=0; i<framesToCalc; i++ ) + { + *wptr++ = 0; /* left */ + *wptr++ = 0; /* right */ + } + } + else + { + for( i=0; i<framesToCalc; i++ ) + { + *wptr++ = *rptr++; /* left */ + *wptr++ = *rptr++; /* right */ + } + } + data->frameIndex += framesToCalc; + return finished; +} +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be 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 playCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + SAMPLE *rptr = &data->recordedSamples[data->frameIndex * data->samplesPerFrame]; + SAMPLE *wptr = (SAMPLE*)outputBuffer; + unsigned long i; + int finished; + unsigned int framesLeft = data->maxFrameIndex - data->frameIndex; + if( outputBuffer == NULL ) return 0; + (void) inputBuffer; /* Prevent unused variable warnings. */ + (void) outTime; + + if( framesLeft < framesPerBuffer ) + { + /* final buffer... */ + for( i=0; i<framesLeft; i++ ) + { + *wptr++ = *rptr++; /* left */ + *wptr++ = *rptr++; /* right */ + } + for( ; i<framesPerBuffer; i++ ) + { + *wptr++ = 0; /* left */ + *wptr++ = 0; /* right */ + } + data->frameIndex += framesLeft; + finished = 1; + } + else + { + for( i=0; i<framesPerBuffer; i++ ) + { + *wptr++ = *rptr++; /* left */ + *wptr++ = *rptr++; /* right */ + } + data->frameIndex += framesPerBuffer; + finished = 0; + } + return finished; +} + +/****************************************************************/ +PaError TestRecording( paTestData *dataPtr ) +{ + PortAudioStream *stream; + PaError err; + int i; + + /* Record some audio. */ + err = Pa_OpenStream( + &stream, + Pa_GetDefaultInputDeviceID(), + dataPtr->samplesPerFrame, /* stereo input */ + PA_SAMPLE_TYPE, + NULL, + paNoDevice, + 0, + PA_SAMPLE_TYPE, + NULL, + SAMPLE_RATE, + FRAMES_PER_BUFFER, /* frames per buffer */ + NUM_REC_BUFS, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + recordCallback, + dataPtr ); + if( err != paNoError ) goto error; + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Now recording!\n"); fflush(stdout); + for( i=0; i<(NUM_SECONDS*1000/SLEEP_DUR_MSEC); i++ ) + { + if( Pa_StreamActive( stream ) <= 0) + { + printf("Stream inactive!\n"); + break; + } + if( dataPtr->maxFrameIndex <= dataPtr->frameIndex ) + { + printf("Buffer recording complete.\n"); + break; + } + Pa_Sleep(100); + printf("index = %d\n", dataPtr->frameIndex ); fflush(stdout); + } + + printf("Finished loop. Close stream.\n"); fflush(stdout); + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + printf("Done.\n"); fflush(stdout); + { + SAMPLE max = 0; + SAMPLE posVal; + int i; + for( i=0; i<dataPtr->numSamples; i++ ) + { + posVal = dataPtr->recordedSamples[i]; + if( posVal < 0 ) posVal = -posVal; + if( posVal > max ) max = posVal; + } + printf("Largest recorded sample = %d\n", max ); + } + /* Write recorded data to a file. */ +#if 0 + { + FILE *fid; + fid = fopen("recorded.raw", "wb"); + if( fid == NULL ) + { + printf("Could not open file."); + } + else + { + fwrite( dataPtr->recordedSamples, dataPtr->samplesPerFrame * sizeof(SAMPLE), totalFrames, fid ); + fclose( fid ); + printf("Wrote data to 'recorded.raw'\n"); + } + } +#endif + +error: + return err; +} + +/****************************************************************/ +PaError TestPlayback( paTestData *dataPtr ) +{ + PortAudioStream *stream; + PaError err; + int i; + + /* Playback recorded data. */ + dataPtr->frameIndex = 0; + printf("Begin playback.\n"); fflush(stdout); + err = Pa_OpenStream( + &stream, + paNoDevice, + 0, /* NO input */ + PA_SAMPLE_TYPE, + NULL, + Pa_GetDefaultOutputDeviceID(), + dataPtr->samplesPerFrame, /* stereo output */ + PA_SAMPLE_TYPE, + NULL, + SAMPLE_RATE, + FRAMES_PER_BUFFER, /* frames per buffer */ + 0, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + playCallback, + dataPtr ); + if( err != paNoError ) goto error; + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + printf("Waiting for playback to finish.\n"); fflush(stdout); + for( i=0; i<(NUM_SECONDS*1000/SLEEP_DUR_MSEC); i++ ) + { + Pa_Sleep(100); + printf("index = %d\n", dataPtr->frameIndex ); + } + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + +error: + return err; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PaError err; + paTestData data; + long totalFrames; + long numBytes; + long i; + printf("patest_record.c\n"); fflush(stdout); + + data.frameIndex = 0; + data.samplesPerFrame = 2; + data.maxFrameIndex = totalFrames = NUM_SECONDS*SAMPLE_RATE; + + printf("totalFrames = %d\n", totalFrames ); fflush(stdout); + data.numSamples = totalFrames * data.samplesPerFrame; + + numBytes = data.numSamples * sizeof(SAMPLE); + data.recordedSamples = (SAMPLE *) malloc( numBytes ); + if( data.recordedSamples == NULL ) + { + printf("Could not allocate record array.\n"); + exit(1); + } + for( i=0; i<data.numSamples; i++ ) data.recordedSamples[i] = 0; + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + for( i=0; i<2; i++ ) + { + err = TestRecording( &data ); + if( err != paNoError ) goto error; + + err = TestPlayback( &data ); + if( err != paNoError ) goto error; + } + + free( data.recordedSamples ); + Pa_Terminate(); + return 0; + +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + if( err == paHostError ) + { + fprintf( stderr, "Host Error number: %d\n", Pa_GetHostError() ); + } + return -1; +} diff --git a/src/audio/portaudio/pa_tests/debug_record_reuse.c b/src/audio/portaudio/pa_tests/debug_record_reuse.c new file mode 100644 index 0000000000000000000000000000000000000000..46f937d21bd24a4bfdf548feaaed70ebdb984b46 --- /dev/null +++ b/src/audio/portaudio/pa_tests/debug_record_reuse.c @@ -0,0 +1,351 @@ +/* + * $Id$ + * debug_record_reuse.c + * Record input into an array. + * Save array to a file. + * Based on patest_record.c but with various ugly debug hacks thrown in. + * Loop twice and reuse same streams. + * + * Author: Phil Burk http://www.softsynth.com + * + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <memory.h> +#include "portaudio.h" +#define SAMPLE_RATE (22050) +#define NUM_SECONDS (4) +#define SLEEP_DUR_MSEC (200) +#define FRAMES_PER_BUFFER (256) +#define NUM_REC_BUFS (0) + +#if 1 +#define PA_SAMPLE_TYPE paFloat32 +typedef float SAMPLE; +#else +#define PA_SAMPLE_TYPE paInt16 +typedef short SAMPLE; +#endif + +typedef struct +{ + long frameIndex; /* Index into sample array. */ + long maxFrameIndex; + long samplesPerFrame; + long numSamples; + PortAudioStream *outputStream; + PortAudioStream *inputStream; + SAMPLE *recordedSamples; +} +paTestData; + +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be 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 recordCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + SAMPLE *rptr = (SAMPLE*)inputBuffer; + SAMPLE *wptr = &data->recordedSamples[data->frameIndex * data->samplesPerFrame]; + long framesToCalc; + unsigned long i; + int finished; + unsigned long framesLeft = data->maxFrameIndex - data->frameIndex; + + (void) outputBuffer; /* Prevent unused variable warnings. */ + (void) outTime; + + if( framesLeft < framesPerBuffer ) + { + framesToCalc = framesLeft; + finished = 1; + } + else + { + framesToCalc = framesPerBuffer; + finished = 0; + } + if( inputBuffer == NULL ) + { + for( i=0; i<framesToCalc; i++ ) + { + *wptr++ = 0; /* left */ + *wptr++ = 0; /* right */ + } + } + else + { + for( i=0; i<framesToCalc; i++ ) + { + *wptr++ = *rptr++; /* left */ + *wptr++ = *rptr++; /* right */ + } + } + data->frameIndex += framesToCalc; + return finished; +} + +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be 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 playCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + SAMPLE *rptr = &data->recordedSamples[data->frameIndex * data->samplesPerFrame]; + SAMPLE *wptr = (SAMPLE*)outputBuffer; + unsigned long i; + int finished; + unsigned int framesLeft = data->maxFrameIndex - data->frameIndex; + if( outputBuffer == NULL ) return 0; + (void) inputBuffer; /* Prevent unused variable warnings. */ + (void) outTime; + + if( framesLeft < framesPerBuffer ) + { + /* final buffer... */ + for( i=0; i<framesLeft; i++ ) + { + *wptr++ = *rptr++; /* left */ + *wptr++ = *rptr++; /* right */ + } + for( ; i<framesPerBuffer; i++ ) + { + *wptr++ = 0; /* left */ + *wptr++ = 0; /* right */ + } + data->frameIndex += framesLeft; + finished = 1; + } + else + { + for( i=0; i<framesPerBuffer; i++ ) + { + *wptr++ = *rptr++; /* left */ + *wptr++ = *rptr++; /* right */ + } + data->frameIndex += framesPerBuffer; + finished = 0; + } + return finished; +} + +/****************************************************************/ +PaError TestRecording( paTestData *dataPtr ) +{ + PaError err; + int i; + int lastIndex = 0; + +/* Open input stream if not already open. */ + if( dataPtr->inputStream == NULL ) + { + /* Record some audio. */ + err = Pa_OpenStream( + &dataPtr->inputStream, + Pa_GetDefaultInputDeviceID(), + dataPtr->samplesPerFrame, /* stereo input */ + PA_SAMPLE_TYPE, + NULL, + paNoDevice, + 0, + PA_SAMPLE_TYPE, + NULL, + SAMPLE_RATE, + FRAMES_PER_BUFFER, /* frames per buffer */ + NUM_REC_BUFS, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + recordCallback, + dataPtr ); + if( err != paNoError ) goto error; + } + + dataPtr->frameIndex = 0; + + err = Pa_StartStream( dataPtr->inputStream ); + if( err != paNoError ) goto error; + + printf("Now recording!\n"); fflush(stdout); + for( i=0; i<(NUM_SECONDS*1000/SLEEP_DUR_MSEC); i++ ) + { + int frameIndex, delta; + Pa_Sleep(SLEEP_DUR_MSEC); + + frameIndex = dataPtr->frameIndex; + if( Pa_StreamActive( dataPtr->inputStream ) <= 0) + { + printf("Stream inactive!\n"); + break; + } + if( dataPtr->maxFrameIndex <= frameIndex ) + { + printf("Buffer recording complete.\n"); + break; + } + + delta = frameIndex - lastIndex; + lastIndex = frameIndex; + printf("index = %d, delta = %d\n", frameIndex, delta ); fflush(stdout); + } + + err = Pa_StopStream( dataPtr->inputStream ); + if( err != paNoError ) goto error; + + printf("Done.\n"); fflush(stdout); + +error: + return err; +} + +/****************************************************************/ +PaError TestPlayback( paTestData *dataPtr ) +{ + PaError err; + int i; + int lastIndex = 0; + + /* Playback recorded data. */ + dataPtr->frameIndex = 0; + printf("Begin playback.\n"); fflush(stdout); + +/* Open output stream if not already open. */ + if( dataPtr->outputStream == NULL ) + { + err = Pa_OpenStream( + &dataPtr->outputStream, + paNoDevice, + 0, /* NO input */ + PA_SAMPLE_TYPE, + NULL, + Pa_GetDefaultOutputDeviceID(), + dataPtr->samplesPerFrame, /* stereo output */ + PA_SAMPLE_TYPE, + NULL, + SAMPLE_RATE, + FRAMES_PER_BUFFER, /* frames per buffer */ + 0, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + playCallback, + dataPtr ); + if( err != paNoError ) goto error; + } + + err = Pa_StartStream( dataPtr->outputStream ); + if( err != paNoError ) goto error; + + printf("Waiting for playback to finish.\n"); fflush(stdout); + for( i=0; i<(NUM_SECONDS*1000/SLEEP_DUR_MSEC); i++ ) + { + int frameIndex, delta; + Pa_Sleep(SLEEP_DUR_MSEC); + frameIndex = dataPtr->frameIndex; + delta = frameIndex - lastIndex; + lastIndex = frameIndex; + printf("index = %d, delta = %d\n", frameIndex, delta ); fflush(stdout); + } + + err = Pa_StopStream( dataPtr->outputStream ); + if( err != paNoError ) goto error; + +error: + return err; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PaError err; + paTestData data = { 0 }; + long totalFrames; + long numBytes; + long i; + printf("patest_record.c\n"); fflush(stdout); + +/* Set up test data structure and sample array. */ + data.frameIndex = 0; + data.samplesPerFrame = 2; + data.maxFrameIndex = totalFrames = NUM_SECONDS*SAMPLE_RATE; + + printf("totalFrames = %d\n", totalFrames ); fflush(stdout); + data.numSamples = totalFrames * data.samplesPerFrame; + + numBytes = data.numSamples * sizeof(SAMPLE); + data.recordedSamples = (SAMPLE *) malloc( numBytes ); + if( data.recordedSamples == NULL ) + { + printf("Could not allocate record array.\n"); + exit(1); + } + for( i=0; i<data.numSamples; i++ ) data.recordedSamples[i] = 0; + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + +/* Record and playback multiple times. */ + for( i=0; i<2; i++ ) + { + err = TestRecording( &data ); + if( err != paNoError ) goto error; + + err = TestPlayback( &data ); + if( err != paNoError ) goto error; + } + +/* Clean up. */ + err = Pa_CloseStream( data.inputStream ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( data.outputStream ); + if( err != paNoError ) goto error; + + if( err != paNoError ) goto error; + + free( data.recordedSamples ); + Pa_Terminate(); + + printf("Test complete.\n"); fflush(stdout); + return 0; + +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + if( err == paHostError ) + { + fprintf( stderr, "Host Error number: %d\n", Pa_GetHostError() ); + } + return -1; +} diff --git a/src/audio/portaudio/pa_tests/debug_sine.c b/src/audio/portaudio/pa_tests/debug_sine.c new file mode 100644 index 0000000000000000000000000000000000000000..86610d3f6adc4582bcdc059730d1f59a8185f048 --- /dev/null +++ b/src/audio/portaudio/pa_tests/debug_sine.c @@ -0,0 +1,192 @@ +/* + * $Id$ + * debug_sine.c + * Play a sine sweep using the Portable Audio api for several seconds. + * Hacked test for debugging PA. + * + * Author: Phil Burk <philburk@softsynth.com> + * + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" +#define OUTPUT_DEVICE (Pa_GetDefaultOutputDeviceID()) +//#define OUTPUT_DEVICE (11) +#define NUM_SECONDS (8) +#define SLEEP_DUR (800) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (256) +#if 0 +#define MIN_LATENCY_MSEC (200) +#define NUM_BUFFERS ((MIN_LATENCY_MSEC * SAMPLE_RATE) / (FRAMES_PER_BUFFER * 1000)) +#else +#define NUM_BUFFERS (0) +#endif +#define MIN_FREQ (100.0f) +#define MAX_FREQ (4000.0f) +#define FREQ_SCALAR (1.00002f) +#define CalcPhaseIncrement(freq) (freq/SAMPLE_RATE) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (400) +typedef struct +{ + float sine[TABLE_SIZE + 1]; // add one for guard point for interpolation + float phase_increment; + float left_phase; + float right_phase; + unsigned int framesToGo; +} +paTestData; +/* Convert phase between and 1.0 to sine value + * using linear interpolation. + */ +float LookupSine( paTestData *data, float phase ); +float LookupSine( paTestData *data, float phase ) +{ + float fIndex = phase*TABLE_SIZE; + int index = (int) fIndex; + float fract = fIndex - index; + float lo = data->sine[index]; + float hi = data->sine[index+1]; + float val = lo + fract*(hi-lo); + return val; +} +/* 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 patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + int framesToCalc; + int i; + int finished = 0; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + if( data->framesToGo < framesPerBuffer ) + { + framesToCalc = data->framesToGo; + data->framesToGo = 0; + finished = 1; + } + else + { + framesToCalc = framesPerBuffer; + data->framesToGo -= framesPerBuffer; + } + + for( i=0; i<framesToCalc; i++ ) + { + *out++ = LookupSine(data, data->left_phase); /* left */ + *out++ = LookupSine(data, data->right_phase); /* right */ + data->left_phase += data->phase_increment; + if( data->left_phase >= 1.0f ) data->left_phase -= 1.0f; + data->right_phase += (data->phase_increment * 1.5f); /* fifth above */ + if( data->right_phase >= 1.0f ) data->right_phase -= 1.0f; + /* sweep frequency then start over. */ + data->phase_increment *= FREQ_SCALAR; + if( data->phase_increment > CalcPhaseIncrement(MAX_FREQ) ) data->phase_increment = CalcPhaseIncrement(MIN_FREQ); + } + /* zero remainder of final buffer */ + for( ; i<(int)framesPerBuffer; i++ ) + { + *out++ = 0; /* left */ + *out++ = 0; /* right */ + } + return finished; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream; + PaError err; + paTestData data; + int i; + int totalSamps; + printf("PortAudio Test: output sine sweep. ask for %d buffers\n", NUM_BUFFERS ); + /* initialise sinusoidal wavetable */ + for( i=0; i<TABLE_SIZE; i++ ) + { + data.sine[i] = (float) sin( ((double)i/(double)TABLE_SIZE) * M_PI * 2. ); + } + data.sine[TABLE_SIZE] = data.sine[0]; // set guard point + data.left_phase = data.right_phase = 0.0; + data.phase_increment = CalcPhaseIncrement(MIN_FREQ); + data.framesToGo = totalSamps = NUM_SECONDS * SAMPLE_RATE; /* Play for a few seconds. */ + printf("totalSamps = %d\n", totalSamps ); + err = Pa_Initialize(); + if( err != paNoError ) goto error; + printf("PortAudio Test: output device = %d\n", OUTPUT_DEVICE ); + err = Pa_OpenStream( + &stream, + paNoDevice, + 0, /* no input */ + paFloat32, /* 32 bit floating point input */ + NULL, + OUTPUT_DEVICE, + 2, /* stereo output */ + paFloat32, /* 32 bit floating point output */ + NULL, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + NUM_BUFFERS, /* number of buffers, if zero then use default minimum */ + paClipOff|paDitherOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + printf("Is callback being called?\n"); + for( i=0; i<((NUM_SECONDS+1)*1000); i+=SLEEP_DUR ) + { + printf("data.framesToGo = %d\n", data.framesToGo ); fflush(stdout); + Pa_Sleep( SLEEP_DUR ); + } + /* Stop sound until ENTER hit. */ + printf("Call Pa_StopStream()\n"); + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/debug_sine_amp.c b/src/audio/portaudio/pa_tests/debug_sine_amp.c new file mode 100644 index 0000000000000000000000000000000000000000..ceaf5723871e237820a905fd20b379e4b8b0ab50 --- /dev/null +++ b/src/audio/portaudio/pa_tests/debug_sine_amp.c @@ -0,0 +1,157 @@ +/* + * $Id$ + * Play a different sine wave on each channels, + * using the Portable Audio api. + * Allos amplitude to be set interactively. + * + * Author: Phil Burk http://www.softsynth.com + * + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" + +#define OUTPUT_DEVICE (Pa_GetDefaultOutputDeviceID()) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (256) +#define FREQ_INCR (300.0 / SAMPLE_RATE) +#define MAX_CHANNELS (64) + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +typedef struct +{ + int numChannels; + double phases[MAX_CHANNELS]; + float amplitude; +} +paTestData; + +/* 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 patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + int frameIndex, channelIndex; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + for( frameIndex=0; frameIndex<(int)framesPerBuffer; frameIndex++ ) + { + for( channelIndex=0; channelIndex<data->numChannels; channelIndex++ ) + { + /* Output sine wave on every channel. */ + *out++ = (float) ( data->amplitude * sin(data->phases[channelIndex]) ); + + /* Play each channel at a higher frequency. */ + data->phases[channelIndex] += FREQ_INCR * (4 + channelIndex); + if( data->phases[channelIndex] >= (2.0 * M_PI) ) data->phases[channelIndex] -= (2.0 * M_PI); + } + } + + return 0; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + char pad[256]; + PortAudioStream *stream; + PaError err; + const PaDeviceInfo *pdi; + paTestData data = {0}; + printf("PortAudio Test: output sine wave on each channel.\n" ); + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + pdi = Pa_GetDeviceInfo( OUTPUT_DEVICE ); + data.numChannels = pdi->maxOutputChannels; + if( data.numChannels > MAX_CHANNELS ) data.numChannels = MAX_CHANNELS; + printf("Number of Channels = %d\n", data.numChannels ); + data.amplitude = 1.0; + + err = Pa_OpenStream( + &stream, + paNoDevice, /* default input device */ + 0, /* no input */ + paFloat32, /* 32 bit floating point input */ + NULL, + OUTPUT_DEVICE, + data.numChannels, + paFloat32, /* 32 bit floating point output */ + NULL, + SAMPLE_RATE, + FRAMES_PER_BUFFER, /* frames per buffer */ + 0, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + do + { + printf("Current amplitude = %f\n", data.amplitude ); + printf("Enter new amplitude or 'q' to quit.\n"); + fflush(stdout); + gets( pad ); + if( pad[0] != 'q' ) + { + // I tried to use atof but it seems to be broken on Mac OS X 10.1 + float amp; + sscanf( pad, "%f", & ); + data.amplitude = amp; + } + } while( pad[0] != 'q' ); + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + Pa_CloseStream( stream ); + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/debug_sine_formats.c b/src/audio/portaudio/pa_tests/debug_sine_formats.c new file mode 100644 index 0000000000000000000000000000000000000000..a1fce567ccdde350a421c631c11d56a09f9bac10 --- /dev/null +++ b/src/audio/portaudio/pa_tests/debug_sine_formats.c @@ -0,0 +1,202 @@ +/* + * $Id$ + * patest_sine_formats.c + * Play a sine wave using the Portable Audio api for several seconds. + * Test various data formats. + * + * Author: Phil Burk + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" + +#define NUM_SECONDS (10) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (256) + +#define LEFT_FREQ (SAMPLE_RATE/512.0) /* So we hit 1.0 */ +#define RIGHT_FREQ (500.0) + +#define AMPLITUDE (1.0) + +/* Select ONE format for testing. */ +#define TEST_UINT8 (1) +#define TEST_INT8 (0) +#define TEST_INT16 (0) +#define TEST_FLOAT32 (0) + +#if TEST_UINT8 +#define TEST_FORMAT paUInt8 +typedef unsigned char SAMPLE_t; +#define SAMPLE_ZERO (0x80) +#define DOUBLE_TO_SAMPLE(x) (SAMPLE_ZERO + (SAMPLE_t)(127.0 * (x))) +#define FORMAT_NAME "Unsigned 8 Bit" + +#elif TEST_INT8 +#define TEST_FORMAT paInt8 +typedef char SAMPLE_t; +#define SAMPLE_ZERO (0) +#define DOUBLE_TO_SAMPLE(x) (SAMPLE_ZERO + (SAMPLE_t)(127.0 * (x))) +#define FORMAT_NAME "Signed 8 Bit" + +#elif TEST_INT16 +#define TEST_FORMAT paInt16 +typedef short SAMPLE_t; +#define SAMPLE_ZERO (0) +#define DOUBLE_TO_SAMPLE(x) (SAMPLE_ZERO + (SAMPLE_t)(32767 * (x))) +#define FORMAT_NAME "Signed 16 Bit" + +#elif TEST_FLOAT32 +#define TEST_FORMAT paFloat32 +typedef float SAMPLE_t; +#define SAMPLE_ZERO (0.0) +#define DOUBLE_TO_SAMPLE(x) ((SAMPLE_t)(x)) +#define FORMAT_NAME "Float 32 Bit" +#endif + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + + +typedef struct +{ + double left_phase; + double right_phase; + unsigned int framesToGo; +} +paTestData; +/* 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 patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + SAMPLE_t *out = (SAMPLE_t *)outputBuffer; + SAMPLE_t sample; + int i; + int framesToCalc; + int finished = 0; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + if( data->framesToGo < framesPerBuffer ) + { + framesToCalc = data->framesToGo; + data->framesToGo = 0; + finished = 1; + } + else + { + framesToCalc = framesPerBuffer; + data->framesToGo -= framesPerBuffer; + } + + for( i=0; i<framesToCalc; i++ ) + { + data->left_phase += (LEFT_FREQ / SAMPLE_RATE); + if( data->left_phase > 1.0) data->left_phase -= 1.0; + sample = DOUBLE_TO_SAMPLE( AMPLITUDE * sin( (data->left_phase * M_PI * 2. ))); /**/ + *out++ = sample; +/* *out++ = sample; /**/ +/* *out++ = 0; /**/ + + data->right_phase += (RIGHT_FREQ / SAMPLE_RATE); + if( data->right_phase > 1.0) data->right_phase -= 1.0; + *out++ = DOUBLE_TO_SAMPLE( AMPLITUDE * sin( (data->right_phase * M_PI * 2. ))); /**/ +/* *out++ = 0; /* */ + } + /* zero remainder of final buffer */ + for( ; i<(int)framesPerBuffer; i++ ) + { + *out++ = SAMPLE_ZERO; /* left */ + *out++ = SAMPLE_ZERO; /* right */ + } + return finished; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream; + PaError err; + paTestData data; + int totalSamps; + + printf("PortAudio Test: output " FORMAT_NAME "\n"); + + + data.left_phase = data.right_phase = 0.0; + data.framesToGo = totalSamps = NUM_SECONDS * SAMPLE_RATE; /* Play for a few seconds. */ + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + err = Pa_OpenStream( + &stream, + paNoDevice,/* default input device */ + 0, /* no input */ + TEST_FORMAT, + NULL, + Pa_GetDefaultOutputDeviceID(), /* default output device */ + 2, /* stereo output */ + TEST_FORMAT, + NULL, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + 0, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Waiting %d seconds for sound to finish.\n", NUM_SECONDS ); + while( Pa_StreamActive( stream ) ) Pa_Sleep(10); + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + + printf("PortAudio Test Finished: " FORMAT_NAME "\n"); + + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/debug_srate.c b/src/audio/portaudio/pa_tests/debug_srate.c new file mode 100644 index 0000000000000000000000000000000000000000..07371f28e5d3623b4bc681d5c817941d0b04d343 --- /dev/null +++ b/src/audio/portaudio/pa_tests/debug_srate.c @@ -0,0 +1,265 @@ +/* + * $Id$ + * debug_record_reuse.c + * Record input into an array. + * Save array to a file. + * Based on patest_record.c but with various ugly debug hacks thrown in. + * Loop twice and reuse same streams. + * + * Author: Phil Burk http://www.softsynth.com + * + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <memory.h> +#include "portaudio.h" + +#define EWS88MT_12_REC (1) +#define EWS88MT_12_PLAY (10) +#define SBLIVE_REC (2) +#define SBLIVE_PLAY (11) + +#if 0 +#define INPUT_DEVICE_ID Pa_GetDefaultInputDeviceID() +#define OUTPUT_DEVICE_ID Pa_GetDefaultOutputDeviceID() +#else +#define INPUT_DEVICE_ID (EWS88MT_12_REC) +#define OUTPUT_DEVICE_ID (SBLIVE_PLAY) +#endif + +#define INPUT_SAMPLE_RATE (22050.0) +#define OUTPUT_SAMPLE_RATE (22050.0) +#define NUM_SECONDS (4) +#define SLEEP_DUR_MSEC (1000) +#define FRAMES_PER_BUFFER (64) +#define NUM_REC_BUFS (0) +#define SAMPLES_PER_FRAME (2) + +#define PA_SAMPLE_TYPE paInt16 +typedef short SAMPLE; + +typedef struct +{ + long frameIndex; /* Index into sample array. */ +} +paTestData; + +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be 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 recordCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData *) userData; + (void) outputBuffer; /* Prevent unused variable warnings. */ + (void) outTime; + + if( inputBuffer != NULL ) + { + data->frameIndex += framesPerBuffer; + } + return 0; +} + +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be 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 playCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData *) userData; + (void) inputBuffer; /* Prevent unused variable warnings. */ + (void) outTime; + + if( outputBuffer != NULL ) + { + data->frameIndex += framesPerBuffer; + } + return 0; +} + +/****************************************************************/ +PaError MeasureStreamRate( PortAudioStream *stream, paTestData *dataPtr, double *ratePtr ) +{ + PaError err; + int i; + int totalFrames = 0; + int totalMSec = 0; + + dataPtr->frameIndex = 0; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + for( i=0; i<(NUM_SECONDS*1000/SLEEP_DUR_MSEC); i++ ) + { + int delta, endIndex; + + int startIndex = dataPtr->frameIndex; + Pa_Sleep(SLEEP_DUR_MSEC); + endIndex = dataPtr->frameIndex; + + delta = endIndex - startIndex; + totalFrames += delta; + totalMSec += SLEEP_DUR_MSEC; + + printf("index = %d, delta = %d\n", endIndex, delta ); fflush(stdout); + } + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + *ratePtr = (totalFrames * 1000.0) / totalMSec; + +error: + return err; +} + +void ReportRate( double measuredRate, double expectedRate ) +{ + double error; + + error = (measuredRate - expectedRate) / expectedRate; + error = (error < 0 ) ? -error : error; + + printf("Measured rate = %6.1f, expected rate = %6.1f\n", + measuredRate, expectedRate ); + if( error > 0.1 ) + { + printf("ERROR: unexpected rate! --------------------- ERROR!\n"); + } + else + { + printf("SUCCESS: rate within tolerance!\n"); + } +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaError err; + paTestData data = { 0 }; + long i; + double rate; + const PaDeviceInfo *pdi; + + PortAudioStream *outputStream; + PortAudioStream *inputStream; + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + + pdi = Pa_GetDeviceInfo( INPUT_DEVICE_ID ); + printf("Input device = %s\n", pdi->name ); + pdi = Pa_GetDeviceInfo( OUTPUT_DEVICE_ID ); + printf("Output device = %s\n", pdi->name ); + +/* Open input stream. */ + err = Pa_OpenStream( + &inputStream, + INPUT_DEVICE_ID, + SAMPLES_PER_FRAME, /* stereo input */ + PA_SAMPLE_TYPE, + NULL, + paNoDevice, + 0, + PA_SAMPLE_TYPE, + NULL, + INPUT_SAMPLE_RATE, + FRAMES_PER_BUFFER, /* frames per buffer */ + NUM_REC_BUFS, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + recordCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_OpenStream( + &outputStream, + paNoDevice, + 0, /* NO input */ + PA_SAMPLE_TYPE, + NULL, + OUTPUT_DEVICE_ID, + SAMPLES_PER_FRAME, /* stereo output */ + PA_SAMPLE_TYPE, + NULL, + OUTPUT_SAMPLE_RATE, + FRAMES_PER_BUFFER, /* frames per buffer */ + 0, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + playCallback, + &data ); + if( err != paNoError ) goto error; + +/* Record and playback multiple times. */ + for( i=0; i<2; i++ ) + { + printf("Measuring INPUT ------------------------- \n"); + err = MeasureStreamRate( inputStream, &data, &rate ); + if( err != paNoError ) goto error; + ReportRate( rate, INPUT_SAMPLE_RATE ); + + printf("Measuring OUTPUT ------------------------- \n"); + err = MeasureStreamRate( outputStream, &data, &rate ); + if( err != paNoError ) goto error; + ReportRate( rate, OUTPUT_SAMPLE_RATE ); + } + +/* Clean up. */ + err = Pa_CloseStream( inputStream ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( outputStream ); + if( err != paNoError ) goto error; + + if( err != paNoError ) goto error; + + Pa_Terminate(); + + printf("Test complete.\n"); fflush(stdout); + return 0; + +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + if( err == paHostError ) + { + fprintf( stderr, "Host Error number: %d\n", Pa_GetHostError() ); + } + return -1; +} diff --git a/src/audio/portaudio/pa_tests/debug_test1.c b/src/audio/portaudio/pa_tests/debug_test1.c new file mode 100644 index 0000000000000000000000000000000000000000..1321f62c80efbcef2de46c9410bd358e39e0412d --- /dev/null +++ b/src/audio/portaudio/pa_tests/debug_test1.c @@ -0,0 +1,114 @@ +/* + * $Id$ + patest1.c + Ring modulate the audio input with a 441hz sine wave for 20 seconds + using the Portable Audio api + Author: Ross Bencina <rossb@audiomulch.com> + Modifications: + April 5th, 2001 - PLB - Check for NULL inputBuffer. +*/ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" +#ifndef M_PI +#define M_PI (3.14159265) +#endif +typedef struct +{ + float sine[100]; + int phase; + int sampsToGo; +} +patest1data; +static int patest1Callback( void *inputBuffer, void *outputBuffer, + unsigned long bufferFrames, + PaTimestamp outTime, void *userData ) +{ + patest1data *data = (patest1data*)userData; + float *in = (float*)inputBuffer; + float *out = (float*)outputBuffer; + int framesToCalc = bufferFrames; + unsigned long i; + int finished = 0; + if(inputBuffer == NULL) return 0; + if( data->sampsToGo < bufferFrames ) + { + finished = 1; + } + for( i=0; i<bufferFrames; i++ ) + { + *out++ = *in++; + *out++ = *in++; + if( data->phase >= 100 ) + data->phase = 0; + } + data->sampsToGo -= bufferFrames; + /* zero remainder of final buffer if not already done */ + for( ; i<bufferFrames; i++ ) + { + *out++ = 0; /* left */ + *out++ = 0; /* right */ + } + return finished; +} +int main(int argc, char* argv[]); +int main(int argc, char* argv[]) +{ + PaStream *stream; + PaError err; + patest1data data; + int i; + int inputDevice = Pa_GetDefaultInputDeviceID(); + int outputDevice = Pa_GetDefaultOutputDeviceID(); + /* initialise sinusoidal wavetable */ + for( i=0; i<100; i++ ) + data.sine[i] = sin( ((double)i/100.) * M_PI * 2. ); + data.phase = 0; + data.sampsToGo = 44100 * 4; // 20 seconds + /* initialise portaudio subsytem */ + Pa_Initialize(); + err = Pa_OpenStream( + &stream, + inputDevice, + 2, /* stereo input */ + paFloat32, /* 32 bit floating point input */ + NULL, + outputDevice, + 2, /* stereo output */ + paFloat32, /* 32 bit floating point output */ + NULL, + 44100., + // 22050, /* half second buffers */ + // 4, /* four buffers */ + 512, /* half second buffers */ + 0, /* four buffers */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patest1Callback, + &data ); + if( err == paNoError ) + { + err = Pa_StartStream( stream ); + // printf( "Press any key to end.\n" ); + // getc( stdin ); //wait for input before exiting + // Pa_AbortStream( stream ); + + printf( "Waiting for stream to complete...\n" ); + + while( Pa_StreamActive( stream ) ) + Pa_Sleep(1000); /* sleep until playback has finished */ + + err = Pa_CloseStream( stream ); + } + else + { + fprintf( stderr, "An error occured while opening the portaudio stream\n" ); + if( err == paHostError ) + fprintf( stderr, "Host error number: %d\n", Pa_GetHostError() ); + else + fprintf( stderr, "Error number: %d\n", err ); + } + Pa_Terminate(); + printf( "bye\n" ); + + return 0; +} diff --git a/src/audio/portaudio/pa_tests/pa_devs.c b/src/audio/portaudio/pa_tests/pa_devs.c new file mode 100644 index 0000000000000000000000000000000000000000..278e0275067696403664df41b38a9c9fee77b8f9 --- /dev/null +++ b/src/audio/portaudio/pa_tests/pa_devs.c @@ -0,0 +1,230 @@ +/** @file pa_devs.c + @brief List available devices, including device information. + @author Phil Burk http://www.softsynth.com + + @note Define PA_NO_ASIO to compile this code on Windows without + ASIO support. +*/ +/* + * $Id$ + * + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" + +#ifdef WIN32 +#ifndef PA_NO_ASIO +#include "pa_asio.h" +#endif +#endif + +/*******************************************************************/ +static void PrintSupportedStandardSampleRates( + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters ) +{ + static double standardSampleRates[] = { + 8000.0, 9600.0, 11025.0, 12000.0, 16000.0, 22050.0, 24000.0, 32000.0, + 44100.0, 48000.0, 88200.0, 96000.0, 192000.0, -1 /* negative terminated list */ + }; + int i, printCount; + PaError err; + + printCount = 0; + for( i=0; standardSampleRates[i] > 0; i++ ) + { + err = Pa_IsFormatSupported( inputParameters, outputParameters, standardSampleRates[i] ); + if( err == paFormatIsSupported ) + { + if( printCount == 0 ) + { + printf( "\t%8.2f", standardSampleRates[i] ); + printCount = 1; + } + else if( printCount == 4 ) + { + printf( ",\n\t%8.2f", standardSampleRates[i] ); + printCount = 1; + } + else + { + printf( ", %8.2f", standardSampleRates[i] ); + ++printCount; + } + } + } + if( !printCount ) + printf( "None\n" ); + else + printf( "\n" ); +} +/*******************************************************************/ +int main(void); +int main(void) +{ + int i, numDevices, defaultDisplayed; + const PaDeviceInfo *deviceInfo; + PaStreamParameters inputParameters, outputParameters; + PaError err; + + + Pa_Initialize(); + + printf( "PortAudio version number = %d\nPortAudio version text = '%s'\n", + Pa_GetVersion(), Pa_GetVersionText() ); + + + numDevices = Pa_GetDeviceCount(); + if( numDevices < 0 ) + { + printf( "ERROR: Pa_CountDevices returned 0x%x\n", numDevices ); + err = numDevices; + goto error; + } + + printf( "Number of devices = %d\n", numDevices ); + for( i=0; i<numDevices; i++ ) + { + deviceInfo = Pa_GetDeviceInfo( i ); + printf( "--------------------------------------- device #%d\n", i ); + + /* Mark global and API specific default devices */ + defaultDisplayed = 0; + if( i == Pa_GetDefaultInputDevice() ) + { + printf( "[ Default Input" ); + defaultDisplayed = 1; + } + else if( i == Pa_GetHostApiInfo( deviceInfo->hostApi )->defaultInputDevice ) + { + const PaHostApiInfo *hostInfo = Pa_GetHostApiInfo( deviceInfo->hostApi ); + printf( "[ Default %s Input", hostInfo->name ); + defaultDisplayed = 1; + } + + if( i == Pa_GetDefaultOutputDevice() ) + { + printf( (defaultDisplayed ? "," : "[") ); + printf( " Default Output" ); + defaultDisplayed = 1; + } + else if( i == Pa_GetHostApiInfo( deviceInfo->hostApi )->defaultOutputDevice ) + { + const PaHostApiInfo *hostInfo = Pa_GetHostApiInfo( deviceInfo->hostApi ); + printf( (defaultDisplayed ? "," : "[") ); + printf( " Default %s Output", hostInfo->name ); + defaultDisplayed = 1; + } + + if( defaultDisplayed ) + printf( " ]\n" ); + + /* print device info fields */ + printf( "Name = %s\n", deviceInfo->name ); + printf( "Host API = %s\n", Pa_GetHostApiInfo( deviceInfo->hostApi )->name ); + printf( "Max inputs = %d", deviceInfo->maxInputChannels ); + printf( ", Max outputs = %d\n", deviceInfo->maxOutputChannels ); + + printf( "Default low input latency = %8.3f\n", deviceInfo->defaultLowInputLatency ); + printf( "Default low output latency = %8.3f\n", deviceInfo->defaultLowOutputLatency ); + printf( "Default high input latency = %8.3f\n", deviceInfo->defaultHighInputLatency ); + printf( "Default high output latency = %8.3f\n", deviceInfo->defaultHighOutputLatency ); + +#ifdef WIN32 +#ifndef PA_NO_ASIO +/* ASIO specific latency information */ + if( Pa_GetHostApiInfo( deviceInfo->hostApi )->type == paASIO ){ + long minLatency, maxLatency, preferredLatency, granularity; + + err = PaAsio_GetAvailableLatencyValues( i, + &minLatency, &maxLatency, &preferredLatency, &granularity ); + + printf( "ASIO minimum buffer size = %ld\n", minLatency ); + printf( "ASIO maximum buffer size = %ld\n", maxLatency ); + printf( "ASIO preferred buffer size = %ld\n", preferredLatency ); + + if( granularity == -1 ) + printf( "ASIO buffer granularity = power of 2\n" ); + else + printf( "ASIO buffer granularity = %ld\n", granularity ); + } +#endif /* !PA_NO_ASIO */ +#endif /* WIN32 */ + + printf( "Default sample rate = %8.2f\n", deviceInfo->defaultSampleRate ); + + /* poll for standard sample rates */ + inputParameters.device = i; + inputParameters.channelCount = deviceInfo->maxInputChannels; + inputParameters.sampleFormat = paInt16; + inputParameters.suggestedLatency = 0; /* ignored by Pa_IsFormatSupported() */ + inputParameters.hostApiSpecificStreamInfo = NULL; + + outputParameters.device = i; + outputParameters.channelCount = deviceInfo->maxOutputChannels; + outputParameters.sampleFormat = paInt16; + outputParameters.suggestedLatency = 0; /* ignored by Pa_IsFormatSupported() */ + outputParameters.hostApiSpecificStreamInfo = NULL; + + if( inputParameters.channelCount > 0 ) + { + printf("Supported standard sample rates\n for half-duplex 16 bit %d channel input = \n", + inputParameters.channelCount ); + PrintSupportedStandardSampleRates( &inputParameters, NULL ); + } + + if( outputParameters.channelCount > 0 ) + { + printf("Supported standard sample rates\n for half-duplex 16 bit %d channel output = \n", + outputParameters.channelCount ); + PrintSupportedStandardSampleRates( NULL, &outputParameters ); + } + + if( inputParameters.channelCount > 0 && outputParameters.channelCount > 0 ) + { + printf("Supported standard sample rates\n for full-duplex 16 bit %d channel input, %d channel output = \n", + inputParameters.channelCount, outputParameters.channelCount ); + PrintSupportedStandardSampleRates( &inputParameters, &outputParameters ); + } + } + + Pa_Terminate(); + + printf("----------------------------------------------\n"); + return 0; + +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/pa_fuzz.c b/src/audio/portaudio/pa_tests/pa_fuzz.c new file mode 100644 index 0000000000000000000000000000000000000000..127c2e3e67710cdac8e83e03436b4e74dd256c34 --- /dev/null +++ b/src/audio/portaudio/pa_tests/pa_fuzz.c @@ -0,0 +1,168 @@ +/** @file pa_fuzz.c + @brief Distort input like a fuzz boz. + @author Phil Burk http://www.softsynth.com +*/ +/* + * $Id$ + * + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" +/* +** Note that many of the older ISA sound cards on PCs do NOT support +** full duplex audio (simultaneous record and playback). +** And some only support full duplex at lower sample rates. +*/ +#define SAMPLE_RATE (44100) +#define PA_SAMPLE_TYPE paFloat32 +#define FRAMES_PER_BUFFER (64) + +typedef float SAMPLE; + +float CubicAmplifier( float input ); +static int fuzzCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ); + +/* Non-linear amplifier with soft distortion curve. */ +float CubicAmplifier( float input ) +{ + float output, temp; + if( input < 0.0 ) + { + temp = input + 1.0f; + output = (temp * temp * temp) - 1.0f; + } + else + { + temp = input - 1.0f; + output = (temp * temp * temp) + 1.0f; + } + + return output; +} +#define FUZZ(x) CubicAmplifier(CubicAmplifier(CubicAmplifier(CubicAmplifier(x)))) + +static int gNumNoInputs = 0; +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be 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 fuzzCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + SAMPLE *out = (SAMPLE*)outputBuffer; + const SAMPLE *in = (const SAMPLE*)inputBuffer; + unsigned int i; + (void) timeInfo; /* Prevent unused variable warnings. */ + (void) statusFlags; + (void) userData; + + if( inputBuffer == NULL ) + { + for( i=0; i<framesPerBuffer; i++ ) + { + *out++ = 0; /* left - silent */ + *out++ = 0; /* right - silent */ + } + gNumNoInputs += 1; + } + else + { + for( i=0; i<framesPerBuffer; i++ ) + { + *out++ = FUZZ(*in++); /* left - distorted */ + *out++ = *in++; /* right - clean */ + } + } + + return paContinue; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters inputParameters, outputParameters; + PaStream *stream; + PaError err; + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + + inputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */ + inputParameters.channelCount = 2; /* stereo input */ + inputParameters.sampleFormat = PA_SAMPLE_TYPE; + inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency; + inputParameters.hostApiSpecificStreamInfo = NULL; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ + outputParameters.channelCount = 2; /* stereo output */ + outputParameters.sampleFormat = PA_SAMPLE_TYPE; + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + &inputParameters, + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + 0, /* paClipOff, */ /* we won't output out of range samples so don't bother clipping them */ + fuzzCallback, + NULL ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Hit ENTER to stop program.\n"); + getchar(); + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + printf("Finished. gNumNoInputs = %d\n", gNumNoInputs ); + Pa_Terminate(); + return 0; + +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return -1; +} diff --git a/src/audio/portaudio/pa_tests/pa_minlat.c b/src/audio/portaudio/pa_tests/pa_minlat.c new file mode 100644 index 0000000000000000000000000000000000000000..8400dfd2799ec5a0cc6204a0a05a4fff19df8d5c --- /dev/null +++ b/src/audio/portaudio/pa_tests/pa_minlat.c @@ -0,0 +1,187 @@ +/** @file pa_minlat.c + @brief Experiment with different numbers of buffers to determine the + minimum latency for a computer. + @author Phil Burk http://www.softsynth.com +*/ +/* + * $Id$ + * + * 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. + * + * 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. + * + * 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. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include <string.h> +#include "portaudio.h" + +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TWOPI (M_PI * 2.0) + +#define DEFAULT_BUFFER_SIZE (32) + +typedef struct +{ + double left_phase; + double right_phase; +} +paTestData; + +/* Very simple synthesis routine to generate two sine waves. */ +static int paminlatCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned int i; + double left_phaseInc = 0.02; + double right_phaseInc = 0.06; + + double left_phase = data->left_phase; + double right_phase = data->right_phase; + + for( i=0; i<framesPerBuffer; i++ ) + { + left_phase += left_phaseInc; + if( left_phase > TWOPI ) left_phase -= TWOPI; + *out++ = (float) sin( left_phase ); + + right_phase += right_phaseInc; + if( right_phase > TWOPI ) right_phase -= TWOPI; + *out++ = (float) sin( right_phase ); + } + + data->left_phase = left_phase; + data->right_phase = right_phase; + return 0; +} + +int main( int argc, char **argv ); +int main( int argc, char **argv ) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + paTestData data; + int go; + int outLatency = 0; + int minLatency = DEFAULT_BUFFER_SIZE * 2; + int framesPerBuffer; + double sampleRate = 44100.0; + char str[256]; + + printf("pa_minlat - Determine minimum latency for your computer.\n"); + printf(" usage: pa_minlat {userBufferSize}\n"); + printf(" for example: pa_minlat 64\n"); + printf("Adjust your stereo until you hear a smooth tone in each speaker.\n"); + printf("Then try to find the smallest number of frames that still sounds smooth.\n"); + printf("Note that the sound will stop momentarily when you change the number of buffers.\n"); + + /* Get bufferSize from command line. */ + framesPerBuffer = ( argc > 1 ) ? atol( argv[1] ) : DEFAULT_BUFFER_SIZE; + printf("Frames per buffer = %d\n", framesPerBuffer ); + + data.left_phase = data.right_phase = 0.0; + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outLatency = sampleRate * 200.0 / 1000.0; /* 200 msec. */ + + /* Try different numBuffers in a loop. */ + go = 1; + while( go ) + { + outputParameters.device = Pa_GetDefaultOutputDevice(); /* Default output device. */ + outputParameters.channelCount = 2; /* Stereo output */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output. */ + outputParameters.suggestedLatency = (double)outLatency / sampleRate; /* In seconds. */ + outputParameters.hostApiSpecificStreamInfo = NULL; + + printf("Latency = %d frames = %6.1f msec.\n", outLatency, outputParameters.suggestedLatency * 1000.0 ); + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + sampleRate, + framesPerBuffer, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + paminlatCallback, + &data ); + if( err != paNoError ) goto error; + if( stream == NULL ) goto error; + + /* Start audio. */ + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + /* Ask user for a new nlatency. */ + printf("\nMove windows around to see if the sound glitches.\n"); + printf("Latency now %d, enter new number of frames, or 'q' to quit: ", outLatency ); + fgets( str, 256, stdin ); + { + /* Get rid of newline */ + size_t l = strlen( str ) - 1; + if( str[ l ] == '\n') + str[ l ] = '\0'; + } + if( str[0] == 'q' ) go = 0; + else + { + outLatency = atol( str ); + if( outLatency < minLatency ) + { + printf( "Latency below minimum of %d! Set to minimum!!!\n", minLatency ); + outLatency = minLatency; + } + } + /* Stop sound until ENTER hit. */ + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + } + printf("A good setting for latency would be somewhat higher than\n"); + printf("the minimum latency that worked.\n"); + printf("PortAudio: Test finished.\n"); + Pa_Terminate(); + return 0; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return 1; +} diff --git a/src/audio/portaudio/pa_tests/paqa_devs.c b/src/audio/portaudio/pa_tests/paqa_devs.c new file mode 100644 index 0000000000000000000000000000000000000000..f52143c1909591a1a7fadbac704a5338b7fb5e4c --- /dev/null +++ b/src/audio/portaudio/pa_tests/paqa_devs.c @@ -0,0 +1,347 @@ +/** @file paqa_devs.c + @brief Self Testing Quality Assurance app for PortAudio + Try to open each device and run through all the + possible configurations. + + @author Phil Burk http://www.softsynth.com + + Pieter adapted to V19 API. Test now relies heavily on + Pa_IsFormatSupported(). Uses same 'standard' sample rates + as in test pa_devs.c. +*/ +/* + * $Id$ + * + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" +#include "pa_trace.h" + +/****************************************** Definitions ***********/ +#define MODE_INPUT (0) +#define MODE_OUTPUT (1) + +typedef struct PaQaData +{ + unsigned long framesLeft; + int numChannels; + int bytesPerSample; + int mode; + short sawPhase; + PaSampleFormat format; +} +PaQaData; + +/****************************************** Prototypes ***********/ +static void TestDevices( int mode ); +static void TestFormats( int mode, PaDeviceIndex deviceID, double sampleRate, + int numChannels ); +static int TestAdvance( int mode, PaDeviceIndex deviceID, double sampleRate, + int numChannels, PaSampleFormat format ); +static int QaCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ); + +/****************************************** Globals ***********/ +static int gNumPassed = 0; +static int gNumFailed = 0; + +/****************************************** Macros ***********/ +/* Print ERROR if it fails. Tally success or failure. */ +/* Odd do-while wrapper seems to be needed for some compilers. */ +#define EXPECT(_exp) \ + do \ + { \ + if ((_exp)) {\ + /* printf("SUCCESS for %s\n", #_exp ); */ \ + gNumPassed++; \ + } \ + else { \ + printf("ERROR - 0x%x - %s for %s\n", result, \ + ((result == 0) ? "-" : Pa_GetErrorText(result)), \ + #_exp ); \ + gNumFailed++; \ + goto error; \ + } \ + } while(0) + +/*******************************************************************/ +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be 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 QaCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + unsigned long i; + short phase; + PaQaData *data = (PaQaData *) userData; + (void) inputBuffer; + + /* Play simle sawtooth wave. */ + if( data->mode == MODE_OUTPUT ) + { + phase = data->sawPhase; + switch( data->format ) + { + case paFloat32: + { + float *out = (float *) outputBuffer; + for( i=0; i<framesPerBuffer; i++ ) + { + phase += 0x123; + *out++ = (float) (phase * (1.0 / 32768.0)); + if( data->numChannels == 2 ) + { + *out++ = (float) (phase * (1.0 / 32768.0)); + } + } + } + break; + + case paInt32: + { + int *out = (int *) outputBuffer; + for( i=0; i<framesPerBuffer; i++ ) + { + phase += 0x123; + *out++ = ((int) phase ) << 16; + if( data->numChannels == 2 ) + { + *out++ = ((int) phase ) << 16; + } + } + } + break; + case paInt16: + { + short *out = (short *) outputBuffer; + for( i=0; i<framesPerBuffer; i++ ) + { + phase += 0x123; + *out++ = phase; + if( data->numChannels == 2 ) + { + *out++ = phase; + } + } + } + break; + + default: + { + unsigned char *out = (unsigned char *) outputBuffer; + unsigned long numBytes = framesPerBuffer * data->numChannels * data->bytesPerSample; + for( i=0; i<numBytes; i++ ) + { + *out++ = 0; + } + } + break; + } + data->sawPhase = phase; + } + /* Are we through yet? */ + if( data->framesLeft > framesPerBuffer ) + { + PaUtil_AddTraceMessage("QaCallback: running. framesLeft", data->framesLeft ); + data->framesLeft -= framesPerBuffer; + return 0; + } + else + { + PaUtil_AddTraceMessage("QaCallback: DONE! framesLeft", data->framesLeft ); + data->framesLeft = 0; + return 1; + } +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaError result; + EXPECT( ((result=Pa_Initialize()) == 0) ); + printf("Test OUTPUT ---------------\n"); + TestDevices( MODE_OUTPUT ); + printf("Test INPUT ---------------\n"); + TestDevices( MODE_INPUT ); +error: + Pa_Terminate(); + printf("QA Report: %d passed, %d failed.\n", gNumPassed, gNumFailed ); + return 0; +} + +/******************************************************************* +* Try each output device, through its full range of capabilities. */ +static void TestDevices( int mode ) +{ + int id, jc, i; + int maxChannels; + const PaDeviceInfo *pdi; + static double standardSampleRates[] = { 8000.0, 9600.0, 11025.0, 12000.0, + 16000.0, 22050.0, 24000.0, + 32000.0, 44100.0, 48000.0, + 88200.0, 96000.0, + -1.0 }; /* Negative terminated list. */ + int numDevices = Pa_GetDeviceCount(); + for( id=0; id<numDevices; id++ ) /* Iterate through all devices. */ + { + pdi = Pa_GetDeviceInfo( id ); + /* Try 1 to maxChannels on each device. */ + maxChannels = (( mode == MODE_INPUT ) ? pdi->maxInputChannels : pdi->maxOutputChannels); + + for( jc=1; jc<=maxChannels; jc++ ) + { + printf("Name = %s\n", pdi->name ); + /* Try each standard sample rate. */ + for( i=0; standardSampleRates[i] > 0; i++ ) + { + TestFormats( mode, (PaDeviceIndex)id, standardSampleRates[i], jc ); + } + } + } +} + +/*******************************************************************/ +static void TestFormats( int mode, PaDeviceIndex deviceID, double sampleRate, + int numChannels ) +{ + TestAdvance( mode, deviceID, sampleRate, numChannels, paFloat32 ); + TestAdvance( mode, deviceID, sampleRate, numChannels, paInt16 ); + TestAdvance( mode, deviceID, sampleRate, numChannels, paInt32 ); + /* TestAdvance( mode, deviceID, sampleRate, numChannels, paInt24 ); */ +} + +/*******************************************************************/ +static int TestAdvance( int mode, PaDeviceIndex deviceID, double sampleRate, + int numChannels, PaSampleFormat format ) +{ + PaStreamParameters inputParameters, outputParameters, *ipp, *opp; + PaStream *stream = NULL; + PaError result = paNoError; + PaQaData myData; + #define FRAMES_PER_BUFFER (64) + + /* Setup data for synthesis thread. */ + myData.framesLeft = (unsigned long) (sampleRate * 100); /* 100 seconds */ + myData.numChannels = numChannels; + myData.mode = mode; + myData.format = format; + switch( format ) + { + case paFloat32: + case paInt32: + case paInt24: + myData.bytesPerSample = 4; + break; +/* case paPackedInt24: + myData.bytesPerSample = 3; + break; */ + default: + myData.bytesPerSample = 2; + break; + } + + if( mode == MODE_INPUT ) + { + inputParameters.device = deviceID; + inputParameters.channelCount = numChannels; + inputParameters.sampleFormat = format; + inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency; + inputParameters.hostApiSpecificStreamInfo = NULL; + ipp = &inputParameters; + } + else + ipp = NULL; + if( mode == MODE_OUTPUT ) /* Pa_GetDeviceInfo(paNoDevice) COREDUMPS!!! */ + { + outputParameters.device = deviceID; + outputParameters.channelCount = numChannels; + outputParameters.sampleFormat = format; + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + opp = &outputParameters; + } + else + opp = NULL; + + if(paFormatIsSupported == Pa_IsFormatSupported( ipp, opp, sampleRate )) + { + printf("------ TestAdvance: %s, device = %d, rate = %g, numChannels = %d, format = %lu -------\n", + ( mode == MODE_INPUT ) ? "INPUT" : "OUTPUT", + deviceID, sampleRate, numChannels, (unsigned long)format); + EXPECT( ((result = Pa_OpenStream( &stream, + ipp, + opp, + sampleRate, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + QaCallback, + &myData ) ) == 0) ); + if( stream ) + { + PaTime oldStamp, newStamp; + unsigned long oldFrames; + int minDelay = ( mode == MODE_INPUT ) ? 1000 : 400; + /* Was: + int minNumBuffers = Pa_GetMinNumBuffers( FRAMES_PER_BUFFER, sampleRate ); + int msec = (int) ((minNumBuffers * 3 * 1000.0 * FRAMES_PER_BUFFER) / sampleRate); + */ + int msec = (int)( 3.0 * + (( mode == MODE_INPUT ) ? inputParameters.suggestedLatency : outputParameters.suggestedLatency )); + if( msec < minDelay ) msec = minDelay; + printf("msec = %d\n", msec); /**/ + EXPECT( ((result=Pa_StartStream( stream )) == 0) ); + /* Check to make sure PortAudio is advancing timeStamp. */ + oldStamp = Pa_GetStreamTime(stream); + Pa_Sleep(msec); + newStamp = Pa_GetStreamTime(stream); + printf("oldStamp = %g,newStamp = %g\n", oldStamp, newStamp ); /**/ + EXPECT( (oldStamp < newStamp) ); + /* Check to make sure callback is decrementing framesLeft. */ + oldFrames = myData.framesLeft; + Pa_Sleep(msec); + printf("oldFrames = %lu, myData.framesLeft = %lu\n", oldFrames, myData.framesLeft ); /**/ + EXPECT( (oldFrames > myData.framesLeft) ); + EXPECT( ((result=Pa_CloseStream( stream )) == 0) ); + stream = NULL; + } + } +error: + if( stream != NULL ) Pa_CloseStream( stream ); + return result; +} diff --git a/src/audio/portaudio/pa_tests/paqa_errs.c b/src/audio/portaudio/pa_tests/paqa_errs.c new file mode 100644 index 0000000000000000000000000000000000000000..3f9077173ba4798caaa5b1e34cb0b5bfd6ec442c --- /dev/null +++ b/src/audio/portaudio/pa_tests/paqa_errs.c @@ -0,0 +1,380 @@ +/** @file paqa_errs.c + @brief Self Testing Quality Assurance app for PortAudio + Do lots of bad things to test error reporting. + @author Phil Burk http://www.softsynth.com + Pieter Suurmond adapted to V19 API. +*/ +/* + * $Id$ + * + * 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. + * + * 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. + * + * 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. + * + */ + +#include <stdio.h> +#include <math.h> + +#include "portaudio.h" + +/*--------- Definitions ---------*/ +#define MODE_INPUT (0) +#define MODE_OUTPUT (1) +#define FRAMES_PER_BUFFER (64) +#define SAMPLE_RATE (44100.0) + +typedef struct PaQaData +{ + unsigned long framesLeft; + int numChannels; + int bytesPerSample; + int mode; +} +PaQaData; + +static int gNumPassed = 0; /* Two globals */ +static int gNumFailed = 0; + +/*------------------- Macros ------------------------------*/ +/* Print ERROR if it fails. Tally success or failure. Odd */ +/* do-while wrapper seems to be needed for some compilers. */ + +#define EXPECT(_exp) \ + do \ + { \ + if ((_exp)) {\ + gNumPassed++; \ + } \ + else { \ + printf("\nERROR - 0x%x - %s for %s\n", result, Pa_GetErrorText(result), #_exp ); \ + gNumFailed++; \ + goto error; \ + } \ + } while(0) + +#define HOPEFOR(_exp) \ + do \ + { \ + if ((_exp)) {\ + gNumPassed++; \ + } \ + else { \ + printf("\nERROR - 0x%x - %s for %s\n", result, Pa_GetErrorText(result), #_exp ); \ + gNumFailed++; \ + } \ + } while(0) + +/*-------------------------------------------------------------------------*/ +/* This routine will be called by the PortAudio engine when audio is needed. + It may be 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 QaCallback( const void* inputBuffer, + void* outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void* userData ) +{ + unsigned long i; + unsigned char* out = (unsigned char *) outputBuffer; + PaQaData* data = (PaQaData *) userData; + + (void)inputBuffer; /* Prevent "unused variable" warnings. */ + + /* Zero out buffer so we don't hear terrible noise. */ + if( data->mode == MODE_OUTPUT ) + { + unsigned long numBytes = framesPerBuffer * data->numChannels * data->bytesPerSample; + for( i=0; i<numBytes; i++ ) + { + *out++ = 0; + } + } + /* Are we through yet? */ + if( data->framesLeft > framesPerBuffer ) + { + data->framesLeft -= framesPerBuffer; + return 0; + } + else + { + data->framesLeft = 0; + return 1; + } +} + +static PaDeviceIndex FindInputOnlyDevice(void) +{ + PaDeviceIndex result = Pa_GetDefaultInputDevice(); + if( result != paNoDevice && Pa_GetDeviceInfo(result)->maxOutputChannels == 0 ) + return result; + + for( result = 0; result < Pa_GetDeviceCount(); ++result ) + { + if( Pa_GetDeviceInfo(result)->maxOutputChannels == 0 ) + return result; + } + + return paNoDevice; +} + +static PaDeviceIndex FindOutputOnlyDevice(void) +{ + PaDeviceIndex result = Pa_GetDefaultOutputDevice(); + if( result != paNoDevice && Pa_GetDeviceInfo(result)->maxInputChannels == 0 ) + return result; + + for( result = 0; result < Pa_GetDeviceCount(); ++result ) + { + if( Pa_GetDeviceInfo(result)->maxInputChannels == 0 ) + return result; + } + + return paNoDevice; +} + +/*-------------------------------------------------------------------------------------------------*/ +static int TestBadOpens( void ) +{ + PaStream* stream = NULL; + PaError result; + PaQaData myData; + PaStreamParameters ipp, opp; + + /* Setup data for synthesis thread. */ + myData.framesLeft = (unsigned long) (SAMPLE_RATE * 100); /* 100 seconds */ + myData.numChannels = 1; + myData.mode = MODE_OUTPUT; + + /*----------------------------- No devices specified: */ + ipp.device = opp.device = paNoDevice; + ipp.channelCount = opp.channelCount = 0; /* Also no channels. */ + ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL; + ipp.sampleFormat = opp.sampleFormat = paFloat32; + /* Take the low latency of the default device for all subsequent tests. */ + ipp.suggestedLatency = Pa_GetDeviceInfo(Pa_GetDefaultInputDevice())->defaultLowInputLatency; + opp.suggestedLatency = Pa_GetDeviceInfo(Pa_GetDefaultOutputDevice())->defaultLowOutputLatency; + HOPEFOR(((result = Pa_OpenStream(&stream, &ipp, &opp, + SAMPLE_RATE, FRAMES_PER_BUFFER, + paClipOff, QaCallback, &myData )) == paInvalidDevice)); + + /*----------------------------- No devices specified #2: */ + HOPEFOR(((result = Pa_OpenStream(&stream, NULL, NULL, + SAMPLE_RATE, FRAMES_PER_BUFFER, + paClipOff, QaCallback, &myData )) == paInvalidDevice)); + + /*----------------------------- Out of range input device specified: */ + ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL; + ipp.sampleFormat = opp.sampleFormat = paFloat32; + ipp.channelCount = 0; ipp.device = Pa_GetDeviceCount(); /* And no output device, and no channels. */ + opp.channelCount = 0; opp.device = paNoDevice; + HOPEFOR(((result = Pa_OpenStream(&stream, &ipp, NULL, + SAMPLE_RATE, FRAMES_PER_BUFFER, + paClipOff, QaCallback, &myData )) == paInvalidDevice)); + + /*----------------------------- Out of range output device specified: */ + ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL; + ipp.sampleFormat = opp.sampleFormat = paFloat32; + ipp.channelCount = 0; ipp.device = paNoDevice; /* And no input device, and no channels. */ + opp.channelCount = 0; opp.device = Pa_GetDeviceCount(); + HOPEFOR(((result = Pa_OpenStream(&stream, NULL, &opp, + SAMPLE_RATE, FRAMES_PER_BUFFER, + paClipOff, QaCallback, &myData )) == paInvalidDevice)); + + /*----------------------------- Zero input channels: */ + ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL; + ipp.sampleFormat = opp.sampleFormat = paFloat32; + ipp.channelCount = 0; ipp.device = Pa_GetDefaultInputDevice(); + opp.channelCount = 0; opp.device = paNoDevice; /* And no output device, and no output channels. */ + HOPEFOR(((result = Pa_OpenStream(&stream, &ipp, NULL, + SAMPLE_RATE, FRAMES_PER_BUFFER, + paClipOff, QaCallback, &myData )) == paInvalidChannelCount)); + + /*----------------------------- Zero output channels: */ + ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL; + ipp.sampleFormat = opp.sampleFormat = paFloat32; + ipp.channelCount = 0; ipp.device = paNoDevice; /* And no input device, and no input channels. */ + opp.channelCount = 0; opp.device = Pa_GetDefaultOutputDevice(); + HOPEFOR(((result = Pa_OpenStream(&stream, NULL, &opp, + SAMPLE_RATE, FRAMES_PER_BUFFER, + paClipOff, QaCallback, &myData )) == paInvalidChannelCount)); + + /*----------------------------- Nonzero input and output channels but no output device: */ + ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL; + ipp.sampleFormat = opp.sampleFormat = paFloat32; + ipp.channelCount = 2; ipp.device = Pa_GetDefaultInputDevice(); /* Both stereo. */ + opp.channelCount = 2; opp.device = paNoDevice; + HOPEFOR(((result = Pa_OpenStream(&stream, &ipp, &opp, + SAMPLE_RATE, FRAMES_PER_BUFFER, + paClipOff, QaCallback, &myData )) == paInvalidDevice)); + + /*----------------------------- Nonzero input and output channels but no input device: */ + ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL; + ipp.sampleFormat = opp.sampleFormat = paFloat32; + ipp.channelCount = 2; ipp.device = paNoDevice; + opp.channelCount = 2; opp.device = Pa_GetDefaultOutputDevice(); + HOPEFOR(((result = Pa_OpenStream(&stream, &ipp, &opp, + SAMPLE_RATE, FRAMES_PER_BUFFER, + paClipOff, QaCallback, &myData )) == paInvalidDevice)); + + /*----------------------------- NULL stream pointer: */ + ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL; + ipp.sampleFormat = opp.sampleFormat = paFloat32; + ipp.channelCount = 0; ipp.device = paNoDevice; /* Output is more likely than input. */ + opp.channelCount = 2; opp.device = Pa_GetDefaultOutputDevice(); /* Only 2 output channels. */ + HOPEFOR(((result = Pa_OpenStream(NULL, &ipp, &opp, + SAMPLE_RATE, FRAMES_PER_BUFFER, + paClipOff, QaCallback, &myData )) == paBadStreamPtr)); + + /*----------------------------- Low sample rate: */ + ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL; + ipp.sampleFormat = opp.sampleFormat = paFloat32; + ipp.channelCount = 0; ipp.device = paNoDevice; + opp.channelCount = 2; opp.device = Pa_GetDefaultOutputDevice(); + HOPEFOR(((result = Pa_OpenStream(&stream, NULL, &opp, + 1.0, FRAMES_PER_BUFFER, /* 1 cycle per second (1 Hz) is too low. */ + paClipOff, QaCallback, &myData )) == paInvalidSampleRate)); + + /*----------------------------- High sample rate: */ + ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL; + ipp.sampleFormat = opp.sampleFormat = paFloat32; + ipp.channelCount = 0; ipp.device = paNoDevice; + opp.channelCount = 2; opp.device = Pa_GetDefaultOutputDevice(); + HOPEFOR(((result = Pa_OpenStream(&stream, NULL, &opp, + 10000000.0, FRAMES_PER_BUFFER, /* 10^6 cycles per second (10 MHz) is too high. */ + paClipOff, QaCallback, &myData )) == paInvalidSampleRate)); + + /*----------------------------- NULL callback: */ + /* NULL callback is valid in V19 -- it means use blocking read/write stream + + ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL; + ipp.sampleFormat = opp.sampleFormat = paFloat32; + ipp.channelCount = 0; ipp.device = paNoDevice; + opp.channelCount = 2; opp.device = Pa_GetDefaultOutputDevice(); + HOPEFOR(((result = Pa_OpenStream(&stream, NULL, &opp, + SAMPLE_RATE, FRAMES_PER_BUFFER, + paClipOff, + NULL, + &myData )) == paNullCallback)); + */ + + /*----------------------------- Bad flag: */ + ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL; + ipp.sampleFormat = opp.sampleFormat = paFloat32; + ipp.channelCount = 0; ipp.device = paNoDevice; + opp.channelCount = 2; opp.device = Pa_GetDefaultOutputDevice(); + HOPEFOR(((result = Pa_OpenStream(&stream, NULL, &opp, + SAMPLE_RATE, FRAMES_PER_BUFFER, + 255, /* Is 8 maybe legal V19 API? */ + QaCallback, &myData )) == paInvalidFlag)); + + /*----------------------------- using input device as output device: */ + if( FindInputOnlyDevice() != paNoDevice ) + { + ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL; + ipp.sampleFormat = opp.sampleFormat = paFloat32; + ipp.channelCount = 0; ipp.device = paNoDevice; /* And no input device, and no channels. */ + opp.channelCount = 2; opp.device = FindInputOnlyDevice(); + HOPEFOR(((result = Pa_OpenStream(&stream, NULL, &opp, + SAMPLE_RATE, FRAMES_PER_BUFFER, + paClipOff, QaCallback, &myData )) == paInvalidChannelCount)); + } + + /*----------------------------- using output device as input device: */ + if( FindOutputOnlyDevice() != paNoDevice ) + { + ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL; + ipp.sampleFormat = opp.sampleFormat = paFloat32; + ipp.channelCount = 2; ipp.device = FindOutputOnlyDevice(); + opp.channelCount = 0; opp.device = paNoDevice; /* And no output device, and no channels. */ + HOPEFOR(((result = Pa_OpenStream(&stream, &ipp, NULL, + SAMPLE_RATE, FRAMES_PER_BUFFER, + paClipOff, QaCallback, &myData )) == paInvalidChannelCount)); + + } + + if( stream != NULL ) Pa_CloseStream( stream ); + return result; +} + +/*-----------------------------------------------------------------------------------------*/ +static int TestBadActions( void ) +{ + PaStream* stream = NULL; + PaError result; + PaQaData myData; + PaStreamParameters opp; + + /* Setup data for synthesis thread. */ + myData.framesLeft = (unsigned long)(SAMPLE_RATE * 100); /* 100 seconds */ + myData.numChannels = 1; + myData.mode = MODE_OUTPUT; + + opp.device = Pa_GetDefaultOutputDevice(); /* Default output. */ + opp.channelCount = 2; /* Stereo output. */ + opp.hostApiSpecificStreamInfo = NULL; + opp.sampleFormat = paFloat32; + opp.suggestedLatency = Pa_GetDeviceInfo(opp.device)->defaultLowOutputLatency; + + HOPEFOR(((result = Pa_OpenStream(&stream, NULL, /* Take NULL as input parame- */ + &opp, /* ters, meaning try only output. */ + SAMPLE_RATE, FRAMES_PER_BUFFER, + paClipOff, QaCallback, &myData )) == paNoError)); + + HOPEFOR(((result = Pa_StartStream(NULL)) == paBadStreamPtr)); + HOPEFOR(((result = Pa_StopStream(NULL)) == paBadStreamPtr)); + HOPEFOR(((result = Pa_IsStreamStopped(NULL)) == paBadStreamPtr)); + HOPEFOR(((result = Pa_IsStreamActive(NULL)) == paBadStreamPtr)); + HOPEFOR(((result = Pa_CloseStream(NULL)) == paBadStreamPtr)); + HOPEFOR(((result = Pa_SetStreamFinishedCallback(NULL, NULL)) == paBadStreamPtr)); + HOPEFOR(((result = !Pa_GetStreamInfo(NULL)))); + HOPEFOR(((result = Pa_GetStreamTime(NULL)) == 0.0)); + HOPEFOR(((result = Pa_GetStreamCpuLoad(NULL)) == 0.0)); + HOPEFOR(((result = Pa_ReadStream(NULL, NULL, 0)) == paBadStreamPtr)); + HOPEFOR(((result = Pa_WriteStream(NULL, NULL, 0)) == paBadStreamPtr)); + + /** @todo test Pa_GetStreamReadAvailable and Pa_GetStreamWriteAvailable */ + + if (stream != NULL) Pa_CloseStream(stream); + return result; +} + +/*---------------------------------------------------------------------*/ +int main(void); +int main(void) +{ + PaError result; + + EXPECT(((result = Pa_Initialize()) == paNoError)); + TestBadOpens(); + TestBadActions(); +error: + Pa_Terminate(); + printf("QA Report: %d passed, %d failed.\n", gNumPassed, gNumFailed); + return 0; +} diff --git a/src/audio/portaudio/pa_tests/patest1.c b/src/audio/portaudio/pa_tests/patest1.c new file mode 100644 index 0000000000000000000000000000000000000000..15fdfdd3d18eefd107c9086e21bac0ac00bacd13 --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest1.c @@ -0,0 +1,192 @@ +/** @file patest1.c + @brief Ring modulate the audio input with a sine wave for 20 seconds. + @author Ross Bencina <rossb@audiomulch.com> +*/ +/* + * $Id$ + * + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +#define SAMPLE_RATE (44100) + +typedef struct +{ + float sine[100]; + int phase; + int sampsToGo; +} +patest1data; + +static int patest1Callback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + patest1data *data = (patest1data*)userData; + float *in = (float*)inputBuffer; + float *out = (float*)outputBuffer; + int framesToCalc = framesPerBuffer; + unsigned long i = 0; + int finished; + + if( data->sampsToGo < framesPerBuffer ) + { + framesToCalc = data->sampsToGo; + finished = paComplete; + } + else + { + finished = paContinue; + } + + for( ; i<framesToCalc; i++ ) + { + *out++ = *in++ * data->sine[data->phase]; /* left */ + *out++ = *in++ * data->sine[data->phase++]; /* right */ + if( data->phase >= 100 ) + data->phase = 0; + } + + data->sampsToGo -= framesToCalc; + + /* zero remainder of final buffer if not already done */ + for( ; i<framesPerBuffer; i++ ) + { + *out++ = 0; /* left */ + *out++ = 0; /* right */ + } + + return finished; +} + +int main(int argc, char* argv[]); +int main(int argc, char* argv[]) +{ + PaStream *stream; + PaError err; + patest1data data; + int i; + PaStreamParameters inputParameters, outputParameters; + const PaHostErrorInfo* herr; + + printf("patest1.c\n"); fflush(stdout); + printf("Ring modulate input for 20 seconds.\n"); fflush(stdout); + + /* initialise sinusoidal wavetable */ + for( i=0; i<100; i++ ) + data.sine[i] = sin( ((double)i/100.) * M_PI * 2. ); + data.phase = 0; + data.sampsToGo = SAMPLE_RATE * 20; /* 20 seconds. */ + + /* initialise portaudio subsytem */ + err = Pa_Initialize(); + + inputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */ + inputParameters.channelCount = 2; /* stereo input */ + inputParameters.sampleFormat = paFloat32; /* 32 bit floating point input */ + inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency; + inputParameters.hostApiSpecificStreamInfo = NULL; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ + outputParameters.channelCount = 2; /* stereo output */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + &inputParameters, + &outputParameters, + (double)SAMPLE_RATE, /* Samplerate in Hertz. */ + 512, /* Small buffers */ + paClipOff, /* We won't output out of range samples so don't bother clipping them. */ + patest1Callback, + &data ); + if( err != paNoError ) goto done; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto done; + + printf( "Press any key to end.\n" ); fflush(stdout); + + getc( stdin ); /* wait for input before exiting */ + + err = Pa_AbortStream( stream ); + if( err != paNoError ) goto done; + + printf( "Waiting for stream to complete...\n" ); + + /* sleep until playback has finished */ + while( ( err = Pa_IsStreamActive( stream ) ) == 1 ) Pa_Sleep(1000); + if( err < 0 ) goto done; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto done; + +done: + Pa_Terminate(); + + if( err != paNoError ) + { + fprintf( stderr, "An error occured while using portaudio\n" ); + if( err == paUnanticipatedHostError ) + { + fprintf( stderr, " unanticipated host error.\n"); + herr = Pa_GetLastHostErrorInfo(); + if (herr) + { + fprintf( stderr, " Error number: %ld\n", herr->errorCode ); + if (herr->errorText) + fprintf( stderr, " Error text: %s\n", herr->errorText ); + } + else + fprintf( stderr, " Pa_GetLastHostErrorInfo() failed!\n" ); + } + else + { + fprintf( stderr, " Error number: %d\n", err ); + fprintf( stderr, " Error text: %s\n", Pa_GetErrorText( err ) ); + } + + err = 1; /* Always return 0 or 1, but no other return codes. */ + } + + printf( "bye\n" ); + + return err; +} diff --git a/src/audio/portaudio/pa_tests/patest_buffer.c b/src/audio/portaudio/pa_tests/patest_buffer.c new file mode 100644 index 0000000000000000000000000000000000000000..39c5dd21dcff946a3a63774ce537a787d2cad68e --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_buffer.c @@ -0,0 +1,180 @@ +/** @file patest_buffer.c + @brief Test opening streams with different buffer sizes. + @author Phil Burk http://www.softsynth.com +*/ +/* + * $Id$ + * + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include "portaudio.h" +#define NUM_SECONDS (1) +#define SAMPLE_RATE (44100) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (200) + +#define BUFFER_TABLE 9 +long buffer_table[] = {200,256,500,512,600, 723, 1000, 1024, 2345}; + +typedef struct +{ + short sine[TABLE_SIZE]; + int left_phase; + int right_phase; + unsigned int sampsToGo; +} +paTestData; +PaError TestOnce( int buffersize ); + +/* 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 patest1Callback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + short *out = (short*)outputBuffer; + unsigned int i; + int finished = 0; + (void) inputBuffer; /* Prevent "unused variable" warnings. */ + + if( data->sampsToGo < framesPerBuffer ) + { + /* final buffer... */ + + for( i=0; i<data->sampsToGo; i++ ) + { + *out++ = data->sine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + /* zero remainder of final buffer */ + for( ; i<framesPerBuffer; i++ ) + { + *out++ = 0; /* left */ + *out++ = 0; /* right */ + } + + finished = 1; + } + else + { + for( i=0; i<framesPerBuffer; i++ ) + { + *out++ = data->sine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + data->sampsToGo -= framesPerBuffer; + } + return finished; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + int i; + PaError err; + printf("Test opening streams with different buffer sizes\n\n"); + + for (i = 0 ; i < BUFFER_TABLE; i++) + { + printf("Buffer size %ld\n", buffer_table[i]); + err = TestOnce(buffer_table[i]); + if( err < 0 ) return 0; + + } + return 0; +} + + +PaError TestOnce( int buffersize ) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + paTestData data; + int i; + int totalSamps; + /* initialise sinusoidal wavetable */ + for( i=0; i<TABLE_SIZE; i++ ) + { + data.sine[i] = (short) (32767.0 * sin( ((double)i/(double)TABLE_SIZE) * M_PI * 2. )); + } + data.left_phase = data.right_phase = 0; + data.sampsToGo = totalSamps = NUM_SECONDS * SAMPLE_RATE; /* Play for a few seconds. */ + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ + outputParameters.channelCount = 2; /* stereo output */ + outputParameters.sampleFormat = paInt16; /* 32 bit floating point output */ + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + buffersize, /* frames per buffer */ + (paClipOff | paDitherOff), + patest1Callback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + printf("Waiting for sound to finish.\n"); + Pa_Sleep(1000); + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + return paNoError; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/patest_callbackstop.c b/src/audio/portaudio/pa_tests/patest_callbackstop.c new file mode 100644 index 0000000000000000000000000000000000000000..2da642e4380df46f73c19c0f075e0fa3a57d9fbb --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_callbackstop.c @@ -0,0 +1,221 @@ +/** @file patest_callbackstop.c + @brief Test the paComplete callback result code. + @author Ross Bencina <rossb@audiomulch.com> +*/ +/* + * $Id$ + * + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" + +#define NUM_SECONDS (5) +#define NUM_LOOPS (4) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (67) + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +#define TABLE_SIZE (200) +typedef struct +{ + float sine[TABLE_SIZE]; + int phase; + unsigned long generatedFramesCount; + volatile int callbackReturnedPaComplete; + volatile int callbackInvokedAfterReturningPaComplete; +} +TestData; + +/* + This routine will be called by the PortAudio stream when audio is needed. + It may be 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 TestCallback( const void *input, void *output, + unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + TestData *data = (TestData*)userData; + float *out = (float*)output; + unsigned long i; + float x; + + (void) input; /* Prevent unused variable warnings. */ + (void) timeInfo; + (void) statusFlags; + + + if( data->callbackReturnedPaComplete ) + data->callbackInvokedAfterReturningPaComplete = 1; + + for( i=0; i<frameCount; i++ ) + { + /* generate tone */ + + x = data->sine[ data->phase++ ]; + if( data->phase >= TABLE_SIZE ) + data->phase -= TABLE_SIZE; + + *out++ = x; /* left */ + *out++ = x; /* right */ + } + + data->generatedFramesCount += frameCount; + if( data->generatedFramesCount >= (NUM_SECONDS * SAMPLE_RATE) ) + { + data->callbackReturnedPaComplete = 1; + return paComplete; + } + else + { + return paContinue; + } +} + +/*----------------------------------------------------------------------------*/ +int main(void); +int main(void) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + TestData data; + int i, j; + + + printf( "PortAudio Test: output sine wave. SR = %d, BufSize = %d\n", + SAMPLE_RATE, FRAMES_PER_BUFFER ); + + /* initialise sinusoidal wavetable */ + for( i=0; i<TABLE_SIZE; i++ ) + { + data.sine[i] = (float) sin( ((double)i/(double)TABLE_SIZE) * M_PI * 2. ); + } + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outputParameters.device = Pa_GetDefaultOutputDevice(); + outputParameters.channelCount = 2; /* stereo output */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* output will be in-range, so no need to clip */ + TestCallback, + &data ); + if( err != paNoError ) goto error; + + printf("Repeating test %d times.\n", NUM_LOOPS ); + + for( i=0; i < NUM_LOOPS; ++i ) + { + data.phase = 0; + data.generatedFramesCount = 0; + data.callbackReturnedPaComplete = 0; + data.callbackInvokedAfterReturningPaComplete = 0; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Play for %d seconds.\n", NUM_SECONDS ); + + /* wait for the callback to complete generating NUM_SECONDS of tone */ + + do + { + Pa_Sleep( 500 ); + } + while( !data.callbackReturnedPaComplete ); + + printf( "Callback returned paComplete.\n" ); + printf( "Waiting for buffers to finish playing...\n" ); + + /* wait for stream to become inactive, + or for a timeout of approximately NUM_SECONDS + */ + + j = 0; + while( (err = Pa_IsStreamActive( stream )) == 1 && j < NUM_SECONDS * 2 ) + { + printf(".\n" ); + Pa_Sleep( 500 ); + ++j; + } + + if( err < 0 ) + { + goto error; + } + else if( err == 1 ) + { + printf( "TEST FAILED: Timed out waiting for buffers to finish playing.\n" ); + } + else + { + printf("Buffers finished.\n" ); + } + + if( data.callbackInvokedAfterReturningPaComplete ) + { + printf( "TEST FAILED: Callback was invoked after returning paComplete.\n" ); + } + + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + } + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Terminate(); + printf("Test finished.\n"); + + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/patest_clip.c b/src/audio/portaudio/pa_tests/patest_clip.c new file mode 100644 index 0000000000000000000000000000000000000000..1d17d11b25f9e9c1fdf0eb282ebddd1ccb2cf834 --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_clip.c @@ -0,0 +1,178 @@ +/** @file patest_clip.c + @brief Play a sine wave for several seconds at an amplitude + that would require clipping. + + @author Phil Burk http://www.softsynth.com +*/ +/* + * $Id$ + * + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" + +#define NUM_SECONDS (4) +#define SAMPLE_RATE (44100) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (200) + +typedef struct paTestData +{ + float sine[TABLE_SIZE]; + float amplitude; + int left_phase; + int right_phase; +} +paTestData; + +PaError PlaySine( paTestData *data, unsigned long flags, float amplitude ); + +/* 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 sineCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + float amplitude = data->amplitude; + unsigned int i; + (void) inputBuffer; /* Prevent "unused variable" warnings. */ + (void) timeInfo; + (void) statusFlags; + + for( i=0; i<framesPerBuffer; i++ ) + { + *out++ = amplitude * data->sine[data->left_phase]; /* left */ + *out++ = amplitude * data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + return 0; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PaError err; + paTestData data; + int i; + + printf("PortAudio Test: output sine wave with and without clipping.\n"); + /* initialise sinusoidal wavetable */ + for( i=0; i<TABLE_SIZE; i++ ) + { + data.sine[i] = (float) sin( ((double)i/(double)TABLE_SIZE) * M_PI * 2. ); + } + + printf("\nHalf amplitude. Should sound like sine wave.\n"); fflush(stdout); + err = PlaySine( &data, paClipOff | paDitherOff, 0.5f ); + if( err < 0 ) goto error; + + printf("\nFull amplitude. Should sound like sine wave.\n"); fflush(stdout); + err = PlaySine( &data, paClipOff | paDitherOff, 0.999f ); + if( err < 0 ) goto error; + + printf("\nOver range with clipping and dithering turned OFF. Should sound very nasty.\n"); + fflush(stdout); + err = PlaySine( &data, paClipOff | paDitherOff, 1.1f ); + if( err < 0 ) goto error; + + printf("\nOver range with clipping and dithering turned ON. Should sound smoother than previous.\n"); + fflush(stdout); + err = PlaySine( &data, paNoFlag, 1.1f ); + if( err < 0 ) goto error; + + printf("\nOver range with paClipOff but dithering ON.\n" + "That forces clipping ON so it should sound the same as previous.\n"); + fflush(stdout); + err = PlaySine( &data, paClipOff, 1.1f ); + if( err < 0 ) goto error; + + return 0; +error: + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return 1; +} +/*****************************************************************************/ +PaError PlaySine( paTestData *data, unsigned long flags, float amplitude ) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + + data->left_phase = data->right_phase = 0; + data->amplitude = amplitude; + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ + outputParameters.channelCount = 2; /* stereo output */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + 1024, + flags, + sineCallback, + data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + Pa_Sleep( NUM_SECONDS * 1000 ); + printf("CPULoad = %8.6f\n", Pa_GetStreamCpuLoad( stream ) ); + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Terminate(); + return paNoError; +error: + return err; +} diff --git a/src/audio/portaudio/pa_tests/patest_dither.c b/src/audio/portaudio/pa_tests/patest_dither.c new file mode 100644 index 0000000000000000000000000000000000000000..10f03e42b9e0a09367c3a6e4b31efc8ad32acc4c --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_dither.c @@ -0,0 +1,178 @@ +/** @file patest_dither.c + @brief Attempt to hear difference between dithered and non-dithered signal. + + This only has an effect if the native format is 16 bit. + + @author Phil Burk http://www.softsynth.com +*/ +/* + * $Id$ + * + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> + +#include "portaudio.h" + +#define NUM_SECONDS (5) +#define SAMPLE_RATE (44100) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (200) + +typedef struct paTestData +{ + float sine[TABLE_SIZE]; + float amplitude; + int left_phase; + int right_phase; +} +paTestData; + +/* 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 sineCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo *timeInfo, + PaStreamCallbackFlags statusFlags, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + float amplitude = data->amplitude; + unsigned int i; + (void) inputBuffer; + + for( i=0; i<framesPerBuffer; i++ ) + { + *out++ = amplitude * data->sine[data->left_phase]; /* left */ + *out++ = amplitude * data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + return 0; +} + +/*****************************************************************************/ +/* + V18 version did not call Pa_Terminate() if Pa_Initialize() failed. + This V19 version ALWAYS calls Pa_Terminate(). PS. +*/ +PaError PlaySine( paTestData *data, PaStreamFlags flags, float amplitude ); +PaError PlaySine( paTestData *data, PaStreamFlags flags, float amplitude ) +{ + PaStream* stream; + PaStreamParameters outputParameters; + PaError err; + + data->left_phase = data->right_phase = 0; + data->amplitude = amplitude; + + err = Pa_Initialize(); + if (err != paNoError) + goto done; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ + outputParameters.channelCount = 2; /* stereo output */ + outputParameters.hostApiSpecificStreamInfo = NULL; + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output. */ + /* When you change this, also */ + /* adapt the callback routine! */ + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device ) + ->defaultLowOutputLatency; /* Low latency. */ + err = Pa_OpenStream( &stream, + NULL, /* No input. */ + &outputParameters, + SAMPLE_RATE, + 1024, /* frames per buffer */ + flags, + sineCallback, + (void*)data ); + if (err != paNoError) + goto done; + + err = Pa_StartStream( stream ); + if (err != paNoError) + goto done; + + Pa_Sleep( NUM_SECONDS * 1000 ); + printf("CPULoad = %8.6f\n", Pa_GetStreamCpuLoad(stream)); + + err = Pa_CloseStream( stream ); +done: + Pa_Sleep( 250 ); /* Just a small silence. */ + Pa_Terminate(); + return err; +} + + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaError err; + paTestData DATA; + int i; + float amplitude = 4.0 / (1<<15); + + printf("PortAudio Test: output EXTREMELY QUIET sine wave with and without dithering.\n"); + /* initialise sinusoidal wavetable */ + for( i=0; i<TABLE_SIZE; i++ ) + { + DATA.sine[i] = (float) sin( ((double)i/(double)TABLE_SIZE) * M_PI * 2. ); + } + printf("\nNo treatment..\n"); fflush(stdout); + err = PlaySine( &DATA, paClipOff | paDitherOff, amplitude ); + if( err < 0 ) goto done; + + printf("\nClip..\n"); + fflush(stdout); + err = PlaySine( &DATA, paDitherOff, amplitude ); + if( err < 0 ) goto done; + + printf("\nClip and Dither..\n"); + fflush(stdout); + err = PlaySine( &DATA, paNoFlag, amplitude ); +done: + if (err) + { + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + err = 1; /* Though PlaySine() already called Pa_Terminate(), */ + } /* we may still call Pa_GetErrorText(). */ + else + printf("\n(Don't forget to turn the VOLUME DOWN after listening so carefully.)\n"); + return err; /* 0 or 1. */ +} diff --git a/src/audio/portaudio/pa_tests/patest_hang.c b/src/audio/portaudio/pa_tests/patest_hang.c new file mode 100644 index 0000000000000000000000000000000000000000..42858f2d0e0b8f07c124d94f75f23adca9be490e --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_hang.c @@ -0,0 +1,152 @@ +/** @file patest_hang.c + @brief Play a sine then hang audio callback to test watchdog. + @author Ross Bencina <rossb@audiomulch.com> + @author Phil Burk <philburk@softsynth.com> +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> + +#include "portaudio.h" + +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (1024) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TWOPI (M_PI * 2.0) + +typedef struct paTestData +{ + int sleepFor; + double phase; +} +paTestData; + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned long i; + int finished = 0; + double phaseInc = 0.02; + double phase = data->phase; + + (void) inputBuffer; /* Prevent unused argument warning. */ + + for( i=0; i<framesPerBuffer; i++ ) + { + phase += phaseInc; + if( phase > TWOPI ) phase -= TWOPI; + /* This is not a very efficient way to calc sines. */ + *out++ = (float) sin( phase ); /* mono */ + } + + if( data->sleepFor > 0 ) + { + Pa_Sleep( data->sleepFor ); + } + + data->phase = phase; + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStream* stream; + PaStreamParameters outputParameters; + PaError err; + int i; + paTestData data = {0}; + + printf("PortAudio Test: output sine wave. SR = %d, BufSize = %d\n", + SAMPLE_RATE, FRAMES_PER_BUFFER ); + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* Default output device. */ + outputParameters.channelCount = 1; /* Mono output. */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point. */ + outputParameters.hostApiSpecificStreamInfo = NULL; + outputParameters.suggestedLatency = Pa_GetDeviceInfo(outputParameters.device) + ->defaultLowOutputLatency; + err = Pa_OpenStream(&stream, + NULL, /* No input. */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* No out of range samples. */ + patestCallback, + &data); + if (err != paNoError) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + /* Gradually increase sleep time. */ + /* Was: for( i=0; i<10000; i+= 1000 ) */ + for(i=0; i <= 1000; i += 100) + { + printf("Sleep for %d milliseconds in audio callback.\n", i ); + data.sleepFor = i; + Pa_Sleep( ((i<1000) ? 1000 : i) ); + } + + printf("Suffer for 10 seconds.\n"); + Pa_Sleep( 10000 ); + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/patest_in_overflow.c b/src/audio/portaudio/pa_tests/patest_in_overflow.c new file mode 100644 index 0000000000000000000000000000000000000000..a0be2c6a1fa21a5e55ef34e28a8a5ca1d160d49a --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_in_overflow.c @@ -0,0 +1,224 @@ +/** @file patest_in_overflow.c + @brief Count input overflows (using paInputOverflow flag) under + overloaded and normal conditions. + This test uses the same method to overload the stream as does + patest_out_underflow.c -- it generates sine waves until the cpu load + exceeds a certain level. However this test is only concerned with + input and so doesn't ouput any sound. + + @author Ross Bencina <rossb@audiomulch.com> + @author Phil Burk <philburk@softsynth.com> +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2004 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" + +#define MAX_SINES (500) +#define MAX_LOAD (1.2) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (512) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TWOPI (M_PI * 2.0) + +typedef struct paTestData +{ + int sineCount; + double phases[MAX_SINES]; + int countOverflows; + int inputOverflowCount; +} +paTestData; + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float out; /* variable to hold dummy output */ + unsigned long i; + int j; + int finished = paContinue; + (void) timeInfo; /* Prevent unused variable warning. */ + (void) inputBuffer; /* Prevent unused variable warning. */ + (void) outputBuffer; /* Prevent unused variable warning. */ + + if( data->countOverflows && (statusFlags & paInputOverflow) ) + data->inputOverflowCount++; + + for( i=0; i<framesPerBuffer; i++ ) + { + float output = 0.0; + double phaseInc = 0.02; + double phase; + + for( j=0; j<data->sineCount; j++ ) + { + /* Advance phase of next oscillator. */ + phase = data->phases[j]; + phase += phaseInc; + if( phase > TWOPI ) phase -= TWOPI; + + phaseInc *= 1.02; + if( phaseInc > 0.5 ) phaseInc *= 0.5; + + /* This is not a very efficient way to calc sines. */ + output += (float) sin( phase ); + data->phases[j] = phase; + } + /* this is an input-only stream so we don't actually use the output */ + out = (float) (output / data->sineCount); + (void) out; /* suppress unused variable warning*/ + } + + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters inputParameters; + PaStream *stream; + PaError err; + int safeSineCount, stressedSineCount; + int safeOverflowCount, stressedOverflowCount; + paTestData data = {0}; + double load; + + + printf("PortAudio Test: input only, no sound output. Load callback by performing calculations, count input overflows. SR = %d, BufSize = %d. MAX_LOAD = %f\n", + SAMPLE_RATE, FRAMES_PER_BUFFER, (float)MAX_LOAD ); + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + inputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */ + inputParameters.channelCount = 1; /* mono output */ + inputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency; + inputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + &inputParameters, + NULL, /* no output */ + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Establishing load conditions...\n" ); + + /* Determine number of sines required to get to 50% */ + do + { + data.sineCount++; + Pa_Sleep( 100 ); + + load = Pa_GetStreamCpuLoad( stream ); + printf("sineCount = %d, CPU load = %f\n", data.sineCount, load ); + } + while( load < 0.5 && data.sineCount < (MAX_SINES-1)); + + safeSineCount = data.sineCount; + + /* Calculate target stress value then ramp up to that level*/ + stressedSineCount = (int) (2.0 * data.sineCount * MAX_LOAD ); + if( stressedSineCount > MAX_SINES ) + stressedSineCount = MAX_SINES; + for( ; data.sineCount < stressedSineCount; data.sineCount++ ) + { + Pa_Sleep( 100 ); + load = Pa_GetStreamCpuLoad( stream ); + printf("STRESSING: sineCount = %d, CPU load = %f\n", data.sineCount, load ); + } + + printf("Counting overflows for 5 seconds.\n"); + data.countOverflows = 1; + Pa_Sleep( 5000 ); + + stressedOverflowCount = data.inputOverflowCount; + + data.countOverflows = 0; + data.sineCount = safeSineCount; + + printf("Resuming safe load...\n"); + Pa_Sleep( 1500 ); + data.inputOverflowCount = 0; + Pa_Sleep( 1500 ); + load = Pa_GetStreamCpuLoad( stream ); + printf("sineCount = %d, CPU load = %f\n", data.sineCount, load ); + + printf("Counting overflows for 5 seconds.\n"); + data.countOverflows = 1; + Pa_Sleep( 5000 ); + + safeOverflowCount = data.inputOverflowCount; + + printf("Stop stream.\n"); + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Terminate(); + + if( stressedOverflowCount == 0 ) + printf("Test failed, no input overflows detected under stress.\n"); + else if( safeOverflowCount != 0 ) + printf("Test failed, %d unexpected overflows detected under safe load.\n", safeOverflowCount); + else + printf("Test passed, %d expected input overflows detected under stress, 0 unexpected overflows detected under safe load.\n", stressedOverflowCount ); + + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/patest_latency.c b/src/audio/portaudio/pa_tests/patest_latency.c new file mode 100644 index 0000000000000000000000000000000000000000..f4225112aec28c2419395866816f3044066dbb1f --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_latency.c @@ -0,0 +1,182 @@ +/** @file + @brief Hear the latency caused by big buffers. + Play a sine wave and change frequency based on letter input. + @author Phil Burk <philburk@softsynth.com>, and Darren Gibbs +*/ +/* + * $Id$ + * + * 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. + * + * 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. + * + * 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. + * + */ + +#include <stdio.h> +#include <math.h> +#include "portaudio.h" + +#define OUTPUT_DEVICE (Pa_GetDefaultOutputDevice()) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (64) + +#define MIN_FREQ (100.0f) +#define CalcPhaseIncrement(freq) ((freq)/SAMPLE_RATE) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (400) + +typedef struct +{ + float sine[TABLE_SIZE + 1]; /* add one for guard point for interpolation */ + float phase_increment; + float left_phase; + float right_phase; +} +paTestData; + +float LookupSine( paTestData *data, float phase ); +/* Convert phase between and 1.0 to sine value + * using linear interpolation. + */ +float LookupSine( paTestData *data, float phase ) +{ + float fIndex = phase*TABLE_SIZE; + int index = (int) fIndex; + float fract = fIndex - index; + float lo = data->sine[index]; + float hi = data->sine[index+1]; + float val = lo + fract*(hi-lo); + return val; +} + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + int i; + + (void) inputBuffer; /* Prevent unused variable warning. */ + + for( i=0; i<framesPerBuffer; i++ ) + { + *out++ = LookupSine(data, data->left_phase); /* left */ + *out++ = LookupSine(data, data->right_phase); /* right */ + data->left_phase += data->phase_increment; + if( data->left_phase >= 1.0f ) data->left_phase -= 1.0f; + data->right_phase += (data->phase_increment * 1.5f); /* fifth above */ + if( data->right_phase >= 1.0f ) data->right_phase -= 1.0f; + } + return 0; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStream *stream; + PaStreamParameters outputParameters; + PaError err; + paTestData data; + int i; + int done = 0; + + printf("PortAudio Test: enter letter then hit ENTER.\n" ); + /* initialise sinusoidal wavetable */ + for( i=0; i<TABLE_SIZE; i++ ) + { + data.sine[i] = 0.90f * (float) sin( ((double)i/(double)TABLE_SIZE) * M_PI * 2. ); + } + data.sine[TABLE_SIZE] = data.sine[0]; /* set guard point. */ + data.left_phase = data.right_phase = 0.0; + data.phase_increment = CalcPhaseIncrement(MIN_FREQ); + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + printf("PortAudio Test: output device = %d\n", OUTPUT_DEVICE ); + + outputParameters.device = OUTPUT_DEVICE; + outputParameters.channelCount = 2; /* stereo output */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + printf("Requested output latency = %.4f seconds.\n", outputParameters.suggestedLatency ); + printf("%d frames per buffer.\n.", FRAMES_PER_BUFFER ); + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff|paDitherOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + printf("Play ASCII keyboard. Hit 'q' to stop. (Use RETURN key on Mac)\n"); + fflush(stdout); + while ( !done ) + { + float freq; + int index; + char c; + do + { + c = getchar(); + } + while( c < ' '); /* Strip white space and control chars. */ + + if( c == 'q' ) done = 1; + index = c % 26; + freq = MIN_FREQ + (index * 40.0); + data.phase_increment = CalcPhaseIncrement(freq); + } + printf("Call Pa_StopStream()\n"); + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/patest_leftright.c b/src/audio/portaudio/pa_tests/patest_leftright.c new file mode 100644 index 0000000000000000000000000000000000000000..158223649df803f72ca3abf4d414ebd13f382df8 --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_leftright.c @@ -0,0 +1,173 @@ +/** @file patest_leftright.c + @brief Play different tone sine waves that + alternate between left and right channel. + + The low tone should be on the left channel. + + @author Ross Bencina <rossb@audiomulch.com> + @author Phil Burk <philburk@softsynth.com> +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" + +#define NUM_SECONDS (8) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (512) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (200) +typedef struct +{ + float sine[TABLE_SIZE]; + int left_phase; + int right_phase; + int toggle; + int countDown; +} +paTestData; +/* 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 patestCallback( const void *inputBuffer, + void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned long i; + int finished = 0; + /* Prevent unused variable warnings. */ + (void) inputBuffer; + + for( i=0; i<framesPerBuffer; i++ ) + { + if( data->toggle ) + { + *out++ = data->sine[data->left_phase]; /* left */ + *out++ = 0; /* right */ + } + else + { + *out++ = 0; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + } + + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + + if( data->countDown < 0 ) + { + data->countDown = SAMPLE_RATE; + data->toggle = !data->toggle; + } + data->countDown -= framesPerBuffer; + + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStream *stream; + PaStreamParameters outputParameters; + PaError err; + paTestData data; + int i; + int timeout; + + printf("Play different tone sine waves that alternate between left and right channel.\n"); + printf("The low tone should be on the left channel.\n"); + + /* initialise sinusoidal wavetable */ + for( i=0; i<TABLE_SIZE; i++ ) + { + data.sine[i] = (float) sin( ((double)i/(double)TABLE_SIZE) * M_PI * 2. ); + } + data.left_phase = data.right_phase = data.toggle = 0; + data.countDown = SAMPLE_RATE; + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ + outputParameters.channelCount = 2; /* stereo output */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( &stream, + NULL, /* No input. */ + &outputParameters, /* As above. */ + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Play for several seconds.\n"); + timeout = NUM_SECONDS * 4; + while( timeout > 0 ) + { + Pa_Sleep( 300 ); /*(Irix very much likes sleeps <= 1000 ms.)*/ + timeout -= 1; + } + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/patest_longsine.c b/src/audio/portaudio/pa_tests/patest_longsine.c new file mode 100644 index 0000000000000000000000000000000000000000..3a6ec39ef5f38d5b0e500f53c861b0ce751e02ae --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_longsine.c @@ -0,0 +1,140 @@ +/** @file patest_longsine.c + @brief Play a sine wave until ENTER hit. + @author Phil Burk http://www.softsynth.com +*/ +/* + * $Id$ + * + * 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. + * + * 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. + * + * 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. + * + */ + +#include <stdio.h> +#include <math.h> + +#include "portaudio.h" + +#define SAMPLE_RATE (44100) + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +#define TABLE_SIZE (200) +typedef struct +{ + float sine[TABLE_SIZE]; + int left_phase; + int right_phase; +} +paTestData; + +/* 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 patestCallback(const void* inputBuffer, + void* outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void* userData) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned int i; + (void) inputBuffer; /* Prevent unused argument warning. */ + for( i=0; i<framesPerBuffer; i++ ) + { + *out++ = data->sine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + return 0; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + paTestData data; + int i; + printf("PortAudio Test: output sine wave.\n"); + + /* initialise sinusoidal wavetable */ + for( i=0; i<TABLE_SIZE; i++ ) + { + data.sine[i] = (float) sin( ((double)i/(double)TABLE_SIZE) * M_PI * 2. ); + } + data.left_phase = data.right_phase = 0; + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ + outputParameters.channelCount = 2; /* stereo output */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( &stream, + NULL, /* No input. */ + &outputParameters, /* As above. */ + SAMPLE_RATE, + 256, /* Frames per buffer. */ + paClipOff, /* No out of range samples expected. */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Hit ENTER to stop program.\n"); + getchar(); + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + + printf("Test finished.\n"); + return err; + +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/patest_many.c b/src/audio/portaudio/pa_tests/patest_many.c new file mode 100644 index 0000000000000000000000000000000000000000..6a678467b86b26a7a0bb9114b130b118a9739e03 --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_many.c @@ -0,0 +1,198 @@ +/** @file patest_many.c + @brief Start and stop the PortAudio Driver multiple times. + @author Phil Burk http://www.softsynth.com +*/ +/* + * $Id$ + * + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include "portaudio.h" +#define NUM_SECONDS (1) +#define SAMPLE_RATE (44100) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (200) +typedef struct +{ + short sine[TABLE_SIZE]; + int left_phase; + int right_phase; + unsigned int sampsToGo; +} +paTestData; +PaError TestOnce( void ); +static int patest1Callback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ); + +/* 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 patest1Callback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + short *out = (short*)outputBuffer; + unsigned int i; + int finished = 0; + (void) inputBuffer; /* Prevent "unused variable" warnings. */ + + if( data->sampsToGo < framesPerBuffer ) + { + /* final buffer... */ + + for( i=0; i<data->sampsToGo; i++ ) + { + *out++ = data->sine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + /* zero remainder of final buffer */ + for( ; i<framesPerBuffer; i++ ) + { + *out++ = 0; /* left */ + *out++ = 0; /* right */ + } + + finished = 1; + } + else + { + for( i=0; i<framesPerBuffer; i++ ) + { + *out++ = data->sine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + data->sampsToGo -= framesPerBuffer; + } + return finished; +} +/*******************************************************************/ +#ifdef MACINTOSH +int main(void); +int main(void) +{ + int i; + PaError err; + int numLoops = 10; + printf("Loop %d times.\n", numLoops ); + for( i=0; i<numLoops; i++ ) + { + printf("Loop %d out of %d.\n", i+1, numLoops ); + err = TestOnce(); + if( err < 0 ) return 0; + } +} +#else +int main(int argc, char **argv); +int main(int argc, char **argv) +{ + PaError err; + int i, numLoops = 10; + if( argc > 1 ) + { + numLoops = atoi(argv[1]); + } + for( i=0; i<numLoops; i++ ) + { + printf("Loop %d out of %d.\n", i+1, numLoops ); + err = TestOnce(); + if( err < 0 ) return 1; + } + printf("Test complete.\n"); + return 0; +} +#endif +PaError TestOnce( void ) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + paTestData data; + int i; + int totalSamps; + /* initialise sinusoidal wavetable */ + for( i=0; i<TABLE_SIZE; i++ ) + { + data.sine[i] = (short) (32767.0 * sin( ((double)i/(double)TABLE_SIZE) * M_PI * 2. )); + } + data.left_phase = data.right_phase = 0; + data.sampsToGo = totalSamps = NUM_SECONDS * SAMPLE_RATE; /* Play for a few seconds. */ + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ + outputParameters.channelCount = 2; /* stereo output */ + outputParameters.sampleFormat = paInt16; + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + 1024, /* frames per buffer */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patest1Callback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + printf("Waiting for sound to finish.\n"); + Pa_Sleep(1000); + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + return paNoError; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/patest_maxsines.c b/src/audio/portaudio/pa_tests/patest_maxsines.c new file mode 100644 index 0000000000000000000000000000000000000000..56592ea47d73e8dba5139f3e715895bacbb9fd56 --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_maxsines.c @@ -0,0 +1,204 @@ +/** @file patest_maxsines.c + @brief How many sine waves can we calculate and play in less than 80% CPU Load. + @author Ross Bencina <rossb@audiomulch.com> + @author Phil Burk <philburk@softsynth.com> +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" + +#define MAX_SINES (500) +#define MAX_USAGE (0.8) +#define SAMPLE_RATE (44100) +#define FREQ_TO_PHASE_INC(freq) (freq/(float)SAMPLE_RATE) + +#define MIN_PHASE_INC FREQ_TO_PHASE_INC(200.0f) +#define MAX_PHASE_INC (MIN_PHASE_INC * (1 << 5)) + +#define FRAMES_PER_BUFFER (512) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TWOPI (M_PI * 2.0) + +#define TABLE_SIZE (512) + +typedef struct paTestData +{ + int numSines; + float sine[TABLE_SIZE + 1]; /* add one for guard point for interpolation */ + float phases[MAX_SINES]; +} +paTestData; + +/* Convert phase between and 1.0 to sine value + * using linear interpolation. + */ +float LookupSine( paTestData *data, float phase ); +float LookupSine( paTestData *data, float phase ) +{ + float fIndex = phase*TABLE_SIZE; + int index = (int) fIndex; + float fract = fIndex - index; + float lo = data->sine[index]; + float hi = data->sine[index+1]; + float val = lo + fract*(hi-lo); + return val; +} + +/* 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 patestCallback(const void* inputBuffer, + void* outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void* userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + float outSample; + float scaler; + int numForScale; + unsigned long i; + int j; + int finished = 0; + (void) inputBuffer; /* Prevent unused argument warning. */ + + /* Determine amplitude scaling factor */ + numForScale = data->numSines; + if( numForScale < 8 ) numForScale = 8; /* prevent pops at beginning */ + scaler = 1.0f / numForScale; + + for( i=0; i<framesPerBuffer; i++ ) + { + float output = 0.0; + float phaseInc = MIN_PHASE_INC; + float phase; + for( j=0; j<data->numSines; j++ ) + { + /* Advance phase of next oscillator. */ + phase = data->phases[j]; + phase += phaseInc; + if( phase >= 1.0 ) phase -= 1.0; + + output += LookupSine(data, phase); + data->phases[j] = phase; + + phaseInc *= 1.02f; + if( phaseInc > MAX_PHASE_INC ) phaseInc = MIN_PHASE_INC; + } + + outSample = (float) (output * scaler); + *out++ = outSample; /* Left */ + *out++ = outSample; /* Right */ + } + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + int i; + PaStream* stream; + PaStreamParameters outputParameters; + PaError err; + paTestData data = {0}; + double load; + + printf("PortAudio Test: output sine wave. SR = %d, BufSize = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER); + + /* initialise sinusoidal wavetable */ + for( i=0; i<TABLE_SIZE; i++ ) + { + data.sine[i] = (float) sin( ((double)i/(double)TABLE_SIZE) * M_PI * 2. ); + } + data.sine[TABLE_SIZE] = data.sine[0]; /* set guard point */ + + err = Pa_Initialize(); + if( err != paNoError ) + goto error; + outputParameters.device = Pa_GetDefaultOutputDevice(); /* Default output device. */ + outputParameters.channelCount = 2; /* Stereo output. */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output. */ + outputParameters.hostApiSpecificStreamInfo = NULL; + outputParameters.suggestedLatency = Pa_GetDeviceInfo(outputParameters.device) + ->defaultHighOutputLatency; + err = Pa_OpenStream(&stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* No out of range samples should occur. */ + patestCallback, + &data); + if( err != paNoError ) + goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) + goto error; + + /* Play an increasing number of sine waves until we hit MAX_USAGE */ + do { + data.numSines++; + Pa_Sleep(200); + load = Pa_GetStreamCpuLoad(stream); + printf("numSines = %d, CPU load = %f\n", data.numSines, load ); + fflush(stdout); + } while((load < MAX_USAGE) && (data.numSines < MAX_SINES)); + + Pa_Sleep(2000); /* Stay for 2 seconds around 80% CPU. */ + + err = Pa_StopStream( stream ); + if( err != paNoError ) + goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) + goto error; + + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/patest_mono.c b/src/audio/portaudio/pa_tests/patest_mono.c new file mode 100644 index 0000000000000000000000000000000000000000..e7b57362fdf166f45173551e31d572359691ddc7 --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_mono.c @@ -0,0 +1,145 @@ +/* + * $Id$ + * patest_sine.c + * Play a monophonic sine wave using the Portable Audio api for several seconds. + * + * Authors: + * Ross Bencina <rossb@audiomulch.com> + * Phil Burk <philburk@softsynth.com> + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" + +#define NUM_SECONDS (10) +#define SAMPLE_RATE (44100) +#define AMPLITUDE (0.8) +#define FRAMES_PER_BUFFER (64) +#define OUTPUT_DEVICE Pa_GetDefaultOutputDevice() + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +#define TABLE_SIZE (200) +typedef struct +{ + float sine[TABLE_SIZE]; + int phase; +} +paTestData; + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned long i; + int finished = 0; + /* avoid unused variable warnings */ + (void) inputBuffer; + (void) timeInfo; + (void) statusFlags; + for( i=0; i<framesPerBuffer; i++ ) + { + *out++ = data->sine[data->phase]; /* left */ + data->phase += 1; + if( data->phase >= TABLE_SIZE ) data->phase -= TABLE_SIZE; + } + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + paTestData data; + int i; + printf("PortAudio Test: output MONO sine wave. SR = %d, BufSize = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER); + /* initialise sinusoidal wavetable */ + for( i=0; i<TABLE_SIZE; i++ ) + { + data.sine[i] = (float) (AMPLITUDE * sin( ((double)i/(double)TABLE_SIZE) * M_PI * 2. )); + } + data.phase = 0; + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outputParameters.device = OUTPUT_DEVICE; + outputParameters.channelCount = 1; /* MONO output */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Play for %d seconds.\n", NUM_SECONDS ); fflush(stdout); + Pa_Sleep( NUM_SECONDS * 1000 ); + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} \ No newline at end of file diff --git a/src/audio/portaudio/pa_tests/patest_mono_asio_channel_select.c b/src/audio/portaudio/pa_tests/patest_mono_asio_channel_select.c new file mode 100644 index 0000000000000000000000000000000000000000..4c4ffccdfa9aab056e117027a8867a6218dc6936 --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_mono_asio_channel_select.c @@ -0,0 +1,155 @@ +/* + * $Id$ + * patest_sine.c + * Play a monophonic sine wave using the Portable Audio api for several seconds. + * + * Authors: + * Ross Bencina <rossb@audiomulch.com> + * Phil Burk <philburk@softsynth.com> + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" +#include "pa_asio.h" + +#define NUM_SECONDS (10) +#define SAMPLE_RATE (44100) +#define AMPLITUDE (0.8) +#define FRAMES_PER_BUFFER (64) +#define OUTPUT_DEVICE Pa_GetDefaultOutputDevice() + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +#define TABLE_SIZE (200) +typedef struct +{ + float sine[TABLE_SIZE]; + int phase; +} +paTestData; + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned long i; + int finished = 0; + /* avoid unused variable warnings */ + (void) inputBuffer; + (void) timeInfo; + (void) statusFlags; + for( i=0; i<framesPerBuffer; i++ ) + { + *out++ = data->sine[data->phase]; /* left */ + data->phase += 1; + if( data->phase >= TABLE_SIZE ) data->phase -= TABLE_SIZE; + } + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters outputParameters; + PaAsioStreamInfo asioOutputInfo; + PaStream *stream; + PaError err; + paTestData data; + int outputChannelSelectors[1]; + int i; + printf("PortAudio Test: output MONO sine wave. SR = %d, BufSize = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER); + /* initialise sinusoidal wavetable */ + for( i=0; i<TABLE_SIZE; i++ ) + { + data.sine[i] = (float) (AMPLITUDE * sin( ((double)i/(double)TABLE_SIZE) * M_PI * 2. )); + } + data.phase = 0; + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outputParameters.device = OUTPUT_DEVICE; + outputParameters.channelCount = 1; /* MONO output */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + + asioOutputInfo.size = sizeof(PaAsioStreamInfo); + asioOutputInfo.hostApiType = paASIO; + asioOutputInfo.version = 1; + asioOutputInfo.flags = paAsioUseChannelSelectors; + outputChannelSelectors[0] = 1; /* select the second (right) ASIO device channel */ + asioOutputInfo.channelSelectors = outputChannelSelectors; + outputParameters.hostApiSpecificStreamInfo = &asioOutputInfo; + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Play for %d seconds.\n", NUM_SECONDS ); fflush(stdout); + Pa_Sleep( NUM_SECONDS * 1000 ); + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/patest_multi_sine.c b/src/audio/portaudio/pa_tests/patest_multi_sine.c new file mode 100644 index 0000000000000000000000000000000000000000..161c88222c9151e037b6dbee3bae4b5d7cc195e0 --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_multi_sine.c @@ -0,0 +1,194 @@ +/** @file patest_multi_sine.c + @brief Play a different sine wave on each channel. + @author Phil Burk http://www.softsynth.com +*/ +/* + * $Id$ + * + * 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. + * + * 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. + * + * 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. + * + */ + +#include <stdio.h> +#include <math.h> + +#include "portaudio.h" + +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (256) +#define FREQ_INCR (300.0 / SAMPLE_RATE) +#define MAX_CHANNELS (64) + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +typedef struct +{ + short interleaved; /* Nonzero for interleaved / zero for non-interleaved. */ + int numChannels; /* Actually used. */ + double phases[MAX_CHANNELS]; /* Each channel gets its' own frequency. */ +} +paTestData; + +/* 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 patestCallback(const void* inputBuffer, + void* outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void* userData) +{ + int frameIndex, channelIndex; + float** outputs = (float**)outputBuffer; + paTestData* data = (paTestData*)userData; + + (void) inputBuffer; /* Prevent unused arg warning. */ + if (data->interleaved) + { + float *out = (float*)outputBuffer; /* interleaved version */ + for( frameIndex=0; frameIndex<(int)framesPerBuffer; frameIndex++ ) + { + for( channelIndex=0; channelIndex<data->numChannels; channelIndex++ ) + { + /* Output sine wave on every channel. */ + *out++ = (float) sin(data->phases[channelIndex]); + + /* Play each channel at a higher frequency. */ + data->phases[channelIndex] += FREQ_INCR * (4 + channelIndex); + if( data->phases[channelIndex] >= (2.0 * M_PI) ) data->phases[channelIndex] -= (2.0 * M_PI); + } + } + } + else + { + for( frameIndex=0; frameIndex<(int)framesPerBuffer; frameIndex++ ) + { + for( channelIndex=0; channelIndex<data->numChannels; channelIndex++ ) + { + /* Output sine wave on every channel. */ + outputs[channelIndex][frameIndex] = (float) sin(data->phases[channelIndex]); + + /* Play each channel at a higher frequency. */ + data->phases[channelIndex] += FREQ_INCR * (4 + channelIndex); + if( data->phases[channelIndex] >= (2.0 * M_PI) ) data->phases[channelIndex] -= (2.0 * M_PI); + } + } + } + return 0; +} + +/*******************************************************************/ +int test(short interleaved) +{ + PaStream* stream; + PaStreamParameters outputParameters; + PaError err; + const PaDeviceInfo* pdi; + paTestData data; + short n; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* Default output device, max channels. */ + pdi = Pa_GetDeviceInfo(outputParameters.device); + outputParameters.channelCount = pdi->maxOutputChannels; + if (outputParameters.channelCount > MAX_CHANNELS) + outputParameters.channelCount = MAX_CHANNELS; + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + outputParameters.suggestedLatency = pdi->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + data.interleaved = interleaved; + data.numChannels = outputParameters.channelCount; + for (n = 0; n < data.numChannels; n++) + data.phases[n] = 0.0; /* Phases wrap and maybe don't need initialisation. */ + printf("%d ", data.numChannels); + if (interleaved) + printf("interleaved "); + else + { + printf(" non-interleaved "); + outputParameters.sampleFormat |= paNonInterleaved; + } + printf("channels.\n"); + + err = Pa_OpenStream(&stream, + NULL, /* No input. */ + &outputParameters, + SAMPLE_RATE, /* Sample rate. */ + FRAMES_PER_BUFFER, /* Frames per buffer. */ + paClipOff, /* Samples never out of range, no clipping. */ + patestCallback, + &data); + if (err == paNoError) + { + err = Pa_StartStream(stream); + if (err == paNoError) + { + printf("Hit ENTER to stop this test.\n"); + getchar(); + err = Pa_StopStream(stream); + } + Pa_CloseStream( stream ); + } + return err; +} + + +/*******************************************************************/ +int main(void) +{ + PaError err; + + printf("PortAudio Test: output sine wave on each channel.\n" ); + + err = Pa_Initialize(); + if (err != paNoError) + goto done; + + err = test(1); /* 1 means interleaved. */ + if (err != paNoError) + goto done; + + err = test(0); /* 0 means not interleaved. */ + if (err != paNoError) + goto done; + + printf("Test finished.\n"); +done: + if (err) + { + fprintf(stderr, "An error occured while using the portaudio stream\n"); + fprintf(stderr, "Error number: %d\n", err ); + fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(err)); + } + Pa_Terminate(); + return 0; +} diff --git a/src/audio/portaudio/pa_tests/patest_out_underflow.c b/src/audio/portaudio/pa_tests/patest_out_underflow.c new file mode 100644 index 0000000000000000000000000000000000000000..8e6f2789777cdf91625089e56a08ca6ae7e98f48 --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_out_underflow.c @@ -0,0 +1,217 @@ +/** @file patest_out_underflow.c + @brief Count output underflows (using paOutputUnderflow flag) + under overloaded and normal conditions. + @author Ross Bencina <rossb@audiomulch.com> + @author Phil Burk <philburk@softsynth.com> +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2004 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" + +#define MAX_SINES (500) +#define MAX_LOAD (1.2) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (512) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TWOPI (M_PI * 2.0) + +typedef struct paTestData +{ + int sineCount; + double phases[MAX_SINES]; + int countUnderflows; + int outputUnderflowCount; +} +paTestData; + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned long i; + int j; + int finished = paContinue; + (void) timeInfo; /* Prevent unused variable warning. */ + (void) inputBuffer; /* Prevent unused variable warning. */ + + + if( data->countUnderflows && (statusFlags & paOutputUnderflow) ) + data->outputUnderflowCount++; + + for( i=0; i<framesPerBuffer; i++ ) + { + float output = 0.0; + double phaseInc = 0.02; + double phase; + + for( j=0; j<data->sineCount; j++ ) + { + /* Advance phase of next oscillator. */ + phase = data->phases[j]; + phase += phaseInc; + if( phase > TWOPI ) phase -= TWOPI; + + phaseInc *= 1.02; + if( phaseInc > 0.5 ) phaseInc *= 0.5; + + /* This is not a very efficient way to calc sines. */ + output += (float) sin( phase ); + data->phases[j] = phase; + } + *out++ = (float) (output / data->sineCount); + } + + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + int safeSineCount, stressedSineCount; + int safeUnderflowCount, stressedUnderflowCount; + paTestData data = {0}; + double load; + + + printf("PortAudio Test: output sine waves, count underflows. SR = %d, BufSize = %d. MAX_LOAD = %f\n", + SAMPLE_RATE, FRAMES_PER_BUFFER, (float)MAX_LOAD ); + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ + outputParameters.channelCount = 1; /* mono output */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Establishing load conditions...\n" ); + + /* Determine number of sines required to get to 50% */ + do + { + data.sineCount++; + Pa_Sleep( 100 ); + + load = Pa_GetStreamCpuLoad( stream ); + printf("sineCount = %d, CPU load = %f\n", data.sineCount, load ); + } + while( load < 0.5 && data.sineCount < (MAX_SINES-1)); + + safeSineCount = data.sineCount; + + /* Calculate target stress value then ramp up to that level*/ + stressedSineCount = (int) (2.0 * data.sineCount * MAX_LOAD ); + if( stressedSineCount > MAX_SINES ) + stressedSineCount = MAX_SINES; + for( ; data.sineCount < stressedSineCount; data.sineCount++ ) + { + Pa_Sleep( 100 ); + load = Pa_GetStreamCpuLoad( stream ); + printf("STRESSING: sineCount = %d, CPU load = %f\n", data.sineCount, load ); + } + + printf("Counting underflows for 5 seconds.\n"); + data.countUnderflows = 1; + Pa_Sleep( 5000 ); + + stressedUnderflowCount = data.outputUnderflowCount; + + data.countUnderflows = 0; + data.sineCount = safeSineCount; + + printf("Resuming safe load...\n"); + Pa_Sleep( 1500 ); + data.outputUnderflowCount = 0; + Pa_Sleep( 1500 ); + load = Pa_GetStreamCpuLoad( stream ); + printf("sineCount = %d, CPU load = %f\n", data.sineCount, load ); + + printf("Counting underflows for 5 seconds.\n"); + data.countUnderflows = 1; + Pa_Sleep( 5000 ); + + safeUnderflowCount = data.outputUnderflowCount; + + printf("Stop stream.\n"); + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Terminate(); + + if( stressedUnderflowCount == 0 ) + printf("Test failed, no output underflows detected under stress.\n"); + else if( safeUnderflowCount != 0 ) + printf("Test failed, %d unexpected underflows detected under safe load.\n", safeUnderflowCount); + else + printf("Test passed, %d expected output underflows detected under stress, 0 unexpected underflows detected under safe load.\n", stressedUnderflowCount ); + + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/patest_pink.c b/src/audio/portaudio/pa_tests/patest_pink.c new file mode 100644 index 0000000000000000000000000000000000000000..f2db85d9c6d4bdc91686d1be4e2799aa7c1a961d --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_pink.c @@ -0,0 +1,269 @@ +/** @file patest_pink.c + @brief Generate Pink Noise using Gardner method. + + Optimization suggested by James McCartney uses a tree + to select which random value to replace. +<pre> + x x x x x x x x x x x x x x x x + x x x x x x x x + x x x x + x x + x +</pre> + Tree is generated by counting trailing zeros in an increasing index. + When the index is zero, no random number is selected. + + @author Phil Burk http://www.softsynth.com +*/ +/* + * $Id$ + * + * 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. + * + * 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. + * + * 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. + * + */ + +#include <stdio.h> +#include <math.h> +#include "portaudio.h" + +#define PINK_MAX_RANDOM_ROWS (30) +#define PINK_RANDOM_BITS (24) +#define PINK_RANDOM_SHIFT ((sizeof(long)*8)-PINK_RANDOM_BITS) + +typedef struct +{ + long pink_Rows[PINK_MAX_RANDOM_ROWS]; + long pink_RunningSum; /* Used to optimize summing of generators. */ + int pink_Index; /* Incremented each sample. */ + int pink_IndexMask; /* Index wrapped by ANDing with this mask. */ + float pink_Scalar; /* Used to scale within range of -1.0 to +1.0 */ +} +PinkNoise; + +/* Prototypes */ +static unsigned long GenerateRandomNumber( void ); +void InitializePinkNoise( PinkNoise *pink, int numRows ); +float GeneratePinkNoise( PinkNoise *pink ); + +/************************************************************/ +/* Calculate pseudo-random 32 bit number based on linear congruential method. */ +static unsigned long GenerateRandomNumber( void ) +{ + /* Change this seed for different random sequences. */ + static unsigned long randSeed = 22222; + randSeed = (randSeed * 196314165) + 907633515; + return randSeed; +} + +/************************************************************/ +/* Setup PinkNoise structure for N rows of generators. */ +void InitializePinkNoise( PinkNoise *pink, int numRows ) +{ + int i; + long pmax; + pink->pink_Index = 0; + pink->pink_IndexMask = (1<<numRows) - 1; + /* Calculate maximum possible signed random value. Extra 1 for white noise always added. */ + pmax = (numRows + 1) * (1<<(PINK_RANDOM_BITS-1)); + pink->pink_Scalar = 1.0f / pmax; + /* Initialize rows. */ + for( i=0; i<numRows; i++ ) pink->pink_Rows[i] = 0; + pink->pink_RunningSum = 0; +} + +#define PINK_MEASURE +#ifdef PINK_MEASURE +float pinkMax = -999.0; +float pinkMin = 999.0; +#endif + +/* Generate Pink noise values between -1.0 and +1.0 */ +float GeneratePinkNoise( PinkNoise *pink ) +{ + long newRandom; + long sum; + float output; + /* Increment and mask index. */ + pink->pink_Index = (pink->pink_Index + 1) & pink->pink_IndexMask; + /* If index is zero, don't update any random values. */ + if( pink->pink_Index != 0 ) + { + /* Determine how many trailing zeros in PinkIndex. */ + /* This algorithm will hang if n==0 so test first. */ + int numZeros = 0; + int n = pink->pink_Index; + while( (n & 1) == 0 ) + { + n = n >> 1; + numZeros++; + } + /* Replace the indexed ROWS random value. + * Subtract and add back to RunningSum instead of adding all the random + * values together. Only one changes each time. + */ + pink->pink_RunningSum -= pink->pink_Rows[numZeros]; + newRandom = ((long)GenerateRandomNumber()) >> PINK_RANDOM_SHIFT; + pink->pink_RunningSum += newRandom; + pink->pink_Rows[numZeros] = newRandom; + } + + /* Add extra white noise value. */ + newRandom = ((long)GenerateRandomNumber()) >> PINK_RANDOM_SHIFT; + sum = pink->pink_RunningSum + newRandom; + /* Scale to range of -1.0 to 0.9999. */ + output = pink->pink_Scalar * sum; +#ifdef PINK_MEASURE + /* Check Min/Max */ + if( output > pinkMax ) pinkMax = output; + else if( output < pinkMin ) pinkMin = output; +#endif + return output; +} + +/*******************************************************************/ +#define PINK_TEST +#ifdef PINK_TEST + +/* Context for callback routine. */ +typedef struct +{ + PinkNoise leftPink; + PinkNoise rightPink; + unsigned int sampsToGo; +} +paTestData; + +/* 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 patestCallback(const void* inputBuffer, + void* outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void* userData) +{ + int finished; + int i; + int numFrames; + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + (void) inputBuffer; /* Prevent "unused variable" warnings. */ + + /* Are we almost at end. */ + if( data->sampsToGo < framesPerBuffer ) + { + numFrames = data->sampsToGo; + finished = 1; + } + else + { + numFrames = framesPerBuffer; + finished = 0; + } + for( i=0; i<numFrames; i++ ) + { + *out++ = GeneratePinkNoise( &data->leftPink ); + *out++ = GeneratePinkNoise( &data->rightPink ); + } + data->sampsToGo -= numFrames; + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStream* stream; + PaError err; + paTestData data; + PaStreamParameters outputParameters; + int totalSamps; + static const double SR = 44100.0; + static const int FPB = 2048; /* Frames per buffer: 46 ms buffers. */ + + /* Initialize two pink noise signals with different numbers of rows. */ + InitializePinkNoise( &data.leftPink, 12 ); + InitializePinkNoise( &data.rightPink, 16 ); + + /* Look at a few values. */ + { + int i; + float pink; + for( i=0; i<20; i++ ) + { + pink = GeneratePinkNoise( &data.leftPink ); + printf("Pink = %f\n", pink ); + } + } + + data.sampsToGo = totalSamps = (int)(60.0 * SR); /* Play a whole minute. */ + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + /* Open a stereo PortAudio stream so we can hear the result. */ + outputParameters.device = Pa_GetDefaultOutputDevice(); /* Take the default output device. */ + outputParameters.channelCount = 2; /* Stereo output, most likely supported. */ + outputParameters.hostApiSpecificStreamInfo = NULL; + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output. */ + outputParameters.suggestedLatency = + Pa_GetDeviceInfo(outputParameters.device)->defaultLowOutputLatency; + err = Pa_OpenStream(&stream, + NULL, /* No input. */ + &outputParameters, + SR, /* Sample rate. */ + FPB, /* Frames per buffer. */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Stereo pink noise for one minute...\n"); + + while( ( err = Pa_IsStreamActive( stream ) ) == 1 ) Pa_Sleep(100); + if( err < 0 ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; +#ifdef PINK_MEASURE + printf("Pink min = %f, max = %f\n", pinkMin, pinkMax ); +#endif + Pa_Terminate(); + return 0; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return 0; +} +#endif /* PINK_TEST */ diff --git a/src/audio/portaudio/pa_tests/patest_prime.c b/src/audio/portaudio/pa_tests/patest_prime.c new file mode 100644 index 0000000000000000000000000000000000000000..47d1f5f563e54a9224e823d26e27a9e2b0b71117 --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_prime.c @@ -0,0 +1,223 @@ +/** @file patest_prime.c + @brief Test stream priming mode. + @author Ross Bencina http://www.audiomulch.com/~rossb +*/ + +/* + * $Id$ + * + * 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. + * + * 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. + * + * 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. + * + */ + +#include <stdio.h> +#include <math.h> +#include "portaudio.h" +#include "pa_util.h" + +#define NUM_BEEPS (3) +#define SAMPLE_RATE (44100) +#define SAMPLE_PERIOD (1.0/44100.0) +#define FRAMES_PER_BUFFER (256) +#define BEEP_DURATION (400) +#define IDLE_DURATION (SAMPLE_RATE*2) /* 2 seconds */ +#define SLEEP_MSEC (50) + +#define STATE_BKG_IDLE (0) +#define STATE_BKG_BEEPING (1) + +typedef struct +{ + float leftPhase; + float rightPhase; + int state; + int beepCountdown; + int idleCountdown; +} +paTestData; + +static void InitializeTestData( paTestData *testData ) +{ + testData->leftPhase = 0; + testData->rightPhase = 0; + testData->state = STATE_BKG_BEEPING; + testData->beepCountdown = BEEP_DURATION; + testData->idleCountdown = IDLE_DURATION; +} + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo *timeInfo, + PaStreamCallbackFlags statusFlags, void *userData ) +{ + /* Cast data passed through stream to our structure. */ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned int i; + int result = paContinue; + + /* supress unused parameter warnings */ + (void) inputBuffer; + (void) timeInfo; + (void) statusFlags; + + for( i=0; i<framesPerBuffer; i++ ) + { + switch( data->state ) + { + case STATE_BKG_IDLE: + *out++ = 0.0; /* left */ + *out++ = 0.0; /* right */ + --data->idleCountdown; + + if( data->idleCountdown <= 0 ) result = paComplete; + break; + + case STATE_BKG_BEEPING: + if( data->beepCountdown <= 0 ) + { + data->state = STATE_BKG_IDLE; + *out++ = 0.0; /* left */ + *out++ = 0.0; /* right */ + } + else + { + /* Play sawtooth wave. */ + *out++ = data->leftPhase; /* left */ + *out++ = data->rightPhase; /* right */ + /* Generate simple sawtooth phaser that ranges between -1.0 and 1.0. */ + data->leftPhase += 0.01f; + /* When signal reaches top, drop back down. */ + if( data->leftPhase >= 1.0f ) data->leftPhase -= 2.0f; + /* higher pitch so we can distinguish left and right. */ + data->rightPhase += 0.03f; + if( data->rightPhase >= 1.0f ) data->rightPhase -= 2.0f; + } + --data->beepCountdown; + break; + } + } + + return result; +} + +/*******************************************************************/ +static PaError DoTest( int flags ) +{ + PaStream *stream; + PaError err; + paTestData data; + PaStreamParameters outputParameters; + + InitializeTestData( &data ); + + outputParameters.device = Pa_GetDefaultOutputDevice(); + outputParameters.channelCount = 2; + outputParameters.hostApiSpecificStreamInfo = NULL; + outputParameters.sampleFormat = paFloat32; + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultHighOutputLatency; + + /* Open an audio I/O stream. */ + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, /* frames per buffer */ + paClipOff | flags, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("hear \"BEEP\"\n" ); + fflush(stdout); + + while( ( err = Pa_IsStreamActive( stream ) ) == 1 ) Pa_Sleep(SLEEP_MSEC); + if( err < 0 ) goto error; + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + return err; +error: + return err; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaError err = paNoError; + int i; + + /* Initialize library before making any other calls. */ + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + printf("PortAudio Test: Testing stream playback with no priming.\n"); + printf("PortAudio Test: you should see BEEP before you hear it.\n"); + printf("BEEP %d times.\n", NUM_BEEPS ); + + for( i=0; i< NUM_BEEPS; ++i ) + { + err = DoTest( 0 ); + if( err != paNoError ) + goto error; + } + + printf("PortAudio Test: Testing stream playback with priming.\n"); + printf("PortAudio Test: you should see BEEP around the same time you hear it.\n"); + for( i=0; i< NUM_BEEPS; ++i ) + { + err = DoTest( paPrimeOutputBuffersUsingStreamCallback ); + if( err != paNoError ) + goto error; + } + + printf("Test finished.\n"); + + Pa_Terminate(); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/patest_read_record.c b/src/audio/portaudio/pa_tests/patest_read_record.c new file mode 100644 index 0000000000000000000000000000000000000000..5e9ca0109e12ce076a8daa7bb674b1894bcef1d1 --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_read_record.c @@ -0,0 +1,229 @@ +/** @file patest_read_record.c + @brief Record input into an array; Save array to a file; Playback recorded + data. Implemented using the blocking API (Pa_ReadStream(), Pa_WriteStream() ) + @author Phil Burk http://www.softsynth.com + @author Ross Bencina rossb@audiomulch.com +*/ +/* + * $Id$ + * + * 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. + * + * 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. + * + * 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. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include "portaudio.h" + +/* #define SAMPLE_RATE (17932) // Test failure to open with this value. */ +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (1024) +#define NUM_SECONDS (5) +#define NUM_CHANNELS (2) +/* #define DITHER_FLAG (paDitherOff) */ +#define DITHER_FLAG (0) /**/ + +/* Select sample format. */ +#if 1 +#define PA_SAMPLE_TYPE paFloat32 +typedef float SAMPLE; +#define SAMPLE_SILENCE (0.0f) +#define PRINTF_S_FORMAT "%.8f" +#elif 1 +#define PA_SAMPLE_TYPE paInt16 +typedef short SAMPLE; +#define SAMPLE_SILENCE (0) +#define PRINTF_S_FORMAT "%d" +#elif 0 +#define PA_SAMPLE_TYPE paInt8 +typedef char SAMPLE; +#define SAMPLE_SILENCE (0) +#define PRINTF_S_FORMAT "%d" +#else +#define PA_SAMPLE_TYPE paUInt8 +typedef unsigned char SAMPLE; +#define SAMPLE_SILENCE (128) +#define PRINTF_S_FORMAT "%d" +#endif + + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters inputParameters, outputParameters; + PaStream *stream; + PaError err; + SAMPLE *recordedSamples; + int i; + int totalFrames; + int numSamples; + int numBytes; + SAMPLE max, average, val; + + + printf("patest_read_record.c\n"); fflush(stdout); + + totalFrames = NUM_SECONDS * SAMPLE_RATE; /* Record for a few seconds. */ + numSamples = totalFrames * NUM_CHANNELS; + + numBytes = numSamples * sizeof(SAMPLE); + recordedSamples = (SAMPLE *) malloc( numBytes ); + if( recordedSamples == NULL ) + { + printf("Could not allocate record array.\n"); + exit(1); + } + for( i=0; i<numSamples; i++ ) recordedSamples[i] = 0; + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + inputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */ + inputParameters.channelCount = NUM_CHANNELS; + inputParameters.sampleFormat = PA_SAMPLE_TYPE; + inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency; + inputParameters.hostApiSpecificStreamInfo = NULL; + + /* Record some audio. -------------------------------------------- */ + err = Pa_OpenStream( + &stream, + &inputParameters, + NULL, /* &outputParameters, */ + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + NULL, /* no callback, use blocking API */ + NULL ); /* no callback, so no callback userData */ + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + printf("Now recording!!\n"); fflush(stdout); + + err = Pa_ReadStream( stream, recordedSamples, totalFrames ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + /* Measure maximum peak amplitude. */ + max = 0; + average = 0; + for( i=0; i<numSamples; i++ ) + { + val = recordedSamples[i]; + if( val < 0 ) val = -val; /* ABS */ + if( val > max ) + { + max = val; + } + average += val; + } + + average = average / numSamples; + + printf("Sample max amplitude = "PRINTF_S_FORMAT"\n", max ); + printf("Sample average = "PRINTF_S_FORMAT"\n", average ); +/* Was as below. Better choose at compile time because this + keeps generating compiler-warnings: + if( PA_SAMPLE_TYPE == paFloat32 ) + { + printf("sample max amplitude = %f\n", max ); + printf("sample average = %f\n", average ); + } + else + { + printf("sample max amplitude = %d\n", max ); + printf("sample average = %d\n", average ); + } +*/ + /* Write recorded data to a file. */ +#if 0 + { + FILE *fid; + fid = fopen("recorded.raw", "wb"); + if( fid == NULL ) + { + printf("Could not open file."); + } + else + { + fwrite( recordedSamples, NUM_CHANNELS * sizeof(SAMPLE), totalFrames, fid ); + fclose( fid ); + printf("Wrote data to 'recorded.raw'\n"); + } + } +#endif + + /* Playback recorded data. -------------------------------------------- */ + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ + outputParameters.channelCount = NUM_CHANNELS; + outputParameters.sampleFormat = PA_SAMPLE_TYPE; + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + printf("Begin playback.\n"); fflush(stdout); + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + NULL, /* no callback, use blocking API */ + NULL ); /* no callback, so no callback userData */ + if( err != paNoError ) goto error; + + if( stream ) + { + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + printf("Waiting for playback to finish.\n"); fflush(stdout); + + err = Pa_WriteStream( stream, recordedSamples, totalFrames ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + printf("Done.\n"); fflush(stdout); + } + free( recordedSamples ); + + Pa_Terminate(); + return 0; + +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return -1; +} + diff --git a/src/audio/portaudio/pa_tests/patest_record.c b/src/audio/portaudio/pa_tests/patest_record.c new file mode 100644 index 0000000000000000000000000000000000000000..7ba35825dbbe3c7dacf4032a2c4a0acc10ee7ca5 --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_record.c @@ -0,0 +1,337 @@ +/** @file patest_record.c + @brief Record input into an array; Save array to a file; Playback recorded data. + @author Phil Burk http://www.softsynth.com +*/ +/* + * $Id$ + * + * 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. + * + * 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. + * + * 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. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include "portaudio.h" + +/* #define SAMPLE_RATE (17932) // Test failure to open with this value. */ +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (1024) +#define NUM_SECONDS (5) +#define NUM_CHANNELS (2) +/* #define DITHER_FLAG (paDitherOff) */ +#define DITHER_FLAG (0) /**/ + +/* Select sample format. */ +#if 1 +#define PA_SAMPLE_TYPE paFloat32 +typedef float SAMPLE; +#define SAMPLE_SILENCE (0.0f) +#define PRINTF_S_FORMAT "%.8f" +#elif 1 +#define PA_SAMPLE_TYPE paInt16 +typedef short SAMPLE; +#define SAMPLE_SILENCE (0) +#define PRINTF_S_FORMAT "%d" +#elif 0 +#define PA_SAMPLE_TYPE paInt8 +typedef char SAMPLE; +#define SAMPLE_SILENCE (0) +#define PRINTF_S_FORMAT "%d" +#else +#define PA_SAMPLE_TYPE paUInt8 +typedef unsigned char SAMPLE; +#define SAMPLE_SILENCE (128) +#define PRINTF_S_FORMAT "%d" +#endif + +typedef struct +{ + int frameIndex; /* Index into sample array. */ + int maxFrameIndex; + SAMPLE *recordedSamples; +} +paTestData; + +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be 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 recordCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + const SAMPLE *rptr = (const SAMPLE*)inputBuffer; + SAMPLE *wptr = &data->recordedSamples[data->frameIndex * NUM_CHANNELS]; + long framesToCalc; + long i; + int finished; + unsigned long framesLeft = data->maxFrameIndex - data->frameIndex; + + (void) outputBuffer; /* Prevent unused variable warnings. */ + (void) timeInfo; + (void) statusFlags; + (void) userData; + + if( framesLeft < framesPerBuffer ) + { + framesToCalc = framesLeft; + finished = paComplete; + } + else + { + framesToCalc = framesPerBuffer; + finished = paContinue; + } + + if( inputBuffer == NULL ) + { + for( i=0; i<framesToCalc; i++ ) + { + *wptr++ = SAMPLE_SILENCE; /* left */ + if( NUM_CHANNELS == 2 ) *wptr++ = SAMPLE_SILENCE; /* right */ + } + } + else + { + for( i=0; i<framesToCalc; i++ ) + { + *wptr++ = *rptr++; /* left */ + if( NUM_CHANNELS == 2 ) *wptr++ = *rptr++; /* right */ + } + } + data->frameIndex += framesToCalc; + return finished; +} + +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be 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 playCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + SAMPLE *rptr = &data->recordedSamples[data->frameIndex * NUM_CHANNELS]; + SAMPLE *wptr = (SAMPLE*)outputBuffer; + unsigned int i; + int finished; + unsigned int framesLeft = data->maxFrameIndex - data->frameIndex; + + (void) inputBuffer; /* Prevent unused variable warnings. */ + (void) timeInfo; + (void) statusFlags; + (void) userData; + + if( framesLeft < framesPerBuffer ) + { + /* final buffer... */ + for( i=0; i<framesLeft; i++ ) + { + *wptr++ = *rptr++; /* left */ + if( NUM_CHANNELS == 2 ) *wptr++ = *rptr++; /* right */ + } + for( ; i<framesPerBuffer; i++ ) + { + *wptr++ = 0; /* left */ + if( NUM_CHANNELS == 2 ) *wptr++ = 0; /* right */ + } + data->frameIndex += framesLeft; + finished = paComplete; + } + else + { + for( i=0; i<framesPerBuffer; i++ ) + { + *wptr++ = *rptr++; /* left */ + if( NUM_CHANNELS == 2 ) *wptr++ = *rptr++; /* right */ + } + data->frameIndex += framesPerBuffer; + finished = paContinue; + } + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters inputParameters, + outputParameters; + PaStream* stream; + PaError err = paNoError; + paTestData data; + int i; + int totalFrames; + int numSamples; + int numBytes; + SAMPLE max, val; + double average; + + printf("patest_record.c\n"); fflush(stdout); + + data.maxFrameIndex = totalFrames = NUM_SECONDS * SAMPLE_RATE; /* Record for a few seconds. */ + data.frameIndex = 0; + numSamples = totalFrames * NUM_CHANNELS; + numBytes = numSamples * sizeof(SAMPLE); + data.recordedSamples = (SAMPLE *) malloc( numBytes ); /* From now on, recordedSamples is initialised. */ + if( data.recordedSamples == NULL ) + { + printf("Could not allocate record array.\n"); + goto done; + } + for( i=0; i<numSamples; i++ ) data.recordedSamples[i] = 0; + + err = Pa_Initialize(); + if( err != paNoError ) goto done; + + inputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */ + inputParameters.channelCount = 2; /* stereo input */ + inputParameters.sampleFormat = PA_SAMPLE_TYPE; + inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency; + inputParameters.hostApiSpecificStreamInfo = NULL; + + /* Record some audio. -------------------------------------------- */ + err = Pa_OpenStream( + &stream, + &inputParameters, + NULL, /* &outputParameters, */ + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + recordCallback, + &data ); + if( err != paNoError ) goto done; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto done; + printf("Now recording!!\n"); fflush(stdout); + + while( ( err = Pa_IsStreamActive( stream ) ) == 1 ) + { + Pa_Sleep(1000); + printf("index = %d\n", data.frameIndex ); fflush(stdout); + } + if( err < 0 ) goto done; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto done; + + /* Measure maximum peak amplitude. */ + max = 0; + average = 0.0; + for( i=0; i<numSamples; i++ ) + { + val = data.recordedSamples[i]; + if( val < 0 ) val = -val; /* ABS */ + if( val > max ) + { + max = val; + } + average += val; + } + + average = average / (double)numSamples; + + printf("sample max amplitude = "PRINTF_S_FORMAT"\n", max ); + printf("sample average = %lf\n", average ); + + /* Write recorded data to a file. */ +#if 0 + { + FILE *fid; + fid = fopen("recorded.raw", "wb"); + if( fid == NULL ) + { + printf("Could not open file."); + } + else + { + fwrite( data.recordedSamples, NUM_CHANNELS * sizeof(SAMPLE), totalFrames, fid ); + fclose( fid ); + printf("Wrote data to 'recorded.raw'\n"); + } + } +#endif + + /* Playback recorded data. -------------------------------------------- */ + data.frameIndex = 0; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ + outputParameters.channelCount = 2; /* stereo output */ + outputParameters.sampleFormat = PA_SAMPLE_TYPE; + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + printf("Begin playback.\n"); fflush(stdout); + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + playCallback, + &data ); + if( err != paNoError ) goto done; + + if( stream ) + { + err = Pa_StartStream( stream ); + if( err != paNoError ) goto done; + + printf("Waiting for playback to finish.\n"); fflush(stdout); + + while( ( err = Pa_IsStreamActive( stream ) ) == 1 ) Pa_Sleep(100); + if( err < 0 ) goto done; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto done; + + printf("Done.\n"); fflush(stdout); + } + +done: + Pa_Terminate(); + if( data.recordedSamples ) /* Sure it is NULL or valid. */ + free( data.recordedSamples ); + if( err != paNoError ) + { + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + err = 1; /* Always return 0 or 1, but no other return codes. */ + } + return err; +} + diff --git a/src/audio/portaudio/pa_tests/patest_ringmix.c b/src/audio/portaudio/pa_tests/patest_ringmix.c new file mode 100644 index 0000000000000000000000000000000000000000..1df0623881c961eff6fd87ec66044675bacfaa30 --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_ringmix.c @@ -0,0 +1,79 @@ +/** @file patest_ringmix.c + @brief Ring modulate inputs to left output, mix inputs to right output. +*/ +/* + * $Id$ + * + * 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. + * + * 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. + * + * 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. + * + */ + + +#include "stdio.h" +#include "portaudio.h" +/* This will be called asynchronously by the PortAudio engine. */ +static int myCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + const float *in = (const float *) inputBuffer; + float *out = (float *) outputBuffer; + float leftInput, rightInput; + unsigned int i; + + /* Read input buffer, process data, and fill output buffer. */ + for( i=0; i<framesPerBuffer; i++ ) + { + leftInput = *in++; /* Get interleaved samples from input buffer. */ + rightInput = *in++; + *out++ = leftInput * rightInput; /* ring modulation */ + *out++ = 0.5f * (leftInput + rightInput); /* mix */ + } + return 0; +} + +/* Open a PortAudioStream to input and output audio data. */ +int main(void) +{ + PaStream *stream; + Pa_Initialize(); + Pa_OpenDefaultStream( + &stream, + 2, 2, /* stereo input and output */ + paFloat32, 44100.0, + 64, /* 64 frames per buffer */ + myCallback, NULL ); + Pa_StartStream( stream ); + Pa_Sleep( 10000 ); /* Sleep for 10 seconds while processing. */ + Pa_StopStream( stream ); + Pa_CloseStream( stream ); + Pa_Terminate(); + return 0; +} diff --git a/src/audio/portaudio/pa_tests/patest_saw.c b/src/audio/portaudio/pa_tests/patest_saw.c new file mode 100644 index 0000000000000000000000000000000000000000..e76e3cb92403c9496309bc4e47e3236115aafced --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_saw.c @@ -0,0 +1,125 @@ +/** @file patest_saw.c + @brief Play a simple (aliasing) sawtooth wave. + @author Phil Burk http://www.softsynth.com +*/ +/* + * $Id$ + * + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" +#define NUM_SECONDS (4) +#define SAMPLE_RATE (44100) + +typedef struct +{ + float left_phase; + float right_phase; +} +paTestData; + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + /* Cast data passed through stream to our structure. */ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned int i; + (void) inputBuffer; /* Prevent unused variable warning. */ + + for( i=0; i<framesPerBuffer; i++ ) + { + *out++ = data->left_phase; /* left */ + *out++ = data->right_phase; /* right */ + /* Generate simple sawtooth phaser that ranges between -1.0 and 1.0. */ + data->left_phase += 0.01f; + /* When signal reaches top, drop back down. */ + if( data->left_phase >= 1.0f ) data->left_phase -= 2.0f; + /* higher pitch so we can distinguish left and right. */ + data->right_phase += 0.03f; + if( data->right_phase >= 1.0f ) data->right_phase -= 2.0f; + } + return 0; +} + +/*******************************************************************/ +static paTestData data; +int main(void); +int main(void) +{ + PaStream *stream; + PaError err; + + printf("PortAudio Test: output sawtooth wave.\n"); + /* Initialize our data for use by callback. */ + data.left_phase = data.right_phase = 0.0; + /* Initialize library before making any other calls. */ + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + /* Open an audio I/O stream. */ + err = Pa_OpenDefaultStream( &stream, + 0, /* no input channels */ + 2, /* stereo output */ + paFloat32, /* 32 bit floating point output */ + SAMPLE_RATE, + 256, /* frames per buffer */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + /* Sleep for several seconds. */ + Pa_Sleep(NUM_SECONDS*1000); + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/patest_sine.c b/src/audio/portaudio/pa_tests/patest_sine.c new file mode 100644 index 0000000000000000000000000000000000000000..c952b5805684af2e2dc2bb2d6c3a940341a60805 --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_sine.c @@ -0,0 +1,151 @@ +/** @file patest_sine.c + @brief Play a sine wave for several seconds. + @author Ross Bencina <rossb@audiomulch.com> + @author Phil Burk <philburk@softsynth.com> +*/ +/* + * $Id$ + * + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" + +#define NUM_SECONDS (5) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (64) + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +#define TABLE_SIZE (200) +typedef struct +{ + float sine[TABLE_SIZE]; + int left_phase; + int right_phase; +} +paTestData; + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned long i; + + (void) timeInfo; /* Prevent unused variable warnings. */ + (void) statusFlags; + (void) inputBuffer; + + for( i=0; i<framesPerBuffer; i++ ) + { + *out++ = data->sine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + + return paContinue; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + paTestData data; + int i; + + + printf("PortAudio Test: output sine wave. SR = %d, BufSize = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER); + + /* initialise sinusoidal wavetable */ + for( i=0; i<TABLE_SIZE; i++ ) + { + data.sine[i] = (float) sin( ((double)i/(double)TABLE_SIZE) * M_PI * 2. ); + } + data.left_phase = data.right_phase = 0; + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ + outputParameters.channelCount = 2; /* stereo output */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Play for %d seconds.\n", NUM_SECONDS ); + Pa_Sleep( NUM_SECONDS * 1000 ); + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Terminate(); + printf("Test finished.\n"); + + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/patest_sine8.c b/src/audio/portaudio/pa_tests/patest_sine8.c new file mode 100644 index 0000000000000000000000000000000000000000..5a9d1fe72776075b09c77d1f63671e0bb4b329bb --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_sine8.c @@ -0,0 +1,212 @@ +/** @file patest_sine8.c + @brief Test 8 bit data: play a sine wave for several seconds. + @author Ross Bencina <rossb@audiomulch.com> +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * 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. + * + * 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. + * + * 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. + * + */ + +#include <stdio.h> +#include <math.h> +#include "portaudio.h" + +#define NUM_SECONDS (8) +#define SAMPLE_RATE (44100) +#define TABLE_SIZE (200) +#define TEST_UNSIGNED (0) + +#if TEST_UNSIGNED +#define TEST_FORMAT paUInt8 +#else +#define TEST_FORMAT paInt8 +#endif + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +typedef struct +{ +#if TEST_UNSIGNED + unsigned char sine[TABLE_SIZE]; +#else + char sine[TABLE_SIZE]; +#endif + int left_phase; + int right_phase; + unsigned int framesToGo; +} +paTestData; + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + char *out = (char*)outputBuffer; + int i; + int framesToCalc; + int finished = 0; + (void) inputBuffer; /* Prevent unused variable warnings. */ + + if( data->framesToGo < framesPerBuffer ) + { + framesToCalc = data->framesToGo; + data->framesToGo = 0; + finished = 1; + } + else + { + framesToCalc = framesPerBuffer; + data->framesToGo -= framesPerBuffer; + } + + for( i=0; i<framesToCalc; i++ ) + { + *out++ = data->sine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + /* zero remainder of final buffer */ + for( ; i<(int)framesPerBuffer; i++ ) + { +#if TEST_UNSIGNED + *out++ = (unsigned char) 0x80; /* left */ + *out++ = (unsigned char) 0x80; /* right */ +#else + *out++ = 0; /* left */ + *out++ = 0; /* right */ +#endif + + } + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters outputParameters; + PaStream* stream; + PaError err; + paTestData data; + PaTime streamOpened; + int i, totalSamps; + +#if TEST_UNSIGNED + printf("PortAudio Test: output UNsigned 8 bit sine wave.\n"); +#else + printf("PortAudio Test: output signed 8 bit sine wave.\n"); +#endif + /* initialise sinusoidal wavetable */ + for( i=0; i<TABLE_SIZE; i++ ) + { + data.sine[i] = (char) (127.0 * sin( ((double)i/(double)TABLE_SIZE) * M_PI * 2. )); +#if TEST_UNSIGNED + data.sine[i] += (unsigned char) 0x80; +#endif + } + data.left_phase = data.right_phase = 0; + data.framesToGo = totalSamps = NUM_SECONDS * SAMPLE_RATE; /* Play for a few seconds. */ + + err = Pa_Initialize(); + if( err != paNoError ) + goto error; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* Default output device. */ + outputParameters.channelCount = 2; /* Stereo output. */ + outputParameters.sampleFormat = TEST_FORMAT; + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + err = Pa_OpenStream( &stream, + NULL, /* No input. */ + &outputParameters, + SAMPLE_RATE, + 256, /* Frames per buffer. */ + paClipOff, /* We won't output out of range samples so don't bother clipping them. */ + patestCallback, + &data ); + if( err != paNoError ) + goto error; + + streamOpened = Pa_GetStreamTime( stream ); /* Time in seconds when stream was opened (approx). */ + + err = Pa_StartStream( stream ); + if( err != paNoError ) + goto error; + + /* Watch until sound is halfway finished. */ + /* (Was ( Pa_StreamTime( stream ) < (totalSamps/2) ) in V18. */ + while( (Pa_GetStreamTime( stream ) - streamOpened) < (PaTime)NUM_SECONDS / 2.0 ) + Pa_Sleep(10); + + /* Stop sound until ENTER hit. (Hu? don't see any keyboard-input here.) */ + err = Pa_StopStream( stream ); + if( err != paNoError ) + goto error; + + printf("Pause for 2 seconds.\n"); + Pa_Sleep( 2000 ); + + err = Pa_StartStream( stream ); + if( err != paNoError ) + goto error; + + printf("Waiting for sound to finish.\n"); + + while( ( err = Pa_IsStreamActive( stream ) ) == 1 ) + Pa_Sleep(100); + if( err < 0 ) + goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) + goto error; + + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/patest_sine_formats.c b/src/audio/portaudio/pa_tests/patest_sine_formats.c new file mode 100644 index 0000000000000000000000000000000000000000..448a4ebe96cdfdd04e630df5389f13e49948f8f2 --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_sine_formats.c @@ -0,0 +1,196 @@ +/** @file patest_sine_formats.c + @brief Play a sine wave for several seconds. Test various data formats. + @author Phil Burk +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" + +#define NUM_SECONDS (10) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (512) +#define LEFT_FREQ (SAMPLE_RATE/256.0) /* So we hit 1.0 */ +#define RIGHT_FREQ (500.0) +#define AMPLITUDE (1.0) + +/* Select ONE format for testing. */ +#define TEST_UINT8 (0) +#define TEST_INT8 (0) +#define TEST_INT16 (1) +#define TEST_FLOAT32 (0) + +#if TEST_UINT8 +#define TEST_FORMAT paUInt8 +typedef unsigned char SAMPLE_t; +#define SAMPLE_ZERO (0x80) +#define DOUBLE_TO_SAMPLE(x) (SAMPLE_ZERO + (SAMPLE_t)(127.0 * (x))) +#define FORMAT_NAME "Unsigned 8 Bit" + +#elif TEST_INT8 +#define TEST_FORMAT paInt8 +typedef char SAMPLE_t; +#define SAMPLE_ZERO (0) +#define DOUBLE_TO_SAMPLE(x) (SAMPLE_ZERO + (SAMPLE_t)(127.0 * (x))) +#define FORMAT_NAME "Signed 8 Bit" + +#elif TEST_INT16 +#define TEST_FORMAT paInt16 +typedef short SAMPLE_t; +#define SAMPLE_ZERO (0) +#define DOUBLE_TO_SAMPLE(x) (SAMPLE_ZERO + (SAMPLE_t)(32767 * (x))) +#define FORMAT_NAME "Signed 16 Bit" + +#elif TEST_FLOAT32 +#define TEST_FORMAT paFloat32 +typedef float SAMPLE_t; +#define SAMPLE_ZERO (0.0) +#define DOUBLE_TO_SAMPLE(x) ((SAMPLE_t)(x)) +#define FORMAT_NAME "Float 32 Bit" +#endif + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + + +typedef struct +{ + double left_phase; + double right_phase; + unsigned int framesToGo; +} +paTestData; +/* 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 patestCallback( const void *inputBuffer, + void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + SAMPLE_t *out = (SAMPLE_t *)outputBuffer; + int i; + int framesToCalc; + int finished = 0; + (void) inputBuffer; /* Prevent unused variable warnings. */ + + if( data->framesToGo < framesPerBuffer ) + { + framesToCalc = data->framesToGo; + data->framesToGo = 0; + finished = 1; + } + else + { + framesToCalc = framesPerBuffer; + data->framesToGo -= framesPerBuffer; + } + + for( i=0; i<framesToCalc; i++ ) + { + data->left_phase += (LEFT_FREQ / SAMPLE_RATE); + if( data->left_phase > 1.0) data->left_phase -= 1.0; + *out++ = DOUBLE_TO_SAMPLE( AMPLITUDE * sin( (data->left_phase * M_PI * 2. ))); + + data->right_phase += (RIGHT_FREQ / SAMPLE_RATE); + if( data->right_phase > 1.0) data->right_phase -= 1.0; + *out++ = DOUBLE_TO_SAMPLE( AMPLITUDE * sin( (data->right_phase * M_PI * 2. ))); + } + /* zero remainder of final buffer */ + for( ; i<(int)framesPerBuffer; i++ ) + { + *out++ = SAMPLE_ZERO; /* left */ + *out++ = SAMPLE_ZERO; /* right */ + } + return finished; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStream *stream; + PaStreamParameters outputParameters; + PaError err; + paTestData data; + int totalSamps; + + printf("PortAudio Test: output " FORMAT_NAME "\n"); + + data.left_phase = data.right_phase = 0.0; + data.framesToGo = totalSamps = NUM_SECONDS * SAMPLE_RATE; /* Play for a few seconds. */ + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* Default output device. */ + outputParameters.channelCount = 2; /* Stereo output */ + outputParameters.sampleFormat = TEST_FORMAT; /* Selected above. */ + outputParameters.suggestedLatency = Pa_GetDeviceInfo(outputParameters.device)->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + err = Pa_OpenStream( &stream, + NULL, /* No input. */ + &outputParameters, /* As above. */ + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Waiting %d seconds for sound to finish.\n", NUM_SECONDS ); + + while( ( err = Pa_IsStreamActive( stream ) ) == 1 ) Pa_Sleep(100); + if( err < 0 ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + + printf("PortAudio Test Finished: " FORMAT_NAME "\n"); + + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/patest_sine_time.c b/src/audio/portaudio/pa_tests/patest_sine_time.c new file mode 100644 index 0000000000000000000000000000000000000000..cf45275fe1bd3c768fc15864a0a802e28201671e --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_sine_time.c @@ -0,0 +1,208 @@ +/** @file patest_sine_time.c + @brief Play a sine wave for several seconds, pausing in the middle. + Uses the Pa_GetStreamTime() call. + @author Ross Bencina <rossb@audiomulch.com> + @author Phil Burk <philburk@softsynth.com> +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> + +#include "portaudio.h" +#include "pa_util.h" + +#define NUM_SECONDS (8) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (64) + +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TWOPI (M_PI * 2.0) + +#define TABLE_SIZE (200) + +typedef struct +{ + double left_phase; + double right_phase; + volatile PaTime outTime; +} +paTestData; + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned int i; + + double left_phaseInc = 0.02; + double right_phaseInc = 0.06; + + double left_phase = data->left_phase; + double right_phase = data->right_phase; + + (void) statusFlags; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + data->outTime = timeInfo->outputBufferDacTime; + + for( i=0; i<framesPerBuffer; i++ ) + { + left_phase += left_phaseInc; + if( left_phase > TWOPI ) left_phase -= TWOPI; + *out++ = (float) sin( left_phase ); + + right_phase += right_phaseInc; + if( right_phase > TWOPI ) right_phase -= TWOPI; + *out++ = (float) sin( right_phase ); + } + + data->left_phase = left_phase; + data->right_phase = right_phase; + + return paContinue; +} + +/*******************************************************************/ +static void ReportStreamTime( PaStream *stream, paTestData *data ); +static void ReportStreamTime( PaStream *stream, paTestData *data ) +{ + PaTime streamTime, latency, outTime; + + streamTime = Pa_GetStreamTime( stream ); + outTime = data->outTime; + if( outTime < 0.0 ) + { + printf("Stream time = %8.1f\n", streamTime ); + } + else + { + latency = outTime - streamTime; + printf("Stream time = %8.4f, outTime = %8.4f, latency = %8.4f\n", + streamTime, outTime, latency ); + } + fflush(stdout); +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + paTestData data; + PaTime startTime; + + printf("PortAudio Test: output sine wave. SR = %d, BufSize = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER); + + data.left_phase = data.right_phase = 0; + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ + outputParameters.channelCount = 2; /* stereo output */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + /* Watch until sound is halfway finished. */ + printf("Play for %d seconds.\n", NUM_SECONDS/2 ); fflush(stdout); + + data.outTime = -1.0; /* mark time for callback as undefined */ + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + startTime = Pa_GetStreamTime( stream ); + + do + { + ReportStreamTime( stream, &data ); + Pa_Sleep(100); + } while( (Pa_GetStreamTime( stream ) - startTime) < (NUM_SECONDS/2) ); + + /* Stop sound for 2 seconds. */ + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + printf("Pause for 2 seconds.\n"); fflush(stdout); + Pa_Sleep( 2000 ); + + data.outTime = -1.0; /* mark time for callback as undefined */ + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + startTime = Pa_GetStreamTime( stream ); + + printf("Play until sound is finished.\n"); fflush(stdout); + do + { + ReportStreamTime( stream, &data ); + Pa_Sleep(100); + } while( (Pa_GetStreamTime( stream ) - startTime) < (NUM_SECONDS/2) ); + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Terminate(); + printf("Test finished.\n"); + return err; + +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/patest_start_stop.c b/src/audio/portaudio/pa_tests/patest_start_stop.c new file mode 100644 index 0000000000000000000000000000000000000000..03ade17210bb754acf3bce644b4a355773a1f451 --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_start_stop.c @@ -0,0 +1,162 @@ +/** @file patest_start_stop.c + @brief Play a sine wave for several seconds + - start and stop the stream multiple times. + + @author Ross Bencina <rossb@audiomulch.com> + @author Phil Burk <philburk@softsynth.com> +*/ +/* + * $Id$ + * + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" + +#define OUTPUT_DEVICE Pa_GetDefaultOutputDevice() /* default output device */ + +#define NUM_SECONDS (3) +#define NUM_LOOPS (4) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (400) + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +#define TABLE_SIZE (200) +typedef struct +{ + float sine[TABLE_SIZE]; + int left_phase; + int right_phase; +} +paTestData; + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned long i; + + (void) timeInfo; /* Prevent unused variable warnings. */ + (void) statusFlags; + (void) inputBuffer; + + for( i=0; i<framesPerBuffer; i++ ) + { + *out++ = data->sine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + + return paContinue; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + paTestData data; + int i; + + + printf("PortAudio Test: output sine wave. SR = %d, BufSize = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER); + + /* initialise sinusoidal wavetable */ + for( i=0; i<TABLE_SIZE; i++ ) + { + data.sine[i] = (float) sin( ((double)i/(double)TABLE_SIZE) * M_PI * 2. ); + } + data.left_phase = data.right_phase = 0; + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outputParameters.device = OUTPUT_DEVICE; + outputParameters.channelCount = 2; /* stereo output */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + for( i=0; i<NUM_LOOPS; i++ ) + { + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Play for %d seconds.\n", NUM_SECONDS ); + Pa_Sleep( NUM_SECONDS * 1000 ); + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + printf("Stopped.\n" ); + Pa_Sleep( 1000 ); + } + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Terminate(); + printf("Test finished.\n"); + + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/patest_stop.c b/src/audio/portaudio/pa_tests/patest_stop.c new file mode 100644 index 0000000000000000000000000000000000000000..deb850e9cbfd405bc8e632f5b101e8805f1669cc --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_stop.c @@ -0,0 +1,313 @@ +/** @file patest_stop.c + @brief Test different ways of stopping audio. + + Test the three ways of stopping audio: + - calling Pa_StopStream(), + - calling Pa_AbortStream(), + - and returning a 1 from the callback function. + + A long latency is set up so that you can hear the difference. + Then a simple 8 note sequence is repeated twice. + The program will print what you should hear. + + @author Phil Burk <philburk@softsynth.com> +*/ +/* + * $Id$ + * + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" + +#define OUTPUT_DEVICE (Pa_GetDefaultOutputDevice()) +#define SLEEP_DUR (200) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (256) +#define LATENCY_SECONDS (3.f) +#define FRAMES_PER_NOTE (SAMPLE_RATE/2) +#define MAX_REPEATS (2) +#define FUNDAMENTAL (400.0f / SAMPLE_RATE) +#define NOTE_0 (FUNDAMENTAL * 1.0f / 1.0f) +#define NOTE_1 (FUNDAMENTAL * 5.0f / 4.0f) +#define NOTE_2 (FUNDAMENTAL * 4.0f / 3.0f) +#define NOTE_3 (FUNDAMENTAL * 3.0f / 2.0f) +#define NOTE_4 (FUNDAMENTAL * 2.0f / 1.0f) +#define MODE_FINISH (0) +#define MODE_STOP (1) +#define MODE_ABORT (2) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (400) + +typedef struct +{ + float waveform[TABLE_SIZE + 1]; /* Add one for guard point for interpolation. */ + float phase_increment; + float phase; + float *tune; + int notesPerTune; + int frameCounter; + int noteCounter; + int repeatCounter; + PaTime outTime; + int stopMode; + int done; +} +paTestData; + +/************* Prototypes *****************************/ +int TestStopMode( paTestData *data ); +float LookupWaveform( paTestData *data, float phase ); + +/****************************************************** + * Convert phase between 0.0 and 1.0 to waveform value + * using linear interpolation. + */ +float LookupWaveform( paTestData *data, float phase ) +{ + float fIndex = phase*TABLE_SIZE; + int index = (int) fIndex; + float fract = fIndex - index; + float lo = data->waveform[index]; + float hi = data->waveform[index+1]; + float val = lo + fract*(hi-lo); + return val; +} + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + float value; + unsigned int i = 0; + int finished = paContinue; + + (void) inputBuffer; /* Prevent unused variable warnings. */ + (void) timeInfo; + (void) statusFlags; + + + /* data->outTime = outTime; */ + + if( !data->done ) + { + for( i=0; i<framesPerBuffer; i++ ) + { + /* Are we done with this note? */ + if( data->frameCounter >= FRAMES_PER_NOTE ) + { + data->noteCounter += 1; + data->frameCounter = 0; + /* Are we done with this tune? */ + if( data->noteCounter >= data->notesPerTune ) + { + data->noteCounter = 0; + data->repeatCounter += 1; + /* Are we totally done? */ + if( data->repeatCounter >= MAX_REPEATS ) + { + data->done = 1; + if( data->stopMode == MODE_FINISH ) + { + finished = paComplete; + break; + } + } + } + data->phase_increment = data->tune[data->noteCounter]; + } + value = LookupWaveform(data, data->phase); + *out++ = value; /* left */ + *out++ = value; /* right */ + data->phase += data->phase_increment; + if( data->phase >= 1.0f ) data->phase -= 1.0f; + + data->frameCounter += 1; + } + } + /* zero remainder of final buffer */ + for( ; i<framesPerBuffer; i++ ) + { + *out++ = 0; /* left */ + *out++ = 0; /* right */ + } + return finished; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + paTestData data; + int i; + float simpleTune[] = { NOTE_0, NOTE_1, NOTE_2, NOTE_3, NOTE_4, NOTE_3, NOTE_2, NOTE_1 }; + + printf("PortAudio Test: play song and test stopping. ask for %f seconds latency\n", LATENCY_SECONDS ); + /* initialise sinusoidal wavetable */ + for( i=0; i<TABLE_SIZE; i++ ) + { + data.waveform[i] = (float) ( + (0.2 * sin( ((double)i/(double)TABLE_SIZE) * M_PI * 2. )) + + (0.2 * sin( ((double)(3*i)/(double)TABLE_SIZE) * M_PI * 2. )) + + (0.1 * sin( ((double)(5*i)/(double)TABLE_SIZE) * M_PI * 2. )) + ); + } + data.waveform[TABLE_SIZE] = data.waveform[0]; /* Set guard point. */ + data.tune = &simpleTune[0]; + data.notesPerTune = sizeof(simpleTune) / sizeof(float); + + printf("Test MODE_FINISH - callback returns 1.\n"); + printf("Should hear entire %d note tune repeated twice.\n", data.notesPerTune); + data.stopMode = MODE_FINISH; + if( TestStopMode( &data ) != paNoError ) + { + printf("Test of MODE_FINISH failed!\n"); + goto error; + } + + printf("Test MODE_STOP - stop when song is done.\n"); + printf("Should hear entire %d note tune repeated twice.\n", data.notesPerTune); + data.stopMode = MODE_STOP; + if( TestStopMode( &data ) != paNoError ) + { + printf("Test of MODE_STOP failed!\n"); + goto error; + } + + printf("Test MODE_ABORT - abort immediately.\n"); + printf("Should hear last repetition cut short by %f seconds.\n", LATENCY_SECONDS); + data.stopMode = MODE_ABORT; + if( TestStopMode( &data ) != paNoError ) + { + printf("Test of MODE_ABORT failed!\n"); + goto error; + } + + return 0; + +error: + return 1; +} + +int TestStopMode( paTestData *data ) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + + data->done = 0; + data->phase = 0.0; + data->frameCounter = 0; + data->noteCounter = 0; + data->repeatCounter = 0; + data->phase_increment = data->tune[data->noteCounter]; + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outputParameters.device = OUTPUT_DEVICE; + outputParameters.channelCount = 2; /* stereo output */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + outputParameters.suggestedLatency = LATENCY_SECONDS; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, /* frames per buffer */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + if( data->stopMode == MODE_FINISH ) + { + while( ( err = Pa_IsStreamActive( stream ) ) == 1 ) + { + /*printf("outTime = %g, note# = %d, repeat# = %d\n", data->outTime, + data->noteCounter, data->repeatCounter ); + fflush(stdout); */ + Pa_Sleep( SLEEP_DUR ); + } + if( err < 0 ) goto error; + } + else + { + while( data->repeatCounter < MAX_REPEATS ) + { + /*printf("outTime = %g, note# = %d, repeat# = %d\n", data->outTime, + data->noteCounter, data->repeatCounter ); + fflush(stdout); */ + Pa_Sleep( SLEEP_DUR ); + } + } + + if( data->stopMode == MODE_ABORT ) + { + printf("Call Pa_AbortStream()\n"); + err = Pa_AbortStream( stream ); + } + else + { + printf("Call Pa_StopStream()\n"); + err = Pa_StopStream( stream ); + } + if( err != paNoError ) goto error; + + printf("Call Pa_CloseStream()\n"); fflush(stdout); + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Terminate(); + printf("Test finished.\n"); + + return err; + +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/patest_stop_playout.c b/src/audio/portaudio/pa_tests/patest_stop_playout.c new file mode 100644 index 0000000000000000000000000000000000000000..41134d581669986dd6dfd96462ff159c4e440ff7 --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_stop_playout.c @@ -0,0 +1,432 @@ +/** @file patest_stop_playout.c + @brief Test whether all queued samples are played when Pa_StopStream() + is used with a callback or read/write stream, or when the callback + returns paComplete. + @author Ross Bencina <rossb@audiomulch.com> +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com/ + * Copyright (c) 1999-2004 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" + +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (1024) + +#define TONE_SECONDS (1) /* long tone */ +#define TONE_FADE_SECONDS (.04) /* fades at start and end of long tone */ +#define GAP_SECONDS (.25) /* gap between long tone and blip */ +#define BLIP_SECONDS (.035) /* short blip */ + +#define NUM_REPEATS (3) + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +#define TABLE_SIZE (2048) +typedef struct +{ + float sine[TABLE_SIZE+1]; + + int repeatCount; + + double phase; + double lowIncrement, highIncrement; + + int gap1Length, toneLength, toneFadesLength, gap2Length, blipLength; + int gap1Countdown, toneCountdown, gap2Countdown, blipCountdown; +} +TestData; + + +static void RetriggerTestSignalGenerator( TestData *data ) +{ + data->phase = 0.; + data->gap1Countdown = data->gap1Length; + data->toneCountdown = data->toneLength; + data->gap2Countdown = data->gap2Length; + data->blipCountdown = data->blipLength; +} + + +static void ResetTestSignalGenerator( TestData *data ) +{ + data->repeatCount = 0; + RetriggerTestSignalGenerator( data ); +} + + +static void InitTestSignalGenerator( TestData *data ) +{ + int signalLengthModBufferLength, i; + + /* initialise sinusoidal wavetable */ + for( i=0; i<TABLE_SIZE; i++ ) + { + data->sine[i] = (float) sin( ((double)i/(double)TABLE_SIZE) * M_PI * 2. ); + } + data->sine[TABLE_SIZE] = data->sine[0]; /* guard point for linear interpolation */ + + + + data->lowIncrement = (330. / SAMPLE_RATE) * TABLE_SIZE; + data->highIncrement = (1760. / SAMPLE_RATE) * TABLE_SIZE; + + data->gap1Length = GAP_SECONDS * SAMPLE_RATE; + data->toneLength = TONE_SECONDS * SAMPLE_RATE; + data->toneFadesLength = TONE_FADE_SECONDS * SAMPLE_RATE; + data->gap2Length = GAP_SECONDS * SAMPLE_RATE; + data->blipLength = BLIP_SECONDS * SAMPLE_RATE; + + /* adjust signal length to be a multiple of the buffer length */ + signalLengthModBufferLength = (data->gap1Length + data->toneLength + data->gap2Length + data->blipLength) % FRAMES_PER_BUFFER; + if( signalLengthModBufferLength > 0 ) + data->toneLength += signalLengthModBufferLength; + + ResetTestSignalGenerator( data ); +} + + +#define MIN( a, b ) (((a)<(b))?(a):(b)) + +static void GenerateTestSignal( TestData *data, float *stereo, int frameCount ) +{ + int framesGenerated = 0; + float output; + long index; + float fraction; + int count, i; + + while( framesGenerated < frameCount && data->repeatCount < NUM_REPEATS ) + { + if( framesGenerated < frameCount && data->gap1Countdown > 0 ){ + count = MIN( frameCount - framesGenerated, data->gap1Countdown ); + for( i=0; i < count; ++i ) + { + *stereo++ = 0.f; + *stereo++ = 0.f; + } + + data->gap1Countdown -= count; + framesGenerated += count; + } + + if( framesGenerated < frameCount && data->toneCountdown > 0 ){ + count = MIN( frameCount - framesGenerated, data->toneCountdown ); + for( i=0; i < count; ++i ) + { + /* tone with data->lowIncrement phase increment */ + index = (long)data->phase; + fraction = data->phase - index; + output = data->sine[ index ] + (data->sine[ index + 1 ] - data->sine[ index ]) * fraction; + + data->phase += data->lowIncrement; + while( data->phase >= TABLE_SIZE ) + data->phase -= TABLE_SIZE; + + /* apply fade to ends */ + + if( data->toneCountdown < data->toneFadesLength ) + { + /* cosine-bell fade out at end */ + output *= (-cos(((float)data->toneCountdown / (float)data->toneFadesLength) * M_PI) + 1.) * .5; + } + else if( data->toneCountdown > data->toneLength - data->toneFadesLength ) + { + /* cosine-bell fade in at start */ + output *= (cos(((float)(data->toneCountdown - (data->toneLength - data->toneFadesLength)) / (float)data->toneFadesLength) * M_PI) + 1.) * .5; + } + + output *= .5; /* play tone half as loud as blip */ + + *stereo++ = output; + *stereo++ = output; + + data->toneCountdown--; + } + + framesGenerated += count; + } + + if( framesGenerated < frameCount && data->gap2Countdown > 0 ){ + count = MIN( frameCount - framesGenerated, data->gap2Countdown ); + for( i=0; i < count; ++i ) + { + *stereo++ = 0.f; + *stereo++ = 0.f; + } + + data->gap2Countdown -= count; + framesGenerated += count; + } + + if( framesGenerated < frameCount && data->blipCountdown > 0 ){ + count = MIN( frameCount - framesGenerated, data->blipCountdown ); + for( i=0; i < count; ++i ) + { + /* tone with data->highIncrement phase increment */ + index = (long)data->phase; + fraction = data->phase - index; + output = data->sine[ index ] + (data->sine[ index + 1 ] - data->sine[ index ]) * fraction; + + data->phase += data->highIncrement; + while( data->phase >= TABLE_SIZE ) + data->phase -= TABLE_SIZE; + + /* cosine-bell envelope over whole blip */ + output *= (-cos( ((float)data->blipCountdown / (float)data->blipLength) * 2. * M_PI) + 1.) * .5; + + *stereo++ = output; + *stereo++ = output; + + data->blipCountdown--; + } + + framesGenerated += count; + } + + + if( data->blipCountdown == 0 ) + { + RetriggerTestSignalGenerator( data ); + data->repeatCount++; + } + } + + if( framesGenerated < frameCount ) + { + count = frameCount - framesGenerated; + for( i=0; i < count; ++i ) + { + *stereo++ = 0.f; + *stereo++ = 0.f; + } + } +} + + +static int IsTestSignalFinished( TestData *data ) +{ + if( data->repeatCount >= NUM_REPEATS ) + return 1; + else + return 0; +} + + +static int TestCallback1( const void *inputBuffer, void *outputBuffer, + unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + (void) inputBuffer; /* Prevent unused variable warnings. */ + (void) timeInfo; + (void) statusFlags; + + GenerateTestSignal( (TestData*)userData, (float*)outputBuffer, frameCount ); + + if( IsTestSignalFinished( (TestData*)userData ) ) + return paComplete; + else + return paContinue; +} + + +volatile int testCallback2Finished = 0; + +static int TestCallback2( const void *inputBuffer, void *outputBuffer, + unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + (void) inputBuffer; /* Prevent unused variable warnings. */ + (void) timeInfo; + (void) statusFlags; + + GenerateTestSignal( (TestData*)userData, (float*)outputBuffer, frameCount ); + + if( IsTestSignalFinished( (TestData*)userData ) ) + testCallback2Finished = 1; + + return paContinue; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + TestData data; + float writeBuffer[ FRAMES_PER_BUFFER * 2 ]; + + printf("PortAudio Test: check that stopping stream plays out all queued samples. SR = %d, BufSize = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER); + + InitTestSignalGenerator( &data ); + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ + outputParameters.channelCount = 2; /* stereo output */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultHighOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + +/* test paComplete ---------------------------------------------------------- */ + + ResetTestSignalGenerator( &data ); + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + TestCallback1, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + + printf("\nPlaying 'tone-blip' %d times using callback, stops by returning paComplete from callback.\n", NUM_REPEATS ); + printf("If final blip is not intact, callback+paComplete implementation may be faulty.\n\n" ); + + while( (err = Pa_IsStreamActive( stream )) == 1 ) + Pa_Sleep( 5 ); + + if( err != 0 ) goto error; + + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Sleep( 500 ); + +/* test Pa_StopStream() with callback --------------------------------------- */ + + ResetTestSignalGenerator( &data ); + + testCallback2Finished = 0; + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + TestCallback2, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + + printf("\nPlaying 'tone-blip' %d times using callback, stops by calling Pa_StopStream.\n", NUM_REPEATS ); + printf("If final blip is not intact, callback+Pa_StopStream implementation may be faulty.\n\n" ); + + /* note that polling a volatile flag is not a good way to synchronise with + the callback, but it's the best we can do portably. */ + while( !testCallback2Finished ) + Pa_Sleep( 2 ); + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Sleep( 500 ); + +/* test Pa_StopStream() with Pa_WriteStream --------------------------------- */ + + ResetTestSignalGenerator( &data ); + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + NULL, /* no callback, use blocking API */ + NULL ); /* no callback, so no callback userData */ + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + + printf("\nPlaying 'tone-blip' %d times using Pa_WriteStream, stops by calling Pa_StopStream.\n", NUM_REPEATS ); + printf("If final blip is not intact, Pa_WriteStream+Pa_StopStream implementation may be faulty.\n\n" ); + + do{ + GenerateTestSignal( &data, writeBuffer, FRAMES_PER_BUFFER ); + err = Pa_WriteStream( stream, writeBuffer, FRAMES_PER_BUFFER ); + if( err != paNoError ) goto error; + + }while( !IsTestSignalFinished( &data ) ); + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + +/* -------------------------------------------------------------------------- */ + + Pa_Terminate(); + printf("Test finished.\n"); + + return err; + +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/patest_sync.c b/src/audio/portaudio/pa_tests/patest_sync.c new file mode 100644 index 0000000000000000000000000000000000000000..01e471420cff3a24371bec54645acc6f352e7c0f --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_sync.c @@ -0,0 +1,259 @@ +/** @file patest_sync.c + @brief Test time stamping and synchronization of audio and video. + + A high latency is used so we can hear the difference in time. + Random durations are used so we know we are hearing the right beep + and not the one before or after. + + Sequence of events: + -# Foreground requests a beep. + -# Background randomly schedules a beep. + -# Foreground waits for the beep to be heard based on PaUtil_GetTime(). + -# Foreground outputs video (printf) in sync with audio. + -# Repeat. + + @author Phil Burk http://www.softsynth.com +*/ +/* + * $Id$ + * + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" +#include "pa_util.h" +#define NUM_BEEPS (6) +#define SAMPLE_RATE (44100) +#define SAMPLE_PERIOD (1.0/44100.0) +#define FRAMES_PER_BUFFER (256) +#define BEEP_DURATION (400) +#define LATENCY_MSEC (2000) +#define SLEEP_MSEC (10) +#define TIMEOUT_MSEC (15000) + +#define STATE_BKG_IDLE (0) +#define STATE_BKG_PENDING (1) +#define STATE_BKG_BEEPING (2) +typedef struct +{ + float left_phase; + float right_phase; + int state; + int requestBeep; /* Set by foreground, cleared by background. */ + PaTime beepTime; + int beepCount; + double latency; /* For debugging. */ +} +paTestData; + +static unsigned long GenerateRandomNumber( void ); +/************************************************************/ +/* Calculate pseudo-random 32 bit number based on linear congruential method. */ +static unsigned long GenerateRandomNumber( void ) +{ + static unsigned long randSeed = 99887766; /* Change this for different random sequences. */ + randSeed = (randSeed * 196314165) + 907633515; + return randSeed; +} + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo *timeInfo, + PaStreamCallbackFlags statusFlags, void *userData ) +{ + /* Cast data passed through stream to our structure. */ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned int i; + (void) inputBuffer; + + data->latency = timeInfo->outputBufferDacTime - timeInfo->currentTime; + + for( i=0; i<framesPerBuffer; i++ ) + { + switch( data->state ) + { + case STATE_BKG_IDLE: + /* Schedule beep at some random time in the future. */ + if( data->requestBeep ) + { + int random = GenerateRandomNumber() >> 14; + data->beepTime = timeInfo->outputBufferDacTime + (( (double)(random + SAMPLE_RATE)) * SAMPLE_PERIOD ); + data->state = STATE_BKG_PENDING; + } + *out++ = 0.0; /* left */ + *out++ = 0.0; /* right */ + break; + + case STATE_BKG_PENDING: + if( (timeInfo->outputBufferDacTime + (i*SAMPLE_PERIOD)) >= data->beepTime ) + { + data->state = STATE_BKG_BEEPING; + data->beepCount = BEEP_DURATION; + data->left_phase = data->right_phase = 0.0; + } + *out++ = 0.0; /* left */ + *out++ = 0.0; /* right */ + break; + + case STATE_BKG_BEEPING: + if( data->beepCount <= 0 ) + { + data->state = STATE_BKG_IDLE; + data->requestBeep = 0; + *out++ = 0.0; /* left */ + *out++ = 0.0; /* right */ + } + else + { + /* Play sawtooth wave. */ + *out++ = data->left_phase; /* left */ + *out++ = data->right_phase; /* right */ + /* Generate simple sawtooth phaser that ranges between -1.0 and 1.0. */ + data->left_phase += 0.01f; + /* When signal reaches top, drop back down. */ + if( data->left_phase >= 1.0f ) data->left_phase -= 2.0f; + /* higher pitch so we can distinguish left and right. */ + data->right_phase += 0.03f; + if( data->right_phase >= 1.0f ) data->right_phase -= 2.0f; + } + data->beepCount -= 1; + break; + + default: + data->state = STATE_BKG_IDLE; + break; + } + } + return 0; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStream *stream; + PaError err; + paTestData DATA; + int i, timeout; + PaTime previousTime; + PaStreamParameters outputParameters; + printf("PortAudio Test: you should see BEEP at the same time you hear it.\n"); + printf("Wait for a few seconds random delay between BEEPs.\n"); + printf("BEEP %d times.\n", NUM_BEEPS ); + /* Initialize our DATA for use by callback. */ + DATA.left_phase = DATA.right_phase = 0.0; + DATA.state = STATE_BKG_IDLE; + DATA.requestBeep = 0; + /* Initialize library before making any other calls. */ + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outputParameters.device = Pa_GetDefaultOutputDevice(); + outputParameters.channelCount = 2; + outputParameters.hostApiSpecificStreamInfo = NULL; + outputParameters.sampleFormat = paFloat32; + outputParameters.suggestedLatency = (double)LATENCY_MSEC / 1000; + + /* Open an audio I/O stream. */ + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, /* frames per buffer */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &DATA ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("started\n"); + fflush(stdout); + + previousTime = Pa_GetStreamTime( stream ); + for( i=0; i<NUM_BEEPS; i++ ) + { + /* Request a beep from background. */ + DATA.requestBeep = 1; + + /* Wait for background to acknowledge request. */ + timeout = TIMEOUT_MSEC; + while( (DATA.requestBeep == 1) && (timeout-- > 0 ) ) Pa_Sleep(SLEEP_MSEC); + if( timeout <= 0 ) + { + fprintf( stderr, "Timed out waiting for background to acknowledge request.\n" ); + goto error; + } + printf("calc beep for %9.3f, latency = %6.3f\n", DATA.beepTime, DATA.latency ); + fflush(stdout); + + /* Wait for scheduled beep time. */ + timeout = TIMEOUT_MSEC + (10000/SLEEP_MSEC); + while( (Pa_GetStreamTime( stream ) < DATA.beepTime) && (timeout-- > 0 ) ) + { + Pa_Sleep(SLEEP_MSEC); + } + if( timeout <= 0 ) + { + fprintf( stderr, "Timed out waiting for time. Now = %9.3f, Beep for %9.3f.\n", + PaUtil_GetTime(), DATA.beepTime ); + goto error; + } + + /* Beep should be sounding now so print synchronized BEEP. */ + printf("hear \"BEEP\" at %9.3f, delta = %9.3f\n", + Pa_GetStreamTime( stream ), (DATA.beepTime - previousTime) ); + fflush(stdout); + + previousTime = DATA.beepTime; + } + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/patest_toomanysines.c b/src/audio/portaudio/pa_tests/patest_toomanysines.c new file mode 100644 index 0000000000000000000000000000000000000000..b7b22667c69f8cef3cc3f8e011c32103f97b7e9f --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_toomanysines.c @@ -0,0 +1,173 @@ +/** @file patest_toomanysines.c + @brief Play more sine waves than we can handle in real time as a stress test. + @author Ross Bencina <rossb@audiomulch.com> + @author Phil Burk <philburk@softsynth.com> +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" + +#define MAX_SINES (500) +#define MAX_LOAD (1.2) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (512) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TWOPI (M_PI * 2.0) + +typedef struct paTestData +{ + int numSines; + double phases[MAX_SINES]; +} +paTestData; + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned long i; + int j; + int finished = 0; + (void) inputBuffer; /* Prevent unused variable warning. */ + + for( i=0; i<framesPerBuffer; i++ ) + { + float output = 0.0; + double phaseInc = 0.02; + double phase; + for( j=0; j<data->numSines; j++ ) + { + /* Advance phase of next oscillator. */ + phase = data->phases[j]; + phase += phaseInc; + if( phase > TWOPI ) phase -= TWOPI; + + phaseInc *= 1.02; + if( phaseInc > 0.5 ) phaseInc *= 0.5; + + /* This is not a very efficient way to calc sines. */ + output += (float) sin( phase ); + data->phases[j] = phase; + } + *out++ = (float) (output / data->numSines); + } + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + int numStress; + paTestData data = {0}; + double load; + + printf("PortAudio Test: output sine wave. SR = %d, BufSize = %d. MAX_LOAD = %f\n", + SAMPLE_RATE, FRAMES_PER_BUFFER, MAX_LOAD ); + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ + outputParameters.channelCount = 1; /* mono output */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + /* Determine number of sines required to get to 50% */ + do + { + data.numSines++; + Pa_Sleep( 100 ); + + load = Pa_GetStreamCpuLoad( stream ); + printf("numSines = %d, CPU load = %f\n", data.numSines, load ); + } + while( load < 0.5 ); + + /* Calculate target stress value then ramp up to that level*/ + numStress = (int) (2.0 * data.numSines * MAX_LOAD ); + for( ; data.numSines < numStress; data.numSines++ ) + { + Pa_Sleep( 200 ); + load = Pa_GetStreamCpuLoad( stream ); + printf("STRESSING: numSines = %d, CPU load = %f\n", data.numSines, load ); + } + + printf("Suffer for 5 seconds.\n"); + Pa_Sleep( 5000 ); + + printf("Stop stream.\n"); + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/patest_two_rates.c b/src/audio/portaudio/pa_tests/patest_two_rates.c new file mode 100644 index 0000000000000000000000000000000000000000..99532411ffbc618a5d40884f05f79e4cc8eed800 --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_two_rates.c @@ -0,0 +1,168 @@ +/* + * $Id$ + * patest_two_rates.c + * Play two streams at different rates to make sure they don't interfere. + * + * Author: Phil Burk http://www.softsynth.com + * + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" + +#define OUTPUT_DEVICE (Pa_GetDefaultOutputDeviceID()) +#define SAMPLE_RATE_1 (44100) +#define SAMPLE_RATE_2 (44100) +#define FRAMES_PER_BUFFER (256) +#define FREQ_INCR (0.1) + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +typedef struct +{ + double phase; + double numFrames; +} paTestData; + +/* 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 patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + int frameIndex, channelIndex; + int finished = 0; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + for( frameIndex=0; frameIndex<(int)framesPerBuffer; frameIndex++ ) + { + /* Generate sine wave. */ + float value = (float) 0.3 * sin(data->phase); + /* Stereo - two channels. */ + *out++ = value; + *out++ = value; + + data->phase += FREQ_INCR; + if( data->phase >= (2.0 * M_PI) ) data->phase -= (2.0 * M_PI); + } + + return 0; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaError err; + PortAudioStream *stream1; + PortAudioStream *stream2; + paTestData data1 = {0}; + paTestData data2 = {0}; + printf("PortAudio Test: two rates.\n" ); + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + /* Start first stream. **********************/ + err = Pa_OpenStream( + &stream1, + paNoDevice, /* default input device */ + 0, /* no input */ + paFloat32, /* 32 bit floating point input */ + NULL, + OUTPUT_DEVICE, + 2, /* Stereo */ + paFloat32, /* 32 bit floating point output */ + NULL, + SAMPLE_RATE_1, + FRAMES_PER_BUFFER, /* frames per buffer */ + 0, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data1 ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream1 ); + if( err != paNoError ) goto error; + + Pa_Sleep( 3 * 1000 ); + + /* Start second stream. **********************/ + err = Pa_OpenStream( + &stream2, + paNoDevice, /* default input device */ + 0, /* no input */ + paFloat32, /* 32 bit floating point input */ + NULL, + OUTPUT_DEVICE, + 2, /* Stereo */ + paFloat32, /* 32 bit floating point output */ + NULL, + SAMPLE_RATE_2, + FRAMES_PER_BUFFER, /* frames per buffer */ + 0, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data2 ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream2 ); + if( err != paNoError ) goto error; + + Pa_Sleep( 3 * 1000 ); + + err = Pa_StopStream( stream2 ); + if( err != paNoError ) goto error; + + Pa_Sleep( 3 * 1000 ); + + err = Pa_StopStream( stream1 ); + if( err != paNoError ) goto error; + + Pa_CloseStream( stream2 ); + Pa_CloseStream( stream1 ); + + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/patest_underflow.c b/src/audio/portaudio/pa_tests/patest_underflow.c new file mode 100644 index 0000000000000000000000000000000000000000..7ff3de6a42d64efc3702d6d2cadc0e6f97f233d3 --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_underflow.c @@ -0,0 +1,151 @@ +/** @file patest_underflow.c + @brief Simulate an output buffer underflow condition. + Tests whether the stream can be stopped when underflowing buffers. + @author Ross Bencina <rossb@audiomulch.com> + @author Phil Burk <philburk@softsynth.com> +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" + +#define NUM_SECONDS (20) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (2048) +#define MSEC_PER_BUFFER ( (FRAMES_PER_BUFFER * 1000) / SAMPLE_RATE ) + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +#define TABLE_SIZE (200) +typedef struct +{ + float sine[TABLE_SIZE]; + int left_phase; + int right_phase; + int sleepTime; +} +paTestData; + +/* 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 patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned long i; + int finished = 0; + (void) inputBuffer; /* Prevent unused variable warnings. */ + for( i=0; i<framesPerBuffer; i++ ) + { + *out++ = data->sine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + + /* Cause underflow to occur. */ + if( data->sleepTime > 0 ) Pa_Sleep( data->sleepTime ); + data->sleepTime += 1; + + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + paTestData data; + int i; + printf("PortAudio Test: output sine wave. SR = %d, BufSize = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER); + /* initialise sinusoidal wavetable */ + for( i=0; i<TABLE_SIZE; i++ ) + { + data.sine[i] = (float) sin( ((double)i/(double)TABLE_SIZE) * M_PI * 2. ); + } + data.left_phase = data.right_phase = data.sleepTime = 0; + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ + outputParameters.channelCount = 2; /* stereo output */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + while( data.sleepTime < (2 * MSEC_PER_BUFFER) ) + { + printf("SleepTime = %d\n", data.sleepTime ); + Pa_Sleep( data.sleepTime ); + } + + printf("Try to stop stream.\n"); + err = Pa_StopStream( stream ); /* */ + err = Pa_AbortStream( stream ); /* */ + if( err != paNoError ) goto error; + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + printf("Test finished.\n"); + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_tests/patest_wire.c b/src/audio/portaudio/pa_tests/patest_wire.c new file mode 100644 index 0000000000000000000000000000000000000000..d83ce22d64d28a63f28c96d53061527f99b34a8c --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_wire.c @@ -0,0 +1,284 @@ +/** @file patest_wire.c + @brief Pass input directly to output. + + Note that some HW devices, for example many ISA audio cards + on PCs, do NOT support full duplex! For a PC, you normally need + a PCI based audio card such as the SBLive. + + @author Phil Burk http://www.softsynth.com + + While adapting to V19-API, I excluded configs with framesPerCallback=0 + because of an assert in file pa_common/pa_process.c. Pieter, Oct 9, 2003. + +*/ +/* + * $Id$ + * + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" + +#define SAMPLE_RATE (44100) + +typedef struct WireConfig_s +{ + int isInputInterleaved; + int isOutputInterleaved; + int numInputChannels; + int numOutputChannels; + int framesPerCallback; +} WireConfig_t; + +#define USE_FLOAT_INPUT (1) +#define USE_FLOAT_OUTPUT (1) + +/* Latencies set to defaults. */ + +#if USE_FLOAT_INPUT + #define INPUT_FORMAT paFloat32 + typedef float INPUT_SAMPLE; +#else + #define INPUT_FORMAT paInt16 + typedef short INPUT_SAMPLE; +#endif + +#if USE_FLOAT_OUTPUT + #define OUTPUT_FORMAT paFloat32 + typedef float OUTPUT_SAMPLE; +#else + #define OUTPUT_FORMAT paInt16 + typedef short OUTPUT_SAMPLE; +#endif + +double gInOutScaler = 1.0; +#define CONVERT_IN_TO_OUT(in) ((OUTPUT_SAMPLE) ((in) * gInOutScaler)) + +#define INPUT_DEVICE (Pa_GetDefaultInputDevice()) +#define OUTPUT_DEVICE (Pa_GetDefaultOutputDevice()) + +static PaError TestConfiguration( WireConfig_t *config ); + +static int wireCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ); + +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be 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 wireCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + INPUT_SAMPLE *in; + OUTPUT_SAMPLE *out; + int inStride; + int outStride; + int inDone = 0; + int outDone = 0; + WireConfig_t *config = (WireConfig_t *) userData; + unsigned int i; + int inChannel, outChannel; + + /* This may get called with NULL inputBuffer during initial setup. */ + if( inputBuffer == NULL) return 0; + + inChannel=0, outChannel=0; + while( !(inDone && outDone) ) + { + if( config->isInputInterleaved ) + { + in = ((INPUT_SAMPLE*)inputBuffer) + inChannel; + inStride = config->numInputChannels; + } + else + { + in = ((INPUT_SAMPLE**)inputBuffer)[inChannel]; + inStride = 1; + } + + if( config->isOutputInterleaved ) + { + out = ((OUTPUT_SAMPLE*)outputBuffer) + outChannel; + outStride = config->numOutputChannels; + } + else + { + out = ((OUTPUT_SAMPLE**)outputBuffer)[outChannel]; + outStride = 1; + } + + for( i=0; i<framesPerBuffer; i++ ) + { + *out = CONVERT_IN_TO_OUT( *in ); + out += outStride; + in += inStride; + } + + if(inChannel < (config->numInputChannels - 1)) inChannel++; + else inDone = 1; + if(outChannel < (config->numOutputChannels - 1)) outChannel++; + else outDone = 1; + } + return 0; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaError err; + WireConfig_t CONFIG; + WireConfig_t *config = &CONFIG; + int configIndex = 0;; + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + printf("Please connect audio signal to input and listen for it on output!\n"); + printf("input format = %lu\n", INPUT_FORMAT ); + printf("output format = %lu\n", OUTPUT_FORMAT ); + printf("input device ID = %d\n", INPUT_DEVICE ); + printf("output device ID = %d\n", OUTPUT_DEVICE ); + + if( INPUT_FORMAT == OUTPUT_FORMAT ) + { + gInOutScaler = 1.0; + } + else if( (INPUT_FORMAT == paInt16) && (OUTPUT_FORMAT == paFloat32) ) + { + gInOutScaler = 1.0/32768.0; + } + else if( (INPUT_FORMAT == paFloat32) && (OUTPUT_FORMAT == paInt16) ) + { + gInOutScaler = 32768.0; + } + + for( config->isInputInterleaved = 0; config->isInputInterleaved < 2; config->isInputInterleaved++ ) + { + for( config->isOutputInterleaved = 0; config->isOutputInterleaved < 2; config->isOutputInterleaved++ ) + { + for( config->numInputChannels = 1; config->numInputChannels < 3; config->numInputChannels++ ) + { + for( config->numOutputChannels = 1; config->numOutputChannels < 3; config->numOutputChannels++ ) + { + /* If framesPerCallback = 0, assertion fails in file pa_common/pa_process.c, line 1413: EX. */ + for( config->framesPerCallback = 64; config->framesPerCallback < 129; config->framesPerCallback += 64 ) + { + printf("-----------------------------------------------\n" ); + printf("Configuration #%d\n", configIndex++ ); + err = TestConfiguration( config ); + /* Give user a chance to bail out. */ + if( err == 1 ) + { + err = paNoError; + goto done; + } + else if( err != paNoError ) goto error; + } + } + } + } + } + +done: + Pa_Terminate(); + printf("Full duplex sound test complete.\n"); fflush(stdout); + printf("Hit ENTER to quit.\n"); fflush(stdout); + getchar(); + return 0; + +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + printf("Hit ENTER to quit.\n"); fflush(stdout); + getchar(); + return -1; +} + +static PaError TestConfiguration( WireConfig_t *config ) +{ + int c; + PaError err; + PaStream *stream; + PaStreamParameters inputParameters, outputParameters; + + printf("input %sinterleaved!\n", (config->isInputInterleaved ? " " : "NOT ") ); + printf("output %sinterleaved!\n", (config->isOutputInterleaved ? " " : "NOT ") ); + printf("input channels = %d\n", config->numInputChannels ); + printf("output channels = %d\n", config->numOutputChannels ); + printf("framesPerCallback = %d\n", config->framesPerCallback ); + + inputParameters.device = INPUT_DEVICE; /* default input device */ + inputParameters.channelCount = config->numInputChannels; + inputParameters.sampleFormat = INPUT_FORMAT | (config->isInputInterleaved ? 0 : paNonInterleaved); + inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency; + inputParameters.hostApiSpecificStreamInfo = NULL; + + outputParameters.device = OUTPUT_DEVICE; /* default output device */ + outputParameters.channelCount = config->numOutputChannels; + outputParameters.sampleFormat = OUTPUT_FORMAT | (config->isOutputInterleaved ? 0 : paNonInterleaved); + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + &inputParameters, + &outputParameters, + SAMPLE_RATE, + config->framesPerCallback, /* frames per buffer */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + wireCallback, + config ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Hit ENTER for next configuration, or 'q' to quit.\n"); fflush(stdout); + c = getchar(); + + printf("Closing stream.\n"); + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + if( c == 'q' ) return 1; + +error: + return err; +} diff --git a/src/audio/portaudio/pa_tests/patest_write_sine.c b/src/audio/portaudio/pa_tests/patest_write_sine.c new file mode 100644 index 0000000000000000000000000000000000000000..f0fcba0b6e6d015d52aee02a62c7c78318ed6303 --- /dev/null +++ b/src/audio/portaudio/pa_tests/patest_write_sine.c @@ -0,0 +1,147 @@ +/** @file patest_write_sine.c + @brief Play a sine wave for several seconds using the blocking API (Pa_WriteStream()) + @author Ross Bencina <rossb@audiomulch.com> + @author Phil Burk <philburk@softsynth.com> +*/ +/* + * $Id$ + * + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <math.h> +#include "portaudio.h" + +#define NUM_SECONDS (5) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (1024) + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +#define TABLE_SIZE (200) + + +int main(void); +int main(void) +{ + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + float buffer[FRAMES_PER_BUFFER][2]; /* stereo output buffer */ + float sine[TABLE_SIZE]; /* sine wavetable */ + int left_phase = 0; + int right_phase = 0; + int left_inc = 1; + int right_inc = 3; /* higher pitch so we can distinguish left and right. */ + int i, j, k; + int bufferCount; + + + printf("PortAudio Test: output sine wave. SR = %d, BufSize = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER); + + /* initialise sinusoidal wavetable */ + for( i=0; i<TABLE_SIZE; i++ ) + { + sine[i] = (float) sin( ((double)i/(double)TABLE_SIZE) * M_PI * 2. ); + } + + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ + outputParameters.channelCount = 2; /* stereo output */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + NULL, /* no callback, use blocking API */ + NULL ); /* no callback, so no callback userData */ + if( err != paNoError ) goto error; + + + printf( "Play 3 times, higher each time.\n" ); + + for( k=0; k < 3; ++k ) + { + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Play for %d seconds.\n", NUM_SECONDS ); + + bufferCount = ((NUM_SECONDS * SAMPLE_RATE) / FRAMES_PER_BUFFER); + + for( i=0; i < bufferCount; i++ ) + { + for( j=0; j < FRAMES_PER_BUFFER; j++ ) + { + buffer[j][0] = sine[left_phase]; /* left */ + buffer[j][1] = sine[right_phase]; /* right */ + left_phase += left_inc; + if( left_phase >= TABLE_SIZE ) left_phase -= TABLE_SIZE; + right_phase += right_inc; + if( right_phase >= TABLE_SIZE ) right_phase -= TABLE_SIZE; + } + + err = Pa_WriteStream( stream, buffer, FRAMES_PER_BUFFER ); + if( err != paNoError ) goto error; + } + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + ++left_inc; + ++right_inc; + + Pa_Sleep( 1000 ); + } + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Terminate(); + printf("Test finished.\n"); + + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/src/audio/portaudio/pa_unix/pa_unix_hostapis.c b/src/audio/portaudio/pa_unix/pa_unix_hostapis.c new file mode 100644 index 0000000000000000000000000000000000000000..570f596e70c1b9dde9762d0d66df968bbbbac78a --- /dev/null +++ b/src/audio/portaudio/pa_unix/pa_unix_hostapis.c @@ -0,0 +1,64 @@ +/* + * $Id$ + * Portable Audio I/O Library UNIX initialization table + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, 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. + * + * 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. + * + * 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. + */ + + +#include "pa_hostapi.h" + +PaError PaJack_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); +PaError PaAlsa_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); +PaError PaOSS_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); +/* Added for IRIX, Pieter, oct 2, 2003: */ +PaError PaSGI_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + + +PaUtilHostApiInitializer *paHostApiInitializers[] = + { +#ifdef PA_USE_OSS + PaOSS_Initialize, +#endif + +#ifdef PA_USE_ALSA + PaAlsa_Initialize, +#endif + +#ifdef PA_USE_JACK + PaJack_Initialize, +#endif + /* Added for IRIX, Pieter, oct 2, 2003: */ +#ifdef PA_USE_SGI + PaSGI_Initialize, +#endif + 0 /* NULL terminated array */ + }; + +int paDefaultHostApiIndex = 0; + + diff --git a/src/audio/portaudio/pa_unix/pa_unix_util.c b/src/audio/portaudio/pa_unix/pa_unix_util.c new file mode 100644 index 0000000000000000000000000000000000000000..f8cab4021c78afc6c5de2680ca47665f354992f0 --- /dev/null +++ b/src/audio/portaudio/pa_unix/pa_unix_util.c @@ -0,0 +1,175 @@ +/* + * $Id$ + * Portable Audio I/O Library + * UNIX platform-specific support functions + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2000 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. + * + * 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. + * + * 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. + */ + + +#include <pthread.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/time.h> +#include <assert.h> +#include <string.h> /* For memset */ + +#include "pa_util.h" +#include "pa_unix_util.h" + +/* + Track memory allocations to avoid leaks. + */ + +#if PA_TRACK_MEMORY +static int numAllocations_ = 0; +#endif + + +void *PaUtil_AllocateMemory( long size ) +{ + void *result = malloc( size ); + +#if PA_TRACK_MEMORY + if( result != NULL ) numAllocations_ += 1; +#endif + return result; +} + + +void PaUtil_FreeMemory( void *block ) +{ + if( block != NULL ) + { + free( block ); +#if PA_TRACK_MEMORY + numAllocations_ -= 1; +#endif + + } +} + + +int PaUtil_CountCurrentlyAllocatedBlocks( void ) +{ +#if PA_TRACK_MEMORY + return numAllocations_; +#else + return 0; +#endif +} + + +void Pa_Sleep( long msec ) +{ + while( msec > 999 ) /* For OpenBSD and IRIX, argument */ + { /* to usleep must be < 1000000. */ + usleep( 999000 ); + msec -= 999; + } + usleep( msec * 1000 ); +} + +/* *** NOT USED YET: *** +static int usePerformanceCounter_; +static double microsecondsPerTick_; +*/ + +void PaUtil_InitializeClock( void ) +{ + /* TODO */ +} + + +PaTime PaUtil_GetTime( void ) +{ + struct timeval tv; + gettimeofday( &tv, NULL ); + return (PaTime) tv.tv_usec / 1000000. + tv.tv_sec; +} + +PaError PaUtil_InitializeThreading( PaUtilThreading *threading ) +{ + (void) paUtilErr_; + return paNoError; +} + +void PaUtil_TerminateThreading( PaUtilThreading *threading ) +{ +} + +PaError PaUtil_StartThreading( PaUtilThreading *threading, void *(*threadRoutine)(void *), void *data ) +{ + pthread_create( &threading->callbackThread, NULL, threadRoutine, data ); + return paNoError; +} + +PaError PaUtil_CancelThreading( PaUtilThreading *threading, int wait, PaError *exitResult ) +{ + PaError result = paNoError; + void *pret; + + if( exitResult ) + *exitResult = paNoError; + + /* Only kill the thread if it isn't in the process of stopping (flushing adaptation buffers) */ + if( !wait ) + pthread_cancel( threading->callbackThread ); /* XXX: Safe to call this if the thread has exited on its own? */ + pthread_join( threading->callbackThread, &pret ); + +#ifdef PTHREAD_CANCELED + if( pret && PTHREAD_CANCELED != pret ) +#else + /* !wait means the thread may have been canceled */ + if( pret && wait ) +#endif + { + if( exitResult ) + *exitResult = *(PaError *) pret; + free( pret ); + } + + return result; +} + +/* +static void *CanaryFunc( void *userData ) +{ + const unsigned intervalMsec = 1000; + PaUtilThreading *th = (PaUtilThreading *) userData; + + while( 1 ) + { + th->canaryTime = PaUtil_GetTime(); + + pthread_testcancel(); + Pa_Sleep( intervalMsec ); + } + + pthread_exit( NULL ); +} +*/ diff --git a/src/audio/portaudio/pa_unix/pa_unix_util.h b/src/audio/portaudio/pa_unix/pa_unix_util.h new file mode 100644 index 0000000000000000000000000000000000000000..01dda01e5f775e8ece5906ffdc29b08057c25519 --- /dev/null +++ b/src/audio/portaudio/pa_unix/pa_unix_util.h @@ -0,0 +1,73 @@ +#ifndef PA_UNIX_UTIL_H +#define PA_UNIX_UTIL_H + +#include "pa_cpuload.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +#define PA_MIN(x,y) ( (x) < (y) ? (x) : (y) ) +#define PA_MAX(x,y) ( (x) > (y) ? (x) : (y) ) + +/* Utilize GCC branch prediction for error tests */ +#if defined __GNUC__ && __GNUC__ >= 3 +#define UNLIKELY(expr) __builtin_expect( (expr), 0 ) +#else +#define UNLIKELY(expr) (expr) +#endif + +#define STRINGIZE_HELPER(expr) #expr +#define STRINGIZE(expr) STRINGIZE_HELPER(expr) + +#define PA_UNLESS(expr, code) \ + do { \ + if( UNLIKELY( (expr) == 0 ) ) \ + { \ + PaUtil_DebugPrint(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \ + result = (code); \ + goto error; \ + } \ + } while (0); + +static PaError paUtilErr_; /* Used with PA_ENSURE */ + +/* Check PaError */ +#define PA_ENSURE(expr) \ + do { \ + if( UNLIKELY( (paUtilErr_ = (expr)) < paNoError ) ) \ + { \ + PaUtil_DebugPrint(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \ + result = paUtilErr_; \ + goto error; \ + } \ + } while (0); + +typedef struct { + pthread_t callbackThread; +} PaUtilThreading; + +PaError PaUtil_InitializeThreading( PaUtilThreading *threading ); +void PaUtil_TerminateThreading( PaUtilThreading *threading ); +PaError PaUtil_StartThreading( PaUtilThreading *threading, void *(*threadRoutine)(void *), void *data ); +PaError PaUtil_CancelThreading( PaUtilThreading *threading, int wait, PaError *exitResult ); + +/* State accessed by utility functions */ + +/* +void PaUnix_SetRealtimeScheduling( int rt ); + +void PaUtil_InitializeThreading( PaUtilThreading *th, PaUtilCpuLoadMeasurer *clm ); + +PaError PaUtil_CreateCallbackThread( PaUtilThreading *th, void *(*CallbackThreadFunc)( void * ), PaStream *s ); + +PaError PaUtil_KillCallbackThread( PaUtilThreading *th, PaError *exitResult ); + +void PaUtil_CallbackUpdate( PaUtilThreading *th ); +*/ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/src/audio/portaudio/pa_unix_oss/low_latency_tip.txt b/src/audio/portaudio/pa_unix_oss/low_latency_tip.txt new file mode 100644 index 0000000000000000000000000000000000000000..2d982b79baa6d943c1fae0c7122d190ebf46c6f4 Binary files /dev/null and b/src/audio/portaudio/pa_unix_oss/low_latency_tip.txt differ diff --git a/src/audio/portaudio/pa_unix_oss/pa_unix_oss.c b/src/audio/portaudio/pa_unix_oss/pa_unix_oss.c new file mode 100644 index 0000000000000000000000000000000000000000..ef87c83b03d4f2939b53f461dca3108ca74b6932 --- /dev/null +++ b/src/audio/portaudio/pa_unix_oss/pa_unix_oss.c @@ -0,0 +1,1918 @@ +/* + * $Id$ + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * OSS implementation by: + * Douglas Repetto + * Phil Burk + * Dominic Mazzoni + * Arve Knudsen + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, 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. + * + * 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. + * + * 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. + */ + +#include <stdio.h> +#include <string.h> +#include <math.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <pthread.h> +#include <alloca.h> +#include <malloc.h> +#include <assert.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/poll.h> +#include <limits.h> +#include <semaphore.h> + +#ifdef __linux__ +# include <linux/soundcard.h> +# define DEVICE_NAME_BASE "/dev/dsp" +#else +# include <machine/soundcard.h> /* JH20010905 */ +# define DEVICE_NAME_BASE "/dev/audio" +#endif + +#include "portaudio.h" +#include "pa_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" +#include "../pa_unix/pa_unix_util.h" + +static int sysErr_; +static pthread_t mainThread_; + +/* Check return value of system call, and map it to PaError */ +#define ENSURE_(expr, code) \ + do { \ + if( UNLIKELY( (sysErr_ = (expr)) < 0 ) ) \ + { \ + /* PaUtil_SetLastHostErrorInfo should only be used in the main thread */ \ + if( (code) == paUnanticipatedHostError && pthread_self() == mainThread_ ) \ + { \ + PaUtil_SetLastHostErrorInfo( paALSA, sysErr_, strerror( errno ) ); \ + } \ + \ + PaUtil_DebugPrint(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \ + result = (code); \ + goto error; \ + } \ + } while( 0 ); + +#ifndef AFMT_S16_NE +#define AFMT_S16_NE Get_AFMT_S16_NE() +/********************************************************************* + * Some versions of OSS do not define AFMT_S16_NE. So check CPU. + * PowerPC is Big Endian. X86 is Little Endian. + */ +static int Get_AFMT_S16_NE( void ) +{ + long testData = 1; + char *ptr = (char *) &testData; + int isLittle = ( *ptr == 1 ); /* Does address point to least significant byte? */ + return isLittle ? AFMT_S16_LE : AFMT_S16_BE; +} +#endif + +/* PaOSSHostApiRepresentation - host api datastructure specific to this implementation */ + +typedef struct +{ + PaUtilHostApiRepresentation inheritedHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup *allocations; + + PaHostApiIndex hostApiIndex; +} +PaOSSHostApiRepresentation; + +/** Per-direction structure for PaOssStream. + * + * Aspect StreamChannels: In case the user requests to open the same device for both capture and playback, + * but with different number of channels we will have to adapt between the number of user and host + * channels for at least one direction, since the configuration space is the same for both directions + * of an OSS device. + */ +typedef struct +{ + int fd; + const char *devName; + int userChannelCount, hostChannelCount; + int userInterleaved; + void *buffer; + PaSampleFormat userFormat, hostFormat; + double latency; + unsigned long hostFrames, numBufs; + void **userBuffers; /* For non-interleaved blocking */ +} PaOssStreamComponent; + +/** Implementation specific representation of a PaStream. + * + */ +typedef struct PaOssStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + + PaUtilThreading threading; + + int sharedDevice; + unsigned long framesPerHostBuffer; + int triggered; /* Have the devices been triggered yet (first start) */ + + int isActive; + int isStopped; + + int lastPosPtr; + double lastStreamBytes; + + int framesProcessed; + + double sampleRate; + + int callbackMode; + int callbackStop, callbackAbort; + + PaOssStreamComponent *capture, *playback; + unsigned long pollTimeout; + sem_t semaphore; +} +PaOssStream; + +typedef enum { + StreamMode_In, + StreamMode_Out +} StreamMode; + +/* prototypes for functions declared in this file */ + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); +static PaError CloseStream( PaStream* stream ); +static PaError StartStream( PaStream *stream ); +static PaError StopStream( PaStream *stream ); +static PaError AbortStream( PaStream *stream ); +static PaError IsStreamStopped( PaStream *s ); +static PaError IsStreamActive( PaStream *stream ); +static PaTime GetStreamTime( PaStream *stream ); +static double GetStreamCpuLoad( PaStream* stream ); +static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames ); +static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames ); +static signed long GetStreamReadAvailable( PaStream* stream ); +static signed long GetStreamWriteAvailable( PaStream* stream ); +static PaError BuildDeviceList( PaOSSHostApiRepresentation *hostApi ); + + +/** Initialize the OSS API implementation. + * + * This function will initialize host API datastructures and query host devices for information. + * + * Aspect DeviceCapabilities: Enumeration of host API devices is initiated from here + * + * Aspect FreeResources: If an error is encountered under way we have to free each resource allocated in this function, + * this happens with the usual "error" label. + */ +PaError PaOSS_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + PaOSSHostApiRepresentation *ossHostApi = NULL; + + PA_UNLESS( ossHostApi = (PaOSSHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaOSSHostApiRepresentation) ), + paInsufficientMemory ); + PA_UNLESS( ossHostApi->allocations = PaUtil_CreateAllocationGroup(), paInsufficientMemory ); + ossHostApi->hostApiIndex = hostApiIndex; + + /* Initialize host API structure */ + *hostApi = &ossHostApi->inheritedHostApiRep; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paOSS; + (*hostApi)->info.name = "OSS"; + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PA_ENSURE( BuildDeviceList( ossHostApi ) ); + + PaUtil_InitializeStreamInterface( &ossHostApi->callbackStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, + PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface( &ossHostApi->blockingStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable ); + + mainThread_ = pthread_self(); + + return result; + +error: + if( ossHostApi ) + { + if( ossHostApi->allocations ) + { + PaUtil_FreeAllAllocations( ossHostApi->allocations ); + PaUtil_DestroyAllocationGroup( ossHostApi->allocations ); + } + + PaUtil_FreeMemory( ossHostApi ); + } + return result; +} + +PaError PaUtil_InitializeDeviceInfo( PaDeviceInfo *deviceInfo, const char *name, PaHostApiIndex hostApiIndex, int maxInputChannels, + int maxOutputChannels, PaTime defaultLowInputLatency, PaTime defaultLowOutputLatency, PaTime defaultHighInputLatency, + PaTime defaultHighOutputLatency, double defaultSampleRate, PaUtilAllocationGroup *allocations ) +{ + PaError result = paNoError; + + deviceInfo->structVersion = 2; + if( allocations ) + { + size_t len = strlen( name ) + 1; + PA_UNLESS( deviceInfo->name = PaUtil_GroupAllocateMemory( allocations, len ), paInsufficientMemory ); + strncpy( (char *)deviceInfo->name, name, len ); + } + else + deviceInfo->name = name; + + deviceInfo->hostApi = hostApiIndex; + deviceInfo->maxInputChannels = maxInputChannels; + deviceInfo->maxOutputChannels = maxOutputChannels; + deviceInfo->defaultLowInputLatency = defaultLowInputLatency; + deviceInfo->defaultLowOutputLatency = defaultLowOutputLatency; + deviceInfo->defaultHighInputLatency = defaultHighInputLatency; + deviceInfo->defaultHighOutputLatency = defaultHighOutputLatency; + deviceInfo->defaultSampleRate = defaultSampleRate; + +error: + return result; +} + +static PaError QueryDirection( const char *deviceName, StreamMode mode, double *defaultSampleRate, int *maxChannelCount, + double *defaultLowLatency, double *defaultHighLatency ) +{ + PaError result = paNoError; + int numChannels, maxNumChannels; + int busy = 0; + int devHandle = -1; + int sr; + *maxChannelCount = 0; /* Default value in case this fails */ + + if ( (devHandle = open( deviceName, (mode == StreamMode_In ? O_RDONLY : O_WRONLY) | O_NONBLOCK )) < 0 ) + { + if( errno == EBUSY || errno == EAGAIN ) + { + PA_DEBUG(( "%s: Device %s busy\n", __FUNCTION__, deviceName )); + } + else + { + PA_DEBUG(( "%s: Can't access device: %s\n", __FUNCTION__, strerror( errno ) )); + } + + return paDeviceUnavailable; + } + + /* Negotiate for the maximum number of channels for this device. PLB20010927 + * Consider up to 16 as the upper number of channels. + * Variable maxNumChannels should contain the actual upper limit after the call. + * Thanks to John Lazzaro and Heiko Purnhagen for suggestions. + */ + maxNumChannels = 0; + for( numChannels = 1; numChannels <= 16; numChannels++ ) + { + int temp = numChannels; + if( ioctl( devHandle, SNDCTL_DSP_CHANNELS, &temp ) < 0 ) + { + busy = EAGAIN == errno || EBUSY == errno; + /* ioctl() failed so bail out if we already have stereo */ + if( maxNumChannels >= 2 ) + break; + } + else + { + /* ioctl() worked but bail out if it does not support numChannels. + * We don't want to leave gaps in the numChannels supported. + */ + if( (numChannels > 2) && (temp != numChannels) ) + break; + if( temp > maxNumChannels ) + maxNumChannels = temp; /* Save maximum. */ + } + } + /* A: We're able to open a device for capture if it's busy playing back and vice versa, + * but we can't configure anything */ + if( 0 == maxNumChannels && busy ) + { + result = paDeviceUnavailable; + goto error; + } + + /* The above negotiation may fail for an old driver so try this older technique. */ + if( maxNumChannels < 1 ) + { + int stereo = 1; + if( ioctl( devHandle, SNDCTL_DSP_STEREO, &stereo ) < 0 ) + { + maxNumChannels = 1; + } + else + { + maxNumChannels = (stereo) ? 2 : 1; + } + PA_DEBUG(( "%s: use SNDCTL_DSP_STEREO, maxNumChannels = %d\n", __FUNCTION__, maxNumChannels )) + } + + /* During channel negotiation, the last ioctl() may have failed. This can + * also cause sample rate negotiation to fail. Hence the following, to return + * to a supported number of channels. SG20011005 */ + { + /* use most reasonable default value */ + int temp = PA_MIN( maxNumChannels, 2 ); + ENSURE_( ioctl( devHandle, SNDCTL_DSP_CHANNELS, &temp ), paUnanticipatedHostError ); + } + + /* Get supported sample rate closest to 44100 Hz */ + if( *defaultSampleRate < 0 ) + { + sr = 44100; + if( ioctl( devHandle, SNDCTL_DSP_SPEED, &sr ) < 0 ) + { + result = paUnanticipatedHostError; + goto error; + } + + *defaultSampleRate = sr; + } + + *maxChannelCount = maxNumChannels; + /* TODO */ + *defaultLowLatency = 512. / *defaultSampleRate; + *defaultHighLatency = 2048. / *defaultSampleRate; + +error: + if( devHandle >= 0 ) + close( devHandle ); + + return result; +} + +/** Query OSS device. + * + * This is where PaDeviceInfo objects are constructed and filled in with relevant information. + * + * Aspect DeviceCapabilities: The inferred device capabilities are recorded in a PaDeviceInfo object that is constructed + * in place. + */ +static PaError QueryDevice( char *deviceName, PaOSSHostApiRepresentation *ossApi, PaDeviceInfo **deviceInfo ) +{ + PaError result = paNoError; + double sampleRate = -1.; + int maxInputChannels, maxOutputChannels; + PaTime defaultLowInputLatency, defaultLowOutputLatency, defaultHighInputLatency, defaultHighOutputLatency; + PaError tmpRes = paNoError; + int busy = 0; + *deviceInfo = NULL; + + /* douglas: + we have to do this querying in a slightly different order. apparently + some sound cards will give you different info based on their settins. + e.g. a card might give you stereo at 22kHz but only mono at 44kHz. + the correct order for OSS is: format, channels, sample rate + */ + + /* Aspect StreamChannels: The number of channels supported for a device may depend on the mode it is + * opened in, it may have more channels available for capture than playback and vice versa. Therefore + * we will open the device in both read- and write-only mode to determine the supported number. + */ + if( (tmpRes = QueryDirection( deviceName, StreamMode_In, &sampleRate, &maxInputChannels, &defaultLowInputLatency, + &defaultHighInputLatency )) != paNoError ) + { + if( tmpRes != paDeviceUnavailable ) + { + PA_DEBUG(( "%s: Querying device %s for capture failed!\n", __FUNCTION__, deviceName )); + /* PA_ENSURE( tmpRes ); */ + } + ++busy; + } + if( (tmpRes = QueryDirection( deviceName, StreamMode_Out, &sampleRate, &maxOutputChannels, &defaultLowOutputLatency, + &defaultHighOutputLatency )) != paNoError ) + { + if( tmpRes != paDeviceUnavailable ) + { + PA_DEBUG(( "%s: Querying device %s for playback failed!\n", __FUNCTION__, deviceName )); + /* PA_ENSURE( tmpRes ); */ + } + ++busy; + } + assert( 0 <= busy && busy <= 2 ); + if( 2 == busy ) /* Both directions are unavailable to us */ + { + result = paDeviceUnavailable; + goto error; + } + + PA_UNLESS( *deviceInfo = PaUtil_GroupAllocateMemory( ossApi->allocations, sizeof (PaDeviceInfo) ), paInsufficientMemory ); + PA_ENSURE( PaUtil_InitializeDeviceInfo( *deviceInfo, deviceName, ossApi->hostApiIndex, maxInputChannels, maxOutputChannels, + defaultLowInputLatency, defaultLowOutputLatency, defaultHighInputLatency, defaultHighOutputLatency, sampleRate, + ossApi->allocations ) ); + +error: + return result; +} + +/** Query host devices. + * + * Loop over host devices and query their capabilitiesu + * + * Aspect DeviceCapabilities: This function calls QueryDevice on each device entry and receives a filled in PaDeviceInfo object + * per device, these are placed in the host api representation's deviceInfos array. + */ +static PaError BuildDeviceList( PaOSSHostApiRepresentation *ossApi ) +{ + PaError result = paNoError; + PaUtilHostApiRepresentation *commonApi = &ossApi->inheritedHostApiRep; + int i; + int numDevices = 0, maxDeviceInfos = 1; + PaDeviceInfo **deviceInfos = NULL; + + /* These two will be set to the first working input and output device, respectively */ + commonApi->info.defaultInputDevice = paNoDevice; + commonApi->info.defaultOutputDevice = paNoDevice; + + /* 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? */ + + for( i = 0; i < 10; 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; + } + if( (testResult = QueryDevice( deviceName, ossApi, &deviceInfo )) != paNoError ) + { + if( testResult != paDeviceUnavailable ) + PA_ENSURE( testResult ); + + continue; + } + + ++numDevices; + if( !deviceInfos || numDevices > maxDeviceInfos ) + { + maxDeviceInfos *= 2; + PA_UNLESS( deviceInfos = (PaDeviceInfo **) realloc( deviceInfos, maxDeviceInfos * sizeof (PaDeviceInfo *) ), + paInsufficientMemory ); + } + deviceInfos[numDevices - 1] = deviceInfo; + + if( commonApi->info.defaultInputDevice == paNoDevice && deviceInfo->maxInputChannels > 0 ) + commonApi->info.defaultInputDevice = i; + if( commonApi->info.defaultOutputDevice == paNoDevice && deviceInfo->maxOutputChannels > 0 ) + commonApi->info.defaultOutputDevice = i; + } + + /* Make an array of PaDeviceInfo pointers out of the linked list */ + + PA_DEBUG(("PaOSS %s: Total number of devices found: %d\n", __FUNCTION__, numDevices)); + + commonApi->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + ossApi->allocations, sizeof(PaDeviceInfo*) * numDevices ); + memcpy( commonApi->deviceInfos, deviceInfos, numDevices * sizeof (PaDeviceInfo *) ); + + commonApi->info.deviceCount = numDevices; + +error: + free( deviceInfos ); + + return result; +} + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaOSSHostApiRepresentation *ossHostApi = (PaOSSHostApiRepresentation*)hostApi; + + if( ossHostApi->allocations ) + { + PaUtil_FreeAllAllocations( ossHostApi->allocations ); + PaUtil_DestroyAllocationGroup( ossHostApi->allocations ); + } + + PaUtil_FreeMemory( ossHostApi ); +} + +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + PaError result = paNoError; + PaDeviceIndex device; + PaDeviceInfo *deviceInfo; + char *deviceName; + int inputChannelCount, outputChannelCount; + int tempDevHandle = -1; + int flags; + PaSampleFormat inputSampleFormat, outputSampleFormat; + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + + /* validate inputStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + inputChannelCount = 0; + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that output device can support inputChannelCount */ + if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + + /* validate outputStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + outputChannelCount = 0; + } + + if (inputChannelCount == 0 && outputChannelCount == 0) + return paInvalidChannelCount; + + /* if full duplex, make sure that they're the same device */ + + if (inputChannelCount > 0 && outputChannelCount > 0 && + inputParameters->device != outputParameters->device) + return paInvalidDevice; + + /* if full duplex, also make sure that they're the same number of channels */ + + if (inputChannelCount > 0 && outputChannelCount > 0 && + inputChannelCount != outputChannelCount) + return paInvalidChannelCount; + + /* open the device so we can do more tests */ + + if( inputChannelCount > 0 ) + { + result = PaUtil_DeviceIndexToHostApiDeviceIndex(&device, inputParameters->device, hostApi); + if (result != paNoError) + return result; + } + else + { + result = PaUtil_DeviceIndexToHostApiDeviceIndex(&device, outputParameters->device, hostApi); + if (result != paNoError) + return result; + } + + deviceInfo = hostApi->deviceInfos[device]; + deviceName = (char *)deviceInfo->name; + + flags = O_NONBLOCK; + if (inputChannelCount > 0 && outputChannelCount > 0) + flags |= O_RDWR; + else if (inputChannelCount > 0) + flags |= O_RDONLY; + else + flags |= O_WRONLY; + + ENSURE_( tempDevHandle = open( deviceInfo->name, flags ), paDeviceUnavailable ); + + /* PaOssStream_Configure will do the rest of the checking for us */ + /* PA_ENSURE( PaOssStream_Configure( tempDevHandle, deviceName, outputChannelCount, &sampleRate ) ); */ + + /* everything succeeded! */ + + error: + if( tempDevHandle >= 0 ) + close( tempDevHandle ); + + return result; +} + +/** Validate stream parameters. + * + * Aspect StreamChannels: We verify that the number of channels is within the allowed range for the device + */ +static PaError ValidateParameters( const PaStreamParameters *parameters, const PaDeviceInfo *deviceInfo, StreamMode mode ) +{ + int maxChans; + + assert( parameters ); + + if( parameters->device == paUseHostApiSpecificDeviceSpecification ) + { + return paInvalidDevice; + } + + maxChans = (mode == StreamMode_In ? deviceInfo->maxInputChannels : + deviceInfo->maxOutputChannels); + if( parameters->channelCount > maxChans ) + { + return paInvalidChannelCount; + } + + return paNoError; +} + +static PaError PaOssStreamComponent_Initialize( PaOssStreamComponent *component, const PaStreamParameters *parameters, + int callbackMode, int fd, const char *deviceName ) +{ + PaError result = paNoError; + assert( component ); + + memset( component, 0, sizeof (PaOssStreamComponent) ); + + component->fd = fd; + component->devName = deviceName; + component->userChannelCount = parameters->channelCount; + component->userFormat = parameters->sampleFormat; + component->latency = parameters->suggestedLatency; + component->userInterleaved = !(parameters->sampleFormat & paNonInterleaved); + + if( !callbackMode && !component->userInterleaved ) + { + /* Pre-allocate non-interleaved user provided buffers */ + PA_UNLESS( component->userBuffers = PaUtil_AllocateMemory( sizeof (void *) * component->userChannelCount ), + paInsufficientMemory ); + } + +error: + return result; +} + +static void PaOssStreamComponent_Terminate( PaOssStreamComponent *component ) +{ + assert( component ); + + if( component->fd >= 0 ) + close( component->fd ); + if( component->buffer ) + PaUtil_FreeMemory( component->buffer ); + + if( component->userBuffers ) + PaUtil_FreeMemory( component->userBuffers ); + + PaUtil_FreeMemory( component ); +} + +static PaError ModifyBlocking( int fd, int blocking ) +{ + PaError result = paNoError; + int fflags; + + ENSURE_( fflags = fcntl( fd, F_GETFL ), paUnanticipatedHostError ); + + if( blocking ) + fflags &= ~O_NONBLOCK; + else + fflags |= O_NONBLOCK; + + ENSURE_( fcntl( fd, F_SETFL, fflags ), paUnanticipatedHostError ); + +error: + return result; +} + +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 ) + { + duplex = 1; + flags |= O_RDWR; + } + else if( idevName ) + flags |= O_RDONLY; + else + flags |= O_WRONLY; + + /* open first in nonblocking mode, in case it's busy... + * A: then unset the non-blocking attribute */ + assert( flags & O_NONBLOCK ); + if( idevName ) + { + 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 ) + { + if( !idevName ) + { + 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 + { + ENSURE_( *odev = dup( *idev ), paUnanticipatedHostError ); + } + } + +error: + return result; +} + +static PaError PaOssStream_Initialize( PaOssStream *stream, const PaStreamParameters *inputParameters, const PaStreamParameters *outputParameters, + PaStreamCallback callback, void *userData, PaStreamFlags streamFlags, + PaOSSHostApiRepresentation *ossApi ) +{ + PaError result = paNoError; + int idev, odev; + PaUtilHostApiRepresentation *hostApi = &ossApi->inheritedHostApiRep; + const char *idevName = NULL, *odevName = NULL; + + assert( stream ); + + memset( stream, 0, sizeof (PaOssStream) ); + stream->isStopped = 1; + + PA_ENSURE( PaUtil_InitializeThreading( &stream->threading ) ); + + if( inputParameters && outputParameters ) + { + if( inputParameters->device == outputParameters->device ) + stream->sharedDevice = 1; + } + + if( inputParameters ) + idevName = hostApi->deviceInfos[inputParameters->device]->name; + if( outputParameters ) + odevName = hostApi->deviceInfos[outputParameters->device]->name; + PA_ENSURE( OpenDevices( idevName, odevName, &idev, &odev ) ); + if( inputParameters ) + { + PA_UNLESS( stream->capture = PaUtil_AllocateMemory( sizeof (PaOssStreamComponent) ), paInsufficientMemory ); + PA_ENSURE( PaOssStreamComponent_Initialize( stream->capture, inputParameters, callback != NULL, idev, idevName ) ); + } + if( outputParameters ) + { + PA_UNLESS( stream->playback = PaUtil_AllocateMemory( sizeof (PaOssStreamComponent) ), paInsufficientMemory ); + PA_ENSURE( PaOssStreamComponent_Initialize( stream->playback, outputParameters, callback != NULL, odev, odevName ) ); + } + + if( callback != NULL ) + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &ossApi->callbackStreamInterface, callback, userData ); + stream->callbackMode = 1; + } + else + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &ossApi->blockingStreamInterface, callback, userData ); + } + + ENSURE_( sem_init( &stream->semaphore, 0, 0 ), paInternalError ); + +error: + return result; +} + +static void PaOssStream_Terminate( PaOssStream *stream ) +{ + assert( stream ); + + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + PaUtil_TerminateThreading( &stream->threading ); + + if( stream->capture ) + PaOssStreamComponent_Terminate( stream->capture ); + if( stream->playback ) + PaOssStreamComponent_Terminate( stream->playback ); + + sem_destroy( &stream->semaphore ); + + PaUtil_FreeMemory( stream ); +} + +/** Translate from PA format to OSS native. + * + */ +static PaError Pa2OssFormat( PaSampleFormat paFormat, int *ossFormat ) +{ + switch( paFormat ) + { + case paUInt8: + *ossFormat = AFMT_U8; + break; + case paInt8: + *ossFormat = AFMT_S8; + break; + case paInt16: + *ossFormat = AFMT_S16_NE; + break; + default: + return paInternalError; /* This shouldn't happen */ + } + + return paNoError; +} + +/** Return the PA-compatible formats that this device can support. + * + */ +static PaError GetAvailableFormats( PaOssStreamComponent *component, PaSampleFormat *availableFormats ) +{ + PaError result = paNoError; + int mask = 0; + PaSampleFormat frmts = 0; + + ENSURE_( ioctl( component->fd, SNDCTL_DSP_GETFMTS, &mask ), paUnanticipatedHostError ); + if( mask & AFMT_U8 ) + frmts |= paUInt8; + if( mask & AFMT_S8 ) + frmts |= paInt8; + if( mask & AFMT_S16_NE ) + frmts |= paInt16; + else + result = paSampleFormatNotSupported; + + *availableFormats = frmts; + +error: + return result; +} + +static unsigned int PaOssStreamComponent_FrameSize( PaOssStreamComponent *component ) +{ + return Pa_GetSampleSize( component->hostFormat ) * component->hostChannelCount; +} + +/** Buffer size in bytes. + * + */ +static unsigned long PaOssStreamComponent_BufferSize( PaOssStreamComponent *component ) +{ + return PaOssStreamComponent_FrameSize( component ) * component->hostFrames * component->numBufs; +} + +static int CalcHigherLogTwo( int n ) +{ + int log2 = 0; + while( (1<<log2) < n ) log2++; + return log2; +} + +static PaError PaOssStreamComponent_Configure( PaOssStreamComponent *component, double sampleRate, unsigned long framesPerBuffer, + StreamMode streamMode, PaOssStreamComponent *master ) +{ + PaError result = paNoError; + int temp, nativeFormat; + int sr = (int)sampleRate; + PaSampleFormat availableFormats, hostFormat; + int chans = component->userChannelCount; + int frgmt; + int numBufs; + int bytesPerBuf; + double bufSz; + unsigned long fragSz; + audio_buf_info bufInfo; + + /* We may have a situation where only one component (the master) is configured, if both point to the same device. + * In that case, the second component will copy settings from the other */ + if( !master ) + { + /* Aspect BufferSettings: If framesPerBuffer is unspecified we have to infer a suitable fragment size. + * The hardware need not respect the requested fragment size, so we may have to adapt. + */ + if( framesPerBuffer == paFramesPerBufferUnspecified ) + { + bufSz = component->latency * sampleRate; + fragSz = bufSz / 4; + } + else + { + fragSz = framesPerBuffer; + bufSz = component->latency * sampleRate + fragSz; /* Latency + 1 buffer */ + } + + PA_ENSURE( GetAvailableFormats( component, &availableFormats ) ); + hostFormat = PaUtil_SelectClosestAvailableFormat( availableFormats, component->userFormat ); + + /* OSS demands at least 2 buffers, and 16 bytes per buffer */ + numBufs = PA_MAX( bufSz / fragSz, 2 ); + bytesPerBuf = PA_MAX( fragSz * Pa_GetSampleSize( hostFormat ) * chans, 16 ); + + /* The fragment parameters are encoded like this: + * Most significant byte: number of fragments + * Least significant byte: exponent of fragment size (i.e., for 256, 8) + */ + frgmt = (numBufs << 16) + (CalcHigherLogTwo( bytesPerBuf ) & 0xffff); + ENSURE_( ioctl( component->fd, SNDCTL_DSP_SETFRAGMENT, &frgmt ), paUnanticipatedHostError ); + + /* A: according to the OSS programmer's guide parameters should be set in this order: + * format, channels, rate */ + + /* This format should be deemed good before we get this far */ + PA_ENSURE( Pa2OssFormat( hostFormat, &temp ) ); + nativeFormat = temp; + ENSURE_( ioctl( component->fd, SNDCTL_DSP_SETFMT, &temp ), paUnanticipatedHostError ); + PA_UNLESS( temp == nativeFormat, paInternalError ); + + /* try to set the number of channels */ + ENSURE_( ioctl( component->fd, SNDCTL_DSP_CHANNELS, &chans ), paSampleFormatNotSupported ); /* XXX: Should be paInvalidChannelCount? */ + /* It's possible that the minimum number of host channels is greater than what the user requested */ + PA_UNLESS( chans >= component->userChannelCount, paInvalidChannelCount ); + + /* try to set the sample rate */ + ENSURE_( ioctl( component->fd, SNDCTL_DSP_SPEED, &sr ), paInvalidSampleRate ); + + /* reject if there's no sample rate within 1% of the one requested */ + if( (fabs( sampleRate - sr ) / sampleRate) > 0.01 ) + { + PA_DEBUG(("%s: Wanted %f, closest sample rate was %d\n", __FUNCTION__, sampleRate, sr )); + PA_ENSURE( paInvalidSampleRate ); + } + + ENSURE_( ioctl( component->fd, streamMode == StreamMode_In ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE, &bufInfo ), + paUnanticipatedHostError ); + component->numBufs = bufInfo.fragstotal; + + /* This needs to be the last ioctl call before the first read/write, according to the OSS programmer's guide */ + ENSURE_( ioctl( component->fd, SNDCTL_DSP_GETBLKSIZE, &bytesPerBuf ), paUnanticipatedHostError ); + + component->hostFrames = bytesPerBuf / Pa_GetSampleSize( hostFormat ) / chans; + component->hostChannelCount = chans; + component->hostFormat = hostFormat; + } + else + { + component->hostFormat = master->hostFormat; + component->hostFrames = master->hostFrames; + component->hostChannelCount = master->hostChannelCount; + component->numBufs = master->numBufs; + } + + PA_UNLESS( component->buffer = PaUtil_AllocateMemory( PaOssStreamComponent_BufferSize( component ) ), + paInsufficientMemory ); + +error: + return result; +} + +static PaError PaOssStreamComponent_Read( PaOssStreamComponent *component, unsigned long *frames ) +{ + PaError result = paNoError; + size_t len = *frames * PaOssStreamComponent_FrameSize( component ); + ssize_t bytesRead; + + ENSURE_( bytesRead = read( component->fd, component->buffer, len ), paUnanticipatedHostError ); + *frames = bytesRead / PaOssStreamComponent_FrameSize( component ); + +error: + return result; +} + +static PaError PaOssStreamComponent_Write( PaOssStreamComponent *component, unsigned long *frames ) +{ + PaError result = paNoError; + size_t len = *frames * PaOssStreamComponent_FrameSize( component ); + ssize_t bytesWritten; + + ENSURE_( bytesWritten = write( component->fd, component->buffer, len ), paUnanticipatedHostError ); + *frames = bytesWritten / PaOssStreamComponent_FrameSize( component ); + +error: + return result; +} + +/** Configure the stream according to input/output parameters. + * + * Aspect StreamChannels: The minimum number of channels supported by the device may exceed that requested by + * the user, if so we'll record the actual number of host channels and adapt later. + */ +static PaError PaOssStream_Configure( PaOssStream *stream, double sampleRate, unsigned long framesPerBuffer, + double *inputLatency, double *outputLatency ) +{ + PaError result = paNoError; + int duplex = stream->capture && stream->playback; + unsigned long framesPerHostBuffer = 0; + + /* We should request full duplex first thing after opening the device */ + if( duplex && stream->sharedDevice ) + ENSURE_( ioctl( stream->capture->fd, SNDCTL_DSP_SETDUPLEX, 0 ), paUnanticipatedHostError ); + + if( stream->capture ) + { + PaOssStreamComponent *component = stream->capture; + PaOssStreamComponent_Configure( component, sampleRate, framesPerBuffer, StreamMode_In, NULL ); + + assert( component->hostChannelCount > 0 ); + assert( component->hostFrames > 0 ); + + *inputLatency = component->hostFrames * (component->numBufs - 1) / sampleRate; + } + if( stream->playback ) + { + PaOssStreamComponent *component = stream->playback, *master = stream->sharedDevice ? stream->capture : NULL; + PA_ENSURE( PaOssStreamComponent_Configure( component, sampleRate, framesPerBuffer, StreamMode_Out, + master ) ); + + assert( component->hostChannelCount > 0 ); + assert( component->hostFrames > 0 ); + + *outputLatency = component->hostFrames * (component->numBufs - 1) / sampleRate; + } + + if( duplex ) + framesPerHostBuffer = PA_MIN( stream->capture->hostFrames, stream->playback->hostFrames ); + else if( stream->capture ) + framesPerHostBuffer = stream->capture->hostFrames; + else if( stream->playback ) + framesPerHostBuffer = stream->playback->hostFrames; + + stream->framesPerHostBuffer = framesPerHostBuffer; + stream->pollTimeout = (int) ceil( 1e6 * framesPerHostBuffer / sampleRate ); /* Period in usecs, rounded up */ + + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + +error: + return result; +} + +/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */ + +/** Open a PA OSS stream. + * + * Aspect StreamChannels: The number of channels is specified per direction (in/out), and can differ between the + * two. However, OSS doesn't support separate configuration spaces for capture and playback so if both + * directions are the same device we will demand the same number of channels. The number of channels can range + * from 1 to the maximum supported by the device. + * + * Aspect BufferSettings: If framesPerBuffer != paFramesPerBufferUnspecified the number of frames per callback + * must reflect this, in addition the host latency per device should approximate the corresponding + * suggestedLatency. Based on these constraints we need to determine a number of frames per host buffer that + * both capture and playback can agree on (they can be different devices), the buffer processor can adapt + * between host and user buffer size, but the ratio should preferably be integral. + */ +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ) +{ + PaError result = paNoError; + PaOSSHostApiRepresentation *ossHostApi = (PaOSSHostApiRepresentation*)hostApi; + PaOssStream *stream = NULL; + int inputChannelCount = 0, outputChannelCount = 0; + PaSampleFormat inputSampleFormat = 0, outputSampleFormat = 0, inputHostFormat = 0, outputHostFormat = 0; + const PaDeviceInfo *inputDeviceInfo = 0, *outputDeviceInfo = 0; + int bpInitialized = 0; + double inLatency, outLatency; + + /* validate platform specific flags */ + if( (streamFlags & paPlatformSpecificFlags) != 0 ) + return paInvalidFlag; /* unexpected platform specific flag */ + + if( inputParameters ) + { + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + inputDeviceInfo = hostApi->deviceInfos[inputParameters->device]; + PA_ENSURE( ValidateParameters( inputParameters, inputDeviceInfo, StreamMode_In ) ); + + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + } + if( outputParameters ) + { + outputDeviceInfo = hostApi->deviceInfos[outputParameters->device]; + PA_ENSURE( ValidateParameters( outputParameters, outputDeviceInfo, StreamMode_Out ) ); + + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + } + + /* Aspect StreamChannels: We currently demand that number of input and output channels are the same, if the same + * device is opened for both directions + */ + if( inputChannelCount > 0 && outputChannelCount > 0 ) + { + if( inputParameters->device == outputParameters->device ) + { + if( inputParameters->channelCount != outputParameters->channelCount ) + return paInvalidChannelCount; + } + } + + /* allocate and do basic initialization of the stream structure */ + PA_UNLESS( stream = (PaOssStream*)PaUtil_AllocateMemory( sizeof(PaOssStream) ), paInsufficientMemory ); + PaOssStream_Initialize( stream, inputParameters, outputParameters, streamCallback, userData, streamFlags, ossHostApi ); + + PA_ENSURE( PaOssStream_Configure( stream, sampleRate, framesPerBuffer, &inLatency, &outLatency ) ); + + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); + + if( inputParameters ) + { + inputHostFormat = stream->capture->hostFormat; + stream->streamRepresentation.streamInfo.inputLatency = inLatency + + PaUtil_GetBufferProcessorInputLatency( &stream->bufferProcessor ) / sampleRate; + } + if( outputParameters ) + { + outputHostFormat = stream->playback->hostFormat; + stream->streamRepresentation.streamInfo.outputLatency = outLatency + + PaUtil_GetBufferProcessorOutputLatency( &stream->bufferProcessor ) / sampleRate; + } + + /* Initialize buffer processor with fixed host buffer size. + * Aspect StreamSampleFormat: Here we commit the user and host sample formats, PA infrastructure will + * convert between the two. + */ + PA_ENSURE( PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, + inputChannelCount, inputSampleFormat, inputHostFormat, outputChannelCount, outputSampleFormat, + outputHostFormat, sampleRate, streamFlags, framesPerBuffer, stream->framesPerHostBuffer, + paUtilFixedHostBufferSize, streamCallback, userData ) ); + bpInitialized = 1; + + *s = (PaStream*)stream; + + return result; + +error: + if( bpInitialized ) + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + if( stream ) + PaOssStream_Terminate( stream ); + + return result; +} + +/*! Poll on I/O filedescriptors. + + Poll till we've determined there's data for read or write. In the full-duplex case, + we don't want to hang around forever waiting for either input or output frames, so + whenever we have a timed out filedescriptor we check if we're nearing under/overrun + for the other direction (critical limit set at one buffer). If so, we exit the waiting + state, and go on with what we got. We align the number of frames on a host buffer + boundary because it is possible that the buffer size differs for the two directions and + the host buffer size is a compromise between the two. + */ +static PaError PaOssStream_WaitForFrames( PaOssStream *stream, unsigned long *frames ) +{ + PaError result = paNoError; + int pollPlayback = 0, pollCapture = 0; + int captureAvail = INT_MAX, playbackAvail = INT_MAX, commonAvail; + audio_buf_info bufInfo; + /* int ofs = 0, nfds = stream->nfds; */ + fd_set readFds, writeFds; + int nfds = 0; + struct timeval selectTimeval = {0, 0}; + unsigned long timeout = stream->pollTimeout; /* In usecs */ + int captureFd = -1, playbackFd = -1; + + assert( stream ); + assert( frames ); + + if( stream->capture ) + { + pollCapture = 1; + captureFd = stream->capture->fd; + /* stream->capture->pfd->events = POLLIN; */ + } + if( stream->playback ) + { + pollPlayback = 1; + playbackFd = stream->playback->fd; + /* stream->playback->pfd->events = POLLOUT; */ + } + + FD_ZERO( &readFds ); + FD_ZERO( &writeFds ); + + while( pollPlayback || pollCapture ) + { + pthread_testcancel(); + + /* select may modify the timeout parameter */ + selectTimeval.tv_usec = timeout; + nfds = 0; + + if( pollCapture ) + { + FD_SET( captureFd, &readFds ); + nfds = captureFd + 1; + } + if( pollPlayback ) + { + FD_SET( playbackFd, &writeFds ); + nfds = PA_MAX( nfds, playbackFd + 1 ); + } + ENSURE_( select( nfds, &readFds, &writeFds, NULL, &selectTimeval ), paUnanticipatedHostError ); + /* + if( poll( stream->pfds + ofs, nfds, stream->pollTimeout ) < 0 ) + { + + ENSURE_( -1, paUnanticipatedHostError ); + } + */ + pthread_testcancel(); + + if( pollCapture ) + { + if( FD_ISSET( captureFd, &readFds ) ) + { + FD_CLR( captureFd, &readFds ); + pollCapture = 0; + } + /* + if( stream->capture->pfd->revents & POLLIN ) + { + --nfds; + ++ofs; + pollCapture = 0; + } + */ + else if( stream->playback ) /* Timed out, go on with playback? */ + { + /*PA_DEBUG(( "%s: Trying to poll again for capture frames, pollTimeout: %d\n", + __FUNCTION__, stream->pollTimeout ));*/ + } + } + if( pollPlayback ) + { + if( FD_ISSET( playbackFd, &writeFds ) ) + { + FD_CLR( playbackFd, &writeFds ); + pollPlayback = 0; + } + /* + if( stream->playback->pfd->revents & POLLOUT ) + { + --nfds; + pollPlayback = 0; + } + */ + else if( stream->capture ) /* Timed out, go on with capture? */ + { + /*PA_DEBUG(( "%s: Trying to poll again for playback frames, pollTimeout: %d\n\n", + __FUNCTION__, stream->pollTimeout ));*/ + } + } + } + + if( stream->capture ) + { + ENSURE_( ioctl( captureFd, SNDCTL_DSP_GETISPACE, &bufInfo ), paUnanticipatedHostError ); + captureAvail = bufInfo.fragments * stream->capture->hostFrames; + if( !captureAvail ) + PA_DEBUG(( "%s: captureAvail: 0\n", __FUNCTION__ )); + + captureAvail = captureAvail == 0 ? INT_MAX : captureAvail; /* Disregard if zero */ + } + if( stream->playback ) + { + ENSURE_( ioctl( playbackFd, SNDCTL_DSP_GETOSPACE, &bufInfo ), paUnanticipatedHostError ); + playbackAvail = bufInfo.fragments * stream->playback->hostFrames; + if( !playbackAvail ) + { + PA_DEBUG(( "%s: playbackAvail: 0\n", __FUNCTION__ )); + } + + playbackAvail = playbackAvail == 0 ? INT_MAX : playbackAvail; /* Disregard if zero */ + } + + commonAvail = PA_MIN( captureAvail, playbackAvail ); + if( commonAvail == INT_MAX ) + commonAvail = 0; + commonAvail -= commonAvail % stream->framesPerHostBuffer; + + assert( commonAvail != INT_MAX ); + assert( commonAvail >= 0 ); + *frames = commonAvail; + +error: + return result; +} + +/** Prepare stream for capture/playback. + * + * In order to synchronize capture and playback properly we use the SETTRIGGER command. + */ +static PaError PaOssStream_Prepare( PaOssStream *stream ) +{ + PaError result = paNoError; + int enableBits = 0; + + if( stream->triggered ) + return result; + + if( stream->playback ) + { + size_t bufSz = PaOssStreamComponent_BufferSize( stream->playback ); + memset( stream->playback->buffer, 0, bufSz ); + + /* Looks like we have to turn off blocking before we try this, but if we don't fill the buffer + * OSS will complain. */ + PA_ENSURE( ModifyBlocking( stream->playback->fd, 0 ) ); + while (1) + { + if( write( stream->playback->fd, stream->playback->buffer, bufSz ) < 0 ) + break; + } + PA_ENSURE( ModifyBlocking( stream->playback->fd, 1 ) ); + } + + if( stream->sharedDevice ) + { + enableBits = PCM_ENABLE_INPUT | PCM_ENABLE_OUTPUT; + ENSURE_( ioctl( stream->capture->fd, SNDCTL_DSP_SETTRIGGER, &enableBits ), paUnanticipatedHostError ); + } + else + { + if( stream->capture ) + { + enableBits = PCM_ENABLE_INPUT; + ENSURE_( ioctl( stream->capture->fd, SNDCTL_DSP_SETTRIGGER, &enableBits ), paUnanticipatedHostError ); + } + if( stream->playback ) + { + enableBits = PCM_ENABLE_OUTPUT; + ENSURE_( ioctl( stream->playback->fd, SNDCTL_DSP_SETTRIGGER, &enableBits ), paUnanticipatedHostError ); + } + } + + /* Ok, we have triggered the stream */ + stream->triggered = 1; + +error: + return result; +} + +/** Stop audio processing + * + */ +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 */ + if( stream->capture ) + { + ENSURE_( ioctl( stream->capture->fd, SNDCTL_DSP_POST, 0 ), paUnanticipatedHostError ); + } + if( stream->playback && !stream->sharedDevice ) + { + ENSURE_( ioctl( stream->playback->fd, SNDCTL_DSP_POST, 0 ), paUnanticipatedHostError ); + } + +error: + return result; +} + +/** Clean up after thread exit. + * + * Aspect StreamState: If the user has registered a streamFinishedCallback it will be called here + */ +static void OnExit( void *data ) +{ + PaOssStream *stream = (PaOssStream *) data; + assert( data ); + + PaUtil_ResetCpuLoadMeasurer( &stream->cpuLoadMeasurer ); + + PaOssStream_Stop( stream, stream->callbackAbort ); + + PA_DEBUG(( "OnExit: Stoppage\n" )); + + /* Eventually notify user all buffers have played */ + if( stream->streamRepresentation.streamFinishedCallback ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + + stream->callbackAbort = 0; /* Clear state */ + stream->isActive = 0; +} + +static PaError SetUpBuffers( PaOssStream *stream, unsigned long framesAvail ) +{ + PaError result = paNoError; + + if( stream->capture ) + { + PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, 0, stream->capture->buffer, + stream->capture->hostChannelCount ); + PaUtil_SetInputFrameCount( &stream->bufferProcessor, framesAvail ); + } + if( stream->playback ) + { + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, 0, stream->playback->buffer, + stream->playback->hostChannelCount ); + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, framesAvail ); + } + + return result; +} + +/** Thread procedure for callback processing. + * + * Aspect StreamState: StartStream will wait on this to initiate audio processing, useful in case the + * callback should be used for buffer priming. When the stream is cancelled a separate function will + * take care of the transition to the Callback Finished state (the stream isn't considered Stopped + * before StopStream() or AbortStream() are called). + */ +static void *PaOSS_AudioThreadProc( void *userData ) +{ + PaError result = paNoError; + PaOssStream *stream = (PaOssStream*)userData; + unsigned long framesAvail, framesProcessed; + int callbackResult = paContinue; + int triggered = stream->triggered; /* See if SNDCTL_DSP_TRIGGER has been issued already */ + int initiateProcessing = triggered; /* Already triggered? */ + PaStreamCallbackFlags cbFlags = 0; /* We might want to keep state across iterations */ + + /* +#if ( SOUND_VERSION > 0x030904 ) + audio_errinfo errinfo; +#endif +*/ + + assert( stream ); + + pthread_cleanup_push( &OnExit, stream ); /* Execute OnExit when exiting */ + + /* The first time the stream is started we use SNDCTL_DSP_TRIGGER to accurately start capture and + * playback in sync, when the stream is restarted after being stopped we simply start by reading/ + * writing. + */ + PA_ENSURE( PaOssStream_Prepare( stream ) ); + + /* If we are to initiate processing implicitly by reading/writing data, we start off in blocking mode */ + if( initiateProcessing ) + { + /* Make sure devices are in blocking mode */ + if( stream->capture ) + ModifyBlocking( stream->capture->fd, 1 ); + if( stream->playback ) + ModifyBlocking( stream->playback->fd, 1 ); + } + + while( 1 ) + { + PaStreamCallbackTimeInfo timeInfo = {0,0,0}; /* TODO: IMPLEMENT ME */ + + pthread_testcancel(); + + if( stream->callbackStop && callbackResult == paContinue ) + { + PA_DEBUG(( "Setting callbackResult to paComplete\n" )); + callbackResult = paComplete; + } + + /* Aspect StreamState: Because of the messy OSS scheme we can't explicitly trigger device start unless + * the stream has been recently started, we will have to go right ahead and read/write in blocking + * fashion to trigger operation. Therefore we begin with processing one host buffer before we switch + * to non-blocking mode. + */ + if( !initiateProcessing ) + { + PA_ENSURE( PaOssStream_WaitForFrames( stream, &framesAvail ) ); /* Wait on available frames */ + assert( framesAvail % stream->framesPerHostBuffer == 0 ); + } + else + { + framesAvail = stream->framesPerHostBuffer; + } + + while( framesAvail > 0 ) + { + unsigned long frames = framesAvail; + + pthread_testcancel(); + + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + + /* Read data */ + if ( stream->capture ) + { + PA_ENSURE( PaOssStreamComponent_Read( stream->capture, &frames ) ); + assert( frames == framesAvail ); + } + +#if ( SOUND_VERSION >= 0x030904 ) + /* + Check with OSS to see if there have been any under/overruns + since last time we checked. + */ + /* + if( ioctl( stream->deviceHandle, SNDCTL_DSP_GETERROR, &errinfo ) >= 0 ) + { + if( errinfo.play_underruns ) + cbFlags |= paOutputUnderflow ; + if( errinfo.record_underruns ) + cbFlags |= paInputUnderflow ; + } + else + PA_DEBUG(( "SNDCTL_DSP_GETERROR command failed: %s\n", strerror( errno ) )); + */ +#endif + + PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, + cbFlags ); + cbFlags = 0; + PA_ENSURE( SetUpBuffers( stream, framesAvail ) ); + + framesProcessed = PaUtil_EndBufferProcessing( &stream->bufferProcessor, + &callbackResult ); + assert( framesProcessed == framesAvail ); + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed ); + + if ( stream->playback ) + { + frames = framesAvail; + + PA_ENSURE( PaOssStreamComponent_Write( stream->playback, &frames ) ); + assert( frames == framesAvail ); + + /* TODO: handle bytesWritten != bytesRequested (slippage?) */ + } + + framesAvail -= framesProcessed; + stream->framesProcessed += framesProcessed; + + if( callbackResult != paContinue ) + break; + } + + if( initiateProcessing || !triggered ) + { + /* Non-blocking */ + if( stream->capture ) + PA_ENSURE( ModifyBlocking( stream->capture->fd, 0 ) ); + if( stream->playback && !stream->sharedDevice ) + PA_ENSURE( ModifyBlocking( stream->playback->fd, 0 ) ); + + initiateProcessing = 0; + sem_post( &stream->semaphore ); + } + + if( callbackResult != paContinue ) + { + stream->callbackAbort = callbackResult == paAbort; + if( stream->callbackAbort || PaUtil_IsBufferProcessorOutputEmpty( &stream->bufferProcessor ) ) + break; + } + } + + pthread_cleanup_pop( 1 ); + +error: + pthread_exit( NULL ); +} + +/** Close the stream. + * + */ +static PaError CloseStream( PaStream* s ) +{ + PaError result = paNoError; + PaOssStream *stream = (PaOssStream*)s; + + assert( stream ); + + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + PaOssStream_Terminate( stream ); + + return result; +} + +/** Start the stream. + * + * Aspect StreamState: After returning, the stream shall be in the Active state, implying that an eventual + * callback will be repeatedly called in a separate thread. If a separate thread is started this function + * will block untill it has started processing audio, otherwise audio processing is started directly. + */ +static PaError StartStream( PaStream *s ) +{ + PaError result = paNoError; + PaOssStream *stream = (PaOssStream*)s; + + stream->isActive = 1; + stream->isStopped = 0; + stream->lastPosPtr = 0; + stream->lastStreamBytes = 0; + stream->framesProcessed = 0; + + /* only use the thread for callback streams */ + if( stream->bufferProcessor.streamCallback ) + { + PA_ENSURE( PaUtil_StartThreading( &stream->threading, &PaOSS_AudioThreadProc, stream ) ); + sem_wait( &stream->semaphore ); + } + else + PA_ENSURE( PaOssStream_Prepare( stream ) ); + +error: + return result; +} + +static PaError RealStop( PaOssStream *stream, int abort ) +{ + PaError result = paNoError; + + if( stream->callbackMode ) + { + if( abort ) + stream->callbackAbort = 1; + else + stream->callbackStop = 1; + + PA_ENSURE( PaUtil_CancelThreading( &stream->threading, !abort, NULL ) ); + + stream->callbackStop = stream->callbackAbort = 0; + } + else + PA_ENSURE( PaOssStream_Stop( stream, abort ) ); + + stream->isStopped = 1; + +error: + return result; +} + +/** Stop the stream. + * + * Aspect StreamState: This will cause the stream to transition to the Stopped state, playing all enqueued + * buffers. + */ +static PaError StopStream( PaStream *s ) +{ + return RealStop( (PaOssStream *)s, 0 ); +} + +/** Abort the stream. + * + * Aspect StreamState: This will cause the stream to transition to the Stopped state, discarding all enqueued + * buffers. Note that the buffers are not currently correctly discarded, this is difficult without closing + * the OSS device. + */ +static PaError AbortStream( PaStream *s ) +{ + return RealStop( (PaOssStream *)s, 1 ); +} + +/** Is the stream in the Stopped state. + * + */ +static PaError IsStreamStopped( PaStream *s ) +{ + PaOssStream *stream = (PaOssStream*)s; + + return (stream->isStopped); +} + +/** Is the stream in the Active state. + * + */ +static PaError IsStreamActive( PaStream *s ) +{ + PaOssStream *stream = (PaOssStream*)s; + + return (stream->isActive); +} + +static PaTime GetStreamTime( PaStream *s ) +{ + PaOssStream *stream = (PaOssStream*)s; + count_info info; + int delta; + + if( stream->playback ) { + if( ioctl( stream->playback->fd, SNDCTL_DSP_GETOPTR, &info) == 0 ) { + delta = ( info.bytes - stream->lastPosPtr ) & 0x000FFFFF; + return ( stream->lastStreamBytes + delta) / PaOssStreamComponent_FrameSize( stream->playback ) / stream->sampleRate; + } + } + else { + if (ioctl( stream->capture->fd, SNDCTL_DSP_GETIPTR, &info) == 0) { + delta = (info.bytes - stream->lastPosPtr) & 0x000FFFFF; + return ( stream->lastStreamBytes + delta) / PaOssStreamComponent_FrameSize( stream->capture ) / stream->sampleRate; + } + } + + /* the ioctl failed, but we can still give a coarse estimate */ + + return stream->framesProcessed / stream->sampleRate; +} + + +static double GetStreamCpuLoad( PaStream* s ) +{ + PaOssStream *stream = (PaOssStream*)s; + + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); +} + + +/* + As separate stream interfaces are used for blocking and callback + streams, the following functions can be guaranteed to only be called + for blocking streams. +*/ + + +static PaError ReadStream( PaStream* s, + void *buffer, + unsigned long frames ) +{ + PaOssStream *stream = (PaOssStream*)s; + int bytesRequested, bytesRead; + unsigned long framesRequested; + void *userBuffer; + + /* If user input is non-interleaved, PaUtil_CopyInput will manipulate the channel pointers, + * so we copy the user provided pointers */ + if( stream->bufferProcessor.userInputIsInterleaved ) + userBuffer = buffer; + else /* Copy channels into local array */ + { + userBuffer = stream->capture->userBuffers; + memcpy( (void *)userBuffer, buffer, sizeof (void *) * stream->capture->userChannelCount ); + } + + while( frames ) + { + framesRequested = PA_MIN( frames, stream->capture->hostFrames ); + + bytesRequested = framesRequested * PaOssStreamComponent_FrameSize( stream->capture ); + bytesRead = read( stream->capture->fd, stream->capture->buffer, bytesRequested ); + if ( 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; +} + + +static PaError WriteStream( PaStream* s, + const void *buffer, + unsigned long frames ) +{ + PaOssStream *stream = (PaOssStream*)s; + int bytesRequested, bytesWritten; + unsigned long framesConverted; + const void *userBuffer; + + /* If user output is non-interleaved, PaUtil_CopyOutput will manipulate the channel pointers, + * so we copy the user provided pointers */ + if( stream->bufferProcessor.userOutputIsInterleaved ) + userBuffer = buffer; + else /* Copy channels into local array */ + { + userBuffer = stream->playback->userBuffers; + memcpy( (void *)userBuffer, buffer, sizeof (void *) * stream->playback->userChannelCount ); + } + + while( frames ) + { + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, stream->playback->hostFrames ); + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, 0, stream->playback->buffer, stream->playback->hostChannelCount ); + + framesConverted = PaUtil_CopyOutput( &stream->bufferProcessor, &userBuffer, frames ); + frames -= framesConverted; + + bytesRequested = framesConverted * PaOssStreamComponent_FrameSize( stream->playback ); + bytesWritten = write( stream->playback->fd, stream->playback->buffer, bytesRequested ); + + if ( bytesRequested != bytesWritten ) + return paUnanticipatedHostError; + } + return paNoError; +} + + +static signed long GetStreamReadAvailable( PaStream* s ) +{ + PaOssStream *stream = (PaOssStream*)s; + audio_buf_info info; + + if( ioctl( stream->capture->fd, SNDCTL_DSP_GETISPACE, &info ) < 0 ) + return paUnanticipatedHostError; + return info.fragments * stream->capture->hostFrames; +} + + +/* TODO: Compute number of allocated bytes somewhere else, can we use ODELAY with capture */ +static signed long GetStreamWriteAvailable( PaStream* s ) +{ + PaOssStream *stream = (PaOssStream*)s; + int delay = 0; + + if( ioctl( stream->playback->fd, SNDCTL_DSP_GETODELAY, &delay ) < 0 ) + return paUnanticipatedHostError; + + return (PaOssStreamComponent_BufferSize( stream->playback ) - delay) / PaOssStreamComponent_FrameSize( stream->playback ); +} + diff --git a/src/audio/portaudio/pa_unix_oss/recplay.c b/src/audio/portaudio/pa_unix_oss/recplay.c new file mode 100644 index 0000000000000000000000000000000000000000..9d4c78cfa3073a7367c7d3d24dcc5db63e556728 --- /dev/null +++ b/src/audio/portaudio/pa_unix_oss/recplay.c @@ -0,0 +1,114 @@ +/* + * recplay.c + * Phil Burk + * Minimal record and playback test. + * + */ +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#ifndef __STDC__ +/* #include <getopt.h> */ +#endif /* __STDC__ */ +#include <fcntl.h> +#ifdef __STDC__ +#include <string.h> +#else /* __STDC__ */ +#include <strings.h> +#endif /* __STDC__ */ +#include <sys/soundcard.h> + +#define NUM_BYTES (64*1024) +#define BLOCK_SIZE (4*1024) + +#define AUDIO "/dev/dsp" + +char buffer[NUM_BYTES]; + +int audioDev = 0; + +main (int argc, char *argv[]) +{ + int numLeft; + char *ptr; + int num; + int samplesize; + + /********** RECORD ********************/ + /* Open audio device. */ + audioDev = open (AUDIO, O_RDONLY, 0); + if (audioDev == -1) + { + perror (AUDIO); + exit (-1); + } + + /* Set to 16 bit samples. */ + samplesize = 16; + ioctl(audioDev, SNDCTL_DSP_SAMPLESIZE, &samplesize); + if (samplesize != 16) + { + perror("Unable to set the sample size."); + exit(-1); + } + + /* Record in blocks */ + printf("Begin recording.\n"); + numLeft = NUM_BYTES; + ptr = buffer; + while( numLeft >= BLOCK_SIZE ) + { + if ( (num = read (audioDev, ptr, BLOCK_SIZE)) < 0 ) + { + perror (AUDIO); + exit (-1); + } + else + { + printf("Read %d bytes\n", num); + ptr += num; + numLeft -= num; + } + } + + close( audioDev ); + + /********** PLAYBACK ********************/ + /* Open audio device for writing. */ + audioDev = open (AUDIO, O_WRONLY, 0); + if (audioDev == -1) + { + perror (AUDIO); + exit (-1); + } + + /* Set to 16 bit samples. */ + samplesize = 16; + ioctl(audioDev, SNDCTL_DSP_SAMPLESIZE, &samplesize); + if (samplesize != 16) + { + perror("Unable to set the sample size."); + exit(-1); + } + + /* Play in blocks */ + printf("Begin playing.\n"); + numLeft = NUM_BYTES; + ptr = buffer; + while( numLeft >= BLOCK_SIZE ) + { + if ( (num = write (audioDev, ptr, BLOCK_SIZE)) < 0 ) + { + perror (AUDIO); + exit (-1); + } + else + { + printf("Wrote %d bytes\n", num); + ptr += num; + numLeft -= num; + } + } + + close( audioDev ); +} diff --git a/src/audio/portaudio/pa_win/dev-cpp/Makefile-dll b/src/audio/portaudio/pa_win/dev-cpp/Makefile-dll new file mode 100644 index 0000000000000000000000000000000000000000..856af638e304b0913f369f6b2ec2fb5901dda4d1 --- /dev/null +++ b/src/audio/portaudio/pa_win/dev-cpp/Makefile-dll @@ -0,0 +1,78 @@ +# Project: portaudio-dll +# Makefile created by Dev-C++ 4.9.8.2 + +CPP = g++.exe +CC = gcc.exe +WINDRES = windres.exe +RES = +OBJ = ./pa_skeleton.o ./pa_stream.o ./pa_trace.o ./pa_allocation.o ./pa_converters.o ./pa_cpuload.o ./pa_dither.o ./pa_front.o ./pa_process.o ./pa_asio.o ./pa_win_util.o ./pa_win_hostapis.o ./pa_win_ds.o ./dsound_wrapper.o ./pa_win_wmme.o ./iasiothiscallresolver.o $(RES) +LINKOBJ = ./pa_skeleton.o ./pa_stream.o ./pa_trace.o ./pa_allocation.o ./pa_converters.o ./pa_cpuload.o ./pa_dither.o ./pa_front.o ./pa_process.o ./pa_asio.o ./pa_win_util.o ./pa_win_hostapis.o ./pa_win_ds.o ./dsound_wrapper.o ./pa_win_wmme.o ./iasiothiscallresolver.o $(RES) +LIBS = -L"C:/Dev-CPP/lib" -fmessage-length=0 --no-export-all-symbols --add-stdcall-alias ../../../asiosdk2/asiosdk2.a -lkernel32 -luser32 -lgdi32 -lwinspool -lcomdlg32 -ladvapi32 -lshell32 -lole32 -loleaut32 -luuid -lodbc32 -lodbccp32 -lwinmm -O3 -s +INCS = -I"C:/Dev-CPP/include" -I"../../../asiosdk2" -I"../../../asiosdk2/common" -I"../../../asiosdk2/host" -I"../../../asiosdk2/host/pc" -I"../../pa_common" +CXXINCS = -I"C:/Dev-CPP/include/c++" -I"C:/Dev-CPP/include/c++/mingw32" -I"C:/Dev-CPP/include/c++/backward" -I"C:/Dev-CPP/include" -I"../../../asiosdk2" -I"../../../asiosdk2/common" -I"../../../asiosdk2/host" -I"../../../asiosdk2/host/pc" -I"../../pa_common" +BIN = portaudio-dll.dll +CXXFLAGS = $(CXXINCS)-O3 -fmessage-length=0 -Wall +CFLAGS = $(INCS)-DBUILDING_DLL=1 -O3 -fmessage-length=0 -Wall + +.PHONY: all all-before all-after clean clean-custom + +all: all-before portaudio-dll.dll all-after + + +clean: clean-custom + rm -f $(OBJ) $(BIN) + +DLLWRAP=dllwrap.exe +DEFFILE=libportaudio-dll.def +STATICLIB=libportaudio-dll.a + +$(BIN): $(LINKOBJ) + $(DLLWRAP) --output-def $(DEFFILE) --driver-name c++ --implib $(STATICLIB) $(LINKOBJ) $(LIBS) -o $(BIN) + +./pa_skeleton.o: ../../pa_common/pa_skeleton.c + $(CPP) -c ../../pa_common/pa_skeleton.c -o ./pa_skeleton.o $(CXXFLAGS) + +./pa_stream.o: ../../pa_common/pa_stream.c + $(CPP) -c ../../pa_common/pa_stream.c -o ./pa_stream.o $(CXXFLAGS) + +./pa_trace.o: ../../pa_common/pa_trace.c + $(CPP) -c ../../pa_common/pa_trace.c -o ./pa_trace.o $(CXXFLAGS) + +./pa_allocation.o: ../../pa_common/pa_allocation.c + $(CPP) -c ../../pa_common/pa_allocation.c -o ./pa_allocation.o $(CXXFLAGS) + +./pa_converters.o: ../../pa_common/pa_converters.c + $(CPP) -c ../../pa_common/pa_converters.c -o ./pa_converters.o $(CXXFLAGS) + +./pa_cpuload.o: ../../pa_common/pa_cpuload.c + $(CPP) -c ../../pa_common/pa_cpuload.c -o ./pa_cpuload.o $(CXXFLAGS) + +./pa_dither.o: ../../pa_common/pa_dither.c + $(CPP) -c ../../pa_common/pa_dither.c -o ./pa_dither.o $(CXXFLAGS) + +./pa_front.o: ../../pa_common/pa_front.c + $(CPP) -c ../../pa_common/pa_front.c -o ./pa_front.o $(CXXFLAGS) + +./pa_process.o: ../../pa_common/pa_process.c + $(CPP) -c ../../pa_common/pa_process.c -o ./pa_process.o $(CXXFLAGS) + +./pa_asio.o: ../../pa_asio/pa_asio.cpp + $(CPP) -c ../../pa_asio/pa_asio.cpp -o ./pa_asio.o $(CXXFLAGS) + +./pa_win_util.o: ../pa_win_util.c + $(CPP) -c ../pa_win_util.c -o ./pa_win_util.o $(CXXFLAGS) + +./pa_win_hostapis.o: ../pa_win_hostapis.c + $(CPP) -c ../pa_win_hostapis.c -o ./pa_win_hostapis.o $(CXXFLAGS) + +./pa_win_ds.o: ../../pa_win_ds/pa_win_ds.c + $(CPP) -c ../../pa_win_ds/pa_win_ds.c -o ./pa_win_ds.o $(CXXFLAGS) + +./dsound_wrapper.o: ../../pa_win_ds/dsound_wrapper.c + $(CPP) -c ../../pa_win_ds/dsound_wrapper.c -o ./dsound_wrapper.o $(CXXFLAGS) + +./pa_win_wmme.o: ../../pa_win_wmme/pa_win_wmme.c + $(CPP) -c ../../pa_win_wmme/pa_win_wmme.c -o ./pa_win_wmme.o $(CXXFLAGS) + +./iasiothiscallresolver.o: ../../pa_asio/iasiothiscallresolver.cpp + $(CPP) -c ../../pa_asio/iasiothiscallresolver.cpp -o ./iasiothiscallresolver.o $(CXXFLAGS) diff --git a/src/audio/portaudio/pa_win/dev-cpp/Makefile-static b/src/audio/portaudio/pa_win/dev-cpp/Makefile-static new file mode 100644 index 0000000000000000000000000000000000000000..2a1647ddc5f3a60f24312d60168da26357965e50 --- /dev/null +++ b/src/audio/portaudio/pa_win/dev-cpp/Makefile-static @@ -0,0 +1,75 @@ +# Project: portaudio-static +# Makefile created by Dev-C++ 4.9.8.2 + +CPP = g++.exe +CC = gcc.exe +WINDRES = windres.exe +RES = +OBJ = ./pa_skeleton.o ./pa_stream.o ./pa_trace.o ./pa_allocation.o ./pa_converters.o ./pa_cpuload.o ./pa_dither.o ./pa_front.o ./pa_process.o ./pa_asio.o ./pa_win_util.o ./pa_win_hostapis.o ./pa_win_ds.o ./dsound_wrapper.o ./pa_win_wmme.o ./iasiothiscallresolver.o $(RES) +LINKOBJ = ./pa_skeleton.o ./pa_stream.o ./pa_trace.o ./pa_allocation.o ./pa_converters.o ./pa_cpuload.o ./pa_dither.o ./pa_front.o ./pa_process.o ./pa_asio.o ./pa_win_util.o ./pa_win_hostapis.o ./pa_win_ds.o ./dsound_wrapper.o ./pa_win_wmme.o ./iasiothiscallresolver.o $(RES) +LIBS = -L"C:/Dev-CPP/lib" -fmessage-length=0 -O3 -s +INCS = -I"C:/Dev-CPP/include" -I"../../../asiosdk2" -I"../../../asiosdk2/common" -I"../../../asiosdk2/host" -I"../../../asiosdk2/host/pc" -I"../../pa_common" +CXXINCS = -I"C:/Dev-CPP/include/c++" -I"C:/Dev-CPP/include/c++/mingw32" -I"C:/Dev-CPP/include/c++/backward" -I"C:/Dev-CPP/include" -I"../../../asiosdk2" -I"../../../asiosdk2/common" -I"../../../asiosdk2/host" -I"../../../asiosdk2/host/pc" -I"../../pa_common" +BIN = portaudio-static.a +CXXFLAGS = $(CXXINCS)-O3 -fmessage-length=0 -Wall +CFLAGS = $(INCS)-O3 -fmessage-length=0 -Wall + +.PHONY: all all-before all-after clean clean-custom + +all: all-before portaudio-static.a all-after + + +clean: clean-custom + rm -f $(OBJ) $(BIN) + +$(BIN): $(LINKOBJ) + ar r $(BIN) $(LINKOBJ) + ranlib $(BIN) + +./pa_skeleton.o: ../../pa_common/pa_skeleton.c + $(CPP) -c ../../pa_common/pa_skeleton.c -o ./pa_skeleton.o $(CXXFLAGS) + +./pa_stream.o: ../../pa_common/pa_stream.c + $(CPP) -c ../../pa_common/pa_stream.c -o ./pa_stream.o $(CXXFLAGS) + +./pa_trace.o: ../../pa_common/pa_trace.c + $(CPP) -c ../../pa_common/pa_trace.c -o ./pa_trace.o $(CXXFLAGS) + +./pa_allocation.o: ../../pa_common/pa_allocation.c + $(CPP) -c ../../pa_common/pa_allocation.c -o ./pa_allocation.o $(CXXFLAGS) + +./pa_converters.o: ../../pa_common/pa_converters.c + $(CPP) -c ../../pa_common/pa_converters.c -o ./pa_converters.o $(CXXFLAGS) + +./pa_cpuload.o: ../../pa_common/pa_cpuload.c + $(CPP) -c ../../pa_common/pa_cpuload.c -o ./pa_cpuload.o $(CXXFLAGS) + +./pa_dither.o: ../../pa_common/pa_dither.c + $(CPP) -c ../../pa_common/pa_dither.c -o ./pa_dither.o $(CXXFLAGS) + +./pa_front.o: ../../pa_common/pa_front.c + $(CPP) -c ../../pa_common/pa_front.c -o ./pa_front.o $(CXXFLAGS) + +./pa_process.o: ../../pa_common/pa_process.c + $(CPP) -c ../../pa_common/pa_process.c -o ./pa_process.o $(CXXFLAGS) + +./pa_asio.o: ../../pa_asio/pa_asio.cpp + $(CPP) -c ../../pa_asio/pa_asio.cpp -o ./pa_asio.o $(CXXFLAGS) + +./pa_win_util.o: ../pa_win_util.c + $(CPP) -c ../pa_win_util.c -o ./pa_win_util.o $(CXXFLAGS) + +./pa_win_hostapis.o: ../pa_win_hostapis.c + $(CPP) -c ../pa_win_hostapis.c -o ./pa_win_hostapis.o $(CXXFLAGS) + +./pa_win_ds.o: ../../pa_win_ds/pa_win_ds.c + $(CPP) -c ../../pa_win_ds/pa_win_ds.c -o ./pa_win_ds.o $(CXXFLAGS) + +./dsound_wrapper.o: ../../pa_win_ds/dsound_wrapper.c + $(CPP) -c ../../pa_win_ds/dsound_wrapper.c -o ./dsound_wrapper.o $(CXXFLAGS) + +./pa_win_wmme.o: ../../pa_win_wmme/pa_win_wmme.c + $(CPP) -c ../../pa_win_wmme/pa_win_wmme.c -o ./pa_win_wmme.o $(CXXFLAGS) + +./iasiothiscallresolver.o: ../../pa_asio/iasiothiscallresolver.cpp + $(CPP) -c ../../pa_asio/iasiothiscallresolver.cpp -o ./iasiothiscallresolver.o $(CXXFLAGS) diff --git a/src/audio/portaudio/pa_win/dev-cpp/portaudio-dll.dev b/src/audio/portaudio/pa_win/dev-cpp/portaudio-dll.dev new file mode 100644 index 0000000000000000000000000000000000000000..086e109eef88fc09f125091a4908bca902b87d23 --- /dev/null +++ b/src/audio/portaudio/pa_win/dev-cpp/portaudio-dll.dev @@ -0,0 +1,209 @@ +[Project] +FileName=portaudio-dll.dev +Name=portaudio-dll +UnitCount=16 +Type=3 +Ver=1 +ObjFiles= +Includes=..\..\..\asiosdk2;..\..\..\asiosdk2\common;..\..\..\asiosdk2\host;..\..\..\asiosdk2\host\pc;..\..\pa_common +Libs= +PrivateResource= +ResourceIncludes= +MakeIncludes= +Compiler=-DBUILDING_DLL=1_@@_-O3_@@_ +CppCompiler=-O3_@@_ +Linker=--no-export-all-symbols --add-stdcall-alias_@@_../../../asiosdk2/asiosdk2.a_@@_-lkernel32 -luser32 -lgdi32 -lwinspool -lcomdlg32 -ladvapi32 -lshell32 -lole32 -loleaut32 -luuid -lodbc32 -lodbccp32 -lwinmm_@@_-O3 -s_@@_ +IsCpp=1 +Icon= +ExeOutput=. +ObjectOutput=. +OverrideOutput=0 +OverrideOutputName=portaudio.a +HostApplication= +Folders= +CommandLine= +IncludeVersionInfo=0 +SupportXPThemes=0 +CompilerSet=0 +CompilerSettings=0000000000000000000 +UseCustomMakefile=0 +CustomMakefile= + +[Unit1] +FileName=..\..\pa_common\pa_skeleton.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_skeleton.c -o ./pa_skeleton.o $(CFLAGS) + +[Unit2] +FileName=..\..\pa_common\pa_stream.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_stream.c -o ./pa_stream.o $(CFLAGS) + +[Unit3] +FileName=..\..\pa_common\pa_trace.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_trace.c -o ./pa_trace.o $(CFLAGS) + +[Unit4] +FileName=..\..\pa_common\pa_allocation.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_allocation.c -o ./pa_allocation.o $(CFLAGS) + +[Unit5] +FileName=..\..\pa_common\pa_converters.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_converters.c -o ./pa_converters.o $(CFLAGS) + +[Unit6] +FileName=..\..\pa_common\pa_cpuload.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_cpuload.c -o ./pa_cpuload.o $(CFLAGS) + +[Unit7] +FileName=..\..\pa_common\pa_dither.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_dither.c -o ./pa_dither.o $(CFLAGS) + +[Unit8] +FileName=..\..\pa_common\pa_front.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_front.c -o ./pa_front.o $(CFLAGS) + +[Unit9] +FileName=..\..\pa_common\pa_process.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_process.c -o ./pa_process.o $(CFLAGS) + +[VersionInfo] +Major=0 +Minor=1 +Release=1 +Build=1 +LanguageID=1033 +CharsetID=1252 +CompanyName= +FileVersion= +FileDescription=Developed using the Dev-C++ IDE +InternalName= +LegalCopyright= +LegalTrademarks= +OriginalFilename= +ProductName= +ProductVersion= +AutoIncBuildNr=0 + +[Unit10] +FileName=..\..\pa_asio\pa_asio.cpp +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CPP) -c pa_asio.cpp -o ./pa_asio.o $(CXXFLAGS) + +[Unit11] +FileName=..\pa_win_util.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_win_util.c -o ./pa_win_util.o $(CFLAGS) + +[Unit12] +FileName=..\pa_win_hostapis.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_win_hostapis.c -o ./pa_win_hostapis.o $(CFLAGS) + +[Unit13] +FileName=..\..\pa_win_ds\pa_win_ds.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_win_ds.c -o ./pa_win_ds.o $(CFLAGS) + +[Unit14] +FileName=..\..\pa_win_ds\dsound_wrapper.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c dsound_wrapper.c -o ./dsound_wrapper.o $(CFLAGS) + +[Unit15] +FileName=..\..\pa_win_wmme\pa_win_wmme.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_win_wmme.c -o ./pa_win_wmme.o $(CFLAGS) + +[Unit16] +FileName=..\..\pa_asio\iasiothiscallresolver.cpp +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + diff --git a/src/audio/portaudio/pa_win/dev-cpp/portaudio-static.dev b/src/audio/portaudio/pa_win/dev-cpp/portaudio-static.dev new file mode 100644 index 0000000000000000000000000000000000000000..2aae584df4a7a706f526a9296fcafe6783cd646c --- /dev/null +++ b/src/audio/portaudio/pa_win/dev-cpp/portaudio-static.dev @@ -0,0 +1,209 @@ +[Project] +FileName=portaudio-static.dev +Name=portaudio-static +UnitCount=16 +Type=2 +Ver=1 +ObjFiles= +Includes=..\..\..\asiosdk2;..\..\..\asiosdk2\common;..\..\..\asiosdk2\host;..\..\..\asiosdk2\host\pc;..\..\pa_common +Libs= +PrivateResource= +ResourceIncludes= +MakeIncludes= +Compiler=-O3_@@_ +CppCompiler=-O3_@@_ +Linker=-O3 -s_@@_ +IsCpp=1 +Icon= +ExeOutput=. +ObjectOutput=. +OverrideOutput=0 +OverrideOutputName=portaudio.a +HostApplication= +Folders= +CommandLine= +IncludeVersionInfo=0 +SupportXPThemes=0 +CompilerSet=0 +CompilerSettings=0000000000000000000 +UseCustomMakefile=0 +CustomMakefile= + +[Unit1] +FileName=..\..\pa_common\pa_skeleton.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_skeleton.c -o ./pa_skeleton.o $(CFLAGS) + +[Unit2] +FileName=..\..\pa_common\pa_stream.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_stream.c -o ./pa_stream.o $(CFLAGS) + +[Unit3] +FileName=..\..\pa_common\pa_trace.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_trace.c -o ./pa_trace.o $(CFLAGS) + +[Unit4] +FileName=..\..\pa_common\pa_allocation.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_allocation.c -o ./pa_allocation.o $(CFLAGS) + +[Unit5] +FileName=..\..\pa_common\pa_converters.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_converters.c -o ./pa_converters.o $(CFLAGS) + +[Unit6] +FileName=..\..\pa_common\pa_cpuload.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_cpuload.c -o ./pa_cpuload.o $(CFLAGS) + +[Unit7] +FileName=..\..\pa_common\pa_dither.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_dither.c -o ./pa_dither.o $(CFLAGS) + +[Unit8] +FileName=..\..\pa_common\pa_front.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_front.c -o ./pa_front.o $(CFLAGS) + +[Unit9] +FileName=..\..\pa_common\pa_process.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_process.c -o ./pa_process.o $(CFLAGS) + +[VersionInfo] +Major=0 +Minor=1 +Release=1 +Build=1 +LanguageID=1033 +CharsetID=1252 +CompanyName= +FileVersion= +FileDescription=Developed using the Dev-C++ IDE +InternalName= +LegalCopyright= +LegalTrademarks= +OriginalFilename= +ProductName= +ProductVersion= +AutoIncBuildNr=0 + +[Unit10] +FileName=..\..\pa_asio\pa_asio.cpp +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CPP) -c pa_asio.cpp -o ./pa_asio.o $(CXXFLAGS) + +[Unit11] +FileName=..\..\pa_win\pa_win_util.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_win_util.c -o ./pa_win_util.o $(CFLAGS) + +[Unit12] +FileName=..\..\pa_win\pa_win_hostapis.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_win_hostapis.c -o ./pa_win_hostapis.o $(CFLAGS) + +[Unit13] +FileName=..\..\pa_win_ds\pa_win_ds.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_win_ds.c -o ./pa_win_ds.o $(CFLAGS) + +[Unit14] +FileName=..\..\pa_win_ds\dsound_wrapper.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c dsound_wrapper.c -o ./dsound_wrapper.o $(CFLAGS) + +[Unit15] +FileName=..\..\pa_win_wmme\pa_win_wmme.c +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd=$(CC) -c pa_win_wmme.c -o ./pa_win_wmme.o $(CFLAGS) + +[Unit16] +FileName=..\..\pa_asio\iasiothiscallresolver.cpp +CompileCpp=1 +Folder=portaudio +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + diff --git a/src/audio/portaudio/pa_win/dev-cpp/readme.txt b/src/audio/portaudio/pa_win/dev-cpp/readme.txt new file mode 100644 index 0000000000000000000000000000000000000000..07108a7dde89bb7b848294b822f0af5586d4b3ec --- /dev/null +++ b/src/audio/portaudio/pa_win/dev-cpp/readme.txt @@ -0,0 +1,23 @@ +From: "Peter L Jones" +Sent: Wednesday, September 17, 2003 5:18 AM +Subject: Dev-C++ project files + +I attach two project files intended for portaudio/pa_win/dev-cpp (i.e. in +parallel with the msvc directory), if you want them. One is for a static +library build and one for a DLL. I've used the static library (in building +a single monolithic DLL) but I can't guarantee the DLL version will build a +working library (I think it's mostly there, though!). + +I also attach the resulting makefiles, which may be of use to other MinGW +users. + +They're rooted in the directory given above and drop their object and +library files in the same place. They assume the asiosdk2 files are in the +same directory as portaudio/ in a sub-directory called asiosdk2/. Oh! The +DLL is built against a static asiosdk2.a library... maybe not the best way +to do it... I ought to figure out how to link against a "home made" dll in +Dev-C++, I guess ;-) + +Cheers, + +-- Peter diff --git a/src/audio/portaudio/pa_win/msvc/Makefile.msvc b/src/audio/portaudio/pa_win/msvc/Makefile.msvc new file mode 100644 index 0000000000000000000000000000000000000000..2f1a104e7ab2bacae05a29a436270faebc098a4d --- /dev/null +++ b/src/audio/portaudio/pa_win/msvc/Makefile.msvc @@ -0,0 +1,159 @@ +# Portaudio v1.9-devel VC6 DLL makefile 1.0 +# +# David Viens, davidv@plogue.com +# (im far from a VC6 makefile expert, so please bear with me :) +# +# For more info, look at readme.txt +# +#if you keep the ASIODIR as ".", it will use the SDK files that direclty in "pa_win/msvc" dir +ASIODIR=. +ASIOINC=/I ".\host" /I ".\host\pc" /I ".\common" +# + +LIBZ=kernel32.lib user32.lib gdi32.lib wininet.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib winmm.lib dsound.lib dxguid.lib + +CPP=cl.exe +LINK32=link.exe + +#release +CFLAGS=/nologo /MD /W3 /GX /O2 /Ob2 /I "src" /I "Win32" /I "$(MSVCDir)\Include" /D "WIN32" /D "NDEBUG" /D "_USRDLL" /YX /FD +DLL_LINK_FLAGS= /nologo /dll /incremental:no /libpath:"$(MSVCDir)\Lib" $(LIBZ) /pdb:"portaudio.pdb" /implib:".\portaudio.lib" /machine:I386 /out:"portaudio.dll" + + +COMMONINC=/I "..\..\pa_common" /I "." + +#==================================================================== +# Targets + +ALL : portaudio.dll + +CLEAN : + -@erase "*.obj" + +#==================================================================== + + +LINK32_OBJS= \ + ".\pa_allocation.obj" \ + ".\pa_converters.obj" \ + ".\pa_x86_plain_converters.obj" \ +# ".\pa_cppHelp.obj" \ + ".\pa_cpuload.obj" \ + ".\pa_dither.obj" \ + ".\pa_front.obj" \ + ".\pa_process.obj" \ + ".\pa_skeleton.obj" \ + ".\pa_stream.obj" \ + ".\pa_trace.obj" \ +# + ".\pa_win_hostapis.obj" \ + ".\pa_win_util.obj" \ +# + ".\pa_win_wmme.obj" \ +# + ".\pa_win_ds.obj" \ + ".\dsound_wrapper.obj" \ +# + ".\pa_asio.obj" \ +# + ".\asio.obj" \ + ".\ASIOConvertSamples.obj" \ + ".\asiodrivers.obj" \ + ".\asiolist.obj" \ + ".\combase.obj" \ + ".\debugmessage.obj" \ +# ".\dllentry.obj" \ + ".\register.obj" + + +portaudio.dll : $(LINK32_OBJS) ".\portaudio.def" + $(LINK32) $(DLL_LINK_FLAGS) /def:".\portaudio.def" $(LINK32_OBJS) + +#==================================================================== +# asio files (need to agree to steinberg agreement) +# this makefile assumes all files have being copied in the pa_win/msvc dir (for now) +# see readme.txt for details + +".\asio.obj" : ".\common\asio.cpp" + $(CPP) $(CFLAGS) $(ASIOINC) /Fo".\asio.obj" /c ".\common\asio.cpp" + +".\ASIOConvertSamples.obj" : ".\host\ASIOConvertSamples.cpp" + $(CPP) $(CFLAGS) $(ASIOINC) /Fo".\ASIOConvertSamples.obj" /c ".\host\ASIOConvertSamples.cpp" + +".\asiodrivers.obj" : ".\host\asiodrivers.cpp" + $(CPP) $(CFLAGS) $(ASIOINC) /Fo".\asiodrivers.obj" /c ".\host\asiodrivers.cpp" + +".\asiolist.obj" : ".\host\pc\asiolist.cpp" + $(CPP) $(CFLAGS) $(ASIOINC) /Fo".\asiolist.obj" /c ".\host\pc\asiolist.cpp" + +".\combase.obj" : ".\common\combase.cpp" + $(CPP) $(CFLAGS) $(ASIOINC) /Fo".\combase.obj" /c ".\common\combase.cpp" + +".\debugmessage.obj" : ".\common\debugmessage.cpp" + $(CPP) $(CFLAGS) $(ASIOINC) /Fo".\debugmessage.obj" /c ".\common\debugmessage.cpp" + +".\register.obj" : ".\common\register.cpp" + $(CPP) $(CFLAGS) $(ASIOINC) /Fo".\register.obj" /c ".\common\register.cpp" + +#==================================================================== +# Portaudio Common +# +".\pa_allocation.obj" : "..\..\pa_common\pa_allocation.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_allocation.obj" /c "..\..\pa_common\pa_allocation.c" + +".\pa_converters.obj" : "..\..\pa_common\pa_converters.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_converters.obj" /c "..\..\pa_common\pa_converters.c" + +".\pa_cppHelp.obj" : "..\..\pa_common\pa_cppHelp.cpp" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_cppHelp.obj" /c "..\..\pa_common\pa_cppHelp.cpp" + +".\pa_cpuload.obj" : "..\..\pa_common\pa_cpuload.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_cpuload.obj" /c "..\..\pa_common\pa_cpuload.c" + +".\pa_dither.obj" : "..\..\pa_common\pa_dither.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_dither.obj" /c "..\..\pa_common\pa_dither.c" + +".\pa_front.obj" : "..\..\pa_common\pa_front.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_front.obj" /c "..\..\pa_common\pa_front.c" + +".\pa_process.obj" : "..\..\pa_common\pa_process.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_process.obj" /c "..\..\pa_common\pa_process.c" + +".\pa_skeleton.obj" : "..\..\pa_common\pa_skeleton.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_skeleton.obj" /c "..\..\pa_common\pa_skeleton.c" + +".\pa_stream.obj" : "..\..\pa_common\pa_stream.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_stream.obj" /c "..\..\pa_common\pa_stream.c" + +".\pa_trace.obj" : "..\..\pa_common\pa_trace.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_trace.obj" /c "..\..\pa_common\pa_trace.c" + +#==================================================================== +# Portaudio implementations +# + +".\pa_win_hostapis.obj" : "..\..\pa_win\pa_win_hostapis.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_win_hostapis.obj" /c "..\..\pa_win\pa_win_hostapis.c" + +".\pa_win_util.obj" : "..\..\pa_win\pa_win_util.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_win_util.obj" /c "..\..\pa_win\pa_win_util.c" + +".\pa_x86_plain_converters.obj" : "..\..\pa_win\pa_x86_plain_converters.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_x86_plain_converters.obj" /c "..\..\pa_win\pa_x86_plain_converters.c" + +".\pa_asio.obj" : "..\..\pa_asio\pa_asio.cpp" + $(CPP) $(CFLAGS) $(ASIOINC) $(COMMONINC) /Fo".\pa_asio.obj" /c "..\..\pa_asio\pa_asio.cpp" + +".\pa_win_wmme.obj" : "..\..\pa_win_wmme\pa_win_wmme.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_win_wmme.obj" /c "..\..\pa_win_wmme\pa_win_wmme.c" + +".\pa_win_ds.obj" : "..\..\pa_win_ds\pa_win_ds.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\pa_win_ds.obj" /c "..\..\pa_win_ds\pa_win_ds.c" + +".\dsound_wrapper.obj" : "..\..\pa_win_ds\dsound_wrapper.c" + $(CPP) $(CFLAGS) $(COMMONINC) /Fo".\dsound_wrapper.obj" /c "..\..\pa_win_ds\dsound_wrapper.c" + + +# End of Makefile +#==================================================================== +# \ No newline at end of file diff --git a/src/audio/portaudio/pa_win/msvc/clean.bat b/src/audio/portaudio/pa_win/msvc/clean.bat new file mode 100755 index 0000000000000000000000000000000000000000..601c0d314d394840b67aa400f19ec35317a38788 --- /dev/null +++ b/src/audio/portaudio/pa_win/msvc/clean.bat @@ -0,0 +1,7 @@ +del *.obj +del *.dll +del *.lib +del *.exp +del *.pch +del *.idb + diff --git a/src/audio/portaudio/pa_win/msvc/make.bat b/src/audio/portaudio/pa_win/msvc/make.bat new file mode 100755 index 0000000000000000000000000000000000000000..34d6aed48aa80aead2fdbfba063c704cff0acada --- /dev/null +++ b/src/audio/portaudio/pa_win/msvc/make.bat @@ -0,0 +1,8 @@ +CALL C:\progra~1\micros~2\VC98\bin\vcvars32 +del *.dll +del *.lib +nmake Makefile.msvc +del *.obj +del *.exp +del *.pch +del *.idb diff --git a/src/audio/portaudio/pa_win/msvc/portaudio.def b/src/audio/portaudio/pa_win/msvc/portaudio.def new file mode 100644 index 0000000000000000000000000000000000000000..97a1c3502b7f123a977a3d28d2d7f5a23a7c502f --- /dev/null +++ b/src/audio/portaudio/pa_win/msvc/portaudio.def @@ -0,0 +1,43 @@ +LIBRARY portaudio.dll +EXPORTS + +; +Pa_GetVersion @1 +Pa_GetVersionText @2 +Pa_GetErrorText @3 +Pa_Initialize @4 +Pa_Terminate @5 +Pa_GetHostApiCount @6 +Pa_GetDefaultHostApi @7 +Pa_GetHostApiInfo @8 +Pa_HostApiTypeIdToHostApiIndex @9 +Pa_HostApiDeviceIndexToDeviceIndex @10 +Pa_GetLastHostErrorInfo @11 +Pa_GetDeviceCount @12 +Pa_GetDefaultInputDevice @13 +Pa_GetDefaultOutputDevice @14 +Pa_GetDeviceInfo @15 +Pa_IsFormatSupported @16 +Pa_OpenStream @17 +Pa_OpenDefaultStream @18 +Pa_CloseStream @19 +Pa_SetStreamFinishedCallback @20 +Pa_StartStream @21 +Pa_StopStream @22 +Pa_AbortStream @23 +Pa_IsStreamStopped @24 +Pa_IsStreamActive @25 +Pa_GetStreamInfo @26 +Pa_GetStreamTime @27 +Pa_GetStreamCpuLoad @28 +Pa_ReadStream @29 +Pa_WriteStream @30 +Pa_GetStreamReadAvailable @31 +Pa_GetStreamWriteAvailable @32 +Pa_GetSampleSize @33 +Pa_Sleep @34 +PaAsio_GetAvailableLatencyValues @50 +PaAsio_ShowControlPanel @51 +PaUtil_InitializeX86PlainConverters @52 +PaAsio_GetInputChannelName @53 +PaAsio_GetOutputChannelName @54 \ No newline at end of file diff --git a/src/audio/portaudio/pa_win/msvc/readme.txt b/src/audio/portaudio/pa_win/msvc/readme.txt new file mode 100644 index 0000000000000000000000000000000000000000..19ced4c4c21186b56841988872c161122d877ced --- /dev/null +++ b/src/audio/portaudio/pa_win/msvc/readme.txt @@ -0,0 +1,56 @@ +Hello + + This is a small list of steps in order to build portaudio +(Currently v19-devel) into a VC6 DLL and lib file. +This DLL contains all 3 current win32 PA APIS (MM/DS/ASIO) + +1)Copy the source dirs that comes with the ASIO SDK inside pa_win\msvc + so you should now have: + + pa_win\msvc\common + pa_win\msvc\host + pa_win\msvc\host\sample + pa_win\msvc\host\pc + pa_win\msvc\host\mac (not needed) + + You dont need "driver" + + +2)execure "make.bat", this assumes VC6 is installed in + C:\Program Files\Microsoft Visual Studio\ + + if its not, + + Open a command Prompt and execute "vcvars32.bat" which sets the environment + so that you can use Microsoft's "nmake" + EX: C:\Program Files\Microsoft Visual Studio\VC98\Bin\vcvars32.bat + or (C:\progra~1\micros~2\VC98\bin\vcvars32) dumb de dumb + + You should now have seen a line that said: + "Setting environment for using Microsoft Visual C++ tools." + While in pa_win\msvc , type "nmake makefile.msvc" + this _should_ create portaudio.dll and portaudio.lib + +3)Now in any VC6 project, in which you require portaudio, + you can just link with portaudio.lib, and of course include the + relevant headers + (portaudio.h, and/or pa_asio.h , pa_x86_plain_converters.h) See (*) + +4) Your new exe should now use portaudio.dll. + + +Have fun! + +(*): you may want to add/remove some DLL entry points. +Right now those 3 entries are _not_ from portaudio.h + +(from portaudio.def) +(...) +PaAsio_GetAvailableLatencyValues @50 +PaAsio_ShowControlPanel @51 +PaUtil_InitializeX86PlainConverters @52 + + +----- +last update April 16th 2003 +David Viens, davidv@plogue.com \ No newline at end of file diff --git a/src/audio/portaudio/pa_win/msvc/setenv.bat b/src/audio/portaudio/pa_win/msvc/setenv.bat new file mode 100755 index 0000000000000000000000000000000000000000..f4c815a7832d09b8a5bc972d7ece24e02dc567ca --- /dev/null +++ b/src/audio/portaudio/pa_win/msvc/setenv.bat @@ -0,0 +1 @@ +C:\progra~1\micros~2\VC98\bin\vcvars32 diff --git a/src/audio/portaudio/pa_win/pa_win_hostapis.c b/src/audio/portaudio/pa_win/pa_win_hostapis.c new file mode 100644 index 0000000000000000000000000000000000000000..5d5ac87f3a5fcea0f240d3674d53783be2882080 --- /dev/null +++ b/src/audio/portaudio/pa_win/pa_win_hostapis.c @@ -0,0 +1,86 @@ +/* + * $Id$ + * 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 + * + * 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. + * + * 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. + * + * 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. + */ + +/** @file + 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. +*/ + + +#include "pa_hostapi.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +PaError PaSkeleton_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); +PaError PaWinMme_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); +PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); +PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); +PaError PaWinWdm_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +PaUtilHostApiInitializer *paHostApiInitializers[] = + { + +#ifndef PA_NO_WMME + PaWinMme_Initialize, +#endif + +#ifndef PA_NO_DS + PaWinDs_Initialize, +#endif + +#ifndef PA_NO_ASIO + PaAsio_Initialize, +#endif + +/* +#ifndef PA_NO_WDMKS + PaWinWdm_Initialize, +#endif +*/ + + PaSkeleton_Initialize, /* just for testing */ + + 0 /* NULL terminated array */ + }; + + +int paDefaultHostApiIndex = 0; + diff --git a/src/audio/portaudio/pa_win/pa_win_util.c b/src/audio/portaudio/pa_win/pa_win_util.c new file mode 100644 index 0000000000000000000000000000000000000000..32d0dbb9867e795b2e1b255eb93c429ab106d5bf --- /dev/null +++ b/src/audio/portaudio/pa_win/pa_win_util.c @@ -0,0 +1,134 @@ +/* + * $Id$ + * 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 + * + * 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. + * + * 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. + * + * 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. + */ + +/** @file + Win32 platform-specific support functions. + + @todo Implement workaround for QueryPerformanceCounter() skipping forward + bug. (see msdn kb Q274323). +*/ + +#include <windows.h> +#include <mmsystem.h> /* for timeGetTime() */ + +#include "pa_util.h" + + +/* + Track memory allocations to avoid leaks. + */ + +#if PA_TRACK_MEMORY +static int numAllocations_ = 0; +#endif + + +void *PaUtil_AllocateMemory( long size ) +{ + void *result = GlobalAlloc( GPTR, size ); + +#if PA_TRACK_MEMORY + if( result != NULL ) numAllocations_ += 1; +#endif + return result; +} + + +void PaUtil_FreeMemory( void *block ) +{ + if( block != NULL ) + { + GlobalFree( block ); +#if PA_TRACK_MEMORY + numAllocations_ -= 1; +#endif + + } +} + + +int PaUtil_CountCurrentlyAllocatedBlocks( void ) +{ +#if PA_TRACK_MEMORY + return numAllocations_; +#else + return 0; +#endif +} + + +void Pa_Sleep( long msec ) +{ + Sleep( msec ); +} + +static int usePerformanceCounter_; +static double secondsPerTick_; + +void PaUtil_InitializeClock( void ) +{ + LARGE_INTEGER ticksPerSecond; + + if( QueryPerformanceFrequency( &ticksPerSecond ) != 0 ) + { + usePerformanceCounter_ = 1; + secondsPerTick_ = 1.0 / (double)ticksPerSecond.QuadPart; + } + else + { + usePerformanceCounter_ = 0; + } +} + + +double PaUtil_GetTime( void ) +{ + LARGE_INTEGER time; + + if( usePerformanceCounter_ ) + { + /* FIXME: + according to this knowledge-base article, QueryPerformanceCounter + can skip forward by seconds! + http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q274323& + + it may be better to use the rtdsc instruction using inline asm, + however then a method is needed to calculate a ticks/seconds ratio. + */ + QueryPerformanceCounter( &time ); + return time.QuadPart * secondsPerTick_; + } + else + { + return timeGetTime() * .001; + } +} diff --git a/src/audio/portaudio/pa_win/pa_x86_plain_converters.c b/src/audio/portaudio/pa_win/pa_x86_plain_converters.c new file mode 100644 index 0000000000000000000000000000000000000000..98442a8c61500b4a8fdc9bf28c34a2ce2d75b430 --- /dev/null +++ b/src/audio/portaudio/pa_win/pa_x86_plain_converters.c @@ -0,0 +1,1167 @@ +#include "pa_x86_plain_converters.h" + +#include "pa_converters.h" +#include "pa_dither.h" + +/* + plain intel assemby versions of standard pa converter functions. + + the main reason these versions are faster than the equivalent C versions + is that float -> int casting is expensive in C on x86 because the rounding + mode needs to be changed for every cast. these versions only set + the rounding mode once outside the loop. + + small additional speed gains are made by the way that clamping is + implemented. + +TODO: + o- inline dither code + o- implement Dither only (no-clip) versions + o- implement int8 and uint8 versions + o- test thouroughly + + o- the packed 24 bit functions could benefit from unrolling and avoiding + byte and word sized register access. +*/ + +/* -------------------------------------------------------------------------- */ + +/* +#define PA_CLIP_( val, min, max )\ + { val = ((val) < (min)) ? (min) : (((val) > (max)) ? (max) : (val)); } +*/ + +/* + the following notes were used to determine whether a floating point + value should be saturated (ie >1 or <-1) by loading it into an integer + register. these should be rewritten so that they make sense. + + an ieee floating point value + + 1.xxxxxxxxxxxxxxxxxxxx? + + + is less than or equal to 1 and greater than or equal to -1 either: + + if the mantissa is 0 and the unbiased exponent is 0 + + OR + + if the unbiased exponent < 0 + + this translates to: + + if the mantissa is 0 and the biased exponent is 7F + + or + + if the biased exponent is less than 7F + + + therefore the value is greater than 1 or less than -1 if + + the mantissa is not 0 and the biased exponent is 7F + + or + + if the biased exponent is greater than 7F + + + in other words, if we mask out the sign bit, the value is + greater than 1 or less than -1 if its integer representation is greater than: + + 0 01111111 0000 0000 0000 0000 0000 000 + + 0011 1111 1000 0000 0000 0000 0000 0000 => 0x3F800000 +*/ + +/* -------------------------------------------------------------------------- */ + +static const short fpuControlWord_ = 0x033F; /*round to nearest, 64 bit precision, all exceptions masked*/ +static const double int32Scaler_ = 0x7FFFFFFF; +static const double ditheredInt32Scaler_ = 0x7FFFFFFE; +static const double int24Scaler_ = 0x7FFFFF; +static const double ditheredInt24Scaler_ = 0x7FFFFE; +static const double int16Scaler_ = 0x7FFF; +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<<PA_DITHER_BITS_)-1)) +static const float const_float_dither_scale_ = PA_FLOAT_DITHER_SCALE_; +#define PA_DITHER_SHIFT_ ((32 - PA_DITHER_BITS_) + 1) + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int32( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, PaUtilTriangularDitherGenerator *ditherGenerator ) +{ +/* + float *src = (float*)sourceBuffer; + signed long *dest = (signed long*)destinationBuffer; + (void)ditherGenerator; // unused parameter + + while( count-- ) + { + // REVIEW + double scaled = *src * 0x7FFFFFFF; + *dest = (signed long) scaled; + + src += sourceStride; + dest += destinationStride; + } +*/ + + short savedFpuControlWord; + + (void) ditherGenerator; /* unused parameter */ + + + __asm{ + // esi -> source ptr + // eax -> source byte stride + // edi -> destination ptr + // ebx -> destination byte stride + // ecx -> source end ptr + // edx -> temp + + mov esi, sourceBuffer + + mov edx, 4 // sizeof float32 and int32 + mov eax, sourceStride + imul eax, edx + + mov ecx, count + imul ecx, eax + add ecx, esi + + mov edi, destinationBuffer + + mov ebx, destinationStride + imul ebx, edx + + fwait + fstcw savedFpuControlWord + fldcw fpuControlWord_ + + fld int32Scaler_ // stack: (int)0x7FFFFFFF + + Float32_To_Int32_loop: + + // load unscaled value into st(0) + fld dword ptr [esi] // stack: value, (int)0x7FFFFFFF + add esi, eax // increment source ptr + //lea esi, [esi+eax] + fmul st(0), st(1) // st(0) *= st(1), stack: value*0x7FFFFFFF, (int)0x7FFFFFFF + /* + note: we could store to a temporary qword here which would cause + wraparound distortion instead of int indefinite 0x10. that would + be more work, and given that not enabling clipping is only advisable + when you know that your signal isn't going to clip it isn't worth it. + */ + fistp dword ptr [edi] // pop st(0) into dest, stack: (int)0x7FFFFFFF + + add edi, ebx // increment destination ptr + //lea edi, [edi+ebx] + + cmp esi, ecx // has src ptr reached end? + jne Float32_To_Int32_loop + + ffree st(0) + fincstp + + fwait + fnclex + fldcw savedFpuControlWord + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int32_Clip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, PaUtilTriangularDitherGenerator *ditherGenerator ) +{ +/* + float *src = (float*)sourceBuffer; + signed long *dest = (signed long*)destinationBuffer; + (void) ditherGenerator; // unused parameter + + while( count-- ) + { + // REVIEW + double scaled = *src * 0x7FFFFFFF; + PA_CLIP_( scaled, -2147483648., 2147483647. ); + *dest = (signed long) scaled; + + src += sourceStride; + dest += destinationStride; + } +*/ + + short savedFpuControlWord; + + (void) ditherGenerator; /* unused parameter */ + + __asm{ + // esi -> source ptr + // eax -> source byte stride + // edi -> destination ptr + // ebx -> destination byte stride + // ecx -> source end ptr + // edx -> temp + + mov esi, sourceBuffer + + mov edx, 4 // sizeof float32 and int32 + mov eax, sourceStride + imul eax, edx + + mov ecx, count + imul ecx, eax + add ecx, esi + + mov edi, destinationBuffer + + mov ebx, destinationStride + imul ebx, edx + + fwait + fstcw savedFpuControlWord + fldcw fpuControlWord_ + + fld int32Scaler_ // stack: (int)0x7FFFFFFF + + Float32_To_Int32_Clip_loop: + + mov edx, dword ptr [esi] // load floating point value into integer register + + and edx, 0x7FFFFFFF // mask off sign + cmp edx, 0x3F800000 // greater than 1.0 or less than -1.0 + + jg Float32_To_Int32_Clip_clamp + + // load unscaled value into st(0) + fld dword ptr [esi] // stack: value, (int)0x7FFFFFFF + add esi, eax // increment source ptr + //lea esi, [esi+eax] + fmul st(0), st(1) // st(0) *= st(1), stack: value*0x7FFFFFFF, (int)0x7FFFFFFF + fistp dword ptr [edi] // pop st(0) into dest, stack: (int)0x7FFFFFFF + jmp Float32_To_Int32_Clip_stored + + Float32_To_Int32_Clip_clamp: + mov edx, dword ptr [esi] // load floating point value into integer register + shr edx, 31 // move sign bit into bit 0 + add esi, eax // increment source ptr + //lea esi, [esi+eax] + add edx, 0x7FFFFFFF // convert to maximum range integers + mov dword ptr [edi], edx + + Float32_To_Int32_Clip_stored: + + //add edi, ebx // increment destination ptr + lea edi, [edi+ebx] + + cmp esi, ecx // has src ptr reached end? + jne Float32_To_Int32_Clip_loop + + ffree st(0) + fincstp + + fwait + fnclex + fldcw savedFpuControlWord + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int32_DitherClip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, PaUtilTriangularDitherGenerator *ditherGenerator ) +{ + /* + float *src = (float*)sourceBuffer; + signed long *dest = (signed long*)destinationBuffer; + + while( count-- ) + { + // REVIEW + double dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); + // use smaller scaler to prevent overflow when we add the dither + double dithered = ((double)*src * (2147483646.0)) + dither; + PA_CLIP_( dithered, -2147483648., 2147483647. ); + *dest = (signed long) dithered; + + + src += sourceStride; + dest += destinationStride; + } + */ + + short savedFpuControlWord; + + // spill storage: + signed long sourceByteStride; + signed long highpassedDither; + + // dither state: + unsigned long ditherPrevious = ditherGenerator->previous; + unsigned long ditherRandSeed1 = ditherGenerator->randSeed1; + unsigned long ditherRandSeed2 = ditherGenerator->randSeed2; + + __asm{ + // esi -> source ptr + // eax -> source byte stride + // edi -> destination ptr + // ebx -> destination byte stride + // ecx -> source end ptr + // edx -> temp + + mov esi, sourceBuffer + + mov edx, 4 // sizeof float32 and int32 + mov eax, sourceStride + imul eax, edx + + mov ecx, count + imul ecx, eax + add ecx, esi + + mov edi, destinationBuffer + + mov ebx, destinationStride + imul ebx, edx + + fwait + fstcw savedFpuControlWord + fldcw fpuControlWord_ + + fld ditheredInt32Scaler_ // stack: int scaler + + Float32_To_Int32_DitherClip_loop: + + mov edx, dword ptr [esi] // load floating point value into integer register + + and edx, 0x7FFFFFFF // mask off sign + cmp edx, 0x3F800000 // greater than 1.0 or less than -1.0 + + jg Float32_To_Int32_DitherClip_clamp + + // load unscaled value into st(0) + fld dword ptr [esi] // stack: value, int scaler + add esi, eax // increment source ptr + //lea esi, [esi+eax] + fmul st(0), st(1) // st(0) *= st(1), stack: value*(int scaler), int scaler + + /* + // call PaUtil_GenerateFloatTriangularDither with C calling convention + mov sourceByteStride, eax // save eax + mov sourceEnd, ecx // save ecx + push ditherGenerator // pass ditherGenerator parameter on stack + call PaUtil_GenerateFloatTriangularDither // stack: dither, value*(int scaler), int scaler + pop edx // clear parameter off stack + mov ecx, sourceEnd // restore ecx + mov eax, sourceByteStride // restore eax + */ + + // generate dither + mov sourceByteStride, eax // save eax + mov edx, 196314165 + mov eax, ditherRandSeed1 + mul edx // eax:edx = eax * 196314165 + //add eax, 907633515 + lea eax, [eax+907633515] + mov ditherRandSeed1, eax + mov edx, 196314165 + mov eax, ditherRandSeed2 + mul edx // eax:edx = eax * 196314165 + //add eax, 907633515 + lea eax, [eax+907633515] + mov edx, ditherRandSeed1 + shr edx, PA_DITHER_SHIFT_ + mov ditherRandSeed2, eax + shr eax, PA_DITHER_SHIFT_ + //add eax, edx // eax -> current + lea eax, [eax+edx] + mov edx, ditherPrevious + neg edx + lea edx, [eax+edx] // highpass = current - previous + mov highpassedDither, edx + mov ditherPrevious, eax // previous = current + mov eax, sourceByteStride // restore eax + fild highpassedDither + fmul const_float_dither_scale_ + // end generate dither, dither signal in st(0) + + faddp st(1), st(0) // stack: dither + value*(int scaler), int scaler + fistp dword ptr [edi] // pop st(0) into dest, stack: int scaler + jmp Float32_To_Int32_DitherClip_stored + + Float32_To_Int32_DitherClip_clamp: + mov edx, dword ptr [esi] // load floating point value into integer register + shr edx, 31 // move sign bit into bit 0 + add esi, eax // increment source ptr + //lea esi, [esi+eax] + add edx, 0x7FFFFFFF // convert to maximum range integers + mov dword ptr [edi], edx + + Float32_To_Int32_DitherClip_stored: + + //add edi, ebx // increment destination ptr + lea edi, [edi+ebx] + + cmp esi, ecx // has src ptr reached end? + jne Float32_To_Int32_DitherClip_loop + + ffree st(0) + fincstp + + fwait + fnclex + fldcw savedFpuControlWord + } + + ditherGenerator->previous = ditherPrevious; + ditherGenerator->randSeed1 = ditherRandSeed1; + ditherGenerator->randSeed2 = ditherRandSeed2; +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int24( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, PaUtilTriangularDitherGenerator *ditherGenerator ) +{ +/* + float *src = (float*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + signed long temp; + + (void) ditherGenerator; // unused parameter + + while( count-- ) + { + // convert to 32 bit and drop the low 8 bits + double scaled = *src * 0x7FFFFFFF; + temp = (signed long) scaled; + + dest[0] = (unsigned char)(temp >> 8); + dest[1] = (unsigned char)(temp >> 16); + dest[2] = (unsigned char)(temp >> 24); + + src += sourceStride; + dest += destinationStride * 3; + } +*/ + + short savedFpuControlWord; + + signed long tempInt32; + + (void) ditherGenerator; /* unused parameter */ + + __asm{ + // esi -> source ptr + // eax -> source byte stride + // edi -> destination ptr + // ebx -> destination byte stride + // ecx -> source end ptr + // edx -> temp + + mov esi, sourceBuffer + + mov edx, 4 // sizeof float32 + mov eax, sourceStride + imul eax, edx + + mov ecx, count + imul ecx, eax + add ecx, esi + + mov edi, destinationBuffer + + mov edx, 3 // sizeof int24 + mov ebx, destinationStride + imul ebx, edx + + fwait + fstcw savedFpuControlWord + fldcw fpuControlWord_ + + fld int24Scaler_ // stack: (int)0x7FFFFF + + Float32_To_Int24_loop: + + // load unscaled value into st(0) + fld dword ptr [esi] // stack: value, (int)0x7FFFFF + add esi, eax // increment source ptr + //lea esi, [esi+eax] + fmul st(0), st(1) // st(0) *= st(1), stack: value*0x7FFFFF, (int)0x7FFFFF + fistp tempInt32 // pop st(0) into tempInt32, stack: (int)0x7FFFFF + mov edx, tempInt32 + + mov byte ptr [edi], DL + shr edx, 8 + //mov byte ptr [edi+1], DL + //mov byte ptr [edi+2], DH + mov word ptr [edi+1], DX + + //add edi, ebx // increment destination ptr + lea edi, [edi+ebx] + + cmp esi, ecx // has src ptr reached end? + jne Float32_To_Int24_loop + + ffree st(0) + fincstp + + fwait + fnclex + fldcw savedFpuControlWord + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int24_Clip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, PaUtilTriangularDitherGenerator *ditherGenerator ) +{ +/* + float *src = (float*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + signed long temp; + + (void) ditherGenerator; // unused parameter + + while( count-- ) + { + // convert to 32 bit and drop the low 8 bits + double scaled = *src * 0x7FFFFFFF; + PA_CLIP_( scaled, -2147483648., 2147483647. ); + temp = (signed long) scaled; + + dest[0] = (unsigned char)(temp >> 8); + dest[1] = (unsigned char)(temp >> 16); + dest[2] = (unsigned char)(temp >> 24); + + src += sourceStride; + dest += destinationStride * 3; + } +*/ + + short savedFpuControlWord; + + signed long tempInt32; + + (void) ditherGenerator; /* unused parameter */ + + __asm{ + // esi -> source ptr + // eax -> source byte stride + // edi -> destination ptr + // ebx -> destination byte stride + // ecx -> source end ptr + // edx -> temp + + mov esi, sourceBuffer + + mov edx, 4 // sizeof float32 + mov eax, sourceStride + imul eax, edx + + mov ecx, count + imul ecx, eax + add ecx, esi + + mov edi, destinationBuffer + + mov edx, 3 // sizeof int24 + mov ebx, destinationStride + imul ebx, edx + + fwait + fstcw savedFpuControlWord + fldcw fpuControlWord_ + + fld int24Scaler_ // stack: (int)0x7FFFFF + + Float32_To_Int24_Clip_loop: + + mov edx, dword ptr [esi] // load floating point value into integer register + + and edx, 0x7FFFFFFF // mask off sign + cmp edx, 0x3F800000 // greater than 1.0 or less than -1.0 + + jg Float32_To_Int24_Clip_clamp + + // load unscaled value into st(0) + fld dword ptr [esi] // stack: value, (int)0x7FFFFF + add esi, eax // increment source ptr + //lea esi, [esi+eax] + fmul st(0), st(1) // st(0) *= st(1), stack: value*0x7FFFFF, (int)0x7FFFFF + fistp tempInt32 // pop st(0) into tempInt32, stack: (int)0x7FFFFF + mov edx, tempInt32 + jmp Float32_To_Int24_Clip_store + + Float32_To_Int24_Clip_clamp: + mov edx, dword ptr [esi] // load floating point value into integer register + shr edx, 31 // move sign bit into bit 0 + add esi, eax // increment source ptr + //lea esi, [esi+eax] + add edx, 0x7FFFFF // convert to maximum range integers + + Float32_To_Int24_Clip_store: + + mov byte ptr [edi], DL + shr edx, 8 + //mov byte ptr [edi+1], DL + //mov byte ptr [edi+2], DH + mov word ptr [edi+1], DX + + //add edi, ebx // increment destination ptr + lea edi, [edi+ebx] + + cmp esi, ecx // has src ptr reached end? + jne Float32_To_Int24_Clip_loop + + ffree st(0) + fincstp + + fwait + fnclex + fldcw savedFpuControlWord + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int24_DitherClip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, PaUtilTriangularDitherGenerator *ditherGenerator ) +{ +/* + float *src = (float*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + signed long temp; + + while( count-- ) + { + // convert to 32 bit and drop the low 8 bits + + // FIXME: the dither amplitude here appears to be too small by 8 bits + double dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); + // use smaller scaler to prevent overflow when we add the dither + double dithered = ((double)*src * (2147483646.0)) + dither; + PA_CLIP_( dithered, -2147483648., 2147483647. ); + + temp = (signed long) dithered; + + dest[0] = (unsigned char)(temp >> 8); + dest[1] = (unsigned char)(temp >> 16); + dest[2] = (unsigned char)(temp >> 24); + + src += sourceStride; + dest += destinationStride * 3; + } +*/ + + short savedFpuControlWord; + + // spill storage: + signed long sourceByteStride; + signed long highpassedDither; + + // dither state: + unsigned long ditherPrevious = ditherGenerator->previous; + unsigned long ditherRandSeed1 = ditherGenerator->randSeed1; + unsigned long ditherRandSeed2 = ditherGenerator->randSeed2; + + signed long tempInt32; + + __asm{ + // esi -> source ptr + // eax -> source byte stride + // edi -> destination ptr + // ebx -> destination byte stride + // ecx -> source end ptr + // edx -> temp + + mov esi, sourceBuffer + + mov edx, 4 // sizeof float32 + mov eax, sourceStride + imul eax, edx + + mov ecx, count + imul ecx, eax + add ecx, esi + + mov edi, destinationBuffer + + mov edx, 3 // sizeof int24 + mov ebx, destinationStride + imul ebx, edx + + fwait + fstcw savedFpuControlWord + fldcw fpuControlWord_ + + fld ditheredInt24Scaler_ // stack: int scaler + + Float32_To_Int24_DitherClip_loop: + + mov edx, dword ptr [esi] // load floating point value into integer register + + and edx, 0x7FFFFFFF // mask off sign + cmp edx, 0x3F800000 // greater than 1.0 or less than -1.0 + + jg Float32_To_Int24_DitherClip_clamp + + // load unscaled value into st(0) + fld dword ptr [esi] // stack: value, int scaler + add esi, eax // increment source ptr + //lea esi, [esi+eax] + fmul st(0), st(1) // st(0) *= st(1), stack: value*(int scaler), int scaler + + /* + // call PaUtil_GenerateFloatTriangularDither with C calling convention + mov sourceByteStride, eax // save eax + mov sourceEnd, ecx // save ecx + push ditherGenerator // pass ditherGenerator parameter on stack + call PaUtil_GenerateFloatTriangularDither // stack: dither, value*(int scaler), int scaler + pop edx // clear parameter off stack + mov ecx, sourceEnd // restore ecx + mov eax, sourceByteStride // restore eax + */ + + // generate dither + mov sourceByteStride, eax // save eax + mov edx, 196314165 + mov eax, ditherRandSeed1 + mul edx // eax:edx = eax * 196314165 + //add eax, 907633515 + lea eax, [eax+907633515] + mov ditherRandSeed1, eax + mov edx, 196314165 + mov eax, ditherRandSeed2 + mul edx // eax:edx = eax * 196314165 + //add eax, 907633515 + lea eax, [eax+907633515] + mov edx, ditherRandSeed1 + shr edx, PA_DITHER_SHIFT_ + mov ditherRandSeed2, eax + shr eax, PA_DITHER_SHIFT_ + //add eax, edx // eax -> current + lea eax, [eax+edx] + mov edx, ditherPrevious + neg edx + lea edx, [eax+edx] // highpass = current - previous + mov highpassedDither, edx + mov ditherPrevious, eax // previous = current + mov eax, sourceByteStride // restore eax + fild highpassedDither + fmul const_float_dither_scale_ + // end generate dither, dither signal in st(0) + + faddp st(1), st(0) // stack: dither * value*(int scaler), int scaler + fistp tempInt32 // pop st(0) into tempInt32, stack: int scaler + mov edx, tempInt32 + jmp Float32_To_Int24_DitherClip_store + + Float32_To_Int24_DitherClip_clamp: + mov edx, dword ptr [esi] // load floating point value into integer register + shr edx, 31 // move sign bit into bit 0 + add esi, eax // increment source ptr + //lea esi, [esi+eax] + add edx, 0x7FFFFF // convert to maximum range integers + + Float32_To_Int24_DitherClip_store: + + mov byte ptr [edi], DL + shr edx, 8 + //mov byte ptr [edi+1], DL + //mov byte ptr [edi+2], DH + mov word ptr [edi+1], DX + + //add edi, ebx // increment destination ptr + lea edi, [edi+ebx] + + cmp esi, ecx // has src ptr reached end? + jne Float32_To_Int24_DitherClip_loop + + ffree st(0) + fincstp + + fwait + fnclex + fldcw savedFpuControlWord + } + + ditherGenerator->previous = ditherPrevious; + ditherGenerator->randSeed1 = ditherRandSeed1; + ditherGenerator->randSeed2 = ditherRandSeed2; +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int16( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, PaUtilTriangularDitherGenerator *ditherGenerator ) +{ +/* + float *src = (float*)sourceBuffer; + signed short *dest = (signed short*)destinationBuffer; + (void)ditherGenerator; // unused parameter + + while( count-- ) + { + + short samp = (short) (*src * (32767.0f)); + *dest = samp; + + src += sourceStride; + dest += destinationStride; + } +*/ + + short savedFpuControlWord; + + (void) ditherGenerator; /* unused parameter */ + + __asm{ + // esi -> source ptr + // eax -> source byte stride + // edi -> destination ptr + // ebx -> destination byte stride + // ecx -> source end ptr + // edx -> temp + + mov esi, sourceBuffer + + mov edx, 4 // sizeof float32 + mov eax, sourceStride + imul eax, edx // source byte stride + + mov ecx, count + imul ecx, eax + add ecx, esi // source end ptr = count * source byte stride + source ptr + + mov edi, destinationBuffer + + mov edx, 2 // sizeof int16 + mov ebx, destinationStride + imul ebx, edx // destination byte stride + + fwait + fstcw savedFpuControlWord + fldcw fpuControlWord_ + + fld int16Scaler_ // stack: (int)0x7FFF + + Float32_To_Int16_loop: + + // load unscaled value into st(0) + fld dword ptr [esi] // stack: value, (int)0x7FFF + add esi, eax // increment source ptr + //lea esi, [esi+eax] + fmul st(0), st(1) // st(0) *= st(1), stack: value*0x7FFF, (int)0x7FFF + fistp word ptr [edi] // store scaled int into dest, stack: (int)0x7FFF + + add edi, ebx // increment destination ptr + //lea edi, [edi+ebx] + + cmp esi, ecx // has src ptr reached end? + jne Float32_To_Int16_loop + + ffree st(0) + fincstp + + fwait + fnclex + fldcw savedFpuControlWord + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int16_Clip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, PaUtilTriangularDitherGenerator *ditherGenerator ) +{ +/* + float *src = (float*)sourceBuffer; + signed short *dest = (signed short*)destinationBuffer; + (void)ditherGenerator; // unused parameter + + while( count-- ) + { + long samp = (signed long) (*src * (32767.0f)); + PA_CLIP_( samp, -0x8000, 0x7FFF ); + *dest = (signed short) samp; + + src += sourceStride; + dest += destinationStride; + } +*/ + + short savedFpuControlWord; + + (void) ditherGenerator; /* unused parameter */ + + __asm{ + // esi -> source ptr + // eax -> source byte stride + // edi -> destination ptr + // ebx -> destination byte stride + // ecx -> source end ptr + // edx -> temp + + mov esi, sourceBuffer + + mov edx, 4 // sizeof float32 + mov eax, sourceStride + imul eax, edx // source byte stride + + mov ecx, count + imul ecx, eax + add ecx, esi // source end ptr = count * source byte stride + source ptr + + mov edi, destinationBuffer + + mov edx, 2 // sizeof int16 + mov ebx, destinationStride + imul ebx, edx // destination byte stride + + fwait + fstcw savedFpuControlWord + fldcw fpuControlWord_ + + fld int16Scaler_ // stack: (int)0x7FFF + + Float32_To_Int16_Clip_loop: + + mov edx, dword ptr [esi] // load floating point value into integer register + + and edx, 0x7FFFFFFF // mask off sign + cmp edx, 0x3F800000 // greater than 1.0 or less than -1.0 + + jg Float32_To_Int16_Clip_clamp + + // load unscaled value into st(0) + fld dword ptr [esi] // stack: value, (int)0x7FFF + add esi, eax // increment source ptr + //lea esi, [esi+eax] + fmul st(0), st(1) // st(0) *= st(1), stack: value*0x7FFF, (int)0x7FFF + fistp word ptr [edi] // store scaled int into dest, stack: (int)0x7FFF + jmp Float32_To_Int16_Clip_stored + + Float32_To_Int16_Clip_clamp: + mov edx, dword ptr [esi] // load floating point value into integer register + shr edx, 31 // move sign bit into bit 0 + add esi, eax // increment source ptr + //lea esi, [esi+eax] + add dx, 0x7FFF // convert to maximum range integers + mov word ptr [edi], dx // store clamped into into dest + + Float32_To_Int16_Clip_stored: + + add edi, ebx // increment destination ptr + //lea edi, [edi+ebx] + + cmp esi, ecx // has src ptr reached end? + jne Float32_To_Int16_Clip_loop + + ffree st(0) + fincstp + + fwait + fnclex + fldcw savedFpuControlWord + } +} + +/* -------------------------------------------------------------------------- */ + +static void Float32_To_Int16_DitherClip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count, PaUtilTriangularDitherGenerator *ditherGenerator ) +{ +/* + float *src = (float*)sourceBuffer; + signed short *dest = (signed short*)destinationBuffer; + (void)ditherGenerator; // unused parameter + + while( count-- ) + { + + float dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); + // use smaller scaler to prevent overflow when we add the dither + float dithered = (*src * (32766.0f)) + dither; + signed long samp = (signed long) dithered; + PA_CLIP_( samp, -0x8000, 0x7FFF ); + *dest = (signed short) samp; + + src += sourceStride; + dest += destinationStride; + } +*/ + + short savedFpuControlWord; + + // spill storage: + signed long sourceByteStride; + signed long highpassedDither; + + // dither state: + unsigned long ditherPrevious = ditherGenerator->previous; + unsigned long ditherRandSeed1 = ditherGenerator->randSeed1; + unsigned long ditherRandSeed2 = ditherGenerator->randSeed2; + + __asm{ + // esi -> source ptr + // eax -> source byte stride + // edi -> destination ptr + // ebx -> destination byte stride + // ecx -> source end ptr + // edx -> temp + + mov esi, sourceBuffer + + mov edx, 4 // sizeof float32 + mov eax, sourceStride + imul eax, edx // source byte stride + + mov ecx, count + imul ecx, eax + add ecx, esi // source end ptr = count * source byte stride + source ptr + + mov edi, destinationBuffer + + mov edx, 2 // sizeof int16 + mov ebx, destinationStride + imul ebx, edx // destination byte stride + + fwait + fstcw savedFpuControlWord + fldcw fpuControlWord_ + + fld ditheredInt16Scaler_ // stack: int scaler + + Float32_To_Int16_DitherClip_loop: + + mov edx, dword ptr [esi] // load floating point value into integer register + + and edx, 0x7FFFFFFF // mask off sign + cmp edx, 0x3F800000 // greater than 1.0 or less than -1.0 + + jg Float32_To_Int16_DitherClip_clamp + + // load unscaled value into st(0) + fld dword ptr [esi] // stack: value, int scaler + add esi, eax // increment source ptr + //lea esi, [esi+eax] + fmul st(0), st(1) // st(0) *= st(1), stack: value*(int scaler), int scaler + + /* + // call PaUtil_GenerateFloatTriangularDither with C calling convention + mov sourceByteStride, eax // save eax + mov sourceEnd, ecx // save ecx + push ditherGenerator // pass ditherGenerator parameter on stack + call PaUtil_GenerateFloatTriangularDither // stack: dither, value*(int scaler), int scaler + pop edx // clear parameter off stack + mov ecx, sourceEnd // restore ecx + mov eax, sourceByteStride // restore eax + */ + + // generate dither + mov sourceByteStride, eax // save eax + mov edx, 196314165 + mov eax, ditherRandSeed1 + mul edx // eax:edx = eax * 196314165 + //add eax, 907633515 + lea eax, [eax+907633515] + mov ditherRandSeed1, eax + mov edx, 196314165 + mov eax, ditherRandSeed2 + mul edx // eax:edx = eax * 196314165 + //add eax, 907633515 + lea eax, [eax+907633515] + mov edx, ditherRandSeed1 + shr edx, PA_DITHER_SHIFT_ + mov ditherRandSeed2, eax + shr eax, PA_DITHER_SHIFT_ + //add eax, edx // eax -> current + lea eax, [eax+edx] // current = randSeed1>>x + randSeed2>>x + mov edx, ditherPrevious + neg edx + lea edx, [eax+edx] // highpass = current - previous + mov highpassedDither, edx + mov ditherPrevious, eax // previous = current + mov eax, sourceByteStride // restore eax + fild highpassedDither + fmul const_float_dither_scale_ + // end generate dither, dither signal in st(0) + + faddp st(1), st(0) // stack: dither * value*(int scaler), int scaler + fistp word ptr [edi] // store scaled int into dest, stack: int scaler + jmp Float32_To_Int16_DitherClip_stored + + Float32_To_Int16_DitherClip_clamp: + mov edx, dword ptr [esi] // load floating point value into integer register + shr edx, 31 // move sign bit into bit 0 + add esi, eax // increment source ptr + //lea esi, [esi+eax] + add dx, 0x7FFF // convert to maximum range integers + mov word ptr [edi], dx // store clamped into into dest + + Float32_To_Int16_DitherClip_stored: + + add edi, ebx // increment destination ptr + //lea edi, [edi+ebx] + + cmp esi, ecx // has src ptr reached end? + jne Float32_To_Int16_DitherClip_loop + + ffree st(0) + fincstp + + fwait + fnclex + fldcw savedFpuControlWord + } + + ditherGenerator->previous = ditherPrevious; + ditherGenerator->randSeed1 = ditherRandSeed1; + ditherGenerator->randSeed2 = ditherRandSeed2; +} + +/* -------------------------------------------------------------------------- */ + +void PaUtil_InitializeX86PlainConverters( void ) +{ + paConverters.Float32_To_Int32 = Float32_To_Int32; + paConverters.Float32_To_Int32_Clip = Float32_To_Int32_Clip; + paConverters.Float32_To_Int32_DitherClip = Float32_To_Int32_DitherClip; + + paConverters.Float32_To_Int24 = Float32_To_Int24; + paConverters.Float32_To_Int24_Clip = Float32_To_Int24_Clip; + paConverters.Float32_To_Int24_DitherClip = Float32_To_Int24_DitherClip; + + paConverters.Float32_To_Int16 = Float32_To_Int16; + paConverters.Float32_To_Int16_Clip = Float32_To_Int16_Clip; + paConverters.Float32_To_Int16_DitherClip = Float32_To_Int16_DitherClip; +} + +/* -------------------------------------------------------------------------- */ diff --git a/src/audio/portaudio/pa_win/pa_x86_plain_converters.h b/src/audio/portaudio/pa_win/pa_x86_plain_converters.h new file mode 100644 index 0000000000000000000000000000000000000000..f56c710fda5885470185effb670de69882dfdb81 --- /dev/null +++ b/src/audio/portaudio/pa_win/pa_x86_plain_converters.h @@ -0,0 +1,19 @@ +#ifndef PA_X86_PLAIN_CONVERTERS_H +#define PA_X86_PLAIN_CONVERTERS_H + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +/** + @brief Install optimised converter functions suitable for all IA32 processors +*/ +void PaUtil_InitializeX86PlainConverters( void ); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PA_X86_PLAIN_CONVERTERS_H */ diff --git a/src/audio/portaudio/pa_win_ds/dsound_wrapper.c b/src/audio/portaudio/pa_win_ds/dsound_wrapper.c new file mode 100644 index 0000000000000000000000000000000000000000..e29019a8d5c275e965558b30cd11f9b8aa5718e4 --- /dev/null +++ b/src/audio/portaudio/pa_win_ds/dsound_wrapper.c @@ -0,0 +1,616 @@ +/* + * $Id$ + * Simplified DirectSound interface. + * + * Author: Phil Burk & Robert Marsanyi + * + * PortAudio Portable Real-Time Audio Library + * For more information see: http://www.softsynth.com/portaudio/ + * DirectSound Implementation + * Copyright (c) 1999-2000 Phil Burk & Robert Marsanyi + * + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <math.h> + +#include "dsound_wrapper.h" +#include "pa_trace.h" + +/* + Rather than linking with dxguid.a or using "#define INITGUID" to force a + header file to instantiate the required GUID(s), we define them directly + below. +*/ +#include <initguid.h> // needed for the DEFINE_GUID macro +DEFINE_GUID(IID_IDirectSoundNotify, 0xb0210783, 0x89cd, 0x11d0, 0xaf, 0x8, 0x0, 0xa0, 0xc9, 0x25, 0xcd, 0x16); + + +/************************************************************************************/ +DSoundEntryPoints dswDSoundEntryPoints = { 0, 0, 0, 0, 0, 0, 0 }; +/************************************************************************************/ +static HRESULT WINAPI DummyDirectSoundCreate(LPGUID lpcGuidDevice, LPDIRECTSOUND *ppDS, LPUNKNOWN pUnkOuter) +{ + (void)lpcGuidDevice; /* unused parameter */ + (void)ppDS; /* unused parameter */ + (void)pUnkOuter; /* unused parameter */ + return E_NOTIMPL; +} + +static HRESULT WINAPI DummyDirectSoundEnumerateW(LPDSENUMCALLBACKW lpDSEnumCallback, LPVOID lpContext) +{ + (void)lpDSEnumCallback; /* unused parameter */ + (void)lpContext; /* unused parameter */ + return E_NOTIMPL; +} + +static HRESULT WINAPI DummyDirectSoundEnumerateA(LPDSENUMCALLBACKA lpDSEnumCallback, LPVOID lpContext) +{ + (void)lpDSEnumCallback; /* unused parameter */ + (void)lpContext; /* unused parameter */ + return E_NOTIMPL; +} + +static HRESULT WINAPI DummyDirectSoundCaptureCreate(LPGUID lpcGUID, LPDIRECTSOUNDCAPTURE *lplpDSC, LPUNKNOWN pUnkOuter) +{ + (void)lpcGUID; /* unused parameter */ + (void)lplpDSC; /* unused parameter */ + (void)pUnkOuter; /* unused parameter */ + return E_NOTIMPL; +} + +static HRESULT WINAPI DummyDirectSoundCaptureEnumerateW(LPDSENUMCALLBACKW lpDSCEnumCallback, LPVOID lpContext) +{ + (void)lpDSCEnumCallback; /* unused parameter */ + (void)lpContext; /* unused parameter */ + return E_NOTIMPL; +} + +static HRESULT WINAPI DummyDirectSoundCaptureEnumerateA(LPDSENUMCALLBACKA lpDSCEnumCallback, LPVOID lpContext) +{ + (void)lpDSCEnumCallback; /* unused parameter */ + (void)lpContext; /* unused parameter */ + return E_NOTIMPL; +} +/************************************************************************************/ +void DSW_InitializeDSoundEntryPoints(void) +{ + dswDSoundEntryPoints.hInstance_ = LoadLibrary("dsound.dll"); + if( dswDSoundEntryPoints.hInstance_ != NULL ) + { + dswDSoundEntryPoints.DirectSoundCreate = + (HRESULT (WINAPI *)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN)) + GetProcAddress( dswDSoundEntryPoints.hInstance_, "DirectSoundCreate" ); + if( dswDSoundEntryPoints.DirectSoundCreate == NULL ) + dswDSoundEntryPoints.DirectSoundCreate = DummyDirectSoundCreate; + + dswDSoundEntryPoints.DirectSoundEnumerateW = + (HRESULT (WINAPI *)(LPDSENUMCALLBACKW, LPVOID)) + GetProcAddress( dswDSoundEntryPoints.hInstance_, "DirectSoundEnumerateW" ); + if( dswDSoundEntryPoints.DirectSoundEnumerateW == NULL ) + dswDSoundEntryPoints.DirectSoundEnumerateW = DummyDirectSoundEnumerateW; + + dswDSoundEntryPoints.DirectSoundEnumerateA = + (HRESULT (WINAPI *)(LPDSENUMCALLBACKA, LPVOID)) + GetProcAddress( dswDSoundEntryPoints.hInstance_, "DirectSoundEnumerateA" ); + if( dswDSoundEntryPoints.DirectSoundEnumerateA == NULL ) + dswDSoundEntryPoints.DirectSoundEnumerateA = DummyDirectSoundEnumerateA; + + dswDSoundEntryPoints.DirectSoundCaptureCreate = + (HRESULT (WINAPI *)(LPGUID, LPDIRECTSOUNDCAPTURE *, LPUNKNOWN)) + GetProcAddress( dswDSoundEntryPoints.hInstance_, "DirectSoundCaptureCreate" ); + if( dswDSoundEntryPoints.DirectSoundCaptureCreate == NULL ) + dswDSoundEntryPoints.DirectSoundCaptureCreate = DummyDirectSoundCaptureCreate; + + dswDSoundEntryPoints.DirectSoundCaptureEnumerateW = + (HRESULT (WINAPI *)(LPDSENUMCALLBACKW, LPVOID)) + GetProcAddress( dswDSoundEntryPoints.hInstance_, "DirectSoundCaptureEnumerateW" ); + if( dswDSoundEntryPoints.DirectSoundCaptureEnumerateW == NULL ) + dswDSoundEntryPoints.DirectSoundCaptureEnumerateW = DummyDirectSoundCaptureEnumerateW; + + dswDSoundEntryPoints.DirectSoundCaptureEnumerateA = + (HRESULT (WINAPI *)(LPDSENUMCALLBACKA, LPVOID)) + GetProcAddress( dswDSoundEntryPoints.hInstance_, "DirectSoundCaptureEnumerateA" ); + if( dswDSoundEntryPoints.DirectSoundCaptureEnumerateA == NULL ) + dswDSoundEntryPoints.DirectSoundCaptureEnumerateA = DummyDirectSoundCaptureEnumerateA; + } + else + { + /* initialize with dummy entry points to make live easy when ds isn't present */ + dswDSoundEntryPoints.DirectSoundCreate = DummyDirectSoundCreate; + dswDSoundEntryPoints.DirectSoundEnumerateW = DummyDirectSoundEnumerateW; + dswDSoundEntryPoints.DirectSoundEnumerateA = DummyDirectSoundEnumerateA; + dswDSoundEntryPoints.DirectSoundCaptureCreate = DummyDirectSoundCaptureCreate; + dswDSoundEntryPoints.DirectSoundCaptureEnumerateW = DummyDirectSoundCaptureEnumerateW; + dswDSoundEntryPoints.DirectSoundCaptureEnumerateA = DummyDirectSoundCaptureEnumerateA; + } +} +/************************************************************************************/ +void DSW_TerminateDSoundEntryPoints(void) +{ + if( dswDSoundEntryPoints.hInstance_ != NULL ) + { + FreeLibrary( dswDSoundEntryPoints.hInstance_ ); + dswDSoundEntryPoints.hInstance_ = NULL; + /* ensure that we crash reliably if the entry points arent initialised */ + dswDSoundEntryPoints.DirectSoundCreate = 0; + dswDSoundEntryPoints.DirectSoundEnumerateW = 0; + dswDSoundEntryPoints.DirectSoundEnumerateA = 0; + dswDSoundEntryPoints.DirectSoundCaptureCreate = 0; + dswDSoundEntryPoints.DirectSoundCaptureEnumerateW = 0; + dswDSoundEntryPoints.DirectSoundCaptureEnumerateA = 0; + } +} +/************************************************************************************/ +void DSW_Term( DSoundWrapper *dsw ) +{ + // Cleanup the sound buffers + if (dsw->dsw_OutputBuffer) + { + IDirectSoundBuffer_Stop( dsw->dsw_OutputBuffer ); + IDirectSoundBuffer_Release( dsw->dsw_OutputBuffer ); + dsw->dsw_OutputBuffer = NULL; + } + + if (dsw->dsw_InputBuffer) + { + IDirectSoundCaptureBuffer_Stop( dsw->dsw_InputBuffer ); + IDirectSoundCaptureBuffer_Release( dsw->dsw_InputBuffer ); + dsw->dsw_InputBuffer = NULL; + } + + if (dsw->dsw_pDirectSoundCapture) + { + IDirectSoundCapture_Release( dsw->dsw_pDirectSoundCapture ); + dsw->dsw_pDirectSoundCapture = NULL; + } + + if (dsw->dsw_pDirectSound) + { + IDirectSound_Release( dsw->dsw_pDirectSound ); + dsw->dsw_pDirectSound = NULL; + } +} +/************************************************************************************/ +HRESULT DSW_Init( DSoundWrapper *dsw ) +{ + memset( dsw, 0, sizeof(DSoundWrapper) ); + return 0; +} +/************************************************************************************/ +HRESULT DSW_InitOutputDevice( DSoundWrapper *dsw, LPGUID lpGUID ) +{ + // Create the DS object + HRESULT hr = dswDSoundEntryPoints.DirectSoundCreate( lpGUID, &dsw->dsw_pDirectSound, NULL ); + if( hr != DS_OK ) return hr; + return hr; +} + +/************************************************************************************/ +HRESULT DSW_InitOutputBuffer( DSoundWrapper *dsw, unsigned long nFrameRate, WORD nChannels, int bytesPerBuffer ) +{ + DWORD dwDataLen; + DWORD playCursor; + HRESULT result; + LPDIRECTSOUNDBUFFER pPrimaryBuffer; + HWND hWnd; + HRESULT hr; + WAVEFORMATEX wfFormat; + DSBUFFERDESC primaryDesc; + DSBUFFERDESC secondaryDesc; + unsigned char* pDSBuffData; + LARGE_INTEGER counterFrequency; + + dsw->dsw_OutputSize = bytesPerBuffer; + dsw->dsw_OutputRunning = FALSE; + dsw->dsw_OutputUnderflows = 0; + dsw->dsw_FramesWritten = 0; + dsw->dsw_BytesPerOutputFrame = nChannels * sizeof(short); + + // We were using getForegroundWindow() but sometimes the ForegroundWindow may not be the + // applications's window. Also if that window is closed before the Buffer is closed + // then DirectSound can crash. (Thanks for Scott Patterson for reporting this.) + // So we will use GetDesktopWindow() which was suggested by Miller Puckette. + // hWnd = GetForegroundWindow(); + // + // FIXME: The example code I have on the net creates a hidden window that + // is managed by our code - I think we should do that - one hidden + // window for the whole of Pa_DS + // + hWnd = GetDesktopWindow(); + + // Set cooperative level to DSSCL_EXCLUSIVE so that we can get 16 bit output, 44.1 KHz. + // Exclusize also prevents unexpected sounds from other apps during a performance. + if ((hr = IDirectSound_SetCooperativeLevel( dsw->dsw_pDirectSound, + hWnd, DSSCL_EXCLUSIVE)) != DS_OK) + { + return hr; + } + + // ----------------------------------------------------------------------- + // Create primary buffer and set format just so we can specify our custom format. + // Otherwise we would be stuck with the default which might be 8 bit or 22050 Hz. + // Setup the primary buffer description + ZeroMemory(&primaryDesc, sizeof(DSBUFFERDESC)); + primaryDesc.dwSize = sizeof(DSBUFFERDESC); + primaryDesc.dwFlags = DSBCAPS_PRIMARYBUFFER; // all panning, mixing, etc done by synth + primaryDesc.dwBufferBytes = 0; + primaryDesc.lpwfxFormat = NULL; + // Create the buffer + if ((result = IDirectSound_CreateSoundBuffer( dsw->dsw_pDirectSound, + &primaryDesc, &pPrimaryBuffer, NULL)) != DS_OK) return result; + // Define the buffer format + wfFormat.wFormatTag = WAVE_FORMAT_PCM; + wfFormat.nChannels = nChannels; + wfFormat.nSamplesPerSec = nFrameRate; + wfFormat.wBitsPerSample = 8 * sizeof(short); + wfFormat.nBlockAlign = (WORD)(wfFormat.nChannels * (wfFormat.wBitsPerSample / 8)); + wfFormat.nAvgBytesPerSec = wfFormat.nSamplesPerSec * wfFormat.nBlockAlign; + wfFormat.cbSize = 0; /* No extended format info. */ + // Set the primary buffer's format + if((result = IDirectSoundBuffer_SetFormat( pPrimaryBuffer, &wfFormat)) != DS_OK) return result; + + // ---------------------------------------------------------------------- + // Setup the secondary buffer description + ZeroMemory(&secondaryDesc, sizeof(DSBUFFERDESC)); + secondaryDesc.dwSize = sizeof(DSBUFFERDESC); + secondaryDesc.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2; + secondaryDesc.dwBufferBytes = bytesPerBuffer; + secondaryDesc.lpwfxFormat = &wfFormat; + // Create the secondary buffer + if ((result = IDirectSound_CreateSoundBuffer( dsw->dsw_pDirectSound, + &secondaryDesc, &dsw->dsw_OutputBuffer, NULL)) != DS_OK) return result; + // Lock the DS buffer + if ((result = IDirectSoundBuffer_Lock( dsw->dsw_OutputBuffer, 0, dsw->dsw_OutputSize, (LPVOID*)&pDSBuffData, + &dwDataLen, NULL, 0, 0)) != DS_OK) return result; + // Zero the DS buffer + ZeroMemory(pDSBuffData, dwDataLen); + // Unlock the DS buffer + if ((result = IDirectSoundBuffer_Unlock( dsw->dsw_OutputBuffer, pDSBuffData, dwDataLen, NULL, 0)) != DS_OK) return result; + if( QueryPerformanceFrequency( &counterFrequency ) ) + { + int framesInBuffer = bytesPerBuffer / (nChannels * sizeof(short)); + dsw->dsw_CounterTicksPerBuffer.QuadPart = (counterFrequency.QuadPart * framesInBuffer) / nFrameRate; + } + else + { + dsw->dsw_CounterTicksPerBuffer.QuadPart = 0; + } + // Let DSound set the starting write position because if we set it to zero, it looks like the + // buffer is full to begin with. This causes a long pause before sound starts when using large buffers. + hr = IDirectSoundBuffer_GetCurrentPosition( dsw->dsw_OutputBuffer, &playCursor, &dsw->dsw_WriteOffset ); + if( hr != DS_OK ) + { + return hr; + } + dsw->dsw_FramesWritten = dsw->dsw_WriteOffset / dsw->dsw_BytesPerOutputFrame; + /* printf("DSW_InitOutputBuffer: playCursor = %d, writeCursor = %d\n", playCursor, dsw->dsw_WriteOffset ); */ + return DS_OK; +} + +/************************************************************************************/ +HRESULT DSW_StartOutput( DSoundWrapper *dsw ) +{ + HRESULT hr; + QueryPerformanceCounter( &dsw->dsw_LastPlayTime ); + dsw->dsw_LastPlayCursor = 0; + dsw->dsw_FramesPlayed = 0; + hr = IDirectSoundBuffer_SetCurrentPosition( dsw->dsw_OutputBuffer, 0 ); + if( hr != DS_OK ) + { + return hr; + } + // Start the buffer playback in a loop. + if( dsw->dsw_OutputBuffer != NULL ) + { + hr = IDirectSoundBuffer_Play( dsw->dsw_OutputBuffer, 0, 0, DSBPLAY_LOOPING ); + if( hr != DS_OK ) + { + return hr; + } + dsw->dsw_OutputRunning = TRUE; + } + + return 0; +} +/************************************************************************************/ +HRESULT DSW_StopOutput( DSoundWrapper *dsw ) +{ + // Stop the buffer playback + if( dsw->dsw_OutputBuffer != NULL ) + { + dsw->dsw_OutputRunning = FALSE; + return IDirectSoundBuffer_Stop( dsw->dsw_OutputBuffer ); + } + else return 0; +} + +/************************************************************************************/ +HRESULT DSW_QueryOutputFilled( DSoundWrapper *dsw, long *bytesFilledPtr ) +{ + HRESULT hr; + DWORD playCursor; + DWORD writeCursor; + long bytesFilled; + // Query to see where play position is. + // We don't need the writeCursor but sometimes DirectSound doesn't handle NULLS correctly + // so let's pass a pointer just to be safe. + hr = IDirectSoundBuffer_GetCurrentPosition( dsw->dsw_OutputBuffer, &playCursor, &writeCursor ); + if( hr != DS_OK ) + { + return hr; + } + bytesFilled = dsw->dsw_WriteOffset - playCursor; + if( bytesFilled < 0 ) bytesFilled += dsw->dsw_OutputSize; // unwrap offset + *bytesFilledPtr = bytesFilled; + return hr; +} + +/************************************************************************************ + * Determine how much space can be safely written to in DS buffer. + * Detect underflows and overflows. + * Does not allow writing into safety gap maintained by DirectSound. + */ +HRESULT DSW_QueryOutputSpace( DSoundWrapper *dsw, long *bytesEmpty ) +{ + HRESULT hr; + DWORD playCursor; + DWORD writeCursor; + long numBytesEmpty; + long playWriteGap; + // Query to see how much room is in buffer. + hr = IDirectSoundBuffer_GetCurrentPosition( dsw->dsw_OutputBuffer, &playCursor, &writeCursor ); + if( hr != DS_OK ) + { + return hr; + } + // Determine size of gap between playIndex and WriteIndex that we cannot write into. + playWriteGap = writeCursor - playCursor; + if( playWriteGap < 0 ) playWriteGap += dsw->dsw_OutputSize; // unwrap + /* DirectSound doesn't have a large enough playCursor so we cannot detect wrap-around. */ + /* Attempt to detect playCursor wrap-around and correct it. */ + if( dsw->dsw_OutputRunning && (dsw->dsw_CounterTicksPerBuffer.QuadPart != 0) ) + { + /* How much time has elapsed since last check. */ + LARGE_INTEGER currentTime; + LARGE_INTEGER elapsedTime; + long bytesPlayed; + long bytesExpected; + long buffersWrapped; + QueryPerformanceCounter( ¤tTime ); + elapsedTime.QuadPart = currentTime.QuadPart - dsw->dsw_LastPlayTime.QuadPart; + dsw->dsw_LastPlayTime = currentTime; + /* How many bytes does DirectSound say have been played. */ + bytesPlayed = playCursor - dsw->dsw_LastPlayCursor; + if( bytesPlayed < 0 ) bytesPlayed += dsw->dsw_OutputSize; // unwrap + dsw->dsw_LastPlayCursor = playCursor; + /* Calculate how many bytes we would have expected to been played by now. */ + bytesExpected = (long) ((elapsedTime.QuadPart * dsw->dsw_OutputSize) / dsw->dsw_CounterTicksPerBuffer.QuadPart); + buffersWrapped = (bytesExpected - bytesPlayed) / dsw->dsw_OutputSize; + if( buffersWrapped > 0 ) + { + playCursor += (buffersWrapped * dsw->dsw_OutputSize); + bytesPlayed += (buffersWrapped * dsw->dsw_OutputSize); + } + /* Maintain frame output cursor. */ + dsw->dsw_FramesPlayed += (bytesPlayed / dsw->dsw_BytesPerOutputFrame); + } + numBytesEmpty = playCursor - dsw->dsw_WriteOffset; + if( numBytesEmpty < 0 ) numBytesEmpty += dsw->dsw_OutputSize; // unwrap offset + /* Have we underflowed? */ + if( numBytesEmpty > (dsw->dsw_OutputSize - playWriteGap) ) + { + if( dsw->dsw_OutputRunning ) + { + dsw->dsw_OutputUnderflows += 1; + } + dsw->dsw_WriteOffset = writeCursor; + numBytesEmpty = dsw->dsw_OutputSize - playWriteGap; + } + *bytesEmpty = numBytesEmpty; + return hr; +} + +/************************************************************************************/ +HRESULT DSW_ZeroEmptySpace( DSoundWrapper *dsw ) +{ + HRESULT hr; + LPBYTE lpbuf1 = NULL; + LPBYTE lpbuf2 = NULL; + DWORD dwsize1 = 0; + DWORD dwsize2 = 0; + long bytesEmpty; + hr = DSW_QueryOutputSpace( dsw, &bytesEmpty ); // updates dsw_FramesPlayed + if (hr != DS_OK) return hr; + if( bytesEmpty == 0 ) return DS_OK; + // Lock free space in the DS + hr = IDirectSoundBuffer_Lock( dsw->dsw_OutputBuffer, dsw->dsw_WriteOffset, bytesEmpty, (void **) &lpbuf1, &dwsize1, + (void **) &lpbuf2, &dwsize2, 0); + if (hr == DS_OK) + { + // Copy the buffer into the DS + ZeroMemory(lpbuf1, dwsize1); + if(lpbuf2 != NULL) + { + ZeroMemory(lpbuf2, dwsize2); + } + // Update our buffer offset and unlock sound buffer + dsw->dsw_WriteOffset = (dsw->dsw_WriteOffset + dwsize1 + dwsize2) % dsw->dsw_OutputSize; + IDirectSoundBuffer_Unlock( dsw->dsw_OutputBuffer, lpbuf1, dwsize1, lpbuf2, dwsize2); + dsw->dsw_FramesWritten += bytesEmpty / dsw->dsw_BytesPerOutputFrame; + } + return hr; +} + +/************************************************************************************/ +HRESULT DSW_WriteBlock( DSoundWrapper *dsw, char *buf, long numBytes ) +{ + HRESULT hr; + LPBYTE lpbuf1 = NULL; + LPBYTE lpbuf2 = NULL; + DWORD dwsize1 = 0; + DWORD dwsize2 = 0; + // Lock free space in the DS + hr = IDirectSoundBuffer_Lock( dsw->dsw_OutputBuffer, dsw->dsw_WriteOffset, numBytes, (void **) &lpbuf1, &dwsize1, + (void **) &lpbuf2, &dwsize2, 0); + if (hr == DS_OK) + { + // Copy the buffer into the DS + CopyMemory(lpbuf1, buf, dwsize1); + if(lpbuf2 != NULL) + { + CopyMemory(lpbuf2, buf+dwsize1, dwsize2); + } + // Update our buffer offset and unlock sound buffer + dsw->dsw_WriteOffset = (dsw->dsw_WriteOffset + dwsize1 + dwsize2) % dsw->dsw_OutputSize; + IDirectSoundBuffer_Unlock( dsw->dsw_OutputBuffer, lpbuf1, dwsize1, lpbuf2, dwsize2); + dsw->dsw_FramesWritten += numBytes / dsw->dsw_BytesPerOutputFrame; + } + return hr; +} + +/************************************************************************************/ +DWORD DSW_GetOutputStatus( DSoundWrapper *dsw ) +{ + DWORD status; + if (IDirectSoundBuffer_GetStatus( dsw->dsw_OutputBuffer, &status ) != DS_OK) + return( DSERR_INVALIDPARAM ); + else + return( status ); +} + +/* These routines are used to support audio input. + * Do NOT compile these calls when using NT4 because it does + * not support the entry points. + */ +/************************************************************************************/ +HRESULT DSW_InitInputDevice( DSoundWrapper *dsw, LPGUID lpGUID ) +{ + HRESULT hr = dswDSoundEntryPoints.DirectSoundCaptureCreate( lpGUID, &dsw->dsw_pDirectSoundCapture, NULL ); + if( hr != DS_OK ) return hr; + return hr; +} +/************************************************************************************/ +HRESULT DSW_InitInputBuffer( DSoundWrapper *dsw, unsigned long nFrameRate, WORD nChannels, int bytesPerBuffer ) +{ + DSCBUFFERDESC captureDesc; + WAVEFORMATEX wfFormat; + HRESULT result; + + dsw->dsw_BytesPerInputFrame = nChannels * sizeof(short); + + // Define the buffer format + wfFormat.wFormatTag = WAVE_FORMAT_PCM; + wfFormat.nChannels = nChannels; + wfFormat.nSamplesPerSec = nFrameRate; + wfFormat.wBitsPerSample = 8 * sizeof(short); + wfFormat.nBlockAlign = (WORD)(wfFormat.nChannels * (wfFormat.wBitsPerSample / 8)); + wfFormat.nAvgBytesPerSec = wfFormat.nSamplesPerSec * wfFormat.nBlockAlign; + wfFormat.cbSize = 0; /* No extended format info. */ + dsw->dsw_InputSize = bytesPerBuffer; + // ---------------------------------------------------------------------- + // Setup the secondary buffer description + ZeroMemory(&captureDesc, sizeof(DSCBUFFERDESC)); + captureDesc.dwSize = sizeof(DSCBUFFERDESC); + captureDesc.dwFlags = 0; + captureDesc.dwBufferBytes = bytesPerBuffer; + captureDesc.lpwfxFormat = &wfFormat; + // Create the capture buffer + if ((result = IDirectSoundCapture_CreateCaptureBuffer( dsw->dsw_pDirectSoundCapture, + &captureDesc, &dsw->dsw_InputBuffer, NULL)) != DS_OK) return result; + dsw->dsw_ReadOffset = 0; // reset last read position to start of buffer + return DS_OK; +} + +/************************************************************************************/ +HRESULT DSW_StartInput( DSoundWrapper *dsw ) +{ + // Start the buffer playback + if( dsw->dsw_InputBuffer != NULL ) + { + return IDirectSoundCaptureBuffer_Start( dsw->dsw_InputBuffer, DSCBSTART_LOOPING ); + } + else return 0; +} + +/************************************************************************************/ +HRESULT DSW_StopInput( DSoundWrapper *dsw ) +{ + // Stop the buffer playback + if( dsw->dsw_InputBuffer != NULL ) + { + return IDirectSoundCaptureBuffer_Stop( dsw->dsw_InputBuffer ); + } + else return 0; +} + +/************************************************************************************/ +HRESULT DSW_QueryInputFilled( DSoundWrapper *dsw, long *bytesFilled ) +{ + HRESULT hr; + DWORD capturePos; + DWORD readPos; + long filled; + // Query to see how much data is in buffer. + // We don't need the capture position but sometimes DirectSound doesn't handle NULLS correctly + // so let's pass a pointer just to be safe. + hr = IDirectSoundCaptureBuffer_GetCurrentPosition( dsw->dsw_InputBuffer, &capturePos, &readPos ); + if( hr != DS_OK ) + { + return hr; + } + filled = readPos - dsw->dsw_ReadOffset; + if( filled < 0 ) filled += dsw->dsw_InputSize; // unwrap offset + *bytesFilled = filled; + return hr; +} + +/************************************************************************************/ +HRESULT DSW_ReadBlock( DSoundWrapper *dsw, char *buf, long numBytes ) +{ + HRESULT hr; + LPBYTE lpbuf1 = NULL; + LPBYTE lpbuf2 = NULL; + DWORD dwsize1 = 0; + DWORD dwsize2 = 0; + // Lock free space in the DS + hr = IDirectSoundCaptureBuffer_Lock ( dsw->dsw_InputBuffer, dsw->dsw_ReadOffset, numBytes, (void **) &lpbuf1, &dwsize1, + (void **) &lpbuf2, &dwsize2, 0); + if (hr == DS_OK) + { + // Copy from DS to the buffer + CopyMemory( buf, lpbuf1, dwsize1); + if(lpbuf2 != NULL) + { + CopyMemory( buf+dwsize1, lpbuf2, dwsize2); + } + // Update our buffer offset and unlock sound buffer + dsw->dsw_ReadOffset = (dsw->dsw_ReadOffset + dwsize1 + dwsize2) % dsw->dsw_InputSize; + IDirectSoundCaptureBuffer_Unlock ( dsw->dsw_InputBuffer, lpbuf1, dwsize1, lpbuf2, dwsize2); + } + return hr; +} + diff --git a/src/audio/portaudio/pa_win_ds/dsound_wrapper.h b/src/audio/portaudio/pa_win_ds/dsound_wrapper.h new file mode 100644 index 0000000000000000000000000000000000000000..713f4a71deb5ee818c9d56237bb4fd6c96ba8288 --- /dev/null +++ b/src/audio/portaudio/pa_win_ds/dsound_wrapper.h @@ -0,0 +1,130 @@ +#ifndef __DSOUND_WRAPPER_H +#define __DSOUND_WRAPPER_H +/* + * $Id$ + * Simplified DirectSound interface. + * + * Author: Phil Burk & Robert Marsanyi + * + * For PortAudio Portable Real-Time Audio Library + * For more information see: http://www.softsynth.com/portaudio/ + * DirectSound Implementation + * Copyright (c) 1999-2000 Phil Burk & Robert Marsanyi + * + * 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. + * + * 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. + * + * 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. + * + */ + +/* on Borland compilers, WIN32 doesn't seem to be defined by default, which + breaks DSound.h. Adding the define here fixes the problem. - rossb. */ +#ifdef __BORLANDC__ +#if !defined(WIN32) +#define WIN32 +#endif +#endif + +/* + We are only using DX3 in here, no need to polute the namespace - davidv +*/ +#define DIRECTSOUND_VERSION 0x0300 + +#include <DSound.h> + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +typedef struct +{ + HINSTANCE hInstance_; + + HRESULT (WINAPI *DirectSoundCreate)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN); + HRESULT (WINAPI *DirectSoundEnumerateW)(LPDSENUMCALLBACKW, LPVOID); + HRESULT (WINAPI *DirectSoundEnumerateA)(LPDSENUMCALLBACKA, LPVOID); + + HRESULT (WINAPI *DirectSoundCaptureCreate)(LPGUID, LPDIRECTSOUNDCAPTURE *, LPUNKNOWN); + HRESULT (WINAPI *DirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW, LPVOID); + HRESULT (WINAPI *DirectSoundCaptureEnumerateA)(LPDSENUMCALLBACKA, LPVOID); +}DSoundEntryPoints; + +extern DSoundEntryPoints dswDSoundEntryPoints; + +void DSW_InitializeDSoundEntryPoints(void); +void DSW_TerminateDSoundEntryPoints(void); + +#define DSW_NUM_POSITIONS (4) +#define DSW_NUM_EVENTS (5) +#define DSW_TERMINATION_EVENT (DSW_NUM_POSITIONS) + +typedef struct +{ +/* Output */ + LPDIRECTSOUND dsw_pDirectSound; + LPDIRECTSOUNDBUFFER dsw_OutputBuffer; + DWORD dsw_WriteOffset; /* last write position */ + INT dsw_OutputSize; + INT dsw_BytesPerOutputFrame; + /* Try to detect play buffer underflows. */ + LARGE_INTEGER dsw_CounterTicksPerBuffer; /* counter ticks it should take to play a full buffer */ + LARGE_INTEGER dsw_LastPlayTime; + UINT dsw_LastPlayCursor; + UINT dsw_OutputUnderflows; + BOOL dsw_OutputRunning; + /* use double which lets us can play for several thousand years with enough precision */ + double dsw_FramesWritten; + double dsw_FramesPlayed; +/* Input */ + INT dsw_BytesPerInputFrame; + LPDIRECTSOUNDCAPTURE dsw_pDirectSoundCapture; + LPDIRECTSOUNDCAPTUREBUFFER dsw_InputBuffer; + UINT dsw_ReadOffset; /* last read position */ + UINT dsw_InputSize; +} DSoundWrapper; + +HRESULT DSW_Init( DSoundWrapper *dsw ); +void DSW_Term( DSoundWrapper *dsw ); +HRESULT DSW_InitOutputBuffer( DSoundWrapper *dsw, unsigned long nFrameRate, + WORD nChannels, int bufSize ); +HRESULT DSW_StartOutput( DSoundWrapper *dsw ); +HRESULT DSW_StopOutput( DSoundWrapper *dsw ); +DWORD DSW_GetOutputStatus( DSoundWrapper *dsw ); +HRESULT DSW_WriteBlock( DSoundWrapper *dsw, char *buf, long numBytes ); +HRESULT DSW_ZeroEmptySpace( DSoundWrapper *dsw ); +HRESULT DSW_QueryOutputSpace( DSoundWrapper *dsw, long *bytesEmpty ); +HRESULT DSW_Enumerate( DSoundWrapper *dsw ); + +HRESULT DSW_InitInputBuffer( DSoundWrapper *dsw, unsigned long nFrameRate, + WORD nChannels, int bufSize ); +HRESULT DSW_StartInput( DSoundWrapper *dsw ); +HRESULT DSW_StopInput( DSoundWrapper *dsw ); +HRESULT DSW_ReadBlock( DSoundWrapper *dsw, char *buf, long numBytes ); +HRESULT DSW_QueryInputFilled( DSoundWrapper *dsw, long *bytesFilled ); +HRESULT DSW_QueryOutputFilled( DSoundWrapper *dsw, long *bytesFilled ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* __DSOUND_WRAPPER_H */ diff --git a/src/audio/portaudio/pa_win_ds/pa_win_ds.c b/src/audio/portaudio/pa_win_ds/pa_win_ds.c new file mode 100644 index 0000000000000000000000000000000000000000..69e53a06574c3a8fdcbdcc7e4a737ee9b2f44b79 --- /dev/null +++ b/src/audio/portaudio/pa_win_ds/pa_win_ds.c @@ -0,0 +1,1822 @@ +/* + * $Id$ + * Portable Audio I/O Library DirectSound implementation + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, 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. + * + * 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. + * + * 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. + */ + +/** @file + + @todo implement paInputOverflow callback status flag + + @todo implement paNeverDropInput. + + @todo implement host api specific extension to set i/o buffer sizes in frames + + @todo implement initialisation of PaDeviceInfo default*Latency fields (currently set to 0.) + + @todo implement ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable + + @todo audit handling of DirectSound result codes - in many cases we could convert a HRESULT into + a native portaudio error code. Standard DirectSound result codes are documented at msdn. + + @todo implement IsFormatSupported + + @todo check that CoInitialize() CoUninitialize() are always correctly + paired, even in error cases. + + @todo call PaUtil_SetLastHostErrorInfo with a specific error string (currently just "DSound error"). + + @todo make sure all buffers have been played before stopping the stream + when the stream callback returns paComplete + + old TODOs from phil, need to work out if these have been done: + O- fix "patest_stop.c" +*/ + +#include <stdio.h> +#include <string.h> /* strlen() */ + +#include "pa_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" + +#include "dsound_wrapper.h" + +#if (defined(WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1200))) /* MSC version 6 and above */ +#pragma comment( lib, "dsound.lib" ) +#pragma comment( lib, "winmm.lib" ) +#endif + + +#define PRINT(x) /* { printf x; fflush(stdout); } */ +#define ERR_RPT(x) PRINT(x) +#define DBUG(x) /* PRINT(x) */ +#define DBUGX(x) /* PRINT(x) */ + +#define PA_USE_HIGH_LATENCY (0) +#if PA_USE_HIGH_LATENCY +#define PA_WIN_9X_LATENCY (500) +#define PA_WIN_NT_LATENCY (600) +#else +#define PA_WIN_9X_LATENCY (140) +#define PA_WIN_NT_LATENCY (280) +#endif + +#define PA_WIN_WDM_LATENCY (120) + +#define SECONDS_PER_MSEC (0.001) +#define MSEC_PER_SECOND (1000) + +/* prototypes for functions declared in this file */ + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); +static PaError CloseStream( PaStream* stream ); +static PaError StartStream( PaStream *stream ); +static PaError StopStream( PaStream *stream ); +static PaError AbortStream( PaStream *stream ); +static PaError IsStreamStopped( PaStream *s ); +static PaError IsStreamActive( PaStream *stream ); +static PaTime GetStreamTime( PaStream *stream ); +static double GetStreamCpuLoad( PaStream* stream ); +static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames ); +static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames ); +static signed long GetStreamReadAvailable( PaStream* stream ); +static signed long GetStreamWriteAvailable( PaStream* stream ); + + +/* FIXME: should convert hr to a string */ +#define PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ) \ + PaUtil_SetLastHostErrorInfo( paDirectSound, hr, "DirectSound error" ) + +/************************************************* DX Prototypes **********/ +static BOOL CALLBACK CollectGUIDsProc(LPGUID lpGUID, + LPCTSTR lpszDesc, + LPCTSTR lpszDrvName, + LPVOID lpContext ); + +/************************************************************************************/ +/********************** Structures **************************************************/ +/************************************************************************************/ +/* PaWinDsHostApiRepresentation - host api datastructure specific to this implementation */ + +typedef struct PaWinDsDeviceInfo +{ + GUID guid; + GUID *lpGUID; + double sampleRates[3]; +} PaWinDsDeviceInfo; + +typedef struct +{ + PaUtilHostApiRepresentation inheritedHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup *allocations; + + /* implementation specific data goes here */ + PaWinDsDeviceInfo *winDsDeviceInfos; + +} PaWinDsHostApiRepresentation; + +/* PaWinDsStream - a stream data structure specifically for this implementation */ + +typedef struct PaWinDsStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + +/* DirectSound specific data. */ + DSoundWrapper directSoundWrapper; + MMRESULT timerID; + BOOL ifInsideCallback; /* Test for reentrancy. */ + int framesPerDSBuffer; + double framesWritten; + double secondsPerHostByte; /* Used to optimize latency calculation for outTime */ + + PaStreamCallbackFlags callbackFlags; + +/* FIXME - move all below to PaUtilStreamRepresentation */ + volatile int isStarted; + volatile int isActive; + volatile int stopProcessing; /* stop thread once existing buffers have been returned */ + volatile int abortProcessing; /* stop thread immediately */ +} PaWinDsStream; + + +/************************************************************************************ +** Duplicate the input string using the allocations allocator. +** A NULL string is converted to a zero length string. +** If memory cannot be allocated, NULL is returned. +**/ +static char *DuplicateDeviceNameString( PaUtilAllocationGroup *allocations, const char* src ) +{ + char *result = 0; + + if( src != NULL ) + { + size_t len = strlen(src); + result = (char*)PaUtil_GroupAllocateMemory( allocations, (long)(len + 1) ); + if( result ) + memcpy( (void *) result, src, len+1 ); + } + else + { + result = (char*)PaUtil_GroupAllocateMemory( allocations, 1 ); + if( result ) + result[0] = '\0'; + } + + return result; +} + +/************************************************************************************ +** DSDeviceNameAndGUID, DSDeviceNameAndGUIDVector used for collecting preliminary +** information during device enumeration. +*/ +typedef struct DSDeviceNameAndGUID{ + char *name; // allocated from parent's allocations, never deleted by this structure + GUID guid; + LPGUID lpGUID; +} DSDeviceNameAndGUID; + +typedef struct DSDeviceNameAndGUIDVector{ + PaUtilAllocationGroup *allocations; + PaError enumerationError; + + int count; + int free; + DSDeviceNameAndGUID *items; // Allocated using LocalAlloc() +} DSDeviceNameAndGUIDVector; + +static PaError InitializeDSDeviceNameAndGUIDVector( + DSDeviceNameAndGUIDVector *guidVector, PaUtilAllocationGroup *allocations ) +{ + PaError result = paNoError; + + guidVector->allocations = allocations; + guidVector->enumerationError = paNoError; + + guidVector->count = 0; + guidVector->free = 8; + guidVector->items = (DSDeviceNameAndGUID*)LocalAlloc( LMEM_FIXED, sizeof(DSDeviceNameAndGUID) * guidVector->free ); + if( guidVector->items == NULL ) + result = paInsufficientMemory; + + return result; +} + +static PaError ExpandDSDeviceNameAndGUIDVector( DSDeviceNameAndGUIDVector *guidVector ) +{ + PaError result = paNoError; + DSDeviceNameAndGUID *newItems; + int i; + + /* double size of vector */ + int size = guidVector->count + guidVector->free; + guidVector->free += size; + + newItems = (DSDeviceNameAndGUID*)LocalAlloc( LMEM_FIXED, sizeof(DSDeviceNameAndGUID) * size * 2 ); + if( newItems == NULL ) + { + result = paInsufficientMemory; + } + else + { + for( i=0; i < guidVector->count; ++i ) + { + newItems[i].name = guidVector->items[i].name; + if( guidVector->items[i].lpGUID == NULL ) + { + newItems[i].lpGUID = NULL; + } + else + { + newItems[i].lpGUID = &newItems[i].guid; + memcpy( &newItems[i].guid, guidVector->items[i].lpGUID, sizeof(GUID) );; + } + } + + LocalFree( guidVector->items ); + guidVector->items = newItems; + } + + return result; +} + +/* + it's safe to call DSDeviceNameAndGUIDVector multiple times +*/ +static PaError TerminateDSDeviceNameAndGUIDVector( DSDeviceNameAndGUIDVector *guidVector ) +{ + PaError result = paNoError; + + if( guidVector->items != NULL ) + { + if( LocalFree( guidVector->items ) != NULL ) + result = paInsufficientMemory; /** @todo this isn't the correct error to return from a deallocation failure */ + + guidVector->items = NULL; + } + + return result; +} + +/************************************************************************************ +** Collect preliminary device information during DirectSound enumeration +*/ +static BOOL CALLBACK CollectGUIDsProc(LPGUID lpGUID, + LPCTSTR lpszDesc, + LPCTSTR lpszDrvName, + LPVOID lpContext ) +{ + DSDeviceNameAndGUIDVector *namesAndGUIDs = (DSDeviceNameAndGUIDVector*)lpContext; + PaError error; + + (void) lpszDrvName; /* unused variable */ + + if( namesAndGUIDs->free == 0 ) + { + error = ExpandDSDeviceNameAndGUIDVector( namesAndGUIDs ); + if( error != paNoError ) + { + namesAndGUIDs->enumerationError = error; + return FALSE; + } + } + + /* Set GUID pointer, copy GUID to storage in DSDeviceNameAndGUIDVector. */ + if( lpGUID == NULL ) + { + namesAndGUIDs->items[namesAndGUIDs->count].lpGUID = NULL; + } + else + { + namesAndGUIDs->items[namesAndGUIDs->count].lpGUID = + &namesAndGUIDs->items[namesAndGUIDs->count].guid; + + memcpy( &namesAndGUIDs->items[namesAndGUIDs->count].guid, lpGUID, sizeof(GUID) ); + } + + namesAndGUIDs->items[namesAndGUIDs->count].name = + DuplicateDeviceNameString( namesAndGUIDs->allocations, lpszDesc ); + if( namesAndGUIDs->items[namesAndGUIDs->count].name == NULL ) + { + namesAndGUIDs->enumerationError = paInsufficientMemory; + return FALSE; + } + + ++namesAndGUIDs->count; + --namesAndGUIDs->free; + + return TRUE; +} + + +#define PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_ (13) /* must match array length below */ +static double defaultSampleRateSearchOrder_[] = + { 44100.0, 48000.0, 32000.0, 24000.0, 22050.0, 88200.0, 96000.0, 192000.0, + 16000.0, 12000.0, 11025.0, 9600.0, 8000.0 }; + + +/************************************************************************************ +** Extract capabilities from an output device, and add it to the device info list +** if successful. This function assumes that there is enough room in the +** device info list to accomodate all entries. +** +** The device will not be added to the device list if any errors are encountered. +*/ +static PaError AddOutputDeviceInfoFromDirectSound( + PaWinDsHostApiRepresentation *winDsHostApi, char *name, LPGUID lpGUID ) +{ + PaUtilHostApiRepresentation *hostApi = &winDsHostApi->inheritedHostApiRep; + PaDeviceInfo *deviceInfo = hostApi->deviceInfos[hostApi->info.deviceCount]; + PaWinDsDeviceInfo *winDsDeviceInfo = &winDsHostApi->winDsDeviceInfos[hostApi->info.deviceCount]; + HRESULT hr; + LPDIRECTSOUND lpDirectSound; + DSCAPS caps; + int deviceOK = TRUE; + PaError result = paNoError; + int i; + + /* Copy GUID to the device info structure. Set pointer. */ + if( lpGUID == NULL ) + { + winDsDeviceInfo->lpGUID = NULL; + } + else + { + memcpy( &winDsDeviceInfo->guid, lpGUID, sizeof(GUID) ); + winDsDeviceInfo->lpGUID = &winDsDeviceInfo->guid; + } + + + /* Create a DirectSound object for the specified GUID + Note that using CoCreateInstance doesn't work on windows CE. + */ + hr = dswDSoundEntryPoints.DirectSoundCreate( lpGUID, &lpDirectSound, NULL ); + + /** try using CoCreateInstance because DirectSoundCreate was hanging under + some circumstances - note this was probably related to the + #define BOOL short bug which has now been fixed + @todo delete this comment and the following code once we've ensured + there is no bug. + */ + /* + hr = CoCreateInstance( &CLSID_DirectSound, NULL, CLSCTX_INPROC_SERVER, + &IID_IDirectSound, (void**)&lpDirectSound ); + + if( hr == S_OK ) + { + hr = IDirectSound_Initialize( lpDirectSound, lpGUID ); + } + */ + + if( hr != DS_OK ) + { + DBUG(("Cannot create DirectSound for %s. Result = 0x%x\n", name, hr )); + deviceOK = FALSE; + } + else + { + /* Query device characteristics. */ + memset( &caps, 0, sizeof(caps) ); + caps.dwSize = sizeof(caps); + hr = IDirectSound_GetCaps( lpDirectSound, &caps ); + if( hr != DS_OK ) + { + DBUG(("Cannot GetCaps() for DirectSound device %s. Result = 0x%x\n", name, hr )); + deviceOK = FALSE; + } + else + { + +#ifndef PA_NO_WMME + if( caps.dwFlags & DSCAPS_EMULDRIVER ) + { + /* If WMME supported, then reject Emulated drivers because they are lousy. */ + deviceOK = FALSE; + } +#endif + + if( deviceOK ) + { + deviceInfo->maxInputChannels = 0; + /* Mono or stereo device? */ + deviceInfo->maxOutputChannels = ( caps.dwFlags & DSCAPS_PRIMARYSTEREO ) ? 2 : 1; + + deviceInfo->defaultLowInputLatency = 0.; /** @todo IMPLEMENT ME */ + deviceInfo->defaultLowOutputLatency = 0.; /** @todo IMPLEMENT ME */ + deviceInfo->defaultHighInputLatency = 0.; /** @todo IMPLEMENT ME */ + deviceInfo->defaultHighOutputLatency = 0.; /** @todo IMPLEMENT ME */ + + /* initialize defaultSampleRate */ + + if( caps.dwFlags & DSCAPS_CONTINUOUSRATE ) + { + /* initialize to caps.dwMaxSecondarySampleRate incase none of the standard rates match */ + deviceInfo->defaultSampleRate = caps.dwMaxSecondarySampleRate; + + for( i = 0; i < PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_; ++i ) + { + if( defaultSampleRateSearchOrder_[i] >= caps.dwMinSecondarySampleRate + && defaultSampleRateSearchOrder_[i] <= caps.dwMaxSecondarySampleRate ){ + + deviceInfo->defaultSampleRate = defaultSampleRateSearchOrder_[i]; + break; + } + } + } + else if( caps.dwMinSecondarySampleRate == caps.dwMaxSecondarySampleRate ) + { + if( caps.dwMinSecondarySampleRate == 0 ) + { + /* + ** On my Thinkpad 380Z, DirectSoundV6 returns min-max=0 !! + ** But it supports continuous sampling. + ** So fake range of rates, and hope it really supports it. + */ + deviceInfo->defaultSampleRate = 44100.0f; + + DBUG(("PA - Reported rates both zero. Setting to fake values for device #%d\n", sDeviceIndex )); + } + else + { + deviceInfo->defaultSampleRate = caps.dwMaxSecondarySampleRate; + } + } + else if( (caps.dwMinSecondarySampleRate < 1000.0) && (caps.dwMaxSecondarySampleRate > 50000.0) ) + { + /* The EWS88MT drivers lie, lie, lie. The say they only support two rates, 100 & 100000. + ** But we know that they really support a range of rates! + ** So when we see a ridiculous set of rates, assume it is a range. + */ + deviceInfo->defaultSampleRate = 44100.0f; + DBUG(("PA - Sample rate range used instead of two odd values for device #%d\n", sDeviceIndex )); + } + else deviceInfo->defaultSampleRate = caps.dwMaxSecondarySampleRate; + + + //printf( "min %d max %d\n", caps.dwMinSecondarySampleRate, caps.dwMaxSecondarySampleRate ); + // dwFlags | DSCAPS_CONTINUOUSRATE + } + } + + IDirectSound_Release( lpDirectSound ); + } + + if( deviceOK ) + { + deviceInfo->name = name; + + if( lpGUID == NULL ) + hostApi->info.defaultOutputDevice = hostApi->info.deviceCount; + + hostApi->info.deviceCount++; + } + + return result; +} + + +/************************************************************************************ +** Extract capabilities from an input device, and add it to the device info list +** if successful. This function assumes that there is enough room in the +** device info list to accomodate all entries. +** +** The device will not be added to the device list if any errors are encountered. +*/ +static PaError AddInputDeviceInfoFromDirectSoundCapture( + PaWinDsHostApiRepresentation *winDsHostApi, char *name, LPGUID lpGUID ) +{ + PaUtilHostApiRepresentation *hostApi = &winDsHostApi->inheritedHostApiRep; + PaDeviceInfo *deviceInfo = hostApi->deviceInfos[hostApi->info.deviceCount]; + PaWinDsDeviceInfo *winDsDeviceInfo = &winDsHostApi->winDsDeviceInfos[hostApi->info.deviceCount]; + HRESULT hr; + LPDIRECTSOUNDCAPTURE lpDirectSoundCapture; + DSCCAPS caps; + int deviceOK = TRUE; + PaError result = paNoError; + + /* Copy GUID to the device info structure. Set pointer. */ + if( lpGUID == NULL ) + { + winDsDeviceInfo->lpGUID = NULL; + } + else + { + winDsDeviceInfo->lpGUID = &winDsDeviceInfo->guid; + memcpy( &winDsDeviceInfo->guid, lpGUID, sizeof(GUID) ); + } + + + hr = dswDSoundEntryPoints.DirectSoundCaptureCreate( lpGUID, &lpDirectSoundCapture, NULL ); + + /** try using CoCreateInstance because DirectSoundCreate was hanging under + some circumstances - note this was probably related to the + #define BOOL short bug which has now been fixed + @todo delete this comment and the following code once we've ensured + there is no bug. + */ + /* + hr = CoCreateInstance( &CLSID_DirectSoundCapture, NULL, CLSCTX_INPROC_SERVER, + &IID_IDirectSoundCapture, (void**)&lpDirectSoundCapture ); + */ + if( hr != DS_OK ) + { + DBUG(("Cannot create Capture for %s. Result = 0x%x\n", name, hr )); + deviceOK = FALSE; + } + else + { + /* Query device characteristics. */ + memset( &caps, 0, sizeof(caps) ); + caps.dwSize = sizeof(caps); + hr = IDirectSoundCapture_GetCaps( lpDirectSoundCapture, &caps ); + if( hr != DS_OK ) + { + DBUG(("Cannot GetCaps() for Capture device %s. Result = 0x%x\n", name, hr )); + deviceOK = FALSE; + } + else + { +#ifndef PA_NO_WMME + if( caps.dwFlags & DSCAPS_EMULDRIVER ) + { + /* If WMME supported, then reject Emulated drivers because they are lousy. */ + deviceOK = FALSE; + } +#endif + + if( deviceOK ) + { + deviceInfo->maxInputChannels = caps.dwChannels; + deviceInfo->maxOutputChannels = 0; + + deviceInfo->defaultLowInputLatency = 0.; /** @todo IMPLEMENT ME */ + deviceInfo->defaultLowOutputLatency = 0.; /** @todo IMPLEMENT ME */ + deviceInfo->defaultHighInputLatency = 0.; /** @todo IMPLEMENT ME */ + deviceInfo->defaultHighOutputLatency = 0.; /** @todo IMPLEMENT ME */ + +/* constants from a WINE patch by Francois Gouget, see: + http://www.winehq.com/hypermail/wine-patches/2003/01/0290.html + + --- + Date: Fri, 14 May 2004 10:38:12 +0200 (CEST) + From: Francois Gouget <fgouget@ ... .fr> + To: Ross Bencina <rbencina@ ... .au> + Subject: Re: Permission to use wine 48/96 wave patch in BSD licensed library + + [snip] + + I give you permission to use the patch below under the BSD license. + http://www.winehq.com/hypermail/wine-patches/2003/01/0290.html + + [snip] +*/ +#ifndef WAVE_FORMAT_48M08 +#define WAVE_FORMAT_48M08 0x00001000 /* 48 kHz, Mono, 8-bit */ +#define WAVE_FORMAT_48S08 0x00002000 /* 48 kHz, Stereo, 8-bit */ +#define WAVE_FORMAT_48M16 0x00004000 /* 48 kHz, Mono, 16-bit */ +#define WAVE_FORMAT_48S16 0x00008000 /* 48 kHz, Stereo, 16-bit */ +#define WAVE_FORMAT_96M08 0x00010000 /* 96 kHz, Mono, 8-bit */ +#define WAVE_FORMAT_96S08 0x00020000 /* 96 kHz, Stereo, 8-bit */ +#define WAVE_FORMAT_96M16 0x00040000 /* 96 kHz, Mono, 16-bit */ +#define WAVE_FORMAT_96S16 0x00080000 /* 96 kHz, Stereo, 16-bit */ +#endif + + /* defaultSampleRate */ + if( caps.dwChannels == 2 ) + { + if( caps.dwFormats & WAVE_FORMAT_4S16 ) + deviceInfo->defaultSampleRate = 44100.0; + else if( caps.dwFormats & WAVE_FORMAT_48S16 ) + deviceInfo->defaultSampleRate = 48000.0; + else if( caps.dwFormats & WAVE_FORMAT_2S16 ) + deviceInfo->defaultSampleRate = 22050.0; + else if( caps.dwFormats & WAVE_FORMAT_1S16 ) + deviceInfo->defaultSampleRate = 11025.0; + else if( caps.dwFormats & WAVE_FORMAT_96S16 ) + deviceInfo->defaultSampleRate = 96000.0; + else + deviceInfo->defaultSampleRate = 0.; + } + else if( caps.dwChannels == 1 ) + { + if( caps.dwFormats & WAVE_FORMAT_4M16 ) + deviceInfo->defaultSampleRate = 44100.0; + else if( caps.dwFormats & WAVE_FORMAT_48M16 ) + deviceInfo->defaultSampleRate = 48000.0; + else if( caps.dwFormats & WAVE_FORMAT_2M16 ) + deviceInfo->defaultSampleRate = 22050.0; + else if( caps.dwFormats & WAVE_FORMAT_1M16 ) + deviceInfo->defaultSampleRate = 11025.0; + else if( caps.dwFormats & WAVE_FORMAT_96M16 ) + deviceInfo->defaultSampleRate = 96000.0; + else + deviceInfo->defaultSampleRate = 0.; + } + else deviceInfo->defaultSampleRate = 0.; + } + } + + IDirectSoundCapture_Release( lpDirectSoundCapture ); + } + + if( deviceOK ) + { + deviceInfo->name = name; + + if( lpGUID == NULL ) + hostApi->info.defaultInputDevice = hostApi->info.deviceCount; + + hostApi->info.deviceCount++; + } + + return result; +} + + +/***********************************************************************************/ +PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + int i, deviceCount; + PaWinDsHostApiRepresentation *winDsHostApi; + DSDeviceNameAndGUIDVector inputNamesAndGUIDs, outputNamesAndGUIDs; + PaDeviceInfo *deviceInfoArray; + + HRESULT hr = CoInitialize(NULL); /** @todo: should uninitialize too */ + if( FAILED(hr) ){ + return paUnanticipatedHostError; + } + + /* initialise guid vectors so they can be safely deleted on error */ + inputNamesAndGUIDs.items = NULL; + outputNamesAndGUIDs.items = NULL; + + DSW_InitializeDSoundEntryPoints(); + + winDsHostApi = (PaWinDsHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaWinDsHostApiRepresentation) ); + if( !winDsHostApi ) + { + result = paInsufficientMemory; + goto error; + } + + winDsHostApi->allocations = PaUtil_CreateAllocationGroup(); + if( !winDsHostApi->allocations ) + { + result = paInsufficientMemory; + goto error; + } + + *hostApi = &winDsHostApi->inheritedHostApiRep; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paDirectSound; + (*hostApi)->info.name = "Windows DirectSound"; + + (*hostApi)->info.deviceCount = 0; + (*hostApi)->info.defaultInputDevice = paNoDevice; + (*hostApi)->info.defaultOutputDevice = paNoDevice; + + +/* DSound - enumerate devices to count them and to gather their GUIDs */ + + + result = InitializeDSDeviceNameAndGUIDVector( &inputNamesAndGUIDs, winDsHostApi->allocations ); + if( result != paNoError ) + goto error; + + result = InitializeDSDeviceNameAndGUIDVector( &outputNamesAndGUIDs, winDsHostApi->allocations ); + if( result != paNoError ) + goto error; + + dswDSoundEntryPoints.DirectSoundCaptureEnumerate( (LPDSENUMCALLBACK)CollectGUIDsProc, (void *)&inputNamesAndGUIDs ); + + dswDSoundEntryPoints.DirectSoundEnumerate( (LPDSENUMCALLBACK)CollectGUIDsProc, (void *)&outputNamesAndGUIDs ); + + if( inputNamesAndGUIDs.enumerationError != paNoError ) + { + result = inputNamesAndGUIDs.enumerationError; + goto error; + } + + if( outputNamesAndGUIDs.enumerationError != paNoError ) + { + result = outputNamesAndGUIDs.enumerationError; + goto error; + } + + deviceCount = inputNamesAndGUIDs.count + outputNamesAndGUIDs.count; + + if( deviceCount > 0 ) + { + /* allocate array for pointers to PaDeviceInfo structs */ + (*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + winDsHostApi->allocations, sizeof(PaDeviceInfo*) * deviceCount ); + if( !(*hostApi)->deviceInfos ) + { + result = paInsufficientMemory; + goto error; + } + + /* allocate all PaDeviceInfo structs in a contiguous block */ + deviceInfoArray = (PaDeviceInfo*)PaUtil_GroupAllocateMemory( + winDsHostApi->allocations, sizeof(PaDeviceInfo) * deviceCount ); + if( !deviceInfoArray ) + { + result = paInsufficientMemory; + goto error; + } + + /* allocate all DSound specific info structs in a contiguous block */ + winDsHostApi->winDsDeviceInfos = (PaWinDsDeviceInfo*)PaUtil_GroupAllocateMemory( + winDsHostApi->allocations, sizeof(PaWinDsDeviceInfo) * deviceCount ); + if( !winDsHostApi->winDsDeviceInfos ) + { + result = paInsufficientMemory; + goto error; + } + + for( i=0; i < deviceCount; ++i ) + { + PaDeviceInfo *deviceInfo = &deviceInfoArray[i]; + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; + deviceInfo->name = 0; + (*hostApi)->deviceInfos[i] = deviceInfo; + } + + for( i=0; i< inputNamesAndGUIDs.count; ++i ) + { + result = AddInputDeviceInfoFromDirectSoundCapture( winDsHostApi, + inputNamesAndGUIDs.items[i].name, + inputNamesAndGUIDs.items[i].lpGUID ); + if( result != paNoError ) + goto error; + } + + for( i=0; i< outputNamesAndGUIDs.count; ++i ) + { + result = AddOutputDeviceInfoFromDirectSound( winDsHostApi, + outputNamesAndGUIDs.items[i].name, + outputNamesAndGUIDs.items[i].lpGUID ); + if( result != paNoError ) + goto error; + } + } + + result = TerminateDSDeviceNameAndGUIDVector( &inputNamesAndGUIDs ); + if( result != paNoError ) + goto error; + + result = TerminateDSDeviceNameAndGUIDVector( &outputNamesAndGUIDs ); + if( result != paNoError ) + goto error; + + + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PaUtil_InitializeStreamInterface( &winDsHostApi->callbackStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface( &winDsHostApi->blockingStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable ); + + return result; + +error: + if( winDsHostApi ) + { + if( winDsHostApi->allocations ) + { + PaUtil_FreeAllAllocations( winDsHostApi->allocations ); + PaUtil_DestroyAllocationGroup( winDsHostApi->allocations ); + } + + PaUtil_FreeMemory( winDsHostApi ); + } + + TerminateDSDeviceNameAndGUIDVector( &inputNamesAndGUIDs ); + TerminateDSDeviceNameAndGUIDVector( &outputNamesAndGUIDs ); + + return result; +} + + +/***********************************************************************************/ +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi; + + /* + IMPLEMENT ME: + - clean up any resources not handled by the allocation group + */ + + if( winDsHostApi->allocations ) + { + PaUtil_FreeAllAllocations( winDsHostApi->allocations ); + PaUtil_DestroyAllocationGroup( winDsHostApi->allocations ); + } + + PaUtil_FreeMemory( winDsHostApi ); + + DSW_TerminateDSoundEntryPoints(); + + CoUninitialize(); +} + + +/* Set minimal latency based on whether NT or Win95. + * NT has higher latency. + */ +static int PaWinDS_GetMinSystemLatency( void ) +{ + int minLatencyMsec; + /* Set minimal latency based on whether NT or other OS. + * NT has higher latency. + */ + OSVERSIONINFO osvi; + osvi.dwOSVersionInfoSize = sizeof( osvi ); + GetVersionEx( &osvi ); + DBUG(("PA - PlatformId = 0x%x\n", osvi.dwPlatformId )); + DBUG(("PA - MajorVersion = 0x%x\n", osvi.dwMajorVersion )); + DBUG(("PA - MinorVersion = 0x%x\n", osvi.dwMinorVersion )); + /* Check for NT */ + if( (osvi.dwMajorVersion == 4) && (osvi.dwPlatformId == 2) ) + { + minLatencyMsec = PA_WIN_NT_LATENCY; + } + else if(osvi.dwMajorVersion >= 5) + { + minLatencyMsec = PA_WIN_WDM_LATENCY; + } + else + { + minLatencyMsec = PA_WIN_9X_LATENCY; + } + return minLatencyMsec; +} + +/***********************************************************************************/ +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + + /* validate inputStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + inputChannelCount = 0; + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that output device can support inputChannelCount */ + if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + + /* validate outputStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + outputChannelCount = 0; + } + + /* + IMPLEMENT ME: + + - if a full duplex stream is requested, check that the combination + of input and output parameters is supported if necessary + + - check that the device supports sampleRate + + Because the buffer adapter handles conversion between all standard + sample formats, the following checks are only required if paCustomFormat + is implemented, or under some other unusual conditions. + + - check that input device can support inputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + + - check that output device can support outputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + */ + + return paFormatIsSupported; +} + + +/************************************************************************* +** Determine minimum number of buffers required for this host based +** on minimum latency. Latency can be optionally set by user by setting +** an environment variable. For example, to set latency to 200 msec, put: +** +** set PA_MIN_LATENCY_MSEC=200 +** +** in the AUTOEXEC.BAT file and reboot. +** If the environment variable is not set, then the latency will be determined +** based on the OS. Windows NT has higher latency than Win95. +*/ +#define PA_LATENCY_ENV_NAME ("PA_MIN_LATENCY_MSEC") +#define PA_ENV_BUF_SIZE (32) + +static int PaWinDs_GetMinLatencyFrames( double sampleRate ) +{ + char envbuf[PA_ENV_BUF_SIZE]; + DWORD hresult; + int minLatencyMsec = 0; + + /* Let user determine minimal latency by setting environment variable. */ + hresult = GetEnvironmentVariable( PA_LATENCY_ENV_NAME, envbuf, PA_ENV_BUF_SIZE ); + if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE) ) + { + minLatencyMsec = atoi( envbuf ); + } + else + { + minLatencyMsec = PaWinDS_GetMinSystemLatency(); +#if PA_USE_HIGH_LATENCY + PRINT(("PA - Minimum Latency set to %d msec!\n", minLatencyMsec )); +#endif + + } + + return (int) (minLatencyMsec * sampleRate * SECONDS_PER_MSEC); +} + +/***********************************************************************************/ +/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */ + +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ) +{ + PaError result = paNoError; + PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi; + PaWinDsStream *stream = 0; + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat; + unsigned long suggestedInputLatencyFrames, suggestedOutputLatencyFrames; + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + suggestedInputLatencyFrames = (unsigned long)(inputParameters->suggestedLatency * sampleRate); + + /* IDEA: the following 3 checks could be performed by default by pa_front + unless some flag indicated otherwise */ + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + + /* validate hostApiSpecificStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + inputChannelCount = 0; + suggestedInputLatencyFrames = 0; + } + + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + suggestedOutputLatencyFrames = (unsigned long)(outputParameters->suggestedLatency * sampleRate); + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that output device can support inputChannelCount */ + if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + + /* validate hostApiSpecificStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + outputChannelCount = 0; + suggestedOutputLatencyFrames = 0; + } + + + /* + IMPLEMENT ME: + + ( the following two checks are taken care of by PaUtil_InitializeBufferProcessor() ) + + - check that input device can support inputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + + - check that output device can support outputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + + - if a full duplex stream is requested, check that the combination + of input and output parameters is supported + + - check that the device supports sampleRate + + - alter sampleRate to a close allowable rate if possible / necessary + + - validate suggestedInputLatency and suggestedOutputLatency parameters, + use default values where necessary + */ + + + /* validate platform specific flags */ + if( (streamFlags & paPlatformSpecificFlags) != 0 ) + return paInvalidFlag; /* unexpected platform specific flag */ + + + stream = (PaWinDsStream*)PaUtil_AllocateMemory( sizeof(PaWinDsStream) ); + if( !stream ) + { + result = paInsufficientMemory; + goto error; + } + + if( streamCallback ) + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &winDsHostApi->callbackStreamInterface, streamCallback, userData ); + } + else + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &winDsHostApi->blockingStreamInterface, streamCallback, userData ); + } + + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); + + + if( inputParameters ) + { + /* IMPLEMENT ME - establish which host formats are available */ + hostInputSampleFormat = + PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, inputParameters->sampleFormat ); + } + + if( outputParameters ) + { + /* IMPLEMENT ME - establish which host formats are available */ + hostOutputSampleFormat = + PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, outputParameters->sampleFormat ); + } + + result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, + inputChannelCount, inputSampleFormat, hostInputSampleFormat, + outputChannelCount, outputSampleFormat, hostOutputSampleFormat, + sampleRate, streamFlags, framesPerBuffer, + framesPerBuffer, /* ignored in paUtilVariableHostBufferSizePartialUsageAllowed mode. */ + /* This next mode is required because DS can split the host buffer when it wraps around. */ + paUtilVariableHostBufferSizePartialUsageAllowed, + streamCallback, userData ); + if( result != paNoError ) + goto error; + + + stream->streamRepresentation.streamInfo.inputLatency = + PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor); /* FIXME: not initialised anywhere else */ + stream->streamRepresentation.streamInfo.outputLatency = + PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor); /* FIXME: not initialised anywhere else */ + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + + +/* DirectSound specific initialization */ + { + HRESULT hr; + int bytesPerDirectSoundBuffer; + DSoundWrapper *dsw; + int userLatencyFrames; + int minLatencyFrames; + + stream->timerID = 0; + dsw = &stream->directSoundWrapper; + DSW_Init( dsw ); + + /* Get system minimum latency. */ + minLatencyFrames = PaWinDs_GetMinLatencyFrames( sampleRate ); + + /* Let user override latency by passing latency parameter. */ + userLatencyFrames = (suggestedInputLatencyFrames > suggestedOutputLatencyFrames) + ? suggestedInputLatencyFrames + : suggestedOutputLatencyFrames; + if( userLatencyFrames > 0 ) minLatencyFrames = userLatencyFrames; + + /* Calculate stream->framesPerDSBuffer depending on framesPerBuffer */ + if( framesPerBuffer == paFramesPerBufferUnspecified ) + { + /* App support variable framesPerBuffer */ + stream->framesPerDSBuffer = minLatencyFrames; + + stream->streamRepresentation.streamInfo.outputLatency = (double)(minLatencyFrames - 1) / sampleRate; + } + else + { + /* Round up to number of buffers needed to guarantee that latency. */ + int numUserBuffers = (minLatencyFrames + framesPerBuffer - 1) / framesPerBuffer; + if( numUserBuffers < 1 ) numUserBuffers = 1; + numUserBuffers += 1; /* So we have latency worth of buffers ahead of current buffer. */ + stream->framesPerDSBuffer = framesPerBuffer * numUserBuffers; + + stream->streamRepresentation.streamInfo.outputLatency = (double)(framesPerBuffer * (numUserBuffers-1)) / sampleRate; + } + + { + /** @todo REVIEW: this calculation seems incorrect to me - rossb. */ + int msecLatency = (int) ((stream->framesPerDSBuffer * MSEC_PER_SECOND) / sampleRate); + PRINT(("PortAudio on DirectSound - Latency = %d frames, %d msec\n", stream->framesPerDSBuffer, msecLatency )); + } + + + /* ------------------ OUTPUT */ + if( outputParameters ) + { + /* + PaDeviceInfo *deviceInfo = hostApi->deviceInfos[ outputParameters->device ]; + DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", outputParameters->device)); + */ + + bytesPerDirectSoundBuffer = stream->framesPerDSBuffer * outputParameters->channelCount * sizeof(short); + if( bytesPerDirectSoundBuffer < DSBSIZE_MIN ) + { + result = paBufferTooSmall; + goto error; + } + else if( bytesPerDirectSoundBuffer > DSBSIZE_MAX ) + { + result = paBufferTooBig; + goto error; + } + + + hr = dswDSoundEntryPoints.DirectSoundCreate( winDsHostApi->winDsDeviceInfos[outputParameters->device].lpGUID, + &dsw->dsw_pDirectSound, NULL ); + if( hr != DS_OK ) + { + ERR_RPT(("PortAudio: DirectSoundCreate() failed!\n")); + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); + goto error; + } + hr = DSW_InitOutputBuffer( dsw, + (unsigned long) (sampleRate + 0.5), + (WORD)outputParameters->channelCount, bytesPerDirectSoundBuffer ); + DBUG(("DSW_InitOutputBuffer() returns %x\n", hr)); + if( hr != DS_OK ) + { + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); + goto error; + } + /* Calculate value used in latency calculation to avoid real-time divides. */ + stream->secondsPerHostByte = 1.0 / + (stream->bufferProcessor.bytesPerHostOutputSample * + outputChannelCount * sampleRate); + } + + /* ------------------ INPUT */ + if( inputParameters ) + { + /* + PaDeviceInfo *deviceInfo = hostApi->deviceInfos[ inputParameters->device ]; + DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", inputParameters->device)); + */ + + bytesPerDirectSoundBuffer = stream->framesPerDSBuffer * inputParameters->channelCount * sizeof(short); + if( bytesPerDirectSoundBuffer < DSBSIZE_MIN ) + { + result = paBufferTooSmall; + goto error; + } + else if( bytesPerDirectSoundBuffer > DSBSIZE_MAX ) + { + result = paBufferTooBig; + goto error; + } + + hr = dswDSoundEntryPoints.DirectSoundCaptureCreate( winDsHostApi->winDsDeviceInfos[inputParameters->device].lpGUID, + &dsw->dsw_pDirectSoundCapture, NULL ); + if( hr != DS_OK ) + { + ERR_RPT(("PortAudio: DirectSoundCaptureCreate() failed!\n")); + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); + goto error; + } + hr = DSW_InitInputBuffer( dsw, + (unsigned long) (sampleRate + 0.5), + (WORD)inputParameters->channelCount, bytesPerDirectSoundBuffer ); + DBUG(("DSW_InitInputBuffer() returns %x\n", hr)); + if( hr != DS_OK ) + { + ERR_RPT(("PortAudio: DSW_InitInputBuffer() returns %x\n", hr)); + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); + goto error; + } + } + + } + + *s = (PaStream*)stream; + + return result; + +error: + if( stream ) + PaUtil_FreeMemory( stream ); + + return result; +} + + +/***********************************************************************************/ +static PaError Pa_TimeSlice( PaWinDsStream *stream ) +{ + PaError result = 0; /* FIXME: this should be declared int and this function should also return that type (same as stream callback return type)*/ + DSoundWrapper *dsw; + long numFrames = 0; + long bytesEmpty = 0; + long bytesFilled = 0; + long bytesToXfer = 0; + long framesToXfer = 0; + long numInFramesReady = 0; + long numOutFramesReady = 0; + long bytesProcessed; + HRESULT hresult; + double outputLatency = 0; + PaStreamCallbackTimeInfo timeInfo = {0,0,0}; /** @todo implement inputBufferAdcTime */ + +/* Input */ + LPBYTE lpInBuf1 = NULL; + LPBYTE lpInBuf2 = NULL; + DWORD dwInSize1 = 0; + DWORD dwInSize2 = 0; +/* Output */ + LPBYTE lpOutBuf1 = NULL; + LPBYTE lpOutBuf2 = NULL; + DWORD dwOutSize1 = 0; + DWORD dwOutSize2 = 0; + + dsw = &stream->directSoundWrapper; + + /* How much input data is available? */ + if( stream->bufferProcessor.inputChannelCount > 0 ) + { + DSW_QueryInputFilled( dsw, &bytesFilled ); + framesToXfer = numInFramesReady = bytesFilled / dsw->dsw_BytesPerInputFrame; + outputLatency = ((double)bytesFilled) * stream->secondsPerHostByte; + + /** @todo Check for overflow */ + } + + /* How much output room is available? */ + if( stream->bufferProcessor.outputChannelCount > 0 ) + { + UINT previousUnderflowCount = dsw->dsw_OutputUnderflows; + DSW_QueryOutputSpace( dsw, &bytesEmpty ); + framesToXfer = numOutFramesReady = bytesEmpty / dsw->dsw_BytesPerOutputFrame; + + /* Check for underflow */ + if( dsw->dsw_OutputUnderflows != previousUnderflowCount ) + stream->callbackFlags |= paOutputUnderflow; + } + + if( (numInFramesReady > 0) && (numOutFramesReady > 0) ) + { + framesToXfer = (numOutFramesReady < numInFramesReady) ? numOutFramesReady : numInFramesReady; + } + + if( framesToXfer > 0 ) + { + + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + + /* The outputBufferDacTime parameter should indicates the time at which + the first sample of the output buffer is heard at the DACs. */ + timeInfo.currentTime = PaUtil_GetTime(); + timeInfo.outputBufferDacTime = timeInfo.currentTime + outputLatency; + + + PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, stream->callbackFlags ); + stream->callbackFlags = 0; + + /* Input */ + if( stream->bufferProcessor.inputChannelCount > 0 ) + { + bytesToXfer = framesToXfer * dsw->dsw_BytesPerInputFrame; + hresult = IDirectSoundCaptureBuffer_Lock ( dsw->dsw_InputBuffer, + dsw->dsw_ReadOffset, bytesToXfer, + (void **) &lpInBuf1, &dwInSize1, + (void **) &lpInBuf2, &dwInSize2, 0); + if (hresult != DS_OK) + { + ERR_RPT(("DirectSound IDirectSoundCaptureBuffer_Lock failed, hresult = 0x%x\n",hresult)); + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hresult ); + goto error2; + } + + numFrames = dwInSize1 / dsw->dsw_BytesPerInputFrame; + PaUtil_SetInputFrameCount( &stream->bufferProcessor, numFrames ); + PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, 0, lpInBuf1, 0 ); + /* Is input split into two regions. */ + if( dwInSize2 > 0 ) + { + numFrames = dwInSize2 / dsw->dsw_BytesPerInputFrame; + PaUtil_Set2ndInputFrameCount( &stream->bufferProcessor, numFrames ); + PaUtil_Set2ndInterleavedInputChannels( &stream->bufferProcessor, 0, lpInBuf2, 0 ); + } + } + + /* Output */ + if( stream->bufferProcessor.outputChannelCount > 0 ) + { + bytesToXfer = framesToXfer * dsw->dsw_BytesPerOutputFrame; + hresult = IDirectSoundBuffer_Lock ( dsw->dsw_OutputBuffer, + dsw->dsw_WriteOffset, bytesToXfer, + (void **) &lpOutBuf1, &dwOutSize1, + (void **) &lpOutBuf2, &dwOutSize2, 0); + if (hresult != DS_OK) + { + ERR_RPT(("DirectSound IDirectSoundBuffer_Lock failed, hresult = 0x%x\n",hresult)); + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hresult ); + goto error1; + } + + numFrames = dwOutSize1 / dsw->dsw_BytesPerOutputFrame; + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, numFrames ); + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, 0, lpOutBuf1, 0 ); + + /* Is output split into two regions. */ + if( dwOutSize2 > 0 ) + { + numFrames = dwOutSize2 / dsw->dsw_BytesPerOutputFrame; + PaUtil_Set2ndOutputFrameCount( &stream->bufferProcessor, numFrames ); + PaUtil_Set2ndInterleavedOutputChannels( &stream->bufferProcessor, 0, lpOutBuf2, 0 ); + } + } + + result = paContinue; + numFrames = PaUtil_EndBufferProcessing( &stream->bufferProcessor, &result ); + stream->framesWritten += numFrames; + + if( stream->bufferProcessor.outputChannelCount > 0 ) + { + /* FIXME: an underflow could happen here */ + + /* Update our buffer offset and unlock sound buffer */ + bytesProcessed = numFrames * dsw->dsw_BytesPerOutputFrame; + dsw->dsw_WriteOffset = (dsw->dsw_WriteOffset + bytesProcessed) % dsw->dsw_OutputSize; + IDirectSoundBuffer_Unlock( dsw->dsw_OutputBuffer, lpOutBuf1, dwOutSize1, lpOutBuf2, dwOutSize2); + dsw->dsw_FramesWritten += numFrames; + } + +error1: + if( stream->bufferProcessor.inputChannelCount > 0 ) + { + /* FIXME: an overflow could happen here */ + + /* Update our buffer offset and unlock sound buffer */ + bytesProcessed = numFrames * dsw->dsw_BytesPerInputFrame; + dsw->dsw_ReadOffset = (dsw->dsw_ReadOffset + bytesProcessed) % dsw->dsw_InputSize; + IDirectSoundCaptureBuffer_Unlock( dsw->dsw_InputBuffer, lpInBuf1, dwInSize1, lpInBuf2, dwInSize2); + } +error2: + + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, numFrames ); + + } + + return result; +} +/*******************************************************************/ +static void CALLBACK Pa_TimerCallback(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2) +{ + PaWinDsStream *stream; + + /* suppress unused variable warnings */ + (void) uID; + (void) uMsg; + (void) dw1; + (void) dw2; + + stream = (PaWinDsStream *) dwUser; + if( stream == NULL ) return; + + if( stream->isActive ) + { + if( stream->abortProcessing ) + { + stream->isActive = 0; + } + else if( stream->stopProcessing ) + { + DSoundWrapper *dsw = &stream->directSoundWrapper; + if( stream->bufferProcessor.outputChannelCount > 0 ) + { + DSW_ZeroEmptySpace( dsw ); + /* clear isActive when all sound played */ + if( dsw->dsw_FramesPlayed >= stream->framesWritten ) + { + stream->isActive = 0; + } + } + else + { + stream->isActive = 0; + } + } + else + { + if( Pa_TimeSlice( stream ) != 0) /* Call time slice independant of timing method. */ + { + /* FIXME implement handling of paComplete and paAbort if possible */ + stream->stopProcessing = 1; + } + } + + if( !stream->isActive ){ + if( stream->streamRepresentation.streamFinishedCallback != 0 ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + } + } +} + +/*********************************************************************************** + When CloseStream() is called, the multi-api layer ensures that + the stream has already been stopped or aborted. +*/ +static PaError CloseStream( PaStream* s ) +{ + PaError result = paNoError; + PaWinDsStream *stream = (PaWinDsStream*)s; + + DSW_Term( &stream->directSoundWrapper ); + + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + PaUtil_FreeMemory( stream ); + + return result; +} + +/***********************************************************************************/ +static PaError StartStream( PaStream *s ) +{ + PaError result = paNoError; + PaWinDsStream *stream = (PaWinDsStream*)s; + HRESULT hr; + + PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); + + if( stream->bufferProcessor.inputChannelCount > 0 ) + { + hr = DSW_StartInput( &stream->directSoundWrapper ); + DBUG(("StartStream: DSW_StartInput returned = 0x%X.\n", hr)); + if( hr != DS_OK ) + { + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); + goto error; + } + } + + stream->framesWritten = 0; + stream->callbackFlags = 0; + + stream->abortProcessing = 0; + stream->stopProcessing = 0; + stream->isActive = 1; + + if( stream->bufferProcessor.outputChannelCount > 0 ) + { + /* Give user callback a chance to pre-fill buffer. REVIEW - i thought we weren't pre-filling, rb. */ + result = Pa_TimeSlice( stream ); + if( result != paNoError ) return result; // FIXME - what if finished? + + hr = DSW_StartOutput( &stream->directSoundWrapper ); + DBUG(("PaHost_StartOutput: DSW_StartOutput returned = 0x%X.\n", hr)); + if( hr != DS_OK ) + { + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); + goto error; + } + } + + + /* Create timer that will wake us up so we can fill the DSound buffer. */ + { + int resolution; + int framesPerWakeup = stream->framesPerDSBuffer / 4; + int msecPerWakeup = MSEC_PER_SECOND * framesPerWakeup / (int) stream->streamRepresentation.streamInfo.sampleRate; + if( msecPerWakeup < 10 ) msecPerWakeup = 10; + else if( msecPerWakeup > 100 ) msecPerWakeup = 100; + resolution = msecPerWakeup/4; + stream->timerID = timeSetEvent( msecPerWakeup, resolution, (LPTIMECALLBACK) Pa_TimerCallback, + (DWORD) stream, TIME_PERIODIC ); + } + if( stream->timerID == 0 ) + { + stream->isActive = 0; + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); + goto error; + } + + stream->isStarted = TRUE; + +error: + return result; +} + + +/***********************************************************************************/ +static PaError StopStream( PaStream *s ) +{ + PaError result = paNoError; + PaWinDsStream *stream = (PaWinDsStream*)s; + HRESULT hr; + int timeoutMsec; + + stream->stopProcessing = 1; + /* Set timeout at 20% beyond maximum time we might wait. */ + timeoutMsec = (int) (1200.0 * stream->framesPerDSBuffer / stream->streamRepresentation.streamInfo.sampleRate); + while( stream->isActive && (timeoutMsec > 0) ) + { + Sleep(10); + timeoutMsec -= 10; + } + if( stream->timerID != 0 ) + { + timeKillEvent(stream->timerID); /* Stop callback timer. */ + stream->timerID = 0; + } + + + if( stream->bufferProcessor.outputChannelCount > 0 ) + { + hr = DSW_StopOutput( &stream->directSoundWrapper ); + } + + if( stream->bufferProcessor.inputChannelCount > 0 ) + { + hr = DSW_StopInput( &stream->directSoundWrapper ); + } + + stream->isStarted = FALSE; + + return result; +} + + +/***********************************************************************************/ +static PaError AbortStream( PaStream *s ) +{ + PaWinDsStream *stream = (PaWinDsStream*)s; + + stream->abortProcessing = 1; + return StopStream( s ); +} + + +/***********************************************************************************/ +static PaError IsStreamStopped( PaStream *s ) +{ + PaWinDsStream *stream = (PaWinDsStream*)s; + + return !stream->isStarted; +} + + +/***********************************************************************************/ +static PaError IsStreamActive( PaStream *s ) +{ + PaWinDsStream *stream = (PaWinDsStream*)s; + + return stream->isActive; +} + +/***********************************************************************************/ +static PaTime GetStreamTime( PaStream *s ) +{ + /* suppress unused variable warnings */ + (void) s; + + +/* + new behavior for GetStreamTime is to return a stream based seconds clock + used for the outTime parameter to the callback. + FIXME: delete this comment when the other unnecessary related code has + been cleaned from this file. + + PaWinDsStream *stream = (PaWinDsStream*)s; + DSoundWrapper *dsw; + dsw = &stream->directSoundWrapper; + return dsw->dsw_FramesPlayed; +*/ + return PaUtil_GetTime(); +} + + +/***********************************************************************************/ +static double GetStreamCpuLoad( PaStream* s ) +{ + PaWinDsStream *stream = (PaWinDsStream*)s; + + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); +} + + + +/*********************************************************************************** + As separate stream interfaces are used for blocking and callback + streams, the following functions can be guaranteed to only be called + for blocking streams. +*/ + +static PaError ReadStream( PaStream* s, + void *buffer, + unsigned long frames ) +{ + PaWinDsStream *stream = (PaWinDsStream*)s; + + /* suppress unused variable warnings */ + (void) buffer; + (void) frames; + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + + return paNoError; +} + + +/***********************************************************************************/ +static PaError WriteStream( PaStream* s, + const void *buffer, + unsigned long frames ) +{ + PaWinDsStream *stream = (PaWinDsStream*)s; + + /* suppress unused variable warnings */ + (void) buffer; + (void) frames; + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + + return paNoError; +} + + +/***********************************************************************************/ +static signed long GetStreamReadAvailable( PaStream* s ) +{ + PaWinDsStream *stream = (PaWinDsStream*)s; + + /* suppress unused variable warnings */ + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + + return 0; +} + + +/***********************************************************************************/ +static signed long GetStreamWriteAvailable( PaStream* s ) +{ + PaWinDsStream *stream = (PaWinDsStream*)s; + + /* suppress unused variable warnings */ + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + + return 0; +} + + + diff --git a/src/audio/portaudio/pa_win_wdmks/pa_win_wdmks.c b/src/audio/portaudio/pa_win_wdmks/pa_win_wdmks.c new file mode 100644 index 0000000000000000000000000000000000000000..18ffb28cf0a60d0f776e12d93fc074a0399d166f --- /dev/null +++ b/src/audio/portaudio/pa_win_wdmks/pa_win_wdmks.c @@ -0,0 +1,3179 @@ +/* + * $Id$ + * PortAudio Windows WDM-KS interface + * + * Author: Andrew Baldwin + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2004 Andrew Baldwin, Ross Bencina, 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. + * + * 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. + * + * 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. + */ + +/** @file + @brief Portaudio WDM-KS host API. + + @note This is the implementation of the Portaudio host API using the + Windows WDM/Kernel Streaming API in order to enable very low latency + playback and recording on all modern Windows platforms (e.g. 2K, XP) + Note: This API accesses the device drivers below the usual KMIXER + component which is normally used to enable multi-client mixing and + format conversion. That means that it will lock out all other users + of a device for the duration of active stream using those devices +*/ + +#include <stdio.h> + +/* Debugging/tracing support */ + +#define PA_LOGE_ +#define PA_LOGL_ + +#ifdef __GNUC__ + #include <initguid.h> + #define _WIN32_WINNT 0x0501 + #define WINVER 0x0501 +#endif + +#include <string.h> /* strlen() */ +#include <assert.h> + +#include "pa_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" +#include "portaudio.h" + +#include <windows.h> +#include <winioctl.h> + + +#ifdef __GNUC__ + #undef PA_LOGE_ + #define PA_LOGE_ PA_DEBUG(("%s {\n",__FUNCTION__)) + #undef PA_LOGL_ + #define PA_LOGL_ PA_DEBUG(("} %s\n",__FUNCTION__)) + /* These defines are set in order to allow the WIndows DirectX + * headers to compile with a GCC compiler such as MinGW + * NOTE: The headers may generate a few warning in GCC, but + * they should compile */ + #define _INC_MMSYSTEM + #define _INC_MMREG + #define _NTRTL_ /* Turn off default definition of DEFINE_GUIDEX */ + #define DEFINE_GUID_THUNK(name,guid) DEFINE_GUID(name,guid) + #define DEFINE_GUIDEX(n) DEFINE_GUID_THUNK( n, STATIC_##n ) + #if !defined( DEFINE_WAVEFORMATEX_GUID ) + #define DEFINE_WAVEFORMATEX_GUID(x) (USHORT)(x), 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 + #endif + #define WAVE_FORMAT_ADPCM 0x0002 + #define WAVE_FORMAT_IEEE_FLOAT 0x0003 + #define WAVE_FORMAT_ALAW 0x0006 + #define WAVE_FORMAT_MULAW 0x0007 + #define WAVE_FORMAT_MPEG 0x0050 + #define WAVE_FORMAT_DRM 0x0009 + #define DYNAMIC_GUID_THUNK(l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}} + #define DYNAMIC_GUID(data) DYNAMIC_GUID_THUNK(data) +#endif + +#ifdef _MSC_VER + #define DYNAMIC_GUID(data) {data} + #define _INC_MMREG + #define _NTRTL_ /* Turn off default definition of DEFINE_GUIDEX */ + #undef DEFINE_GUID + #define DEFINE_GUID(n,data) EXTERN_C const GUID n = {data} + #define DEFINE_GUID_THUNK(n,data) DEFINE_GUID(n,data) + #define DEFINE_GUIDEX(n) DEFINE_GUID_THUNK(n, STATIC_##n) + #if !defined( DEFINE_WAVEFORMATEX_GUID ) + #define DEFINE_WAVEFORMATEX_GUID(x) (USHORT)(x), 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 + #endif + #define WAVE_FORMAT_ADPCM 0x0002 + #define WAVE_FORMAT_IEEE_FLOAT 0x0003 + #define WAVE_FORMAT_ALAW 0x0006 + #define WAVE_FORMAT_MULAW 0x0007 + #define WAVE_FORMAT_MPEG 0x0050 + #define WAVE_FORMAT_DRM 0x0009 +#endif + +#include <ks.h> +#include <ksmedia.h> +#include <tchar.h> +#include <assert.h> +#include <stdio.h> + +/* These next definitions allow the use of the KSUSER DLL */ +typedef KSDDKAPI DWORD WINAPI KSCREATEPIN(HANDLE, PKSPIN_CONNECT, ACCESS_MASK, PHANDLE); +extern HMODULE DllKsUser; +extern KSCREATEPIN* FunctionKsCreatePin; + +/* Forward definition to break circular type reference between pin and filter */ +struct __PaWinWdmFilter; +typedef struct __PaWinWdmFilter PaWinWdmFilter; + +/* The Pin structure + * A pin is an input or output node, e.g. for audio flow */ +typedef struct __PaWinWdmPin +{ + HANDLE handle; + PaWinWdmFilter* parentFilter; + unsigned long pinId; + KSPIN_CONNECT* pinConnect; + unsigned long pinConnectSize; + KSDATAFORMAT_WAVEFORMATEX* ksDataFormatWfx; + KSPIN_COMMUNICATION communication; + KSDATARANGE* dataRanges; + KSMULTIPLE_ITEM* dataRangesItem; + KSPIN_DATAFLOW dataFlow; + KSPIN_CINSTANCES instances; + unsigned long frameSize; + int maxChannels; + unsigned long formats; + int bestSampleRate; +} +PaWinWdmPin; + +/* The Filter structure + * A filter has a number of pins and a "friendly name" */ +struct __PaWinWdmFilter +{ + HANDLE handle; + int pinCount; + PaWinWdmPin** pins; + TCHAR filterName[MAX_PATH]; + TCHAR friendlyName[MAX_PATH]; + int maxInputChannels; + int maxOutputChannels; + unsigned long formats; + int usageCount; + int bestSampleRate; +}; + +/* PaWinWdmHostApiRepresentation - host api datastructure specific to this implementation */ +typedef struct __PaWinWdmHostApiRepresentation +{ + PaUtilHostApiRepresentation inheritedHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup *allocations; + PaWinWdmFilter** filters; + int filterCount; +} +PaWinWdmHostApiRepresentation; + +typedef struct __PaWinWdmDeviceInfo +{ + PaDeviceInfo inheritedDeviceInfo; + PaWinWdmFilter* filter; +} +PaWinWdmDeviceInfo; + +typedef struct __DATAPACKET +{ + KSSTREAM_HEADER Header; + OVERLAPPED Signal; +} DATAPACKET; + +/* PaWinWdmStream - a stream data structure specifically for this implementation */ +typedef struct __PaWinWdmStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + + PaWinWdmPin* recordingPin; + PaWinWdmPin* playbackPin; + char* hostBuffer; + unsigned long framesPerHostIBuffer; + unsigned long framesPerHostOBuffer; + int bytesPerInputFrame; + int bytesPerOutputFrame; + int streamStarted; + int streamActive; + int streamStop; + int streamAbort; + int oldProcessPriority; + HANDLE streamThread; + HANDLE events[5]; /* 2 play + 2 record packets + abort events */ + DATAPACKET packets[4]; /* 2 play + 2 record */ + PaStreamFlags streamFlags; + /* These values handle the case where the user wants to use fewer + * channels than the device has */ + int userInputChannels; + int deviceInputChannels; + int userOutputChannels; + int deviceOutputChannels; + int inputSampleSize; + int outputSampleSize; +} +PaWinWdmStream; + +#include <setupapi.h> + +HMODULE DllKsUser = NULL; +KSCREATEPIN* FunctionKsCreatePin = NULL; + +/* prototypes for functions declared in this file */ + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +PaError PaWinWdm_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +/* Low level I/O functions */ +static PaError WdmSyncIoctl(HANDLE handle, + unsigned long ioctlNumber, + void* inBuffer, + unsigned long inBufferCount, + void* outBuffer, + unsigned long outBufferCount, + unsigned long* bytesReturned); +static PaError WdmGetPropertySimple(HANDLE handle, + const GUID* const guidPropertySet, + unsigned long property, + void* value, + unsigned long valueCount, + void* instance, + unsigned long instanceCount); +static PaError WdmSetPropertySimple(HANDLE handle, + const GUID* const guidPropertySet, + unsigned long property, + void* value, + unsigned long valueCount, + void* instance, + unsigned long instanceCount); +static PaError WdmGetPinPropertySimple(HANDLE handle, + unsigned long pinId, + const GUID* const guidPropertySet, + unsigned long property, + void* value, + unsigned long valueCount); +static PaError WdmGetPinPropertyMulti(HANDLE handle, + unsigned long pinId, + const GUID* const guidPropertySet, + unsigned long property, + KSMULTIPLE_ITEM** ksMultipleItem); + +/** Pin management functions */ +static PaWinWdmPin* PinNew(PaWinWdmFilter* parentFilter, unsigned long pinId, PaError* error); +static void PinFree(PaWinWdmPin* pin); +static void PinClose(PaWinWdmPin* pin); +static PaError PinInstantiate(PaWinWdmPin* pin); +/*static PaError PinGetState(PaWinWdmPin* pin, KSSTATE* state); NOT USED */ +static PaError PinSetState(PaWinWdmPin* pin, KSSTATE state); +static PaError PinSetFormat(PaWinWdmPin* pin, const WAVEFORMATEX* format); +static PaError PinIsFormatSupported(PaWinWdmPin* pin, const WAVEFORMATEX* format); + +/* Filter management functions */ +static PaWinWdmFilter* FilterNew( + TCHAR* filterName, + TCHAR* friendlyName, + PaError* error); +static void FilterFree(PaWinWdmFilter* filter); +static PaWinWdmPin* FilterCreateRenderPin( + PaWinWdmFilter* filter, + const WAVEFORMATEX* wfex, + PaError* error); +static PaWinWdmPin* FilterFindViableRenderPin( + PaWinWdmFilter* filter, + const WAVEFORMATEX* wfex, + PaError* error); +static PaError FilterCanCreateRenderPin( + PaWinWdmFilter* filter, + const WAVEFORMATEX* wfex); +static PaWinWdmPin* FilterCreateCapturePin( + PaWinWdmFilter* filter, + const WAVEFORMATEX* wfex, + PaError* error); +static PaWinWdmPin* FilterFindViableCapturePin( + PaWinWdmFilter* filter, + const WAVEFORMATEX* wfex, + PaError* error); +static PaError FilterCanCreateCapturePin( + PaWinWdmFilter* filter, + const WAVEFORMATEX* pwfx); +static PaError FilterUse( + PaWinWdmFilter* filter); +static void FilterRelease( + PaWinWdmFilter* filter); + +/* Interface functions */ +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); +static PaError CloseStream( PaStream* stream ); +static PaError StartStream( PaStream *stream ); +static PaError StopStream( PaStream *stream ); +static PaError AbortStream( PaStream *stream ); +static PaError IsStreamStopped( PaStream *s ); +static PaError IsStreamActive( PaStream *stream ); +static PaTime GetStreamTime( PaStream *stream ); +static double GetStreamCpuLoad( PaStream* stream ); +static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames ); +static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames ); +static signed long GetStreamReadAvailable( PaStream* stream ); +static signed long GetStreamWriteAvailable( PaStream* stream ); + +/* Utility functions */ +static unsigned long GetWfexSize(const WAVEFORMATEX* wfex); +static PaError BuildFilterList(PaWinWdmHostApiRepresentation* wdmHostApi); +static BOOL PinWrite(HANDLE h, DATAPACKET* p); +static BOOL PinRead(HANDLE h, DATAPACKET* p); +static void DuplicateFirstChannelInt16(void* buffer, int channels, int samples); +static void DuplicateFirstChannelInt24(void* buffer, int channels, int samples); +static DWORD WINAPI ProcessingThread(LPVOID pParam); + +/* Function bodies */ + +static unsigned long GetWfexSize(const WAVEFORMATEX* wfex) +{ + if ( wfex->wFormatTag == WAVE_FORMAT_PCM ) + { + return sizeof( WAVEFORMATEX ); + } + else + { + return (sizeof( WAVEFORMATEX ) + wfex->cbSize); + } +} + +/* +Low level pin/filter access functions +*/ +static PaError WdmSyncIoctl(HANDLE handle, + unsigned long ioctlNumber, + void* inBuffer, + unsigned long inBufferCount, + void* outBuffer, + unsigned long outBufferCount, + unsigned long* bytesReturned) +{ + PaError result = paNoError; + OVERLAPPED overlapped; + int boolResult; + unsigned long dummyBytesReturned; + unsigned long error; + + if (!bytesReturned) + { + /* User a dummy as the caller hasn't supplied one */ + bytesReturned = &dummyBytesReturned; + } + + FillMemory((void *)&overlapped,sizeof(overlapped),0); + overlapped.hEvent = CreateEvent(NULL,FALSE,FALSE,NULL); + if ( !overlapped.hEvent ) + { + result = paInsufficientMemory; + goto error; + } + overlapped.hEvent = (HANDLE)((DWORD_PTR)overlapped.hEvent | 0x1); + + boolResult = DeviceIoControl(handle, ioctlNumber, inBuffer, inBufferCount, + outBuffer, outBufferCount, bytesReturned, &overlapped); + if ( !boolResult ) + { + error = GetLastError(); + if ( error == ERROR_IO_PENDING ) + { + error = WaitForSingleObject(overlapped.hEvent,INFINITE); + if ( error != WAIT_OBJECT_0 ) + { + result = paUnanticipatedHostError; + goto error; + } + } + else if ((( error == ERROR_INSUFFICIENT_BUFFER ) || + ( error == ERROR_MORE_DATA )) && + ( ioctlNumber == IOCTL_KS_PROPERTY ) && + ( outBufferCount == 0 )) + { + boolResult = TRUE; + } + else + { + result = paUnanticipatedHostError; + } + } + if ( !boolResult ) + *bytesReturned = 0; + +error: + if ( overlapped.hEvent ) + { + CloseHandle( overlapped.hEvent ); + } + return result; +} + +static PaError WdmGetPropertySimple(HANDLE handle, + const GUID* const guidPropertySet, + unsigned long property, + void* value, + unsigned long valueCount, + void* instance, + unsigned long instanceCount) +{ + PaError result; + KSPROPERTY* ksProperty; + unsigned long propertyCount; + + propertyCount = sizeof(KSPROPERTY) + instanceCount; + ksProperty = (KSPROPERTY*)PaUtil_AllocateMemory( propertyCount ); + if( !ksProperty ) + { + return paInsufficientMemory; + } + + FillMemory((void*)ksProperty,sizeof(ksProperty),0); + ksProperty->Set = *guidPropertySet; + ksProperty->Id = property; + ksProperty->Flags = KSPROPERTY_TYPE_GET; + + if ( instance ) + { + memcpy( (void*)(((char*)ksProperty)+sizeof(KSPROPERTY)), instance, instanceCount ); + } + + result = WdmSyncIoctl( + handle, + IOCTL_KS_PROPERTY, + ksProperty, + propertyCount, + value, + valueCount, + NULL); + + PaUtil_FreeMemory( ksProperty ); + return result; +} + +static PaError WdmSetPropertySimple( + HANDLE handle, + const GUID* const guidPropertySet, + unsigned long property, + void* value, + unsigned long valueCount, + void* instance, + unsigned long instanceCount) +{ + PaError result; + KSPROPERTY* ksProperty; + unsigned long propertyCount = 0; + + propertyCount = sizeof(KSPROPERTY) + instanceCount; + ksProperty = (KSPROPERTY*)PaUtil_AllocateMemory( propertyCount ); + if( !ksProperty ) + { + return paInsufficientMemory; + } + + ksProperty->Set = *guidPropertySet; + ksProperty->Id = property; + ksProperty->Flags = KSPROPERTY_TYPE_SET; + + if ( instance ) + { + memcpy((void*)((char*)ksProperty + sizeof(KSPROPERTY)), instance, instanceCount); + } + + result = WdmSyncIoctl( + handle, + IOCTL_KS_PROPERTY, + ksProperty, + propertyCount, + value, + valueCount, + NULL); + + PaUtil_FreeMemory( ksProperty ); + return result; +} + +static PaError WdmGetPinPropertySimple( + HANDLE handle, + unsigned long pinId, + const GUID* const guidPropertySet, + unsigned long property, + void* value, + unsigned long valueCount) +{ + PaError result; + + KSP_PIN ksPProp; + ksPProp.Property.Set = *guidPropertySet; + ksPProp.Property.Id = property; + ksPProp.Property.Flags = KSPROPERTY_TYPE_GET; + ksPProp.PinId = pinId; + ksPProp.Reserved = 0; + + result = WdmSyncIoctl( + handle, + IOCTL_KS_PROPERTY, + &ksPProp, + sizeof(KSP_PIN), + value, + valueCount, + NULL); + + return result; +} + +static PaError WdmGetPinPropertyMulti( + HANDLE handle, + unsigned long pinId, + const GUID* const guidPropertySet, + unsigned long property, + KSMULTIPLE_ITEM** ksMultipleItem) +{ + PaError result; + unsigned long multipleItemSize = 0; + KSP_PIN ksPProp; + + ksPProp.Property.Set = *guidPropertySet; + ksPProp.Property.Id = property; + ksPProp.Property.Flags = KSPROPERTY_TYPE_GET; + ksPProp.PinId = pinId; + ksPProp.Reserved = 0; + + result = WdmSyncIoctl( + handle, + IOCTL_KS_PROPERTY, + &ksPProp.Property, + sizeof(KSP_PIN), + NULL, + 0, + &multipleItemSize); + if ( result != paNoError ) + { + return result; + } + + *ksMultipleItem = (KSMULTIPLE_ITEM*)PaUtil_AllocateMemory( multipleItemSize ); + if( !*ksMultipleItem ) + { + return paInsufficientMemory; + } + + result = WdmSyncIoctl( + handle, + IOCTL_KS_PROPERTY, + &ksPProp, + sizeof(KSP_PIN), + (void*)*ksMultipleItem, + multipleItemSize, + NULL); + + if ( result != paNoError ) + { + PaUtil_FreeMemory( ksMultipleItem ); + } + + return result; +} + + +/* +Create a new pin object belonging to a filter +The pin object holds all the configuration information about the pin +before it is opened, and then the handle of the pin after is opened +*/ +static PaWinWdmPin* PinNew(PaWinWdmFilter* parentFilter, unsigned long pinId, PaError* error) +{ + PaWinWdmPin* pin; + PaError result; + unsigned long i; + KSMULTIPLE_ITEM* item = NULL; + KSIDENTIFIER* identifier; + KSDATARANGE* dataRange; + + PA_LOGE_; + PA_DEBUG(("Creating pin %d:\n",pinId)); + + /* Allocate the new PIN object */ + pin = (PaWinWdmPin*)PaUtil_AllocateMemory( sizeof(PaWinWdmPin) ); + if( !pin ) + { + result = paInsufficientMemory; + goto error; + } + + /* Zero the pin object */ + /* memset( (void*)pin, 0, sizeof(PaWinWdmPin) ); */ + + pin->parentFilter = parentFilter; + pin->pinId = pinId; + + /* Allocate a connect structure */ + pin->pinConnectSize = sizeof(KSPIN_CONNECT) + sizeof(KSDATAFORMAT_WAVEFORMATEX); + pin->pinConnect = (KSPIN_CONNECT*)PaUtil_AllocateMemory( pin->pinConnectSize ); + if( !pin->pinConnect ) + { + result = paInsufficientMemory; + goto error; + } + + /* Configure the connect structure with default values */ + pin->pinConnect->Interface.Set = KSINTERFACESETID_Standard; + pin->pinConnect->Interface.Id = KSINTERFACE_STANDARD_STREAMING; + pin->pinConnect->Interface.Flags = 0; + pin->pinConnect->Medium.Set = KSMEDIUMSETID_Standard; + pin->pinConnect->Medium.Id = KSMEDIUM_TYPE_ANYINSTANCE; + pin->pinConnect->Medium.Flags = 0; + pin->pinConnect->PinId = pinId; + pin->pinConnect->PinToHandle = NULL; + pin->pinConnect->Priority.PriorityClass = KSPRIORITY_NORMAL; + pin->pinConnect->Priority.PrioritySubClass = 1; + pin->ksDataFormatWfx = (KSDATAFORMAT_WAVEFORMATEX*)(pin->pinConnect + 1); + pin->ksDataFormatWfx->DataFormat.FormatSize = sizeof(KSDATAFORMAT_WAVEFORMATEX); + pin->ksDataFormatWfx->DataFormat.Flags = 0; + pin->ksDataFormatWfx->DataFormat.Reserved = 0; + pin->ksDataFormatWfx->DataFormat.MajorFormat = KSDATAFORMAT_TYPE_AUDIO; + pin->ksDataFormatWfx->DataFormat.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + pin->ksDataFormatWfx->DataFormat.Specifier = KSDATAFORMAT_SPECIFIER_WAVEFORMATEX; + + pin->frameSize = 0; /* Unknown until we instantiate pin */ + + /* Get the COMMUNICATION property */ + result = WdmGetPinPropertySimple( + parentFilter->handle, + pinId, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_COMMUNICATION, + &pin->communication, + sizeof(KSPIN_COMMUNICATION)); + if ( result != paNoError ) + goto error; + + if ( /*(pin->communication != KSPIN_COMMUNICATION_SOURCE) &&*/ + (pin->communication != KSPIN_COMMUNICATION_SINK) && + (pin->communication != KSPIN_COMMUNICATION_BOTH) ) + { + PA_DEBUG(("Not source/sink\n")); + result = paInvalidDevice; + goto error; + } + + /* Get dataflow information */ + result = WdmGetPinPropertySimple( + parentFilter->handle, + pinId, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_DATAFLOW, + &pin->dataFlow, + sizeof(KSPIN_DATAFLOW)); + + if ( result != paNoError ) + goto error; + + /* Get the INTERFACE property list */ + result = WdmGetPinPropertyMulti( + parentFilter->handle, + pinId, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_INTERFACES, + &item); + + if ( result != paNoError ) + goto error; + + identifier = (KSIDENTIFIER*)(item+1); + + /* Check that at least one interface is STANDARD_STREAMING */ + result = paUnanticipatedHostError; + for ( i = 0; i < item->Count; i++ ) + { + if ( !memcmp( (void*)&identifier[i].Set, (void*)&KSINTERFACESETID_Standard, sizeof( GUID ) ) && + ( identifier[i].Id == KSINTERFACE_STANDARD_STREAMING ) ) + { + result = paNoError; + break; + } + } + + if ( result != paNoError ) + { + PA_DEBUG(("No standard streaming\n")); + goto error; + } + + /* Don't need interfaces any more */ + PaUtil_FreeMemory( item ); + item = NULL; + + /* Get the MEDIUM properties list */ + result = WdmGetPinPropertyMulti( + parentFilter->handle, + pinId, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_MEDIUMS, + &item); + + if ( result != paNoError ) + goto error; + + identifier = (KSIDENTIFIER*)(item+1); /* Not actually necessary... */ + + /* Check that at least one medium is STANDARD_DEVIO */ + result = paUnanticipatedHostError; + for ( i = 0; i < item->Count; i++ ) + { + if ( !memcmp( (void*)&identifier[i].Set, (void*)&KSMEDIUMSETID_Standard, sizeof( GUID ) ) && + ( identifier[i].Id == KSMEDIUM_STANDARD_DEVIO ) ) + { + result = paNoError; + break; + } + } + + if ( result != paNoError ) + { + PA_DEBUG(("No standard devio\n")); + goto error; + } + /* Don't need mediums any more */ + PaUtil_FreeMemory( item ); + item = NULL; + + /* Get DATARANGES */ + result = WdmGetPinPropertyMulti( + parentFilter->handle, + pinId, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_DATARANGES, + &pin->dataRangesItem); + + if ( result != paNoError ) + goto error; + + pin->dataRanges = (KSDATARANGE*)(pin->dataRangesItem +1); + + /* Check that at least one datarange supports audio */ + result = paUnanticipatedHostError; + dataRange = pin->dataRanges; + pin->maxChannels = 0; + pin->bestSampleRate = 0; + pin->formats = 0; + for ( i = 0; i <pin->dataRangesItem->Count; i++) + { + PA_DEBUG(("DR major format %x\n",*(unsigned long*)(&(dataRange->MajorFormat)))); + /* Check that subformat is WAVEFORMATEX, PCM or WILDCARD */ + if ( + IS_VALID_WAVEFORMATEX_GUID(&dataRange->SubFormat) || + !memcmp((void*)&dataRange->SubFormat, (void*)&KSDATAFORMAT_SUBTYPE_PCM, sizeof ( GUID ) ) || + ( !memcmp((void*)&dataRange->SubFormat, (void*)&KSDATAFORMAT_SUBTYPE_WILDCARD, sizeof ( GUID ) ) && + ( !memcmp((void*)&dataRange->MajorFormat, (void*)&KSDATAFORMAT_TYPE_AUDIO, sizeof ( GUID ) ) ) ) ) + { + result = paNoError; + /* Record the maximum possible channels with this pin */ + PA_DEBUG(("MaxChannel: %d\n",pin->maxChannels)); + if ((int)((KSDATARANGE_AUDIO*)dataRange)->MaximumChannels > pin->maxChannels) + { + pin->maxChannels = ((KSDATARANGE_AUDIO*)dataRange)->MaximumChannels; + /*PA_DEBUG(("MaxChannel: %d\n",pin->maxChannels));*/ + } + /* Record the formats (bit depths) that are supported */ + if (((KSDATARANGE_AUDIO*)dataRange)->MinimumBitsPerSample <= 16) + { + pin->formats |= paInt16; + PA_DEBUG(("Format 16 bit supported\n")); + } + if (((KSDATARANGE_AUDIO*)dataRange)->MaximumBitsPerSample >= 24) + { + pin->formats |= paInt24; + PA_DEBUG(("Format 24 bit supported\n")); + } + if ( + ( pin->bestSampleRate != 48000) && + (((KSDATARANGE_AUDIO*)dataRange)->MaximumSampleFrequency >= 48000) && + (((KSDATARANGE_AUDIO*)dataRange)->MinimumSampleFrequency <= 48000) ) + { + pin->bestSampleRate = 48000; + PA_DEBUG(("48kHz supported\n")); + } + else if ( + ( pin->bestSampleRate != 48000) && ( pin->bestSampleRate != 44100 ) && + (((KSDATARANGE_AUDIO*)dataRange)->MaximumSampleFrequency >= 44100) && + (((KSDATARANGE_AUDIO*)dataRange)->MinimumSampleFrequency <= 44100) ) + { + pin->bestSampleRate = 44100; + PA_DEBUG(("44.1kHz supported\n")); + } + else + { + pin->bestSampleRate = ((KSDATARANGE_AUDIO*)dataRange)->MaximumSampleFrequency; + } + } + dataRange = (KSDATARANGE*)( ((char*)dataRange) + dataRange->FormatSize); + } + + if ( result != paNoError ) + goto error; + + /* Get instance information */ + result = WdmGetPinPropertySimple( + parentFilter->handle, + pinId, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_CINSTANCES, + &pin->instances, + sizeof(KSPIN_CINSTANCES)); + + if ( result != paNoError ) + goto error; + + /* Success */ + *error = paNoError; + PA_DEBUG(("Pin created successfully\n")); + PA_LOGL_; + return pin; + +error: + /* + Error cleanup + */ + PaUtil_FreeMemory( item ); + if ( pin ) + { + PaUtil_FreeMemory( pin->pinConnect ); + PaUtil_FreeMemory( pin->dataRangesItem ); + PaUtil_FreeMemory( pin ); + } + *error = result; + PA_LOGL_; + return NULL; +} + +/* +Safely free all resources associated with the pin +*/ +static void PinFree(PaWinWdmPin* pin) +{ + PA_LOGE_; + if ( pin ) + { + PinClose(pin); + if ( pin->pinConnect ) + { + PaUtil_FreeMemory( pin->pinConnect ); + } + if ( pin->dataRangesItem ) + { + PaUtil_FreeMemory( pin->dataRangesItem ); + } + PaUtil_FreeMemory( pin ); + } + PA_LOGL_; +} + +/* +If the pin handle is open, close it +*/ +static void PinClose(PaWinWdmPin* pin) +{ + PA_LOGE_; + if ( pin == NULL ) + { + PA_DEBUG(("Closing NULL pin!")); + PA_LOGL_; + return; + } + if ( pin->handle != NULL ) + { + PinSetState( pin, KSSTATE_PAUSE ); + PinSetState( pin, KSSTATE_STOP ); + CloseHandle( pin->handle ); + pin->handle = NULL; + FilterRelease(pin->parentFilter); + } + PA_LOGL_; +} + +/* +Set the state of this (instantiated) pin +*/ +static PaError PinSetState(PaWinWdmPin* pin, KSSTATE state) +{ + PaError result; + + PA_LOGE_; + if ( pin == NULL ) + return paInternalError; + if ( pin->handle == NULL ) + return paInternalError; + + result = WdmSetPropertySimple( + pin->handle, + &KSPROPSETID_Connection, + KSPROPERTY_CONNECTION_STATE, + &state, + sizeof(state), + NULL, + 0); + PA_LOGL_; + return result; +} + +static PaError PinInstantiate(PaWinWdmPin* pin) +{ + PaError result; + unsigned long createResult; + KSALLOCATOR_FRAMING ksaf; + KSALLOCATOR_FRAMING_EX ksafex; + + PA_LOGE_; + + if ( pin == NULL ) + return paInternalError; + if (!pin->pinConnect) + return paInternalError; + + FilterUse(pin->parentFilter); + + createResult = FunctionKsCreatePin( + pin->parentFilter->handle, + pin->pinConnect, + GENERIC_WRITE | GENERIC_READ, + &pin->handle + ); + + PA_DEBUG(("Pin create result = %x\n",createResult)); + if ( createResult != ERROR_SUCCESS ) + { + FilterRelease(pin->parentFilter); + pin->handle = NULL; + return paInvalidDevice; + } + + result = WdmGetPropertySimple( + pin->handle, + &KSPROPSETID_Connection, + KSPROPERTY_CONNECTION_ALLOCATORFRAMING, + &ksaf, + sizeof(ksaf), + NULL, + 0); + + if ( result != paNoError ) + { + result = WdmGetPropertySimple( + pin->handle, + &KSPROPSETID_Connection, + KSPROPERTY_CONNECTION_ALLOCATORFRAMING_EX, + &ksafex, + sizeof(ksafex), + NULL, + 0); + if ( result == paNoError ) + { + pin->frameSize = ksafex.FramingItem[0].FramingRange.Range.MinFrameSize; + } + } + else + { + pin->frameSize = ksaf.FrameSize; + } + + PA_LOGL_; + + return paNoError; +} + +/* NOT USED +static PaError PinGetState(PaWinWdmPin* pin, KSSTATE* state) +{ + PaError result; + + if ( state == NULL ) + return paInternalError; + if ( pin == NULL ) + return paInternalError; + if ( pin->handle == NULL ) + return paInternalError; + + result = WdmGetPropertySimple( + pin->handle, + KSPROPSETID_Connection, + KSPROPERTY_CONNECTION_STATE, + state, + sizeof(KSSTATE), + NULL, + 0); + + return result; +} +*/ +static PaError PinSetFormat(PaWinWdmPin* pin, const WAVEFORMATEX* format) +{ + unsigned long size; + void* newConnect; + + PA_LOGE_; + + if ( pin == NULL ) + return paInternalError; + if ( format == NULL ) + return paInternalError; + + size = GetWfexSize(format) + sizeof(KSPIN_CONNECT) + sizeof(KSDATAFORMAT_WAVEFORMATEX) - sizeof(WAVEFORMATEX); + + if ( pin->pinConnectSize != size ) + { + newConnect = PaUtil_AllocateMemory( size ); + if ( newConnect == NULL ) + return paInsufficientMemory; + memcpy( newConnect, (void*)pin->pinConnect, min(pin->pinConnectSize,size) ); + PaUtil_FreeMemory( pin->pinConnect ); + pin->pinConnect = (KSPIN_CONNECT*)newConnect; + pin->pinConnectSize = size; + pin->ksDataFormatWfx = (KSDATAFORMAT_WAVEFORMATEX*)((KSPIN_CONNECT*)newConnect + 1); + pin->ksDataFormatWfx->DataFormat.FormatSize = size - sizeof(KSPIN_CONNECT); + } + + memcpy( (void*)&(pin->ksDataFormatWfx->WaveFormatEx), format, GetWfexSize(format) ); + pin->ksDataFormatWfx->DataFormat.SampleSize = (unsigned short)(format->nChannels * (format->wBitsPerSample / 8)); + + PA_LOGL_; + + return paNoError; +} + +static PaError PinIsFormatSupported(PaWinWdmPin* pin, const WAVEFORMATEX* format) +{ + KSDATARANGE_AUDIO* dataRange; + unsigned long count; + GUID guid = DYNAMIC_GUID( DEFINE_WAVEFORMATEX_GUID(format->wFormatTag) ); + PaError result = paInvalidDevice; + + PA_LOGE_; + + if ( format->wFormatTag == WAVE_FORMAT_EXTENSIBLE ) + { + guid = ((WAVEFORMATEXTENSIBLE*)format)->SubFormat; + } + dataRange = (KSDATARANGE_AUDIO*)pin->dataRanges; + for (count = 0; count<pin->dataRangesItem->Count; count++) + { + if (( !memcmp(&(dataRange->DataRange.MajorFormat),&KSDATAFORMAT_TYPE_AUDIO,sizeof(GUID)) ) || + ( !memcmp(&(dataRange->DataRange.MajorFormat),&KSDATAFORMAT_TYPE_WILDCARD,sizeof(GUID)) )) + { + /* This is an audio or wildcard datarange... */ + if (( !memcmp(&(dataRange->DataRange.SubFormat),&KSDATAFORMAT_SUBTYPE_WILDCARD,sizeof(GUID)) ) || + ( !memcmp(&(dataRange->DataRange.SubFormat),&guid,sizeof(GUID)) )) + { + if (( !memcmp(&(dataRange->DataRange.Specifier),&KSDATAFORMAT_SPECIFIER_WILDCARD,sizeof(GUID)) ) || + ( !memcmp(&(dataRange->DataRange.Specifier),&KSDATAFORMAT_SPECIFIER_WAVEFORMATEX,sizeof(GUID) ))) + { + + PA_DEBUG(("Pin:%x, DataRange:%d\n",(void*)pin,count)); + PA_DEBUG(("\tFormatSize:%d, SampleSize:%d\n",dataRange->DataRange.FormatSize,dataRange->DataRange.SampleSize)); + PA_DEBUG(("\tMaxChannels:%d\n",dataRange->MaximumChannels)); + PA_DEBUG(("\tBits:%d-%d\n",dataRange->MinimumBitsPerSample,dataRange->MaximumBitsPerSample)); + PA_DEBUG(("\tSampleRate:%d-%d\n",dataRange->MinimumSampleFrequency,dataRange->MaximumSampleFrequency)); + + if ( dataRange->MaximumChannels < format->nChannels ) + { + result = paInvalidChannelCount; + continue; + } + if ( dataRange->MinimumBitsPerSample > format->wBitsPerSample ) + { + result = paSampleFormatNotSupported; + continue; + } + if ( dataRange->MaximumBitsPerSample < format->wBitsPerSample ) + { + result = paSampleFormatNotSupported; + continue; + } + if ( dataRange->MinimumSampleFrequency > format->nSamplesPerSec ) + { + result = paInvalidSampleRate; + continue; + } + if ( dataRange->MaximumSampleFrequency < format->nSamplesPerSec ) + { + result = paInvalidSampleRate; + continue; + } + /* Success! */ + PA_LOGL_; + return paNoError; + } + } + } + dataRange = (KSDATARANGE_AUDIO*)( ((char*)dataRange) + dataRange->DataRange.FormatSize); + } + + PA_LOGL_; + + return result; +} + +/** + * Create a new filter object + */ +static PaWinWdmFilter* FilterNew(TCHAR* filterName, TCHAR* friendlyName, PaError* error) +{ + PaWinWdmFilter* filter; + PaError result; + int pinId; + int valid; + + + /* Allocate the new filter object */ + filter = (PaWinWdmFilter*)PaUtil_AllocateMemory( sizeof(PaWinWdmFilter) ); + if( !filter ) + { + result = paInsufficientMemory; + goto error; + } + + /* Zero the filter object - done by AllocateMemory */ + /* memset( (void*)filter, 0, sizeof(PaWinWdmFilter) ); */ + + /* Copy the filter name */ + _tcsncpy(filter->filterName, filterName, MAX_PATH); + + /* Copy the friendly name */ + _tcsncpy(filter->friendlyName, friendlyName, MAX_PATH); + + /* Open the filter handle */ + result = FilterUse(filter); + if ( result != paNoError ) + { + goto error; + } + + /* Get pin count */ + result = WdmGetPinPropertySimple + ( + filter->handle, + 0, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_CTYPES, + &filter->pinCount, + sizeof(filter->pinCount) + ); + + if ( result != paNoError) + { + goto error; + } + + /* Allocate pointer array to hold the pins */ + filter->pins = (PaWinWdmPin**)PaUtil_AllocateMemory( sizeof(PaWinWdmPin*) * filter->pinCount ); + if( !filter->pins ) + { + result = paInsufficientMemory; + goto error; + } + + /* Create all the pins we can */ + filter->maxInputChannels = 0; + filter->maxOutputChannels = 0; + filter->bestSampleRate = 0; + + valid = 0; + for (pinId = 0; pinId < filter->pinCount; pinId++) + { + /* Create the pin with this Id */ + PaWinWdmPin* newPin; + newPin = PinNew(filter, pinId, &result); + if ( result == paInsufficientMemory ) + goto error; + if ( newPin != NULL ) + { + filter->pins[pinId] = newPin; + valid = 1; + + /* Get the max output channel count */ + if (( newPin->dataFlow == KSPIN_DATAFLOW_IN ) && + (( newPin->communication == KSPIN_COMMUNICATION_SINK) || + ( newPin->communication == KSPIN_COMMUNICATION_BOTH))) + { + if (newPin->maxChannels > filter->maxOutputChannels) + filter->maxOutputChannels = newPin->maxChannels; + filter->formats |= newPin->formats; + } + /* Get the max input channel count */ + if (( newPin->dataFlow == KSPIN_DATAFLOW_OUT ) && + (( newPin->communication == KSPIN_COMMUNICATION_SINK) || + ( newPin->communication == KSPIN_COMMUNICATION_BOTH))) + { + if (newPin->maxChannels > filter->maxInputChannels) + filter->maxInputChannels = newPin->maxChannels; + filter->formats |= newPin->formats; + } + + if (newPin->bestSampleRate > filter->bestSampleRate) + { + filter->bestSampleRate = newPin->bestSampleRate; + } + } + } + + if (( filter->maxInputChannels == 0) && ( filter->maxOutputChannels == 0)) + { + /* No input or output... not valid */ + valid = 0; + } + + if ( !valid ) + { + /* No valid pin was found on this filter so we destroy it */ + result = paDeviceUnavailable; + goto error; + } + + /* Close the filter handle for now + * It will be opened later when needed */ + FilterRelease(filter); + + *error = paNoError; + return filter; + +error: + /* + Error cleanup + */ + if ( filter ) + { + for ( pinId = 0; pinId < filter->pinCount; pinId++ ) + PinFree(filter->pins[pinId]); + PaUtil_FreeMemory( filter->pins ); + if ( filter->handle ) + CloseHandle( filter->handle ); + PaUtil_FreeMemory( filter ); + } + *error = result; + return NULL; +} + +/** + * Free a previously created filter + */ +static void FilterFree(PaWinWdmFilter* filter) +{ + int pinId; + PA_LOGL_; + if ( filter ) + { + for ( pinId = 0; pinId < filter->pinCount; pinId++ ) + PinFree(filter->pins[pinId]); + PaUtil_FreeMemory( filter->pins ); + if ( filter->handle ) + CloseHandle( filter->handle ); + PaUtil_FreeMemory( filter ); + } + PA_LOGE_; +} + +/** + * Reopen the filter handle if necessary so it can be used + **/ +static PaError FilterUse(PaWinWdmFilter* filter) +{ + assert( filter ); + + PA_LOGE_; + if ( filter->handle == NULL ) + { + /* Open the filter */ + filter->handle = CreateFile( + filter->filterName, + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, + NULL); + + if ( filter->handle == NULL ) + { + return paDeviceUnavailable; + } + } + filter->usageCount++; + PA_LOGL_; + return paNoError; +} + +/** + * Release the filter handle if nobody is using it + **/ +static void FilterRelease(PaWinWdmFilter* filter) +{ + assert( filter ); + assert( filter->usageCount > 0 ); + + PA_LOGE_; + filter->usageCount--; + if ( filter->usageCount == 0 ) + { + if ( filter->handle != NULL ) + { + CloseHandle( filter->handle ); + filter->handle = NULL; + } + } + PA_LOGL_; +} + +/** + * Create a render (playback) Pin using the supplied format + **/ +static PaWinWdmPin* FilterCreateRenderPin(PaWinWdmFilter* filter, + const WAVEFORMATEX* wfex, + PaError* error) +{ + PaError result; + PaWinWdmPin* pin; + + assert( filter ); + + pin = FilterFindViableRenderPin(filter,wfex,&result); + if (!pin) + { + goto error; + } + result = PinSetFormat(pin,wfex); + if ( result != paNoError ) + { + goto error; + } + result = PinInstantiate(pin); + if ( result != paNoError ) + { + goto error; + } + + *error = paNoError; + return pin; + +error: + *error = result; + return NULL; +} + +/** + * Find a pin that supports the given format + **/ +static PaWinWdmPin* FilterFindViableRenderPin(PaWinWdmFilter* filter, + const WAVEFORMATEX* wfex, + PaError* error) +{ + int pinId; + PaWinWdmPin* pin; + PaError result = paDeviceUnavailable; + *error = paNoError; + + assert( filter ); + + for ( pinId = 0; pinId<filter->pinCount; pinId++ ) + { + pin = filter->pins[pinId]; + if ( pin != NULL ) + { + if (( pin->dataFlow == KSPIN_DATAFLOW_IN ) && + (( pin->communication == KSPIN_COMMUNICATION_SINK) || + ( pin->communication == KSPIN_COMMUNICATION_BOTH))) + { + result = PinIsFormatSupported( pin, wfex ); + if ( result == paNoError ) + { + return pin; + } + } + } + } + + *error = result; + return NULL; +} + +/** + * Check if there is a pin that should playback + * with the supplied format + **/ +static PaError FilterCanCreateRenderPin(PaWinWdmFilter* filter, + const WAVEFORMATEX* wfex) +{ + PaWinWdmPin* pin; + PaError result; + + assert ( filter ); + + pin = FilterFindViableRenderPin(filter,wfex,&result); + /* result will be paNoError if pin found + * or else an error code indicating what is wrong with the format + **/ + return result; +} + +/** + * Create a capture (record) Pin using the supplied format + **/ +static PaWinWdmPin* FilterCreateCapturePin(PaWinWdmFilter* filter, + const WAVEFORMATEX* wfex, + PaError* error) +{ + PaError result; + PaWinWdmPin* pin; + + assert( filter ); + + pin = FilterFindViableCapturePin(filter,wfex,&result); + if (!pin) + { + goto error; + } + + result = PinSetFormat(pin,wfex); + if ( result != paNoError ) + { + goto error; + } + + result = PinInstantiate(pin); + if ( result != paNoError ) + { + goto error; + } + + *error = paNoError; + return pin; + +error: + *error = result; + return NULL; +} + +/** + * Find a capture pin that supports the given format + **/ +static PaWinWdmPin* FilterFindViableCapturePin(PaWinWdmFilter* filter, + const WAVEFORMATEX* wfex, + PaError* error) +{ + int pinId; + PaWinWdmPin* pin; + PaError result = paDeviceUnavailable; + *error = paNoError; + + assert( filter ); + + for ( pinId = 0; pinId<filter->pinCount; pinId++ ) + { + pin = filter->pins[pinId]; + if ( pin != NULL ) + { + if (( pin->dataFlow == KSPIN_DATAFLOW_OUT ) && + (( pin->communication == KSPIN_COMMUNICATION_SINK) || + ( pin->communication == KSPIN_COMMUNICATION_BOTH))) + { + result = PinIsFormatSupported( pin, wfex ); + if ( result == paNoError ) + { + return pin; + } + } + } + } + + *error = result; + return NULL; +} + +/** + * Check if there is a pin that should playback + * with the supplied format + **/ +static PaError FilterCanCreateCapturePin(PaWinWdmFilter* filter, + const WAVEFORMATEX* wfex) +{ + PaWinWdmPin* pin; + PaError result; + + assert ( filter ); + + pin = FilterFindViableCapturePin(filter,wfex,&result); + /* result will be paNoError if pin found + * or else an error code indicating what is wrong with the format + **/ + return result; +} + +/** + * Build the list of available filters + */ +static PaError BuildFilterList(PaWinWdmHostApiRepresentation* wdmHostApi) +{ + PaError result = paNoError; + HDEVINFO handle = NULL; + int device; + int invalidDevices; + int slot; + SP_DEVICE_INTERFACE_DATA interfaceData; + SP_DEVICE_INTERFACE_DATA aliasData; + SP_DEVINFO_DATA devInfoData; + int noError; + const int sizeInterface = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA) + (MAX_PATH * sizeof(WCHAR)); + unsigned char interfaceDetailsArray[sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA) + (MAX_PATH * sizeof(WCHAR))]; + SP_DEVICE_INTERFACE_DETAIL_DATA* devInterfaceDetails = (SP_DEVICE_INTERFACE_DETAIL_DATA*)interfaceDetailsArray; + TCHAR friendlyName[MAX_PATH]; + HKEY hkey; + DWORD sizeFriendlyName; + DWORD type; + PaWinWdmFilter* newFilter; + GUID* category = (GUID*)&KSCATEGORY_AUDIO; + GUID* alias_render = (GUID*)&KSCATEGORY_RENDER; + GUID* alias_capture = (GUID*)&KSCATEGORY_CAPTURE; + DWORD hasAlias; + + PA_LOGE_; + + devInterfaceDetails->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + + /* Open a handle to search for devices (filters) */ + handle = SetupDiGetClassDevs(category,NULL,NULL,DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + if ( handle == NULL ) + { + return paUnanticipatedHostError; + } + PA_DEBUG(("Setup called\n")); + + /* First let's count the number of devices so we can allocate a list */ + invalidDevices = 0; + for ( device = 0;;device++ ) + { + interfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + interfaceData.Reserved = 0; + aliasData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + aliasData.Reserved = 0; + noError = SetupDiEnumDeviceInterfaces(handle,NULL,category,device,&interfaceData); + PA_DEBUG(("Enum called\n")); + if ( !noError ) + break; /* No more devices */ + + /* Check this one has the render or capture alias */ + hasAlias = 0; + noError = SetupDiGetDeviceInterfaceAlias(handle,&interfaceData,alias_render,&aliasData); + PA_DEBUG(("noError = %d\n",noError)); + if (noError) + { + if (aliasData.Flags && (!(aliasData.Flags & SPINT_REMOVED))) + { + PA_DEBUG(("Device %d has render alias\n",device)); + hasAlias |= 1; /* Has render alias */ + } + else + { + PA_DEBUG(("Device %d has no render alias\n",device)); + } + } + noError = SetupDiGetDeviceInterfaceAlias(handle,&interfaceData,alias_capture,&aliasData); + if (noError) + { + if (aliasData.Flags && (!(aliasData.Flags & SPINT_REMOVED))) + { + PA_DEBUG(("Device %d has capture alias\n",device)); + hasAlias |= 2; /* Has capture alias */ + } + else + { + PA_DEBUG(("Device %d has no capture alias\n",device)); + } + } + if (!hasAlias) + invalidDevices++; /* This was not a valid capture or render audio device */ + + } + /* Remember how many there are */ + wdmHostApi->filterCount = device-invalidDevices; + + PA_DEBUG(("Interfaces found: %d\n",device-invalidDevices)); + + /* Now allocate the list of pointers to devices */ + wdmHostApi->filters = (PaWinWdmFilter**)PaUtil_AllocateMemory( sizeof(PaWinWdmFilter*) * device ); + if( !wdmHostApi->filters ) + { + if (handle != NULL) + SetupDiDestroyDeviceInfoList(handle); + return paInsufficientMemory; + } + + /* Now create filter objects for each interface found */ + slot = 0; + for ( device = 0;;device++ ) + { + interfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + interfaceData.Reserved = 0; + aliasData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + aliasData.Reserved = 0; + devInfoData.cbSize = sizeof(SP_DEVINFO_DATA); + devInfoData.Reserved = 0; + + noError = SetupDiEnumDeviceInterfaces(handle,NULL,category,device,&interfaceData); + if ( !noError ) + break; /* No more devices */ + + /* Check this one has the render or capture alias */ + hasAlias = 0; + noError = SetupDiGetDeviceInterfaceAlias(handle,&interfaceData,alias_render,&aliasData); + if (noError) + { + if (aliasData.Flags && (!(aliasData.Flags & SPINT_REMOVED))) + { + PA_DEBUG(("Device %d has render alias\n",device)); + hasAlias |= 1; /* Has render alias */ + } + } + noError = SetupDiGetDeviceInterfaceAlias(handle,&interfaceData,alias_capture,&aliasData); + if (noError) + { + if (aliasData.Flags && (!(aliasData.Flags & SPINT_REMOVED))) + { + PA_DEBUG(("Device %d has capture alias\n",device)); + hasAlias |= 2; /* Has capture alias */ + } + } + if (!hasAlias) + continue; /* This was not a valid capture or render audio device */ + + noError = SetupDiGetDeviceInterfaceDetail(handle,&interfaceData,devInterfaceDetails,sizeInterface,NULL,&devInfoData); + if ( noError ) + { + /* Try to get the "friendly name" for this interface */ + sizeFriendlyName = sizeof(friendlyName); + /* Fix contributed by Ben Allison + * Removed KEY_SET_VALUE from flags on following call + * as its causes failure when running without admin rights + * and it was not required */ + hkey=SetupDiOpenDeviceInterfaceRegKey(handle,&interfaceData,0,KEY_QUERY_VALUE); + if (hkey!=INVALID_HANDLE_VALUE) + { + noError = RegQueryValueEx(hkey,TEXT("FriendlyName"),0,&type,(BYTE*)friendlyName,&sizeFriendlyName); + if ( noError == ERROR_SUCCESS ) + { + PA_DEBUG(("Interface %d, Name: %s\n",device,friendlyName)); + RegCloseKey(hkey); + } + else + { + friendlyName[0] = 0; + } + } + newFilter = FilterNew(devInterfaceDetails->DevicePath,friendlyName,&result); + if ( result == paNoError ) + { + PA_DEBUG(("Filter created\n")); + wdmHostApi->filters[slot] = newFilter; + slot++; + } + else + { + PA_DEBUG(("Filter NOT created\n")); + /* As there are now less filters than we initially thought + * we must reduce the count by one */ + wdmHostApi->filterCount--; + } + } + } + + /* Clean up */ + if (handle != NULL) + SetupDiDestroyDeviceInfoList(handle); + + return paNoError; +} + +PaError PaWinWdm_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + int i, deviceCount; + PaWinWdmHostApiRepresentation *wdmHostApi; + PaWinWdmDeviceInfo *deviceInfoArray; + PaWinWdmFilter* pFilter; + PaWinWdmDeviceInfo *wdmDeviceInfo; + PaDeviceInfo *deviceInfo; + + PA_LOGE_; + + /* + Attempt to load the KSUSER.DLL without which we cannot create pins + We will unload this on termination + */ + if (DllKsUser == NULL) + { + DllKsUser = LoadLibrary(TEXT("ksuser.dll")); + if (DllKsUser == NULL) + goto error; + } + + FunctionKsCreatePin = (KSCREATEPIN*)GetProcAddress(DllKsUser, "KsCreatePin"); + if (FunctionKsCreatePin == NULL) + goto error; + + wdmHostApi = (PaWinWdmHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaWinWdmHostApiRepresentation) ); + if( !wdmHostApi ) + { + result = paInsufficientMemory; + goto error; + } + + wdmHostApi->allocations = PaUtil_CreateAllocationGroup(); + if( !wdmHostApi->allocations ) + { + result = paInsufficientMemory; + goto error; + } + + result = BuildFilterList( wdmHostApi ); + if ( result != paNoError ) + { + goto error; + } + deviceCount = wdmHostApi->filterCount; + + *hostApi = &wdmHostApi->inheritedHostApiRep; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paWDMKS; + (*hostApi)->info.name = "Windows WDM-KS"; + + (*hostApi)->info.defaultOutputDevice = paNoDevice; + + if( deviceCount > 0 ) + { + (*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + wdmHostApi->allocations, sizeof(PaWinWdmDeviceInfo*) * deviceCount ); + if( !(*hostApi)->deviceInfos ) + { + result = paInsufficientMemory; + goto error; + } + + /* allocate all device info structs in a contiguous block */ + deviceInfoArray = (PaWinWdmDeviceInfo*)PaUtil_GroupAllocateMemory( + wdmHostApi->allocations, sizeof(PaWinWdmDeviceInfo) * deviceCount ); + if( !deviceInfoArray ) + { + result = paInsufficientMemory; + goto error; + } + + for( i=0; i < deviceCount; ++i ) + { + wdmDeviceInfo = &deviceInfoArray[i]; + deviceInfo = &wdmDeviceInfo->inheritedDeviceInfo; + pFilter = wdmHostApi->filters[i]; + if ( pFilter == NULL ) + continue; + wdmDeviceInfo->filter = pFilter; + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; + deviceInfo->name = (char*)pFilter->friendlyName; + PA_DEBUG(("Device found name: %s\n",(char*)pFilter->friendlyName)); + deviceInfo->maxInputChannels = pFilter->maxInputChannels; + if (deviceInfo->maxInputChannels > 0) + { + /* Set the default input device to the first device we find with + * more than zero input channels + **/ + if ((*hostApi)->info.defaultInputDevice == paNoDevice) + { + (*hostApi)->info.defaultInputDevice = i; + } + } + + deviceInfo->maxOutputChannels = pFilter->maxOutputChannels; + if (deviceInfo->maxOutputChannels > 0) + { + /* Set the default output device to the first device we find with + * more than zero output channels + **/ + if ((*hostApi)->info.defaultOutputDevice == paNoDevice) + { + (*hostApi)->info.defaultOutputDevice = i; + } + } + + /* These low values are not very useful because + * a) The lowest latency we end up with can depend on many factors such + * as the device buffer sizes/granularities, sample rate, channels and format + * b) We cannot know the device buffer sizes until we try to open/use it at + * a particular setting + * So: we give 512x48000Hz frames as the default low input latency + **/ + deviceInfo->defaultLowInputLatency = (512.0/48000.0); + deviceInfo->defaultLowOutputLatency = (512.0/48000.0); + deviceInfo->defaultHighInputLatency = (4096.0/48000.0); + deviceInfo->defaultHighOutputLatency = (4096.0/48000.0); + deviceInfo->defaultSampleRate = (double)(pFilter->bestSampleRate); + + (*hostApi)->deviceInfos[i] = deviceInfo; + ++(*hostApi)->info.deviceCount; + } + } + + (*hostApi)->info.deviceCount = deviceCount; + + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PaUtil_InitializeStreamInterface( &wdmHostApi->callbackStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface( &wdmHostApi->blockingStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable ); + + PA_LOGL_; + return result; + +error: + if ( DllKsUser != NULL ) + { + FreeLibrary( DllKsUser ); + DllKsUser = NULL; + } + + if( wdmHostApi ) + { + PaUtil_FreeMemory( wdmHostApi->filters ); + if( wdmHostApi->allocations ) + { + PaUtil_FreeAllAllocations( wdmHostApi->allocations ); + PaUtil_DestroyAllocationGroup( wdmHostApi->allocations ); + } + PaUtil_FreeMemory( wdmHostApi ); + } + PA_LOGL_; + return result; +} + + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi; + int i; + PA_LOGE_; + + if( wdmHostApi->filters ) + { + for ( i=0; i<wdmHostApi->filterCount; i++) + { + if ( wdmHostApi->filters[i] != NULL ) + { + FilterFree( wdmHostApi->filters[i] ); + wdmHostApi->filters[i] = NULL; + } + } + } + PaUtil_FreeMemory( wdmHostApi->filters ); + if( wdmHostApi->allocations ) + { + PaUtil_FreeAllAllocations( wdmHostApi->allocations ); + PaUtil_DestroyAllocationGroup( wdmHostApi->allocations ); + } + PaUtil_FreeMemory( wdmHostApi ); + PA_LOGL_; +} + +static void FillWFEXT( WAVEFORMATEXTENSIBLE* pwfext, PaSampleFormat sampleFormat, double sampleRate, int channelCount) + { + PA_LOGE_; + PA_DEBUG(( "sampleFormat = %lx\n" , sampleFormat )); + PA_DEBUG(( "sampleRate = %f\n" , sampleRate )); + PA_DEBUG(( "chanelCount = %d\n", channelCount )); + + pwfext->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + pwfext->Format.nChannels = channelCount; + pwfext->Format.nSamplesPerSec = (int)sampleRate; + if (channelCount == 1) + pwfext->dwChannelMask = KSAUDIO_SPEAKER_DIRECTOUT; + else + pwfext->dwChannelMask = KSAUDIO_SPEAKER_STEREO; + if (sampleFormat == paFloat32) + { + pwfext->Format.nBlockAlign = channelCount * 4; + pwfext->Format.wBitsPerSample = 32; + pwfext->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX); + pwfext->Samples.wValidBitsPerSample = 32; + pwfext->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + } + else if (sampleFormat == paInt32) + { + pwfext->Format.nBlockAlign = channelCount * 4; + pwfext->Format.wBitsPerSample = 32; + pwfext->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX); + pwfext->Samples.wValidBitsPerSample = 32; + pwfext->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + } + else if (sampleFormat == paInt24) + { + pwfext->Format.nBlockAlign = channelCount * 3; + pwfext->Format.wBitsPerSample = 24; + pwfext->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX); + pwfext->Samples.wValidBitsPerSample = 24; + pwfext->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + } + else if (sampleFormat == paInt16) + { + pwfext->Format.nBlockAlign = channelCount * 2; + pwfext->Format.wBitsPerSample = 16; + pwfext->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX); + pwfext->Samples.wValidBitsPerSample = 16; + pwfext->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + } + pwfext->Format.nAvgBytesPerSec = pwfext->Format.nSamplesPerSec * pwfext->Format.nBlockAlign; + + PA_LOGL_; +} + +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi; + PaWinWdmFilter* pFilter; + int result = paFormatIsSupported; + WAVEFORMATEXTENSIBLE wfx; + + PA_LOGE_; + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* all standard sample formats are supported by the buffer adapter, + this implementation doesn't support any custom sample formats */ + if( inputSampleFormat & paCustomFormat ) + return paSampleFormatNotSupported; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + + /* validate inputStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + + /* Check that the input format is supported */ + FillWFEXT(&wfx,paInt16,sampleRate,inputChannelCount); + + pFilter = wdmHostApi->filters[inputParameters->device]; + result = FilterCanCreateCapturePin(pFilter,(const WAVEFORMATEX*)&wfx); + if ( result != paNoError ) + { + /* Try a WAVE_FORMAT_PCM instead */ + wfx.Format.wFormatTag = WAVE_FORMAT_PCM; + wfx.Format.cbSize = 0; + wfx.Samples.wValidBitsPerSample = 0; + wfx.dwChannelMask = 0; + wfx.SubFormat = GUID_NULL; + result = FilterCanCreateCapturePin(pFilter,(const WAVEFORMATEX*)&wfx); + if ( result != paNoError ) + return result; + } + } + else + { + inputChannelCount = 0; + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* all standard sample formats are supported by the buffer adapter, + this implementation doesn't support any custom sample formats */ + if( outputSampleFormat & paCustomFormat ) + return paSampleFormatNotSupported; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that output device can support outputChannelCount */ + if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + + /* validate outputStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + + /* Check that the output format is supported */ + FillWFEXT(&wfx,paInt16,sampleRate,outputChannelCount); + + pFilter = wdmHostApi->filters[outputParameters->device]; + result = FilterCanCreateRenderPin(pFilter,(const WAVEFORMATEX*)&wfx); + if ( result != paNoError ) + { + /* Try a WAVE_FORMAT_PCM instead */ + wfx.Format.wFormatTag = WAVE_FORMAT_PCM; + wfx.Format.cbSize = 0; + wfx.Samples.wValidBitsPerSample = 0; + wfx.dwChannelMask = 0; + wfx.SubFormat = GUID_NULL; + result = FilterCanCreateRenderPin(pFilter,(const WAVEFORMATEX*)&wfx); + if ( result != paNoError ) + return result; + } + + } + else + { + outputChannelCount = 0; + } + + /* + IMPLEMENT ME: + + - if a full duplex stream is requested, check that the combination + of input and output parameters is supported if necessary + + - check that the device supports sampleRate + + Because the buffer adapter handles conversion between all standard + sample formats, the following checks are only required if paCustomFormat + is implemented, or under some other unusual conditions. + + - check that input device can support inputSampleFormat, or that + we have the capability to convert from inputSampleFormat to + a native format + + - check that output device can support outputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + */ + if ((inputChannelCount == 0)&&(outputChannelCount == 0)) + result = paSampleFormatNotSupported; /* Not right error */ + + PA_LOGL_; + return result; +} + +/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */ + +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ) +{ + PaError result = paNoError; + PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi; + PaWinWdmStream *stream = 0; + /* unsigned long framesPerHostBuffer; these may not be equivalent for all implementations */ + PaSampleFormat inputSampleFormat, outputSampleFormat; + PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat; + int userInputChannels,userOutputChannels; + int size; + PaWinWdmFilter* pFilter; + WAVEFORMATEXTENSIBLE wfx; + + PA_LOGE_; + PA_DEBUG(("sampleRate = %f;",sampleRate)); + PA_DEBUG(("framesPerBuffer = %lu;",framesPerBuffer)); + + if( inputParameters ) + { + userInputChannels = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that input device can support stream->userInputChannels */ + if( userInputChannels > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + + /* validate inputStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + + } + else + { + userInputChannels = 0; + inputSampleFormat = hostInputSampleFormat = paInt16; /* Surpress 'uninitialised var' warnings. */ + } + + if( outputParameters ) + { + userOutputChannels = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that output device can support stream->userInputChannels */ + if( userOutputChannels > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + + /* validate outputStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + + } + else + { + userOutputChannels = 0; + outputSampleFormat = hostOutputSampleFormat = paInt16; /* Surpress 'uninitialized var' warnings. */ + } + + /* validate platform specific flags */ + if( (streamFlags & paPlatformSpecificFlags) != 0 ) + return paInvalidFlag; /* unexpected platform specific flag */ + + stream = (PaWinWdmStream*)PaUtil_AllocateMemory( sizeof(PaWinWdmStream) ); + if( !stream ) + { + result = paInsufficientMemory; + goto error; + } + /* Zero the stream object */ + /* memset((void*)stream,0,sizeof(PaWinWdmStream)); */ + + if( streamCallback ) + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &wdmHostApi->callbackStreamInterface, streamCallback, userData ); + } + else + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &wdmHostApi->blockingStreamInterface, streamCallback, userData ); + } + + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); + + /* Instantiate the input pin if necessary */ + if (userInputChannels > 0) + { + stream->userInputChannels = userInputChannels; + pFilter = wdmHostApi->filters[inputParameters->device]; + hostInputSampleFormat = + PaUtil_SelectClosestAvailableFormat( pFilter->formats, inputSampleFormat ); + if ( hostInputSampleFormat == paInt16 ) + stream->inputSampleSize = 2; + else + stream->inputSampleSize = 3; + FillWFEXT(&wfx,hostInputSampleFormat,sampleRate,stream->userInputChannels); + stream->bytesPerInputFrame = wfx.Format.nBlockAlign; + stream->recordingPin = FilterCreateCapturePin(pFilter,(const WAVEFORMATEX*)&wfx,&result); + stream->deviceInputChannels = stream->userInputChannels; + if (result != paNoError) + { + /* Try a WAVE_FORMAT_PCM instead */ + wfx.Format.wFormatTag = WAVE_FORMAT_PCM; + wfx.Format.cbSize = 0; + wfx.Samples.wValidBitsPerSample = 0; + wfx.dwChannelMask = 0; + wfx.SubFormat = GUID_NULL; + stream->recordingPin = FilterCreateCapturePin(pFilter,(const WAVEFORMATEX*)&wfx,&result); + if ( result != paNoError ) + { + /* Some or all KS devices can only handle the exact number of channels + * they specify. But PortAudio clients expect to be able to + * at least specify mono I/O on a multi-channel device + * If this is the case, then we will do the channel mapping internally + **/ + if ( stream->userInputChannels < pFilter->maxInputChannels ) + { + FillWFEXT(&wfx,hostInputSampleFormat,sampleRate,pFilter->maxInputChannels); + stream->bytesPerInputFrame = wfx.Format.nBlockAlign; + stream->recordingPin = FilterCreateCapturePin(pFilter,(const WAVEFORMATEX*)&wfx,&result); + stream->deviceInputChannels = pFilter->maxInputChannels; + if ( result != paNoError ) + { + /* Try a WAVE_FORMAT_PCM instead */ + wfx.Format.wFormatTag = WAVE_FORMAT_PCM; + wfx.Format.cbSize = 0; + wfx.Samples.wValidBitsPerSample = 0; + wfx.dwChannelMask = 0; + wfx.SubFormat = GUID_NULL; + stream->recordingPin = FilterCreateCapturePin(pFilter,(const WAVEFORMATEX*)&wfx,&result); + } + } + } + } + if (stream->recordingPin == NULL) + { + goto error; + } + stream->recordingPin->frameSize /= stream->bytesPerInputFrame; + PA_DEBUG(("Pin output frames: %d\n",stream->recordingPin->frameSize)); + } + else + { + stream->recordingPin = NULL; + stream->bytesPerInputFrame = 0; + } + + /* Instantiate the output pin if necessary */ + if (userOutputChannels > 0) + { + stream->userOutputChannels = userOutputChannels; + pFilter = wdmHostApi->filters[outputParameters->device]; + hostOutputSampleFormat = + PaUtil_SelectClosestAvailableFormat( pFilter->formats/*paInt16*/, outputSampleFormat ); + if ( hostOutputSampleFormat == paInt16 ) + stream->outputSampleSize = 2; + else + stream->outputSampleSize = 3; + FillWFEXT(&wfx,hostOutputSampleFormat,sampleRate,stream->userOutputChannels); + stream->bytesPerOutputFrame = wfx.Format.nBlockAlign; + stream->playbackPin = FilterCreateRenderPin(pFilter,(WAVEFORMATEX*)&wfx,&result); + stream->deviceOutputChannels = stream->userOutputChannels; + if (result != paNoError) + { + /* Try a WAVE_FORMAT_PCM instead */ + wfx.Format.wFormatTag = WAVE_FORMAT_PCM; + wfx.Format.cbSize = 0; + wfx.Samples.wValidBitsPerSample = 0; + wfx.dwChannelMask = 0; + wfx.SubFormat = GUID_NULL; + stream->playbackPin = FilterCreateRenderPin(pFilter,(WAVEFORMATEX*)&wfx,&result); + if ( result != paNoError ) + { + /* Some or all KS devices can only handle the exact number of channels + * they specify. But PortAudio clients expect to be able to + * at least specify mono I/O on a multi-channel device + * If this is the case, then we will do the channel mapping internally + **/ + if ( stream->userOutputChannels < pFilter->maxOutputChannels ) + { + FillWFEXT(&wfx,hostOutputSampleFormat,sampleRate,pFilter->maxOutputChannels); + stream->bytesPerOutputFrame = wfx.Format.nBlockAlign; + stream->playbackPin = FilterCreateRenderPin(pFilter,(const WAVEFORMATEX*)&wfx,&result); + stream->deviceOutputChannels = pFilter->maxOutputChannels; + if ( result != paNoError ) + { + /* Try a WAVE_FORMAT_PCM instead */ + wfx.Format.wFormatTag = WAVE_FORMAT_PCM; + wfx.Format.cbSize = 0; + wfx.Samples.wValidBitsPerSample = 0; + wfx.dwChannelMask = 0; + wfx.SubFormat = GUID_NULL; + stream->playbackPin = FilterCreateRenderPin(pFilter,(const WAVEFORMATEX*)&wfx,&result); + } + } + } + } + if (stream->playbackPin == NULL) + { + goto error; + } + stream->playbackPin->frameSize /= stream->bytesPerOutputFrame; + PA_DEBUG(("Pin output frames: %d\n",stream->playbackPin->frameSize)); + } + else + { + stream->playbackPin = NULL; + stream->bytesPerOutputFrame = 0; + } + + /* Calculate the framesPerHostXxxxBuffer size based upon the suggested latency values */ + + /* Record the buffer length */ + if (inputParameters) + { + /* Calculate the frames from the user's value - add a bit to round up */ + stream->framesPerHostIBuffer = (unsigned long)((inputParameters->suggestedLatency*sampleRate)+0.0001); + if (stream->framesPerHostIBuffer > (unsigned long)sampleRate) + { /* Upper limit is 1 second */ + stream->framesPerHostIBuffer = (unsigned long)sampleRate; + } + /* Uncomment the following code to make the device-reported + * frame size the lower limit*/ + /* + else if (stream->framesPerHostIBuffer < stream->recordingPin->frameSize) + { + stream->framesPerHostIBuffer = stream->recordingPin->frameSize; + } + */ + PA_DEBUG(("Input frames chosen:%ld\n",stream->framesPerHostIBuffer)); + } + + if (outputParameters) + { + /* Calculate the frames from the user's value - add a bit to round up */ + stream->framesPerHostOBuffer = (unsigned long)((outputParameters->suggestedLatency*sampleRate)+0.0001); + if (stream->framesPerHostOBuffer > (unsigned long)sampleRate) + { /* Upper limit is 1 second */ + stream->framesPerHostOBuffer = (unsigned long)sampleRate; + } + /* Uncomment the following code to make the device-reported + * frame size the lower limit*/ + /* + else if (stream->framesPerHostOBuffer < stream->playbackPin->frameSize) + { + stream->framesPerHostOBuffer = stream->playbackPin->frameSize; + } + */ + PA_DEBUG(("Output frames chosen:%ld\n",stream->framesPerHostOBuffer)); + } + + /* Host buffer size is bounded to the largest of the input and output + frame sizes */ + + result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, + stream->userInputChannels, inputSampleFormat, hostInputSampleFormat, + stream->userOutputChannels, outputSampleFormat, hostOutputSampleFormat, + sampleRate, streamFlags, framesPerBuffer, + max(stream->framesPerHostOBuffer,stream->framesPerHostIBuffer), + paUtilBoundedHostBufferSize, + streamCallback, userData ); + if( result != paNoError ) + goto error; + + stream->streamRepresentation.streamInfo.inputLatency = + ((double)stream->framesPerHostIBuffer) / sampleRate; + stream->streamRepresentation.streamInfo.outputLatency = + ((double)stream->framesPerHostOBuffer) / sampleRate; + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + + + PA_DEBUG(("BytesPerInputFrame = %d\n",stream->bytesPerInputFrame)); + PA_DEBUG(("BytesPerOutputFrame = %d\n",stream->bytesPerOutputFrame)); + + /* Allocate all the buffers for host I/O */ + size = 2 * (stream->framesPerHostIBuffer*stream->bytesPerInputFrame + stream->framesPerHostOBuffer*stream->bytesPerOutputFrame); + PA_DEBUG(("Buffer size = %d\n",size)); + stream->hostBuffer = (char*)PaUtil_AllocateMemory(size); + PA_DEBUG(("Buffer allocated\n")); + if( !stream->hostBuffer ) + { + PA_DEBUG(("Cannot allocate host buffer!\n")); + result = paInsufficientMemory; + goto error; + } + PA_DEBUG(("Buffer start = %p\n",stream->hostBuffer)); + /* memset(stream->hostBuffer,0,size); */ + + /* Set up the packets */ + stream->events[0] = CreateEvent(NULL, FALSE, FALSE, NULL); + ResetEvent(stream->events[0]); /* Record buffer 1 */ + stream->events[1] = CreateEvent(NULL, FALSE, FALSE, NULL); + ResetEvent(stream->events[1]); /* Record buffer 2 */ + stream->events[2] = CreateEvent(NULL, FALSE, FALSE, NULL); + ResetEvent(stream->events[2]); /* Play buffer 1 */ + stream->events[3] = CreateEvent(NULL, FALSE, FALSE, NULL); + ResetEvent(stream->events[3]); /* Play buffer 2 */ + stream->events[4] = CreateEvent(NULL, FALSE, FALSE, NULL); + ResetEvent(stream->events[4]); /* Abort event */ + if (stream->userInputChannels > 0) + { + DATAPACKET *p = &(stream->packets[0]); + p->Signal.hEvent = stream->events[0]; + p->Header.Data = stream->hostBuffer; + p->Header.FrameExtent = stream->framesPerHostIBuffer*stream->bytesPerInputFrame; + p->Header.DataUsed = 0; + p->Header.Size = sizeof(p->Header); + p->Header.PresentationTime.Numerator = 1; + p->Header.PresentationTime.Denominator = 1; + p = &(stream->packets[1]); + p->Signal.hEvent = stream->events[1]; + p->Header.Data = stream->hostBuffer + stream->framesPerHostIBuffer*stream->bytesPerInputFrame; + p->Header.FrameExtent = stream->framesPerHostIBuffer*stream->bytesPerInputFrame; + p->Header.DataUsed = 0; + p->Header.Size = sizeof(p->Header); + p->Header.PresentationTime.Numerator = 1; + p->Header.PresentationTime.Denominator = 1; + } + if (stream->userOutputChannels > 0) + { + DATAPACKET *p = &(stream->packets[2]); + p->Signal.hEvent = stream->events[2]; + p->Header.Data = stream->hostBuffer + 2*stream->framesPerHostIBuffer*stream->bytesPerInputFrame; + p->Header.FrameExtent = stream->framesPerHostOBuffer*stream->bytesPerOutputFrame; + p->Header.DataUsed = stream->framesPerHostOBuffer*stream->bytesPerOutputFrame; + p->Header.Size = sizeof(p->Header); + p->Header.PresentationTime.Numerator = 1; + p->Header.PresentationTime.Denominator = 1; + p = &(stream->packets[3]); + p->Signal.hEvent = stream->events[3]; + p->Header.Data = stream->hostBuffer + 2*stream->framesPerHostIBuffer*stream->bytesPerInputFrame + stream->framesPerHostOBuffer*stream->bytesPerOutputFrame; + p->Header.FrameExtent = stream->framesPerHostOBuffer*stream->bytesPerOutputFrame; + p->Header.DataUsed = stream->framesPerHostOBuffer*stream->bytesPerOutputFrame; + p->Header.Size = sizeof(p->Header); + p->Header.PresentationTime.Numerator = 1; + p->Header.PresentationTime.Denominator = 1; + } + + stream->streamStarted = 0; + stream->streamActive = 0; + stream->streamStop = 0; + stream->streamAbort = 0; + stream->streamFlags = streamFlags; + stream->oldProcessPriority = REALTIME_PRIORITY_CLASS; + + *s = (PaStream*)stream; + + PA_LOGL_; + return result; + +error: + size = 5; + while (size--) + { + if (stream->events[size] != NULL) + { + CloseHandle(stream->events[size]); + stream->events[size] = NULL; + } + } + if (stream->hostBuffer) + PaUtil_FreeMemory( stream->hostBuffer ); + + if (stream->playbackPin) + PinClose(stream->playbackPin); + if (stream->recordingPin) + PinClose(stream->recordingPin); + + if( stream ) + PaUtil_FreeMemory( stream ); + + PA_LOGL_; + return result; +} + +/* + When CloseStream() is called, the multi-api layer ensures that + the stream has already been stopped or aborted. +*/ +static PaError CloseStream( PaStream* s ) +{ + PaError result = paNoError; + PaWinWdmStream *stream = (PaWinWdmStream*)s; + int size; + + PA_LOGE_; + + assert(!stream->streamStarted); + assert(!stream->streamActive); + + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + size = 5; + while (size--) + { + if (stream->events[size] != NULL) + { + CloseHandle(stream->events[size]); + stream->events[size] = NULL; + } + } + if (stream->hostBuffer) + PaUtil_FreeMemory( stream->hostBuffer ); + + if (stream->playbackPin) + PinClose(stream->playbackPin); + if (stream->recordingPin) + PinClose(stream->recordingPin); + + PaUtil_FreeMemory( stream ); + + PA_LOGL_; + return result; +} + +/* +Write the supplied packet to the pin +Asynchronous +Should return false on success +*/ +static BOOL PinWrite(HANDLE h, DATAPACKET* p) +{ + unsigned long cbReturned = 0; + return DeviceIoControl(h,IOCTL_KS_WRITE_STREAM,NULL,0, + &p->Header,p->Header.Size,&cbReturned,&p->Signal); +} + +/* +Read to the supplied packet from the pin +Asynchronous +Should return false on success +*/ +static BOOL PinRead(HANDLE h, DATAPACKET* p) +{ + unsigned long cbReturned = 0; + return DeviceIoControl(h,IOCTL_KS_READ_STREAM,NULL,0, + &p->Header,p->Header.Size,&cbReturned,&p->Signal); +} + +/* +Copy the first interleaved channel of 16 bit data to the other channels +*/ +static void DuplicateFirstChannelInt16(void* buffer, int channels, int samples) +{ + unsigned short* data = (unsigned short*)buffer; + int channel; + unsigned short sourceSample; + while ( samples-- ) + { + sourceSample = *data++; + channel = channels-1; + while ( channel-- ) + { + *data++ = sourceSample; + } + } +} + +/* +Copy the first interleaved channel of 24 bit data to the other channels +*/ +static void DuplicateFirstChannelInt24(void* buffer, int channels, int samples) +{ + unsigned char* data = (unsigned char*)buffer; + int channel; + unsigned char sourceSample[3]; + while ( samples-- ) + { + sourceSample[0] = data[0]; + sourceSample[1] = data[1]; + sourceSample[2] = data[2]; + data += 3; + channel = channels-1; + while ( channel-- ) + { + data[0] = sourceSample[0]; + data[1] = sourceSample[1]; + data[2] = sourceSample[2]; + data += 3; + } + } +} + +static DWORD WINAPI ProcessingThread(LPVOID pParam) +{ + PaWinWdmStream *stream = (PaWinWdmStream*)pParam; + PaStreamCallbackTimeInfo ti; + int cbResult = paContinue; + int inbuf = 0; + int outbuf = 0; + int pending = 0; + PaError result; + unsigned long wait; + unsigned long eventSignaled; + int fillPlaybuf = 0; + int emptyRecordbuf = 0; + int framesProcessed; + unsigned long timeout; + int i; + int doChannelCopy; + int priming = 0; + PaStreamCallbackFlags underover = 0; + + PA_LOGE_; + + ti.inputBufferAdcTime = 0.0; + ti.currentTime = 0.0; + ti.outputBufferDacTime = 0.0; + + /* Get double buffering going */ + + /* Submit buffers */ + if (stream->playbackPin) + { + result = PinSetState(stream->playbackPin, KSSTATE_RUN); + + PA_DEBUG(("play state run = %d;",(int)result)); + SetEvent(stream->events[outbuf+2]); + outbuf = (outbuf+1)&1; + SetEvent(stream->events[outbuf+2]); + outbuf = (outbuf+1)&1; + pending += 2; + priming += 4; + } + if (stream->recordingPin) + { + result = PinSetState(stream->recordingPin, KSSTATE_RUN); + + PA_DEBUG(("recording state run = %d;",(int)result)); + PinRead(stream->recordingPin->handle,&stream->packets[inbuf]); + inbuf = (inbuf+1)&1; // Increment and wrap + PinRead(stream->recordingPin->handle,&stream->packets[inbuf]); + inbuf = (inbuf+1)&1; // Increment and wrap + /* FIXME - do error checking */ + pending += 2; + } + PA_DEBUG(("Out buffer len:%f\n",(2000*stream->framesPerHostOBuffer) / stream->streamRepresentation.streamInfo.sampleRate)); + PA_DEBUG(("In buffer len:%f\n",(2000*stream->framesPerHostIBuffer) / stream->streamRepresentation.streamInfo.sampleRate)); + timeout = max( + ((2000*(DWORD)stream->framesPerHostOBuffer) / (DWORD)stream->streamRepresentation.streamInfo.sampleRate), + ((2000*(DWORD)stream->framesPerHostIBuffer) / (DWORD)stream->streamRepresentation.streamInfo.sampleRate) + ); + timeout = max(timeout,1); + PA_DEBUG(("Timeout = %ld ",timeout)); + + while(!stream->streamAbort) + { + fillPlaybuf = 0; + emptyRecordbuf = 0; + + /* Wait for next input or output buffer to be finished with*/ + assert(pending>0); + + if (stream->streamStop) + { + PA_DEBUG(("ss1:pending=%d ",pending)); + } + wait = WaitForMultipleObjects(5, stream->events, FALSE, 0); + if ( wait == WAIT_TIMEOUT ) + { + /* No (under|over)flow has ocurred */ + wait = WaitForMultipleObjects(5, stream->events, FALSE, timeout); + eventSignaled = wait - WAIT_OBJECT_0; + } + else + { + eventSignaled = wait - WAIT_OBJECT_0; + if ( eventSignaled < 2 ) + { + underover |= paInputOverflow; + PA_DEBUG(("Input overflow\n")); + } + else if (( eventSignaled < 4 )&&(!priming)) + { + underover |= paOutputUnderflow; + PA_DEBUG(("Output underflow\n")); + } + } + + if (stream->streamStop) + { + PA_DEBUG(("ss2:wait=%ld",wait)); + } + if (wait == WAIT_FAILED) + { + PA_DEBUG(("Wait fail = %ld! ",wait)); + break; + } + if (wait == WAIT_TIMEOUT) + { + continue; + } + + if (eventSignaled < 2) + { /* Recording input buffer has been filled */ + PA_DEBUG(("R")); + if (stream->playbackPin) + { + /* First check if also the next playback buffer has been signaled */ + wait = WaitForSingleObject(stream->events[outbuf+2],0); + if (wait == WAIT_OBJECT_0) + { + /* Yes, so do both buffers at same time */ + fillPlaybuf = 1; + pending--; + /* Was this an underflow situation? */ + if ( underover ) + underover |= paOutputUnderflow; /* Yes! */ + } + } + emptyRecordbuf = 1; + pending--; + } + else if (eventSignaled < 4) + { /* Playback output buffer has been emptied */ + if (stream->recordingPin) + { + /* First check if also the next recording buffer has been signaled */ + wait = WaitForSingleObject(stream->events[inbuf],0); + if (wait == WAIT_OBJECT_0) + { /* Yes, so do both buffers at same time */ + emptyRecordbuf = 1; + pending--; + /* Was this an overflow situation? */ + if ( underover ) + underover |= paInputOverflow; /* Yes! */ + } + } + fillPlaybuf = 1; + pending--; + } + else + { + /* Abort event! */ + assert(stream->streamAbort); /* Should have been set */ + PA_DEBUG(("ABORTING ")); + break; + } + ResetEvent(stream->events[eventSignaled]); + + if (stream->streamStop) + { + PA_DEBUG(("Stream stop! pending=%d",pending)); + cbResult = paComplete; /* Stop, but play remaining buffers */ + } + + /* Do necessary buffer processing (which will invoke user callback if necessary */ + doChannelCopy = 0; + if (cbResult==paContinue) + { + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + PaUtil_BeginBufferProcessing(&stream->bufferProcessor,&ti,underover); + underover = 0; /* Reset the (under|over)flow status */ + if (fillPlaybuf) + { + PaUtil_SetOutputFrameCount(&stream->bufferProcessor,0); + if ( stream->userOutputChannels == 1 ) + { + /* Write the single user channel to the first interleaved block */ + PaUtil_SetOutputChannel(&stream->bufferProcessor,0,stream->packets[outbuf+2].Header.Data,stream->deviceOutputChannels); + /* We will do a copy to the other channels after the data has been written */ + doChannelCopy = 1; + } + else + { + for (i=0;i<stream->userOutputChannels;i++) + { + /* Only write the user output channels. Leave the rest blank */ + PaUtil_SetOutputChannel(&stream->bufferProcessor,i,((unsigned char*)(stream->packets[outbuf+2].Header.Data))+(i*stream->outputSampleSize),stream->deviceOutputChannels); + } + } + } + if (emptyRecordbuf) + { + PaUtil_SetInputFrameCount(&stream->bufferProcessor,stream->packets[inbuf].Header.DataUsed/stream->bytesPerInputFrame); + for (i=0;i<stream->userInputChannels;i++) + { + /* Only read as many channels as the user wants */ + PaUtil_SetInputChannel(&stream->bufferProcessor,i,((unsigned char*)(stream->packets[inbuf].Header.Data))+(i*stream->inputSampleSize),stream->deviceInputChannels); + } + } + framesProcessed = PaUtil_EndBufferProcessing(&stream->bufferProcessor,&cbResult); + if ( doChannelCopy ) + { + /* Copy the first output channel to the other channels */ + if ( stream->outputSampleSize == 2 ) + { + DuplicateFirstChannelInt16(stream->packets[outbuf+2].Header.Data,stream->deviceOutputChannels,stream->framesPerHostOBuffer); + } + else if ( stream->outputSampleSize == 3 ) + { + DuplicateFirstChannelInt24(stream->packets[outbuf+2].Header.Data,stream->deviceOutputChannels,stream->framesPerHostOBuffer); + } + else + assert(0); /* Unsupported format! */ + } + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed ); + } + else + { + fillPlaybuf = 0; + emptyRecordbuf = 0; + } + /* + if (cbResult != paContinue) + { + PA_DEBUG(("cbResult=%d, pending=%d:",cbResult,pending)); + } + */ + /* Submit buffers */ + if ((fillPlaybuf)&&(cbResult!=paAbort)) + { + if (!PinWrite(stream->playbackPin->handle,&stream->packets[outbuf+2])) + outbuf = (outbuf+1)&1; /* Increment and wrap */ + pending++; + if ( priming ) + priming--; /* Have to prime twice */ + } + if ((emptyRecordbuf)&&(cbResult==paContinue)) + { + stream->packets[inbuf].Header.DataUsed = 0; /* Reset for reuse */ + PinRead(stream->recordingPin->handle,&stream->packets[inbuf]); + inbuf = (inbuf+1)&1; /* Increment and wrap */ + pending++; + } + if (pending==0) + { + PA_DEBUG(("pending==0 finished...;")); + break; + } + if ((!stream->playbackPin)&&(cbResult!=paContinue)) + { + PA_DEBUG(("record only cbResult=%d...;",cbResult)); + break; + } + } + + PA_DEBUG(("Finished thread")); + + /* Finished, either normally or aborted */ + if (stream->playbackPin) + { + result = PinSetState(stream->playbackPin, KSSTATE_PAUSE); + result = PinSetState(stream->playbackPin, KSSTATE_STOP); + } + if (stream->recordingPin) + { + result = PinSetState(stream->recordingPin, KSSTATE_PAUSE); + result = PinSetState(stream->recordingPin, KSSTATE_STOP); + } + + stream->streamActive = 0; + + if ((!stream->streamStop)&&(!stream->streamAbort)) + { + /* Invoke the user stream finished callback */ + /* Only do it from here if not being stopped/aborted by user */ + if( stream->streamRepresentation.streamFinishedCallback != 0 ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + } + stream->streamStop = 0; + stream->streamAbort = 0; + + /* Reset process priority if necessary */ + if (stream->oldProcessPriority != REALTIME_PRIORITY_CLASS) + { + SetPriorityClass(GetCurrentProcess(),stream->oldProcessPriority); + stream->oldProcessPriority = REALTIME_PRIORITY_CLASS; + } + + PA_LOGL_; + ExitThread(0); + return 0; +} + +static PaError StartStream( PaStream *s ) +{ + PaError result = paNoError; + PaWinWdmStream *stream = (PaWinWdmStream*)s; + DWORD dwID; + BOOL ret; + int size; + + PA_LOGE_; + + stream->streamStop = 0; + stream->streamAbort = 0; + size = 5; + while (size--) + { + if (stream->events[size] != NULL) + { + ResetEvent(stream->events[size]); + } + } + + PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); + + stream->oldProcessPriority = GetPriorityClass(GetCurrentProcess()); + /* Uncomment the following line to enable dynamic boosting of the process + * priority to real time for best low latency support + * Disabled by default because RT processes can easily block the OS */ + /*ret = SetPriorityClass(GetCurrentProcess(),REALTIME_PRIORITY_CLASS); + PA_DEBUG(("Class ret = %d;",ret));*/ + + stream->streamStarted = 1; + stream->streamThread = CreateThread(NULL, 0, ProcessingThread, stream, 0, &dwID); + if (stream->streamThread == NULL) + { + stream->streamStarted = 0; + result = paInsufficientMemory; + goto end; + } + ret = SetThreadPriority(stream->streamThread,THREAD_PRIORITY_TIME_CRITICAL); + PA_DEBUG(("Priority ret = %d;",ret)); + /* Make the stream active */ + stream->streamActive = 1; + +end: + PA_LOGL_; + return result; +} + + +static PaError StopStream( PaStream *s ) +{ + PaError result = paNoError; + PaWinWdmStream *stream = (PaWinWdmStream*)s; + int doCb = 0; + + PA_LOGE_; + + if (stream->streamActive) + { + doCb = 1; + stream->streamStop = 1; + while (stream->streamActive) + { + PA_DEBUG(("W.")); + Sleep(10); /* Let thread sleep for 10 msec */ + } + } + + PA_DEBUG(("Terminating thread")); + if (stream->streamStarted && stream->streamThread) + { + TerminateThread(stream->streamThread,0); + stream->streamThread = NULL; + } + + stream->streamStarted = 0; + + if (stream->oldProcessPriority != REALTIME_PRIORITY_CLASS) + { + SetPriorityClass(GetCurrentProcess(),stream->oldProcessPriority); + stream->oldProcessPriority = REALTIME_PRIORITY_CLASS; + } + + if (doCb) + { + /* Do user callback now after all state has been reset */ + /* This means it should be safe for the called function */ + /* to invoke e.g. StartStream */ + if( stream->streamRepresentation.streamFinishedCallback != 0 ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + } + + PA_LOGL_; + return result; +} + +static PaError AbortStream( PaStream *s ) +{ + PaError result = paNoError; + PaWinWdmStream *stream = (PaWinWdmStream*)s; + int doCb = 0; + + PA_LOGE_; + + if (stream->streamActive) + { + doCb = 1; + stream->streamAbort = 1; + SetEvent(stream->events[4]); /* Signal immediately */ + while (stream->streamActive) + { + Sleep(10); + } + } + + if (stream->streamStarted && stream->streamThread) + { + TerminateThread(stream->streamThread,0); + stream->streamThread = NULL; + } + + stream->streamStarted = 0; + + if (stream->oldProcessPriority != REALTIME_PRIORITY_CLASS) + { + SetPriorityClass(GetCurrentProcess(),stream->oldProcessPriority); + stream->oldProcessPriority = REALTIME_PRIORITY_CLASS; + } + + if (doCb) + { + /* Do user callback now after all state has been reset */ + /* This means it should be safe for the called function */ + /* to invoke e.g. StartStream */ + if( stream->streamRepresentation.streamFinishedCallback != 0 ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + } + + stream->streamActive = 0; + stream->streamStarted = 0; + + PA_LOGL_; + return result; +} + + +static PaError IsStreamStopped( PaStream *s ) +{ + PaWinWdmStream *stream = (PaWinWdmStream*)s; + int result = 0; + + PA_LOGE_; + + if (!stream->streamStarted) + result = 1; + + PA_LOGL_; + return result; +} + + +static PaError IsStreamActive( PaStream *s ) +{ + PaWinWdmStream *stream = (PaWinWdmStream*)s; + int result = 0; + + PA_LOGE_; + + if (stream->streamActive) + result = 1; + + PA_LOGL_; + return result; +} + + +static PaTime GetStreamTime( PaStream* s ) +{ + PA_LOGE_; + PA_LOGL_; + (void)s; + return PaUtil_GetTime(); +} + + +static double GetStreamCpuLoad( PaStream* s ) +{ + PaWinWdmStream *stream = (PaWinWdmStream*)s; + double result; + PA_LOGE_; + result = PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); + PA_LOGL_; + return result; +} + + +/* + As separate stream interfaces are used for blocking and callback + streams, the following functions can be guaranteed to only be called + for blocking streams. +*/ + +static PaError ReadStream( PaStream* s, + void *buffer, + unsigned long frames ) +{ + PaWinWdmStream *stream = (PaWinWdmStream*)s; + + PA_LOGE_; + + /* suppress unused variable warnings */ + (void) buffer; + (void) frames; + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + PA_LOGL_; + return paNoError; +} + + +static PaError WriteStream( PaStream* s, + const void *buffer, + unsigned long frames ) +{ + PaWinWdmStream *stream = (PaWinWdmStream*)s; + + PA_LOGE_; + + /* suppress unused variable warnings */ + (void) buffer; + (void) frames; + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + PA_LOGL_; + return paNoError; +} + + +static signed long GetStreamReadAvailable( PaStream* s ) +{ + PaWinWdmStream *stream = (PaWinWdmStream*)s; + + PA_LOGE_; + + /* suppress unused variable warnings */ + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + PA_LOGL_; + return 0; +} + + +static signed long GetStreamWriteAvailable( PaStream* s ) +{ + PaWinWdmStream *stream = (PaWinWdmStream*)s; + + PA_LOGE_; + /* suppress unused variable warnings */ + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + PA_LOGL_; + return 0; +} + + + + diff --git a/src/audio/portaudio/pa_win_wdmks/readme.txt b/src/audio/portaudio/pa_win_wdmks/readme.txt new file mode 100644 index 0000000000000000000000000000000000000000..2fc6c75c609cb96299e238eb0dc8a122b231caef --- /dev/null +++ b/src/audio/portaudio/pa_win_wdmks/readme.txt @@ -0,0 +1,71 @@ +Notes about WDM-KS host API +--------------------------- + +Status history +-------------- +5th September 2004: +This is the first public version of the code. It should be considered +an alpha release with zero guarantee not to crash on any particular +system. So far it has only been tested in the author's development +environment, which means a Win2k/SP2 PIII laptop with integrated +SoundMAX driver and USB Tascam US-428 compiled with both MinGW +(GCC 3.3) and MSVC++6 using the MS DirectX 9 SDK. +It has been most widely tested with the MinGW build, with most of the +test programs (particularly paqa_devs and paqa_errs) passing. +There are some notable failures: patest_out_underflow and both of the +blocking I/O tests (as blocking I/O is not implemented). +At this point the code needs to be tested with a much wider variety +of configurations and feedback provided from testers regarding +both working and failing cases. + +What is the WDM-KS host API? +---------------------------- +PortAudio for Windows currently has 3 functional host implementations. +MME uses the oldest Windows audio API which does not offer good +play/record latency. +DirectX improves this, but still imposes a penalty +of 10s of milliseconds due to the system mixing of streams from +multiple applications. +ASIO offers very good latency, but requires special drivers which are +not always available for cheaper audio hardware. Also, when ASIO +drivers are available, they are not always so robust because they +bypass all of the standardised Windows device driver architecture +and hit the hardware their own way. +Alternatively there are a couple of free (but closed source) ASIO +implementations which connect to the lower level Windows +"Kernel Streaming" API, but again these require special installation +by the user, and can be limited in functionality or difficult to use. + +This is where the PortAudio "WDM-KS" host implementation comes in. +It directly connects PortAudio to the same Kernel Streaming API which +those ASIO bridges use. This avoids the mixing penatly of DirectX, +giving at least as good latency as any ASIO driver, but it has the +advantage of working with ANY Windows audio hardware which is available +through the normal MME/DirectX routes without the user requiring +any additional device drivers to be installed, and allowing all +device selection to be done through the normal PortAudio API. + +Note that in general you should only be using this host API if your +application has a real requirement for very low latency audio (<20ms), +either because you are generating sounds in real-time based upon +user input, or you a processing recorded audio in real time. + +The only thing to be aware of is that using the KS interface will +block that device from being used by the rest of system through +the higher level APIs, or conversely, if the system is using +a device, the KS API will not be able to use it. MS recommend that +you should keep the device open only when your application has focus. +In PortAudio terms, this means having a stream Open on a WDMKS device. + +Usage +----- +To add the WDMKS backend to your program which is already using +PortAudio, you must undefine PA_NO_WDMKS from your build file, +and include the pa_win_wdmks\pa_win_wdmks.c into your build. +The file should compile in both C and C++. +You will need a DirectX SDK installed on your system for the +ks.h and ksmedia.h header files. +You will need to link to the system "setupapi" library. +Note that if you use MinGW, you will get more warnings from +the DX header files when using GCC(C), and still a few warnings +with G++(CPP). \ No newline at end of file diff --git a/src/audio/portaudio/pa_win_wmme/pa_win_wmme.c b/src/audio/portaudio/pa_win_wmme/pa_win_wmme.c new file mode 100644 index 0000000000000000000000000000000000000000..86b6cdf143e3be3bee5ebd4a77afd62f4a42d3a9 --- /dev/null +++ b/src/audio/portaudio/pa_win_wmme/pa_win_wmme.c @@ -0,0 +1,3623 @@ +/* + * $Id$ + * pa_win_wmme.c + * Implementation of PortAudio for Windows MultiMedia Extensions (WMME) + * + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * + * Authors: Ross Bencina and Phil Burk + * 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. + * + * 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. + * + * 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. + * + */ + +/* Modification History: + PLB = Phil Burk + JM = Julien Maillard + RDB = Ross Bencina + PLB20010402 - sDevicePtrs now allocates based on sizeof(pointer) + PLB20010413 - check for excessive numbers of channels + PLB20010422 - apply Mike Berry's changes for CodeWarrior on PC + including conditional inclusion of memory.h, + and explicit typecasting on memory allocation + PLB20010802 - use GlobalAlloc for sDevicesPtr instead of PaHost_AllocFastMemory + PLB20010816 - pass process instead of thread to SetPriorityClass() + PLB20010927 - use number of frames instead of real-time for CPULoad calculation. + JM20020118 - prevent hung thread when buffers underflow. + PLB20020321 - detect Win XP versus NT, 9x; fix DBUG typo; removed init of CurrentCount + RDB20020411 - various renaming cleanups, factored streamData alloc and cpu usage init + RDB20020417 - stopped counting WAVE_MAPPER when there were no real devices + refactoring, renaming and fixed a few edge case bugs + RDB20020531 - converted to V19 framework + ** NOTE maintanance history is now stored in CVS ** +*/ + +/** @file + + @todo Fix buffer catch up code, can sometimes get stuck (perhaps fixed now, + needs to be reviewed and tested.) + + @todo implement paInputUnderflow, paOutputOverflow streamCallback statusFlags, paNeverDropInput. + + @todo BUG: PA_MME_SET_LAST_WAVEIN/OUT_ERROR is used in functions which may + be called asynchronously from the callback thread. this is bad. + + @todo implement inputBufferAdcTime in callback thread + + @todo review/fix error recovery and cleanup in marked functions + + @todo implement timeInfo for stream priming + + @todo handle the case where the callback returns paAbort or paComplete during stream priming. + + @todo review input overflow and output underflow handling in ReadStream and WriteStream + +Non-critical stuff for the future: + + @todo Investigate supporting host buffer formats > 16 bits + + @todo define UNICODE and _UNICODE in the project settings and see what breaks + +*/ + +/* + How it works: + + For both callback and blocking read/write streams we open the MME devices + in CALLBACK_EVENT mode. In this mode, MME signals an Event object whenever + it has finished with a buffer (either filled it for input, or played it + for output). Where necessary we block waiting for Event objects using + WaitMultipleObjects(). + + When implementing a PA callback stream, we set up a high priority thread + which waits on the MME buffer Events and drains/fills the buffers when + they are ready. + + When implementing a PA blocking read/write stream, we simply wait on these + Events (when necessary) inside the ReadStream() and WriteStream() functions. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include <windows.h> +#include <mmsystem.h> +#include <process.h> +#include <assert.h> +/* PLB20010422 - "memory.h" doesn't work on CodeWarrior for PC. Thanks Mike Berry for the mod. */ +#ifndef __MWERKS__ +#include <malloc.h> +#include <memory.h> +#endif /* __MWERKS__ */ + +#include "portaudio.h" +#include "pa_trace.h" +#include "pa_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" + +#include "pa_win_wmme.h" + +#if (defined(WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1200))) /* MSC version 6 and above */ +#pragma comment(lib, "winmm.lib") +#endif + +/************************************************* Constants ********/ + +#define PA_MME_USE_HIGH_DEFAULT_LATENCY_ (0) /* For debugging glitches. */ + +#if PA_MME_USE_HIGH_DEFAULT_LATENCY_ + #define PA_MME_WIN_9X_DEFAULT_LATENCY_ (0.4) + #define PA_MME_MIN_HOST_OUTPUT_BUFFER_COUNT_ (4) + #define PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_ (4) + #define PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_HALF_DUPLEX_ (4) + #define PA_MME_MIN_HOST_BUFFER_FRAMES_WHEN_UNSPECIFIED_ (16) + #define PA_MME_MAX_HOST_BUFFER_SECS_ (0.3) /* Do not exceed unless user buffer exceeds */ + #define PA_MME_MAX_HOST_BUFFER_BYTES_ (32 * 1024) /* Has precedence over PA_MME_MAX_HOST_BUFFER_SECS_, some drivers are known to crash with buffer sizes > 32k */ +#else + #define PA_MME_WIN_9X_DEFAULT_LATENCY_ (0.2) + #define PA_MME_MIN_HOST_OUTPUT_BUFFER_COUNT_ (2) + #define PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_ (3) + #define PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_HALF_DUPLEX_ (2) + #define PA_MME_MIN_HOST_BUFFER_FRAMES_WHEN_UNSPECIFIED_ (16) + #define PA_MME_MAX_HOST_BUFFER_SECS_ (0.1) /* Do not exceed unless user buffer exceeds */ + #define PA_MME_MAX_HOST_BUFFER_BYTES_ (32 * 1024) /* Has precedence over PA_MME_MAX_HOST_BUFFER_SECS_, some drivers are known to crash with buffer sizes > 32k */ +#endif + +/* Use higher latency for NT because it is even worse at real-time + operation than Win9x. +*/ +#define PA_MME_WIN_NT_DEFAULT_LATENCY_ (PA_MME_WIN_9X_DEFAULT_LATENCY_ * 2) +#define PA_MME_WIN_WDM_DEFAULT_LATENCY_ (PA_MME_WIN_9X_DEFAULT_LATENCY_) + + +#define PA_MME_MIN_TIMEOUT_MSEC_ (1000) + +static const char constInputMapperSuffix_[] = " - Input"; +static const char constOutputMapperSuffix_[] = " - Output"; + +/********************************************************************/ + +typedef struct PaWinMmeStream PaWinMmeStream; /* forward declaration */ + +/* prototypes for functions declared in this file */ + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +PaError PaWinMme_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** stream, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); +static PaError CloseStream( PaStream* stream ); +static PaError StartStream( PaStream *stream ); +static PaError StopStream( PaStream *stream ); +static PaError AbortStream( PaStream *stream ); +static PaError IsStreamStopped( PaStream *s ); +static PaError IsStreamActive( PaStream *stream ); +static PaTime GetStreamTime( PaStream *stream ); +static double GetStreamCpuLoad( PaStream* stream ); +static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames ); +static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames ); +static signed long GetStreamReadAvailable( PaStream* stream ); +static signed long GetStreamWriteAvailable( PaStream* stream ); + + +/* macros for setting last host error information */ + +#ifdef UNICODE + +#define PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ) \ + { \ + wchar_t mmeErrorTextWide[ MAXERRORLENGTH ]; \ + char mmeErrorText[ MAXERRORLENGTH ]; \ + waveInGetErrorText( mmresult, mmeErrorTextWide, MAXERRORLENGTH ); \ + WideCharToMultiByte( CP_ACP, WC_COMPOSITECHECK | WC_DEFAULTCHAR,\ + mmeErrorTextWide, -1, mmeErrorText, MAXERRORLENGTH, NULL, NULL ); \ + PaUtil_SetLastHostErrorInfo( paMME, mmresult, mmeErrorText ); \ + } + +#define PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ) \ + { \ + wchar_t mmeErrorTextWide[ MAXERRORLENGTH ]; \ + char mmeErrorText[ MAXERRORLENGTH ]; \ + waveOutGetErrorText( mmresult, mmeErrorTextWide, MAXERRORLENGTH ); \ + WideCharToMultiByte( CP_ACP, WC_COMPOSITECHECK | WC_DEFAULTCHAR,\ + mmeErrorTextWide, -1, mmeErrorText, MAXERRORLENGTH, NULL, NULL ); \ + PaUtil_SetLastHostErrorInfo( paMME, mmresult, mmeErrorText ); \ + } + +#else /* !UNICODE */ + +#define PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ) \ + { \ + char mmeErrorText[ MAXERRORLENGTH ]; \ + waveInGetErrorText( mmresult, mmeErrorText, MAXERRORLENGTH ); \ + PaUtil_SetLastHostErrorInfo( paMME, mmresult, mmeErrorText ); \ + } + +#define PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ) \ + { \ + char mmeErrorText[ MAXERRORLENGTH ]; \ + waveOutGetErrorText( mmresult, mmeErrorText, MAXERRORLENGTH ); \ + PaUtil_SetLastHostErrorInfo( paMME, mmresult, mmeErrorText ); \ + } + +#endif /* UNICODE */ + + +static void PaMme_SetLastSystemError( DWORD errorCode ) +{ + char *lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + errorCode, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, + 0, + NULL + ); + PaUtil_SetLastHostErrorInfo( paMME, errorCode, lpMsgBuf ); + LocalFree( lpMsgBuf ); +} + +#define PA_MME_SET_LAST_SYSTEM_ERROR( errorCode ) \ + PaMme_SetLastSystemError( errorCode ) + + +/* PaError returning wrappers for some commonly used win32 functions + note that we allow passing a null ptr to have no effect. +*/ + +static PaError CreateEventWithPaError( HANDLE *handle, + LPSECURITY_ATTRIBUTES lpEventAttributes, + BOOL bManualReset, + BOOL bInitialState, + LPCTSTR lpName ) +{ + PaError result = paNoError; + + *handle = NULL; + + *handle = CreateEvent( lpEventAttributes, bManualReset, bInitialState, lpName ); + if( *handle == NULL ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() ); + } + + return result; +} + + +static PaError ResetEventWithPaError( HANDLE handle ) +{ + PaError result = paNoError; + + if( handle ) + { + if( ResetEvent( handle ) == 0 ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() ); + } + } + + return result; +} + + +static PaError CloseHandleWithPaError( HANDLE handle ) +{ + PaError result = paNoError; + + if( handle ) + { + if( CloseHandle( handle ) == 0 ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() ); + } + } + + return result; +} + + +/* PaWinMmeHostApiRepresentation - host api datastructure specific to this implementation */ + +typedef struct +{ + PaUtilHostApiRepresentation inheritedHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup *allocations; + + int inputDeviceCount, outputDeviceCount; + + /** winMmeDeviceIds is an array of WinMme device ids. + fields in the range [0, inputDeviceCount) are input device ids, + and [inputDeviceCount, inputDeviceCount + outputDeviceCount) are output + device ids. + */ + UINT *winMmeDeviceIds; +} +PaWinMmeHostApiRepresentation; + + +typedef struct +{ + PaDeviceInfo inheritedDeviceInfo; + DWORD dwFormats; /**<< standard formats bitmask from the WAVEINCAPS and WAVEOUTCAPS structures */ +} +PaWinMmeDeviceInfo; + + +/************************************************************************* + * Returns recommended device ID. + * On the PC, the recommended device can be specified by the user by + * setting an environment variable. For example, to use device #1. + * + * set PA_RECOMMENDED_OUTPUT_DEVICE=1 + * + * The user should first determine the available device ID by using + * the supplied application "pa_devs". + */ +#define PA_ENV_BUF_SIZE_ (32) +#define PA_REC_IN_DEV_ENV_NAME_ ("PA_RECOMMENDED_INPUT_DEVICE") +#define PA_REC_OUT_DEV_ENV_NAME_ ("PA_RECOMMENDED_OUTPUT_DEVICE") +static PaDeviceIndex GetEnvDefaultDeviceID( char *envName ) +{ + PaDeviceIndex recommendedIndex = paNoDevice; + DWORD hresult; + char envbuf[PA_ENV_BUF_SIZE_]; + +#ifndef WIN32_PLATFORM_PSPC /* no GetEnvironmentVariable on PocketPC */ + + /* Let user determine default device by setting environment variable. */ + hresult = GetEnvironmentVariable( envName, envbuf, PA_ENV_BUF_SIZE_ ); + if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE_) ) + { + recommendedIndex = atoi( envbuf ); + } +#endif + + return recommendedIndex; +} + + +static void InitializeDefaultDeviceIdsFromEnv( PaWinMmeHostApiRepresentation *hostApi ) +{ + PaDeviceIndex device; + + /* input */ + device = GetEnvDefaultDeviceID( PA_REC_IN_DEV_ENV_NAME_ ); + if( device != paNoDevice && + ( device >= 0 && device < hostApi->inheritedHostApiRep.info.deviceCount ) && + hostApi->inheritedHostApiRep.deviceInfos[ device ]->maxInputChannels > 0 ) + { + hostApi->inheritedHostApiRep.info.defaultInputDevice = device; + } + + /* output */ + device = GetEnvDefaultDeviceID( PA_REC_OUT_DEV_ENV_NAME_ ); + if( device != paNoDevice && + ( device >= 0 && device < hostApi->inheritedHostApiRep.info.deviceCount ) && + hostApi->inheritedHostApiRep.deviceInfos[ device ]->maxOutputChannels > 0 ) + { + hostApi->inheritedHostApiRep.info.defaultOutputDevice = device; + } +} + + +/** Convert external PA ID to a windows multimedia device ID +*/ +static UINT LocalDeviceIndexToWinMmeDeviceId( PaWinMmeHostApiRepresentation *hostApi, PaDeviceIndex device ) +{ + assert( device >= 0 && device < hostApi->inputDeviceCount + hostApi->outputDeviceCount ); + + return hostApi->winMmeDeviceIds[ device ]; +} + + +static PaError QueryInputWaveFormatEx( int deviceId, WAVEFORMATEX *waveFormatEx ) +{ + MMRESULT mmresult; + + switch( mmresult = waveInOpen( NULL, deviceId, waveFormatEx, 0, 0, WAVE_FORMAT_QUERY ) ) + { + case MMSYSERR_NOERROR: + return paNoError; + case MMSYSERR_ALLOCATED: /* Specified resource is already allocated. */ + return paDeviceUnavailable; + case MMSYSERR_NODRIVER: /* No device driver is present. */ + return paDeviceUnavailable; + case MMSYSERR_NOMEM: /* Unable to allocate or lock memory. */ + return paInsufficientMemory; + case WAVERR_BADFORMAT: /* Attempted to open with an unsupported waveform-audio format. */ + return paSampleFormatNotSupported; + + case MMSYSERR_BADDEVICEID: /* Specified device identifier is out of range. */ + /* falls through */ + default: + PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); + return paUnanticipatedHostError; + } +} + + +static PaError QueryOutputWaveFormatEx( int deviceId, WAVEFORMATEX *waveFormatEx ) +{ + MMRESULT mmresult; + + switch( mmresult = waveOutOpen( NULL, deviceId, waveFormatEx, 0, 0, WAVE_FORMAT_QUERY ) ) + { + case MMSYSERR_NOERROR: + return paNoError; + case MMSYSERR_ALLOCATED: /* Specified resource is already allocated. */ + return paDeviceUnavailable; + case MMSYSERR_NODRIVER: /* No device driver is present. */ + return paDeviceUnavailable; + case MMSYSERR_NOMEM: /* Unable to allocate or lock memory. */ + return paInsufficientMemory; + case WAVERR_BADFORMAT: /* Attempted to open with an unsupported waveform-audio format. */ + return paSampleFormatNotSupported; + + case MMSYSERR_BADDEVICEID: /* Specified device identifier is out of range. */ + /* falls through */ + default: + PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ); + return paUnanticipatedHostError; + } +} + + +static PaError QueryFormatSupported( PaDeviceInfo *deviceInfo, + PaError (*waveFormatExQueryFunction)(int, WAVEFORMATEX*), + int winMmeDeviceId, int channels, double sampleRate ) +{ + PaWinMmeDeviceInfo *winMmeDeviceInfo = (PaWinMmeDeviceInfo*)deviceInfo; + WAVEFORMATEX waveFormatEx; + + 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; + } + + 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; + + return waveFormatExQueryFunction( winMmeDeviceId, &waveFormatEx ); +} + + +#define PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_ (13) /* must match array length below */ +static double defaultSampleRateSearchOrder_[] = + { 44100.0, 48000.0, 32000.0, 24000.0, 22050.0, 88200.0, 96000.0, 192000.0, + 16000.0, 12000.0, 11025.0, 9600.0, 8000.0 }; + +static void DetectDefaultSampleRate( PaWinMmeDeviceInfo *winMmeDeviceInfo, int winMmeDeviceId, + PaError (*waveFormatExQueryFunction)(int, WAVEFORMATEX*), int maxChannels ) +{ + PaDeviceInfo *deviceInfo = &winMmeDeviceInfo->inheritedDeviceInfo; + int i; + + deviceInfo->defaultSampleRate = 0.; + + for( i=0; i < PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_; ++i ) + { + double sampleRate = defaultSampleRateSearchOrder_[ i ]; + PaError paerror = QueryFormatSupported( deviceInfo, waveFormatExQueryFunction, winMmeDeviceId, maxChannels, sampleRate ); + if( paerror == paNoError ) + { + deviceInfo->defaultSampleRate = sampleRate; + break; + } + } +} + + +static PaError InitializeInputDeviceInfo( PaWinMmeHostApiRepresentation *winMmeHostApi, + PaWinMmeDeviceInfo *winMmeDeviceInfo, UINT winMmeInputDeviceId, int *success ) +{ + PaError result = paNoError; + char *deviceName; /* non-const ptr */ + MMRESULT mmresult; + WAVEINCAPS wic; + PaDeviceInfo *deviceInfo = &winMmeDeviceInfo->inheritedDeviceInfo; + + *success = 0; + + mmresult = waveInGetDevCaps( winMmeInputDeviceId, &wic, sizeof( WAVEINCAPS ) ); + if( mmresult == MMSYSERR_NOMEM ) + { + result = paInsufficientMemory; + goto error; + } + else if( mmresult != MMSYSERR_NOERROR ) + { + /* instead of returning paUnanticipatedHostError we return + paNoError, but leave success set as 0. This allows + Pa_Initialize to just ignore this device, without failing + the entire initialisation process. + */ + return paNoError; + } + + if( winMmeInputDeviceId == WAVE_MAPPER ) + { + /* Append I/O suffix to WAVE_MAPPER device. */ + deviceName = (char *)PaUtil_GroupAllocateMemory( + winMmeHostApi->allocations, strlen( wic.szPname ) + 1 + sizeof(constInputMapperSuffix_) ); + if( !deviceName ) + { + result = paInsufficientMemory; + goto error; + } + strcpy( deviceName, wic.szPname ); + strcat( deviceName, constInputMapperSuffix_ ); + } + else + { + deviceName = (char*)PaUtil_GroupAllocateMemory( + winMmeHostApi->allocations, strlen( wic.szPname ) + 1 ); + if( !deviceName ) + { + result = paInsufficientMemory; + goto error; + } + strcpy( deviceName, wic.szPname ); + } + 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( (deviceInfo->maxInputChannels < 1) || (deviceInfo->maxInputChannels > 256) ) + { + PA_DEBUG(("Pa_GetDeviceInfo: Num input channels reported as %d! Changed to 2.\n", deviceInfo->maxInputChannels )); + deviceInfo->maxInputChannels = 2; + } + + winMmeDeviceInfo->dwFormats = wic.dwFormats; + + DetectDefaultSampleRate( winMmeDeviceInfo, winMmeInputDeviceId, + QueryInputWaveFormatEx, deviceInfo->maxInputChannels ); + + *success = 1; + +error: + return result; +} + + +static PaError InitializeOutputDeviceInfo( PaWinMmeHostApiRepresentation *winMmeHostApi, + PaWinMmeDeviceInfo *winMmeDeviceInfo, UINT winMmeOutputDeviceId, int *success ) +{ + PaError result = paNoError; + char *deviceName; /* non-const ptr */ + MMRESULT mmresult; + WAVEOUTCAPS woc; + PaDeviceInfo *deviceInfo = &winMmeDeviceInfo->inheritedDeviceInfo; + + *success = 0; + + mmresult = waveOutGetDevCaps( winMmeOutputDeviceId, &woc, sizeof( WAVEOUTCAPS ) ); + if( mmresult == MMSYSERR_NOMEM ) + { + result = paInsufficientMemory; + goto error; + } + else if( mmresult != MMSYSERR_NOERROR ) + { + /* instead of returning paUnanticipatedHostError we return + paNoError, but leave success set as 0. This allows + Pa_Initialize to just ignore this device, without failing + the entire initialisation process. + */ + return paNoError; + } + + if( winMmeOutputDeviceId == WAVE_MAPPER ) + { + /* Append I/O suffix to WAVE_MAPPER device. */ + deviceName = (char *)PaUtil_GroupAllocateMemory( + winMmeHostApi->allocations, strlen( woc.szPname ) + 1 + sizeof(constOutputMapperSuffix_) ); + if( !deviceName ) + { + result = paInsufficientMemory; + goto error; + } + strcpy( deviceName, woc.szPname ); + strcat( deviceName, constOutputMapperSuffix_ ); + } + else + { + deviceName = (char*)PaUtil_GroupAllocateMemory( + winMmeHostApi->allocations, strlen( woc.szPname ) + 1 ); + if( !deviceName ) + { + result = paInsufficientMemory; + goto error; + } + strcpy( deviceName, woc.szPname ); + } + 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( (deviceInfo->maxOutputChannels < 1) || (deviceInfo->maxOutputChannels > 256) ) + { + PA_DEBUG(("Pa_GetDeviceInfo: Num output channels reported as %d! Changed to 2.\n", deviceInfo->maxOutputChannels )); + deviceInfo->maxOutputChannels = 2; + } + + winMmeDeviceInfo->dwFormats = woc.dwFormats; + + DetectDefaultSampleRate( winMmeDeviceInfo, winMmeOutputDeviceId, + QueryOutputWaveFormatEx, deviceInfo->maxOutputChannels ); + + *success = 1; + +error: + return result; +} + + +static void GetDefaultLatencies( PaTime *defaultLowLatency, PaTime *defaultHighLatency ) +{ + OSVERSIONINFO osvi; + osvi.dwOSVersionInfoSize = sizeof( osvi ); + GetVersionEx( &osvi ); + + /* Check for NT */ + if( (osvi.dwMajorVersion == 4) && (osvi.dwPlatformId == 2) ) + { + *defaultLowLatency = PA_MME_WIN_NT_DEFAULT_LATENCY_; + } + else if(osvi.dwMajorVersion >= 5) + { + *defaultLowLatency = PA_MME_WIN_WDM_DEFAULT_LATENCY_; + } + else + { + *defaultLowLatency = PA_MME_WIN_9X_DEFAULT_LATENCY_; + } + + *defaultHighLatency = *defaultLowLatency * 2; +} + + +PaError PaWinMme_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + int i; + PaWinMmeHostApiRepresentation *winMmeHostApi; + int inputDeviceCount, outputDeviceCount, maximumPossibleDeviceCount; + PaWinMmeDeviceInfo *deviceInfoArray; + int deviceInfoInitializationSucceeded; + PaTime defaultLowLatency, defaultHighLatency; + + winMmeHostApi = (PaWinMmeHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaWinMmeHostApiRepresentation) ); + if( !winMmeHostApi ) + { + result = paInsufficientMemory; + goto error; + } + + winMmeHostApi->allocations = PaUtil_CreateAllocationGroup(); + if( !winMmeHostApi->allocations ) + { + result = paInsufficientMemory; + goto error; + } + + *hostApi = &winMmeHostApi->inheritedHostApiRep; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paMME; + (*hostApi)->info.name = "MME"; + + + /* initialise device counts and default devices under the assumption that + there are no devices. These values are incremented below if and when + devices are successfully initialized. + */ + (*hostApi)->info.deviceCount = 0; + (*hostApi)->info.defaultInputDevice = paNoDevice; + (*hostApi)->info.defaultOutputDevice = paNoDevice; + winMmeHostApi->inputDeviceCount = 0; + winMmeHostApi->outputDeviceCount = 0; + + + maximumPossibleDeviceCount = 0; + + inputDeviceCount = waveInGetNumDevs(); + if( inputDeviceCount > 0 ) + maximumPossibleDeviceCount += inputDeviceCount + 1; /* assume there is a WAVE_MAPPER */ + + outputDeviceCount = waveOutGetNumDevs(); + if( outputDeviceCount > 0 ) + maximumPossibleDeviceCount += outputDeviceCount + 1; /* assume there is a WAVE_MAPPER */ + + + if( maximumPossibleDeviceCount > 0 ){ + + (*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + winMmeHostApi->allocations, sizeof(PaDeviceInfo*) * maximumPossibleDeviceCount ); + if( !(*hostApi)->deviceInfos ) + { + result = paInsufficientMemory; + goto error; + } + + /* allocate all device info structs in a contiguous block */ + deviceInfoArray = (PaWinMmeDeviceInfo*)PaUtil_GroupAllocateMemory( + winMmeHostApi->allocations, sizeof(PaWinMmeDeviceInfo) * maximumPossibleDeviceCount ); + if( !deviceInfoArray ) + { + result = paInsufficientMemory; + goto error; + } + + winMmeHostApi->winMmeDeviceIds = (UINT*)PaUtil_GroupAllocateMemory( + winMmeHostApi->allocations, sizeof(int) * maximumPossibleDeviceCount ); + if( !winMmeHostApi->winMmeDeviceIds ) + { + result = paInsufficientMemory; + goto error; + } + + GetDefaultLatencies( &defaultLowLatency, &defaultHighLatency ); + + if( inputDeviceCount > 0 ){ + /* -1 is the WAVE_MAPPER */ + for( i = -1; i < inputDeviceCount; ++i ){ + UINT winMmeDeviceId = (UINT)((i==-1) ? WAVE_MAPPER : i); + PaWinMmeDeviceInfo *wmmeDeviceInfo = &deviceInfoArray[ (*hostApi)->info.deviceCount ]; + PaDeviceInfo *deviceInfo = &wmmeDeviceInfo->inheritedDeviceInfo; + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; + + deviceInfo->maxInputChannels = 0; + deviceInfo->maxOutputChannels = 0; + + deviceInfo->defaultLowInputLatency = defaultLowLatency; + deviceInfo->defaultLowOutputLatency = defaultLowLatency; + deviceInfo->defaultHighInputLatency = defaultHighLatency; + deviceInfo->defaultHighOutputLatency = defaultHighLatency; + + result = InitializeInputDeviceInfo( winMmeHostApi, wmmeDeviceInfo, + winMmeDeviceId, &deviceInfoInitializationSucceeded ); + if( result != paNoError ) + goto error; + + if( deviceInfoInitializationSucceeded ){ + if( (*hostApi)->info.defaultInputDevice == paNoDevice ) + (*hostApi)->info.defaultInputDevice = (*hostApi)->info.deviceCount; + + winMmeHostApi->winMmeDeviceIds[ (*hostApi)->info.deviceCount ] = winMmeDeviceId; + (*hostApi)->deviceInfos[ (*hostApi)->info.deviceCount ] = deviceInfo; + + winMmeHostApi->inputDeviceCount++; + (*hostApi)->info.deviceCount++; + } + } + } + + if( outputDeviceCount > 0 ){ + /* -1 is the WAVE_MAPPER */ + for( i = -1; i < outputDeviceCount; ++i ){ + UINT winMmeDeviceId = (UINT)((i==-1) ? WAVE_MAPPER : i); + PaWinMmeDeviceInfo *wmmeDeviceInfo = &deviceInfoArray[ (*hostApi)->info.deviceCount ]; + PaDeviceInfo *deviceInfo = &wmmeDeviceInfo->inheritedDeviceInfo; + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; + + deviceInfo->maxInputChannels = 0; + deviceInfo->maxOutputChannels = 0; + + deviceInfo->defaultLowInputLatency = defaultLowLatency; + deviceInfo->defaultLowOutputLatency = defaultLowLatency; + deviceInfo->defaultHighInputLatency = defaultHighLatency; + deviceInfo->defaultHighOutputLatency = defaultHighLatency; + + result = InitializeOutputDeviceInfo( winMmeHostApi, wmmeDeviceInfo, + winMmeDeviceId, &deviceInfoInitializationSucceeded ); + if( result != paNoError ) + goto error; + + if( deviceInfoInitializationSucceeded ){ + if( (*hostApi)->info.defaultOutputDevice == paNoDevice ) + (*hostApi)->info.defaultOutputDevice = (*hostApi)->info.deviceCount; + + winMmeHostApi->winMmeDeviceIds[ (*hostApi)->info.deviceCount ] = winMmeDeviceId; + (*hostApi)->deviceInfos[ (*hostApi)->info.deviceCount ] = deviceInfo; + + winMmeHostApi->outputDeviceCount++; + (*hostApi)->info.deviceCount++; + } + } + } + } + + + InitializeDefaultDeviceIdsFromEnv( winMmeHostApi ); + + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PaUtil_InitializeStreamInterface( &winMmeHostApi->callbackStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface( &winMmeHostApi->blockingStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable ); + + return result; + +error: + if( winMmeHostApi ) + { + if( winMmeHostApi->allocations ) + { + PaUtil_FreeAllAllocations( winMmeHostApi->allocations ); + PaUtil_DestroyAllocationGroup( winMmeHostApi->allocations ); + } + + PaUtil_FreeMemory( winMmeHostApi ); + } + + return result; +} + + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaWinMmeHostApiRepresentation *winMmeHostApi = (PaWinMmeHostApiRepresentation*)hostApi; + + if( winMmeHostApi->allocations ) + { + PaUtil_FreeAllAllocations( winMmeHostApi->allocations ); + PaUtil_DestroyAllocationGroup( winMmeHostApi->allocations ); + } + + PaUtil_FreeMemory( winMmeHostApi ); +} + + +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + PaWinMmeHostApiRepresentation *winMmeHostApi = (PaWinMmeHostApiRepresentation*)hostApi; + PaDeviceInfo *inputDeviceInfo, *outputDeviceInfo; + int inputChannelCount, outputChannelCount; + int inputMultipleDeviceChannelCount, outputMultipleDeviceChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + PaWinMmeStreamInfo *inputStreamInfo, *outputStreamInfo; + UINT winMmeInputDeviceId, winMmeOutputDeviceId; + unsigned int i; + PaError paerror; + + /* The calls to QueryFormatSupported below are intended to detect invalid + sample rates. If we assume that the channel count and format are OK, + then the only thing that could fail is the sample rate. This isn't + strictly true, but I can't think of a better way to test that the + sample rate is valid. + */ + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + inputStreamInfo = inputParameters->hostApiSpecificStreamInfo; + + /* all standard sample formats are supported by the buffer adapter, + this implementation doesn't support any custom sample formats */ + if( inputSampleFormat & paCustomFormat ) + return paSampleFormatNotSupported; + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification + && inputStreamInfo && (inputStreamInfo->flags & paWinMmeUseMultipleDevices) ) + { + inputMultipleDeviceChannelCount = 0; + for( i=0; i< inputStreamInfo->deviceCount; ++i ) + { + inputMultipleDeviceChannelCount += inputStreamInfo->devices[i].channelCount; + + 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 ) + return paInvalidChannelCount; + + /* 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 ); + if( paerror != paNoError ) + return paInvalidSampleRate; + } + + if( inputMultipleDeviceChannelCount != inputChannelCount ) + return paIncompatibleHostApiSpecificStreamInfo; + } + else + { + if( inputStreamInfo && (inputStreamInfo->flags & paWinMmeUseMultipleDevices) ) + return paIncompatibleHostApiSpecificStreamInfo; /* paUseHostApiSpecificDeviceSpecification was not supplied as the input device */ + + inputDeviceInfo = hostApi->deviceInfos[ inputParameters->device ]; + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > inputDeviceInfo->maxInputChannels ) + return paInvalidChannelCount; + + /* test for valid sample rate, see comment above */ + winMmeInputDeviceId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, inputParameters->device ); + paerror = QueryFormatSupported( inputDeviceInfo, QueryInputWaveFormatEx, winMmeInputDeviceId, inputChannelCount, sampleRate ); + if( paerror != paNoError ) + return paInvalidSampleRate; + } + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + outputStreamInfo = outputParameters->hostApiSpecificStreamInfo; + + /* all standard sample formats are supported by the buffer adapter, + this implementation doesn't support any custom sample formats */ + if( outputSampleFormat & paCustomFormat ) + return paSampleFormatNotSupported; + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification + && outputStreamInfo && (outputStreamInfo->flags & paWinMmeUseMultipleDevices) ) + { + outputMultipleDeviceChannelCount = 0; + for( i=0; i< outputStreamInfo->deviceCount; ++i ) + { + outputMultipleDeviceChannelCount += outputStreamInfo->devices[i].channelCount; + + 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 ) + return paInvalidChannelCount; + + /* 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 ); + if( paerror != paNoError ) + return paInvalidSampleRate; + } + + if( outputMultipleDeviceChannelCount != outputChannelCount ) + return paIncompatibleHostApiSpecificStreamInfo; + } + else + { + if( outputStreamInfo && (outputStreamInfo->flags & paWinMmeUseMultipleDevices) ) + return paIncompatibleHostApiSpecificStreamInfo; /* paUseHostApiSpecificDeviceSpecification was not supplied as the output device */ + + outputDeviceInfo = hostApi->deviceInfos[ outputParameters->device ]; + + /* check that output device can support outputChannelCount */ + if( outputChannelCount > outputDeviceInfo->maxOutputChannels ) + return paInvalidChannelCount; + + /* test for valid sample rate, see comment above */ + winMmeOutputDeviceId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, outputParameters->device ); + paerror = QueryFormatSupported( outputDeviceInfo, QueryOutputWaveFormatEx, winMmeOutputDeviceId, outputChannelCount, sampleRate ); + if( paerror != paNoError ) + return paInvalidSampleRate; + } + } + + /* + - if a full duplex stream is requested, check that the combination + of input and output parameters is supported + + - check that the device supports sampleRate + + for mme all we can do is test that the input and output devices + support the requested sample rate and number of channels. we + cannot test for full duplex compatibility. + */ + + return paFormatIsSupported; +} + + + +static void SelectBufferSizeAndCount( unsigned long baseBufferSize, + unsigned long requestedLatency, + unsigned long baseBufferCount, unsigned long minimumBufferCount, + unsigned long maximumBufferSize, unsigned long *hostBufferSize, + unsigned long *hostBufferCount ) +{ + unsigned long sizeMultiplier, bufferCount, latency; + unsigned long nextLatency, nextBufferSize; + int baseBufferSizeIsPowerOfTwo; + + sizeMultiplier = 1; + bufferCount = baseBufferCount; + + /* count-1 below because latency is always determined by one less + than the total number of buffers. + */ + latency = (baseBufferSize * sizeMultiplier) * (bufferCount-1); + + if( latency > requestedLatency ) + { + + /* reduce number of buffers without falling below suggested latency */ + + nextLatency = (baseBufferSize * sizeMultiplier) * (bufferCount-2); + while( bufferCount > minimumBufferCount && nextLatency >= requestedLatency ) + { + --bufferCount; + nextLatency = (baseBufferSize * sizeMultiplier) * (bufferCount-2); + } + + }else if( latency < requestedLatency ){ + + baseBufferSizeIsPowerOfTwo = (! (baseBufferSize & (baseBufferSize - 1))); + if( baseBufferSizeIsPowerOfTwo ){ + + /* double size of buffers without exceeding requestedLatency */ + + nextBufferSize = (baseBufferSize * (sizeMultiplier*2)); + nextLatency = nextBufferSize * (bufferCount-1); + while( nextBufferSize <= maximumBufferSize + && nextLatency < requestedLatency ) + { + sizeMultiplier *= 2; + nextBufferSize = (baseBufferSize * (sizeMultiplier*2)); + nextLatency = nextBufferSize * (bufferCount-1); + } + + }else{ + + /* increase size of buffers upto first excess of requestedLatency */ + + nextBufferSize = (baseBufferSize * (sizeMultiplier+1)); + nextLatency = nextBufferSize * (bufferCount-1); + while( nextBufferSize <= maximumBufferSize + && nextLatency < requestedLatency ) + { + ++sizeMultiplier; + nextBufferSize = (baseBufferSize * (sizeMultiplier+1)); + nextLatency = nextBufferSize * (bufferCount-1); + } + + if( nextLatency < requestedLatency ) + ++sizeMultiplier; + } + + /* increase number of buffers until requestedLatency is reached */ + + latency = (baseBufferSize * sizeMultiplier) * (bufferCount-1); + while( latency < requestedLatency ) + { + ++bufferCount; + latency = (baseBufferSize * sizeMultiplier) * (bufferCount-1); + } + } + + *hostBufferSize = baseBufferSize * sizeMultiplier; + *hostBufferCount = bufferCount; +} + + +static void ReselectBufferCount( unsigned long bufferSize, + unsigned long requestedLatency, + unsigned long baseBufferCount, unsigned long minimumBufferCount, + unsigned long *hostBufferCount ) +{ + unsigned long bufferCount, latency; + unsigned long nextLatency; + + bufferCount = baseBufferCount; + + /* count-1 below because latency is always determined by one less + than the total number of buffers. + */ + latency = bufferSize * (bufferCount-1); + + if( latency > requestedLatency ) + { + /* reduce number of buffers without falling below suggested latency */ + + nextLatency = bufferSize * (bufferCount-2); + while( bufferCount > minimumBufferCount && nextLatency >= requestedLatency ) + { + --bufferCount; + nextLatency = bufferSize * (bufferCount-2); + } + + }else if( latency < requestedLatency ){ + + /* increase number of buffers until requestedLatency is reached */ + + latency = bufferSize * (bufferCount-1); + while( latency < requestedLatency ) + { + ++bufferCount; + latency = bufferSize * (bufferCount-1); + } + } + + *hostBufferCount = bufferCount; +} + + +/* CalculateBufferSettings() fills the framesPerHostInputBuffer, hostInputBufferCount, + framesPerHostOutputBuffer and hostOutputBufferCount parameters based on the values + of the other parameters. +*/ + +static PaError CalculateBufferSettings( + unsigned long *framesPerHostInputBuffer, unsigned long *hostInputBufferCount, + unsigned long *framesPerHostOutputBuffer, unsigned long *hostOutputBufferCount, + int inputChannelCount, PaSampleFormat hostInputSampleFormat, + PaTime suggestedInputLatency, PaWinMmeStreamInfo *inputStreamInfo, + int outputChannelCount, PaSampleFormat hostOutputSampleFormat, + PaTime suggestedOutputLatency, PaWinMmeStreamInfo *outputStreamInfo, + double sampleRate, unsigned long framesPerBuffer ) +{ + PaError result = paNoError; + int effectiveInputChannelCount, effectiveOutputChannelCount; + int hostInputFrameSize = 0; + unsigned int i; + + if( inputChannelCount > 0 ) + { + int hostInputSampleSize = Pa_GetSampleSize( hostInputSampleFormat ); + if( hostInputSampleSize < 0 ) + { + result = hostInputSampleSize; + goto error; + } + + if( inputStreamInfo + && ( inputStreamInfo->flags & paWinMmeUseMultipleDevices ) ) + { + /* set effectiveInputChannelCount to the largest number of + channels on any one device. + */ + effectiveInputChannelCount = 0; + for( i=0; i< inputStreamInfo->deviceCount; ++i ) + { + if( inputStreamInfo->devices[i].channelCount > effectiveInputChannelCount ) + effectiveInputChannelCount = inputStreamInfo->devices[i].channelCount; + } + } + else + { + effectiveInputChannelCount = inputChannelCount; + } + + hostInputFrameSize = hostInputSampleSize * effectiveInputChannelCount; + + if( inputStreamInfo + && ( inputStreamInfo->flags & paWinMmeUseLowLevelLatencyParameters ) ) + { + if( inputStreamInfo->bufferCount <= 0 + || inputStreamInfo->framesPerBuffer <= 0 ) + { + result = paIncompatibleHostApiSpecificStreamInfo; + goto error; + } + + *framesPerHostInputBuffer = inputStreamInfo->framesPerBuffer; + *hostInputBufferCount = inputStreamInfo->bufferCount; + } + else + { + unsigned long hostBufferSizeBytes, hostBufferCount; + unsigned long minimumBufferCount = (outputChannelCount > 0) + ? PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_ + : PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_HALF_DUPLEX_; + + unsigned long maximumBufferSize = (long) ((PA_MME_MAX_HOST_BUFFER_SECS_ * sampleRate) * hostInputFrameSize); + if( maximumBufferSize > PA_MME_MAX_HOST_BUFFER_BYTES_ ) + maximumBufferSize = PA_MME_MAX_HOST_BUFFER_BYTES_; + + /* compute the following in bytes, then convert back to frames */ + + SelectBufferSizeAndCount( + ((framesPerBuffer == paFramesPerBufferUnspecified) + ? PA_MME_MIN_HOST_BUFFER_FRAMES_WHEN_UNSPECIFIED_ + : framesPerBuffer ) * hostInputFrameSize, /* baseBufferSize */ + ((unsigned long)(suggestedInputLatency * sampleRate)) * hostInputFrameSize, /* suggestedLatency */ + 4, /* baseBufferCount */ + minimumBufferCount, maximumBufferSize, + &hostBufferSizeBytes, &hostBufferCount ); + + *framesPerHostInputBuffer = hostBufferSizeBytes / hostInputFrameSize; + *hostInputBufferCount = hostBufferCount; + } + } + else + { + *framesPerHostInputBuffer = 0; + *hostInputBufferCount = 0; + } + + if( outputChannelCount > 0 ) + { + if( outputStreamInfo + && ( outputStreamInfo->flags & paWinMmeUseLowLevelLatencyParameters ) ) + { + if( outputStreamInfo->bufferCount <= 0 + || outputStreamInfo->framesPerBuffer <= 0 ) + { + result = paIncompatibleHostApiSpecificStreamInfo; + goto error; + } + + *framesPerHostOutputBuffer = outputStreamInfo->framesPerBuffer; + *hostOutputBufferCount = outputStreamInfo->bufferCount; + + + if( inputChannelCount > 0 ) /* full duplex */ + { + if( *framesPerHostInputBuffer != *framesPerHostOutputBuffer ) + { + if( inputStreamInfo + && ( inputStreamInfo->flags & paWinMmeUseLowLevelLatencyParameters ) ) + { + /* a custom StreamInfo was used for specifying both input + and output buffer sizes, the larger buffer size + must be a multiple of the smaller buffer size */ + + if( *framesPerHostInputBuffer < *framesPerHostOutputBuffer ) + { + if( *framesPerHostOutputBuffer % *framesPerHostInputBuffer != 0 ) + { + result = paIncompatibleHostApiSpecificStreamInfo; + goto error; + } + } + else + { + assert( *framesPerHostInputBuffer > *framesPerHostOutputBuffer ); + if( *framesPerHostInputBuffer % *framesPerHostOutputBuffer != 0 ) + { + result = paIncompatibleHostApiSpecificStreamInfo; + goto error; + } + } + } + else + { + /* a custom StreamInfo was not used for specifying the input buffer size, + so use the output buffer size, and approximately the same latency. */ + + *framesPerHostInputBuffer = *framesPerHostOutputBuffer; + *hostInputBufferCount = (((unsigned long)(suggestedInputLatency * sampleRate)) / *framesPerHostInputBuffer) + 1; + + if( *hostInputBufferCount < PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_ ) + *hostInputBufferCount = PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_; + } + } + } + } + else + { + unsigned long hostBufferSizeBytes, hostBufferCount; + unsigned long minimumBufferCount = PA_MME_MIN_HOST_OUTPUT_BUFFER_COUNT_; + unsigned long maximumBufferSize; + int hostOutputFrameSize; + int hostOutputSampleSize; + + hostOutputSampleSize = Pa_GetSampleSize( hostOutputSampleFormat ); + if( hostOutputSampleSize < 0 ) + { + result = hostOutputSampleSize; + goto error; + } + + if( outputStreamInfo + && ( outputStreamInfo->flags & paWinMmeUseMultipleDevices ) ) + { + /* set effectiveOutputChannelCount to the largest number of + channels on any one device. + */ + effectiveOutputChannelCount = 0; + for( i=0; i< outputStreamInfo->deviceCount; ++i ) + { + if( outputStreamInfo->devices[i].channelCount > effectiveOutputChannelCount ) + effectiveOutputChannelCount = outputStreamInfo->devices[i].channelCount; + } + } + else + { + effectiveOutputChannelCount = outputChannelCount; + } + + hostOutputFrameSize = hostOutputSampleSize * effectiveOutputChannelCount; + + maximumBufferSize = (long) ((PA_MME_MAX_HOST_BUFFER_SECS_ * sampleRate) * hostOutputFrameSize); + if( maximumBufferSize > PA_MME_MAX_HOST_BUFFER_BYTES_ ) + maximumBufferSize = PA_MME_MAX_HOST_BUFFER_BYTES_; + + + /* compute the following in bytes, then convert back to frames */ + + SelectBufferSizeAndCount( + ((framesPerBuffer == paFramesPerBufferUnspecified) + ? PA_MME_MIN_HOST_BUFFER_FRAMES_WHEN_UNSPECIFIED_ + : framesPerBuffer ) * hostOutputFrameSize, /* baseBufferSize */ + ((unsigned long)(suggestedOutputLatency * sampleRate)) * hostOutputFrameSize, /* suggestedLatency */ + 4, /* baseBufferCount */ + minimumBufferCount, + maximumBufferSize, + &hostBufferSizeBytes, &hostBufferCount ); + + *framesPerHostOutputBuffer = hostBufferSizeBytes / hostOutputFrameSize; + *hostOutputBufferCount = hostBufferCount; + + + if( inputChannelCount > 0 ) + { + /* ensure that both input and output buffer sizes are the same. + if they don't match at this stage, choose the smallest one + and use that for input and output + */ + + if( *framesPerHostOutputBuffer != *framesPerHostInputBuffer ) + { + if( framesPerHostInputBuffer < framesPerHostOutputBuffer ) + { + unsigned long framesPerHostBuffer = *framesPerHostInputBuffer; + + minimumBufferCount = PA_MME_MIN_HOST_OUTPUT_BUFFER_COUNT_; + ReselectBufferCount( + framesPerHostBuffer * hostOutputFrameSize, /* bufferSize */ + ((unsigned long)(suggestedOutputLatency * sampleRate)) * hostOutputFrameSize, /* suggestedLatency */ + 4, /* baseBufferCount */ + minimumBufferCount, + &hostBufferCount ); + + *framesPerHostOutputBuffer = framesPerHostBuffer; + *hostOutputBufferCount = hostBufferCount; + } + else + { + unsigned long framesPerHostBuffer = *framesPerHostOutputBuffer; + + minimumBufferCount = PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_; + ReselectBufferCount( + framesPerHostBuffer * hostInputFrameSize, /* bufferSize */ + ((unsigned long)(suggestedInputLatency * sampleRate)) * hostInputFrameSize, /* suggestedLatency */ + 4, /* baseBufferCount */ + minimumBufferCount, + &hostBufferCount ); + + *framesPerHostInputBuffer = framesPerHostBuffer; + *hostInputBufferCount = hostBufferCount; + } + } + } + } + } + else + { + *framesPerHostOutputBuffer = 0; + *hostOutputBufferCount = 0; + } + +error: + return result; +} + + +typedef struct +{ + HANDLE bufferEvent; + void *waveHandles; + unsigned int deviceCount; + /* unsigned int channelCount; */ + WAVEHDR **waveHeaders; /* waveHeaders[device][buffer] */ + unsigned int bufferCount; + unsigned int currentBufferIndex; + unsigned int framesPerBuffer; + unsigned int framesUsedInCurrentBuffer; +}PaWinMmeSingleDirectionHandlesAndBuffers; + +/* prototypes for functions operating on PaWinMmeSingleDirectionHandlesAndBuffers */ + +static void InitializeSingleDirectionHandlesAndBuffers( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers ); +static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostApi, + PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, + unsigned long bytesPerHostSample, + double sampleRate, PaWinMmeDeviceAndChannelCount *devices, + unsigned int deviceCount, int isInput ); +static PaError TerminateWaveHandles( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, int isInput, int currentlyProcessingAnError ); +static PaError InitializeWaveHeaders( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, + unsigned long hostBufferCount, + PaSampleFormat hostSampleFormat, + unsigned long framesPerHostBuffer, + PaWinMmeDeviceAndChannelCount *devices, + int isInput ); +static void TerminateWaveHeaders( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, int isInput ); + + +static void InitializeSingleDirectionHandlesAndBuffers( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers ) +{ + handlesAndBuffers->bufferEvent = 0; + handlesAndBuffers->waveHandles = 0; + handlesAndBuffers->deviceCount = 0; + handlesAndBuffers->waveHeaders = 0; + handlesAndBuffers->bufferCount = 0; +} + +static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostApi, + PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, + unsigned long bytesPerHostSample, + double sampleRate, PaWinMmeDeviceAndChannelCount *devices, + unsigned int deviceCount, int isInput ) +{ + PaError result; + MMRESULT mmresult; + unsigned long bytesPerFrame; + WAVEFORMATEX wfx; + signed int i; + + /* for error cleanup we expect that InitializeSingleDirectionHandlesAndBuffers() + has already been called to zero some fields */ + + result = CreateEventWithPaError( &handlesAndBuffers->bufferEvent, NULL, FALSE, FALSE, NULL ); + if( result != paNoError ) goto error; + + if( isInput ) + handlesAndBuffers->waveHandles = (void*)PaUtil_AllocateMemory( sizeof(HWAVEIN) * deviceCount ); + else + handlesAndBuffers->waveHandles = (void*)PaUtil_AllocateMemory( sizeof(HWAVEOUT) * deviceCount ); + if( !handlesAndBuffers->waveHandles ) + { + result = paInsufficientMemory; + goto error; + } + + handlesAndBuffers->deviceCount = deviceCount; + + for( i = 0; i < (signed int)deviceCount; ++i ) + { + if( isInput ) + ((HWAVEIN*)handlesAndBuffers->waveHandles)[i] = 0; + else + ((HWAVEOUT*)handlesAndBuffers->waveHandles)[i] = 0; + } + + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nSamplesPerSec = (DWORD) sampleRate; + wfx.cbSize = 0; + + for( i = 0; i < (signed int)deviceCount; ++i ) + { + UINT winMmeDeviceId; + + winMmeDeviceId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, devices[i].device ); + wfx.nChannels = (WORD)devices[i].channelCount; + + bytesPerFrame = wfx.nChannels * bytesPerHostSample; + + wfx.nAvgBytesPerSec = (DWORD)(bytesPerFrame * sampleRate); + wfx.nBlockAlign = (WORD)bytesPerFrame; + wfx.wBitsPerSample = (WORD)((bytesPerFrame/wfx.nChannels) * 8); + + /* 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, &wfx, + (DWORD)handlesAndBuffers->bufferEvent, (DWORD)0, CALLBACK_EVENT ); + else + mmresult = waveOutOpen( &((HWAVEOUT*)handlesAndBuffers->waveHandles)[i], winMmeDeviceId, &wfx, + (DWORD)handlesAndBuffers->bufferEvent, (DWORD)0, CALLBACK_EVENT ); + + if( mmresult != MMSYSERR_NOERROR ) + { + 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 ); + } + } + goto error; + } + } + + return result; + +error: + TerminateWaveHandles( handlesAndBuffers, isInput, 1 /* currentlyProcessingAnError */ ); + + return result; +} + + +static PaError TerminateWaveHandles( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, int isInput, int currentlyProcessingAnError ) +{ + PaError result = paNoError; + MMRESULT mmresult; + signed int i; + + if( handlesAndBuffers->waveHandles ) + { + for( i = handlesAndBuffers->deviceCount-1; i >= 0; --i ) + { + if( isInput ) + { + if( ((HWAVEIN*)handlesAndBuffers->waveHandles)[i] ) + mmresult = waveInClose( ((HWAVEIN*)handlesAndBuffers->waveHandles)[i] ); + } + else + { + if( ((HWAVEOUT*)handlesAndBuffers->waveHandles)[i] ) + mmresult = waveOutClose( ((HWAVEOUT*)handlesAndBuffers->waveHandles)[i] ); + } + + if( mmresult != MMSYSERR_NOERROR && + !currentlyProcessingAnError ) /* don't update the error state if we're already processing an error */ + { + result = paUnanticipatedHostError; + if( isInput ) + { + PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); + } + else + { + PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ); + } + /* note that we don't break here, we try to continue closing devices */ + } + } + + PaUtil_FreeMemory( handlesAndBuffers->waveHandles ); + handlesAndBuffers->waveHandles = 0; + } + + if( handlesAndBuffers->bufferEvent ) + { + result = CloseHandleWithPaError( handlesAndBuffers->bufferEvent ); + handlesAndBuffers->bufferEvent = 0; + } + + return result; +} + + +static PaError InitializeWaveHeaders( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, + unsigned long hostBufferCount, + PaSampleFormat hostSampleFormat, + unsigned long framesPerHostBuffer, + PaWinMmeDeviceAndChannelCount *devices, + int isInput ) +{ + PaError result = paNoError; + MMRESULT mmresult; + WAVEHDR *deviceWaveHeaders; + signed int i, j; + + /* for error cleanup we expect that InitializeSingleDirectionHandlesAndBuffers() + has already been called to zero some fields */ + + + /* allocate an array of pointers to arrays of wave headers, one array of + wave headers per device */ + handlesAndBuffers->waveHeaders = (WAVEHDR**)PaUtil_AllocateMemory( sizeof(WAVEHDR*) * handlesAndBuffers->deviceCount ); + if( !handlesAndBuffers->waveHeaders ) + { + result = paInsufficientMemory; + goto error; + } + + for( i = 0; i < (signed int)handlesAndBuffers->deviceCount; ++i ) + handlesAndBuffers->waveHeaders[i] = 0; + + handlesAndBuffers->bufferCount = hostBufferCount; + + for( i = 0; i < (signed int)handlesAndBuffers->deviceCount; ++i ) + { + int bufferBytes = Pa_GetSampleSize( hostSampleFormat ) * + framesPerHostBuffer * devices[i].channelCount; + if( bufferBytes < 0 ) + { + result = paInternalError; + goto error; + } + + /* Allocate an array of wave headers for device i */ + deviceWaveHeaders = (WAVEHDR *) PaUtil_AllocateMemory( sizeof(WAVEHDR)*hostBufferCount ); + if( !deviceWaveHeaders ) + { + result = paInsufficientMemory; + goto error; + } + + for( j=0; j < (signed int)hostBufferCount; ++j ) + deviceWaveHeaders[j].lpData = 0; + + handlesAndBuffers->waveHeaders[i] = deviceWaveHeaders; + + /* Allocate a buffer for each wave header */ + for( j=0; j < (signed int)hostBufferCount; ++j ) + { + deviceWaveHeaders[j].lpData = (char *)PaUtil_AllocateMemory( bufferBytes ); + if( !deviceWaveHeaders[j].lpData ) + { + result = paInsufficientMemory; + goto error; + } + deviceWaveHeaders[j].dwBufferLength = bufferBytes; + deviceWaveHeaders[j].dwUser = 0xFFFFFFFF; /* indicates that *PrepareHeader() has not yet been called, for error clean up code */ + + if( isInput ) + { + mmresult = waveInPrepareHeader( ((HWAVEIN*)handlesAndBuffers->waveHandles)[i], &deviceWaveHeaders[j], sizeof(WAVEHDR) ); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); + goto error; + } + } + else /* output */ + { + mmresult = waveOutPrepareHeader( ((HWAVEOUT*)handlesAndBuffers->waveHandles)[i], &deviceWaveHeaders[j], sizeof(WAVEHDR) ); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); + goto error; + } + } + deviceWaveHeaders[j].dwUser = devices[i].channelCount; + } + } + + return result; + +error: + TerminateWaveHeaders( handlesAndBuffers, isInput ); + + return result; +} + + +static void TerminateWaveHeaders( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, int isInput ) +{ + signed int i, j; + WAVEHDR *deviceWaveHeaders; + + if( handlesAndBuffers->waveHeaders ) + { + for( i = handlesAndBuffers->deviceCount-1; i >= 0 ; --i ) + { + deviceWaveHeaders = handlesAndBuffers->waveHeaders[i]; /* wave headers for device i */ + if( deviceWaveHeaders ) + { + for( j = handlesAndBuffers->bufferCount-1; j >= 0; --j ) + { + if( deviceWaveHeaders[j].lpData ) + { + if( deviceWaveHeaders[j].dwUser != 0xFFFFFFFF ) + { + if( isInput ) + waveInUnprepareHeader( ((HWAVEIN*)handlesAndBuffers->waveHandles)[i], &deviceWaveHeaders[j], sizeof(WAVEHDR) ); + else + waveOutUnprepareHeader( ((HWAVEOUT*)handlesAndBuffers->waveHandles)[i], &deviceWaveHeaders[j], sizeof(WAVEHDR) ); + } + + PaUtil_FreeMemory( deviceWaveHeaders[j].lpData ); + } + } + + PaUtil_FreeMemory( deviceWaveHeaders ); + } + } + + PaUtil_FreeMemory( handlesAndBuffers->waveHeaders ); + handlesAndBuffers->waveHeaders = 0; + } +} + + + +/* PaWinMmeStream - a stream data structure specifically for this implementation */ +/* note that struct PaWinMmeStream is typedeffed to PaWinMmeStream above. */ +struct PaWinMmeStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + + int primeStreamUsingCallback; + + PaWinMmeSingleDirectionHandlesAndBuffers input; + PaWinMmeSingleDirectionHandlesAndBuffers output; + + /* Processing thread management -------------- */ + HANDLE abortEvent; + HANDLE processingThread; + DWORD processingThreadId; + + char throttleProcessingThreadOnOverload; /* 0 -> don't throtte, non-0 -> throttle */ + int processingThreadPriority; + int highThreadPriority; + int throttledThreadPriority; + unsigned long throttledSleepMsecs; + + int isStopped; + volatile int isActive; + volatile int stopProcessing; /* stop thread once existing buffers have been returned */ + volatile int abortProcessing; /* stop thread immediately */ + + DWORD allBuffersDurationMs; /* used to calculate timeouts */ +}; + +/* updates deviceCount if PaWinMmeUseMultipleDevices is used */ + +static PaError ValidateWinMmeSpecificStreamInfo( + const PaStreamParameters *streamParameters, + const PaWinMmeStreamInfo *streamInfo, + char *throttleProcessingThreadOnOverload, + unsigned long *deviceCount ) +{ + if( streamInfo ) + { + if( streamInfo->size != sizeof( PaWinMmeStreamInfo ) + || streamInfo->version != 1 ) + { + return paIncompatibleHostApiSpecificStreamInfo; + } + + if( streamInfo->flags & paWinMmeDontThrottleOverloadedProcessingThread ) + *throttleProcessingThreadOnOverload = 0; + + if( streamInfo->flags & paWinMmeUseMultipleDevices ) + { + if( streamParameters->device != paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + *deviceCount = streamInfo->deviceCount; + } + } + + return paNoError; +} + +static PaError RetrieveDevicesFromStreamParameters( + struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *streamParameters, + const PaWinMmeStreamInfo *streamInfo, + PaWinMmeDeviceAndChannelCount *devices, + unsigned long deviceCount ) +{ + PaError result = paNoError; + unsigned int i; + int totalChannelCount; + PaDeviceIndex hostApiDevice; + + if( streamInfo && streamInfo->flags & paWinMmeUseMultipleDevices ) + { + totalChannelCount = 0; + for( i=0; i < deviceCount; ++i ) + { + /* validate that the device number is within range */ + result = PaUtil_DeviceIndexToHostApiDeviceIndex( &hostApiDevice, + streamInfo->devices[i].device, hostApi ); + if( result != paNoError ) + return result; + + devices[i].device = hostApiDevice; + devices[i].channelCount = streamInfo->devices[i].channelCount; + + totalChannelCount += devices[i].channelCount; + } + + if( totalChannelCount != streamParameters->channelCount ) + { + /* channelCount must match total channels specified by multiple devices */ + return paInvalidChannelCount; /* REVIEW use of this error code */ + } + } + else + { + devices[0].device = streamParameters->device; + devices[0].channelCount = streamParameters->channelCount; + } + + return result; +} + +static PaError ValidateInputChannelCounts( + struct PaUtilHostApiRepresentation *hostApi, + PaWinMmeDeviceAndChannelCount *devices, + unsigned long deviceCount ) +{ + unsigned int i; + + for( i=0; i < deviceCount; ++i ) + { + if( devices[i].channelCount < 1 || devices[i].channelCount + > hostApi->deviceInfos[ devices[i].device ]->maxInputChannels ) + return paInvalidChannelCount; + } + + return paNoError; +} + +static PaError ValidateOutputChannelCounts( + struct PaUtilHostApiRepresentation *hostApi, + PaWinMmeDeviceAndChannelCount *devices, + unsigned long deviceCount ) +{ + unsigned int i; + + for( i=0; i < deviceCount; ++i ) + { + if( devices[i].channelCount < 1 || devices[i].channelCount + > hostApi->deviceInfos[ devices[i].device ]->maxOutputChannels ) + return paInvalidChannelCount; + } + + return paNoError; +} + + +/* the following macros are intended to improve the readability of the following code */ +#define PA_IS_INPUT_STREAM_( stream ) ( stream ->input.waveHandles ) +#define PA_IS_OUTPUT_STREAM_( stream ) ( stream ->output.waveHandles ) +#define PA_IS_FULL_DUPLEX_STREAM_( stream ) ( stream ->input.waveHandles && stream ->output.waveHandles ) +#define PA_IS_HALF_DUPLEX_STREAM_( stream ) ( !(stream ->input.waveHandles && stream ->output.waveHandles) ) + +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ) +{ + PaError result; + PaWinMmeHostApiRepresentation *winMmeHostApi = (PaWinMmeHostApiRepresentation*)hostApi; + PaWinMmeStream *stream = 0; + int bufferProcessorIsInitialized = 0; + int streamRepresentationIsInitialized = 0; + PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat; + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + double suggestedInputLatency, suggestedOutputLatency; + PaWinMmeStreamInfo *inputStreamInfo, *outputStreamInfo; + 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 inputDeviceCount = 0; + PaWinMmeDeviceAndChannelCount *outputDevices = 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; + + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + suggestedInputLatency = inputParameters->suggestedLatency; + + inputDeviceCount = 1; + + /* validate input hostApiSpecificStreamInfo */ + inputStreamInfo = (PaWinMmeStreamInfo*)inputParameters->hostApiSpecificStreamInfo; + result = ValidateWinMmeSpecificStreamInfo( inputParameters, inputStreamInfo, + &throttleProcessingThreadOnOverload, + &inputDeviceCount ); + if( result != paNoError ) return result; + + inputDevices = (PaWinMmeDeviceAndChannelCount*)alloca( sizeof(PaWinMmeDeviceAndChannelCount) * inputDeviceCount ); + if( !inputDevices ) return paInsufficientMemory; + + result = RetrieveDevicesFromStreamParameters( hostApi, inputParameters, inputStreamInfo, inputDevices, inputDeviceCount ); + if( result != paNoError ) return result; + + result = ValidateInputChannelCounts( hostApi, inputDevices, inputDeviceCount ); + if( result != paNoError ) return result; + + hostInputSampleFormat = + PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, inputSampleFormat ); + } + else + { + inputChannelCount = 0; + inputSampleFormat = 0; + suggestedInputLatency = 0.; + inputStreamInfo = 0; + hostInputSampleFormat = 0; + } + + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + suggestedOutputLatency = outputParameters->suggestedLatency; + + outputDeviceCount = 1; + + /* validate output hostApiSpecificStreamInfo */ + outputStreamInfo = (PaWinMmeStreamInfo*)outputParameters->hostApiSpecificStreamInfo; + result = ValidateWinMmeSpecificStreamInfo( outputParameters, outputStreamInfo, + &throttleProcessingThreadOnOverload, + &outputDeviceCount ); + if( result != paNoError ) return result; + + outputDevices = (PaWinMmeDeviceAndChannelCount*)alloca( sizeof(PaWinMmeDeviceAndChannelCount) * outputDeviceCount ); + if( !outputDevices ) return paInsufficientMemory; + + result = RetrieveDevicesFromStreamParameters( hostApi, outputParameters, outputStreamInfo, outputDevices, outputDeviceCount ); + if( result != paNoError ) return result; + + result = ValidateOutputChannelCounts( hostApi, outputDevices, outputDeviceCount ); + if( result != paNoError ) return result; + + hostOutputSampleFormat = + PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, outputSampleFormat ); + } + else + { + outputChannelCount = 0; + outputSampleFormat = 0; + outputStreamInfo = 0; + hostOutputSampleFormat = 0; + suggestedOutputLatency = 0.; + } + + + /* + IMPLEMENT ME: + - alter sampleRate to a close allowable rate if possible / necessary + */ + + + /* validate platform specific flags */ + if( (streamFlags & paPlatformSpecificFlags) != 0 ) + return paInvalidFlag; /* unexpected platform specific flag */ + + + result = CalculateBufferSettings( &framesPerHostInputBuffer, &hostInputBufferCount, + &framesPerHostOutputBuffer, &hostOutputBufferCount, + inputChannelCount, hostInputSampleFormat, suggestedInputLatency, inputStreamInfo, + outputChannelCount, hostOutputSampleFormat, suggestedOutputLatency, outputStreamInfo, + sampleRate, framesPerBuffer ); + if( result != paNoError ) goto error; + + + stream = (PaWinMmeStream*)PaUtil_AllocateMemory( sizeof(PaWinMmeStream) ); + if( !stream ) + { + result = paInsufficientMemory; + goto error; + } + + InitializeSingleDirectionHandlesAndBuffers( &stream->input ); + InitializeSingleDirectionHandlesAndBuffers( &stream->output ); + + stream->abortEvent = 0; + stream->processingThread = 0; + + stream->throttleProcessingThreadOnOverload = throttleProcessingThreadOnOverload; + + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + ( (streamCallback) + ? &winMmeHostApi->callbackStreamInterface + : &winMmeHostApi->blockingStreamInterface ), + streamCallback, userData ); + streamRepresentationIsInitialized = 1; + + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); + + + if( inputParameters && outputParameters ) /* full duplex */ + { + if( framesPerHostInputBuffer < framesPerHostOutputBuffer ) + { + assert( (framesPerHostOutputBuffer % framesPerHostInputBuffer) == 0 ); /* CalculateBufferSettings() should guarantee this condition */ + + framesPerBufferProcessorCall = framesPerHostInputBuffer; + } + else + { + assert( (framesPerHostInputBuffer % framesPerHostOutputBuffer) == 0 ); /* CalculateBufferSettings() should guarantee this condition */ + + framesPerBufferProcessorCall = framesPerHostOutputBuffer; + } + } + else if( inputParameters ) + { + framesPerBufferProcessorCall = framesPerHostInputBuffer; + } + else if( outputParameters ) + { + framesPerBufferProcessorCall = framesPerHostOutputBuffer; + } + + stream->input.framesPerBuffer = framesPerHostInputBuffer; + stream->output.framesPerBuffer = framesPerHostOutputBuffer; + + result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, + inputChannelCount, inputSampleFormat, hostInputSampleFormat, + outputChannelCount, outputSampleFormat, hostOutputSampleFormat, + sampleRate, streamFlags, framesPerBuffer, + framesPerBufferProcessorCall, paUtilFixedHostBufferSize, + streamCallback, userData ); + if( result != paNoError ) goto error; + + bufferProcessorIsInitialized = 1; + + stream->streamRepresentation.streamInfo.inputLatency = + (double)(PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor) + +(framesPerHostInputBuffer * (hostInputBufferCount-1))) / sampleRate; + stream->streamRepresentation.streamInfo.outputLatency = + (double)(PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor) + +(framesPerHostOutputBuffer * (hostOutputBufferCount-1))) / sampleRate; + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + + stream->primeStreamUsingCallback = ( (streamFlags&paPrimeOutputBuffersUsingStreamCallback) && streamCallback ) ? 1 : 0; + + /* time to sleep when throttling due to >100% cpu usage. + -a quater of a buffer's duration */ + stream->throttledSleepMsecs = + (unsigned long)(stream->bufferProcessor.framesPerHostBuffer * + stream->bufferProcessor.samplePeriod * .25 * 1000); + + stream->isStopped = 1; + stream->isActive = 0; + + + /* for maximum compatibility with multi-device multichannel drivers, + we first open all devices, then we prepare all buffers, finally + we start all devices ( in StartStream() ). teardown in reverse order. + */ + + if( inputParameters ) + { + result = InitializeWaveHandles( winMmeHostApi, &stream->input, + stream->bufferProcessor.bytesPerHostInputSample, sampleRate, + inputDevices, inputDeviceCount, 1 /* isInput */ ); + if( result != paNoError ) goto error; + } + + if( outputParameters ) + { + result = InitializeWaveHandles( winMmeHostApi, &stream->output, + stream->bufferProcessor.bytesPerHostOutputSample, sampleRate, + outputDevices, outputDeviceCount, 0 /* isInput */ ); + if( result != paNoError ) goto error; + } + + if( inputParameters ) + { + result = InitializeWaveHeaders( &stream->input, hostInputBufferCount, + hostInputSampleFormat, framesPerHostInputBuffer, inputDevices, 1 /* isInput */ ); + if( result != paNoError ) goto error; + } + + if( outputParameters ) + { + result = InitializeWaveHeaders( &stream->output, hostOutputBufferCount, + hostOutputSampleFormat, framesPerHostOutputBuffer, outputDevices, 0 /* not isInput */ ); + if( result != paNoError ) goto error; + + stream->allBuffersDurationMs = (DWORD) (1000.0 * (framesPerHostOutputBuffer * stream->output.bufferCount) / sampleRate); + } + else + { + stream->allBuffersDurationMs = (DWORD) (1000.0 * (framesPerHostInputBuffer * stream->input.bufferCount) / sampleRate); + } + + + if( streamCallback ) + { + /* abort event is only needed for callback streams */ + result = CreateEventWithPaError( &stream->abortEvent, NULL, TRUE, FALSE, NULL ); + if( result != paNoError ) goto error; + } + + *s = (PaStream*)stream; + + return result; + +error: + + if( stream ) + { + if( stream->abortEvent ) + CloseHandle( stream->abortEvent ); + + TerminateWaveHeaders( &stream->output, 0 /* not isInput */ ); + TerminateWaveHeaders( &stream->input, 1 /* isInput */ ); + + TerminateWaveHandles( &stream->output, 0 /* not isInput */, 1 /* currentlyProcessingAnError */ ); + TerminateWaveHandles( &stream->input, 1 /* isInput */, 1 /* currentlyProcessingAnError */ ); + + if( bufferProcessorIsInitialized ) + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + + if( streamRepresentationIsInitialized ) + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + + PaUtil_FreeMemory( stream ); + } + + return result; +} + + +/* return non-zero if all current buffers are done */ +static int BuffersAreDone( WAVEHDR **waveHeaders, unsigned int deviceCount, int bufferIndex ) +{ + unsigned int i; + + for( i=0; i < deviceCount; ++i ) + { + if( !(waveHeaders[i][ bufferIndex ].dwFlags & WHDR_DONE) ) + { + return 0; + } + } + + return 1; +} + +static int CurrentInputBuffersAreDone( PaWinMmeStream *stream ) +{ + return BuffersAreDone( stream->input.waveHeaders, stream->input.deviceCount, stream->input.currentBufferIndex ); +} + +static int CurrentOutputBuffersAreDone( PaWinMmeStream *stream ) +{ + return BuffersAreDone( stream->output.waveHeaders, stream->output.deviceCount, stream->output.currentBufferIndex ); +} + + +/* return non-zero if any buffers are queued */ +static int NoBuffersAreQueued( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers ) +{ + unsigned int i, j; + + if( handlesAndBuffers->waveHandles ) + { + for( i=0; i < handlesAndBuffers->bufferCount; ++i ) + { + for( j=0; j < handlesAndBuffers->deviceCount; ++j ) + { + if( !( handlesAndBuffers->waveHeaders[ j ][ i ].dwFlags & WHDR_DONE) ) + { + return 0; + } + } + } + } + + return 1; +} + + +#define PA_CIRCULAR_INCREMENT_( current, max )\ + ( (((current) + 1) >= (max)) ? (0) : (current+1) ) + +#define PA_CIRCULAR_DECREMENT_( current, max )\ + ( ((current) == 0) ? ((max)-1) : (current-1) ) + + +static signed long GetAvailableFrames( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers ) +{ + signed long result = 0; + unsigned int i; + + if( BuffersAreDone( handlesAndBuffers->waveHeaders, handlesAndBuffers->deviceCount, handlesAndBuffers->currentBufferIndex ) ) + { + /* we could calculate the following in O(1) if we kept track of the + last done buffer */ + result = handlesAndBuffers->framesPerBuffer - handlesAndBuffers->framesUsedInCurrentBuffer; + + i = PA_CIRCULAR_INCREMENT_( handlesAndBuffers->currentBufferIndex, handlesAndBuffers->bufferCount ); + while( i != handlesAndBuffers->currentBufferIndex ) + { + if( BuffersAreDone( handlesAndBuffers->waveHeaders, handlesAndBuffers->deviceCount, i ) ) + { + result += handlesAndBuffers->framesPerBuffer; + i = PA_CIRCULAR_INCREMENT_( i, handlesAndBuffers->bufferCount ); + } + else + break; + } + } + + return result; +} + + +static PaError AdvanceToNextInputBuffer( PaWinMmeStream *stream ) +{ + PaError result = paNoError; + MMRESULT mmresult; + unsigned int i; + + for( i=0; i < stream->input.deviceCount; ++i ) + { + mmresult = waveInAddBuffer( ((HWAVEIN*)stream->input.waveHandles)[i], + &stream->input.waveHeaders[i][ stream->input.currentBufferIndex ], + sizeof(WAVEHDR) ); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); + } + } + + stream->input.currentBufferIndex = + PA_CIRCULAR_INCREMENT_( stream->input.currentBufferIndex, stream->input.bufferCount ); + + stream->input.framesUsedInCurrentBuffer = 0; + + return result; +} + + +static PaError AdvanceToNextOutputBuffer( PaWinMmeStream *stream ) +{ + PaError result = paNoError; + MMRESULT mmresult; + unsigned int i; + + for( i=0; i < stream->output.deviceCount; ++i ) + { + mmresult = waveOutWrite( ((HWAVEOUT*)stream->output.waveHandles)[i], + &stream->output.waveHeaders[i][ stream->output.currentBufferIndex ], + sizeof(WAVEHDR) ); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ); + } + } + + stream->output.currentBufferIndex = + PA_CIRCULAR_INCREMENT_( stream->output.currentBufferIndex, stream->output.bufferCount ); + + stream->output.framesUsedInCurrentBuffer = 0; + + return result; +} + + +/* requeue all but the most recent input with the driver. Used for catching + up after a total input buffer underrun */ +static PaError CatchUpInputBuffers( PaWinMmeStream *stream ) +{ + PaError result = paNoError; + unsigned int i; + + for( i=0; i < stream->input.bufferCount - 1; ++i ) + { + result = AdvanceToNextInputBuffer( stream ); + if( result != paNoError ) + break; + } + + return result; +} + + +/* take the most recent output and duplicate it to all other output buffers + and requeue them. Used for catching up after a total output buffer underrun. +*/ +static PaError CatchUpOutputBuffers( PaWinMmeStream *stream ) +{ + PaError result = paNoError; + unsigned int i, j; + unsigned int previousBufferIndex = + PA_CIRCULAR_DECREMENT_( stream->output.currentBufferIndex, stream->output.bufferCount ); + + for( i=0; i < stream->output.bufferCount - 1; ++i ) + { + for( j=0; j < stream->output.deviceCount; ++j ) + { + if( stream->output.waveHeaders[j][ stream->output.currentBufferIndex ].lpData + != stream->output.waveHeaders[j][ previousBufferIndex ].lpData ) + { + CopyMemory( stream->output.waveHeaders[j][ stream->output.currentBufferIndex ].lpData, + stream->output.waveHeaders[j][ previousBufferIndex ].lpData, + stream->output.waveHeaders[j][ stream->output.currentBufferIndex ].dwBufferLength ); + } + } + + result = AdvanceToNextOutputBuffer( stream ); + if( result != paNoError ) + break; + } + + return result; +} + + +static DWORD WINAPI ProcessingThreadProc( void *pArg ) +{ + PaWinMmeStream *stream = (PaWinMmeStream *)pArg; + HANDLE events[3]; + int eventCount = 0; + DWORD result = paNoError; + DWORD waitResult; + DWORD timeout = (unsigned long)(stream->allBuffersDurationMs * 0.5); + int hostBuffersAvailable; + signed int hostInputBufferIndex, hostOutputBufferIndex; + PaStreamCallbackFlags statusFlags; + int callbackResult; + int done = 0; + unsigned int channel, i; + unsigned long framesProcessed; + + /* prepare event array for call to WaitForMultipleObjects() */ + if( stream->input.bufferEvent ) + events[eventCount++] = stream->input.bufferEvent; + if( stream->output.bufferEvent ) + events[eventCount++] = stream->output.bufferEvent; + events[eventCount++] = stream->abortEvent; + + statusFlags = 0; /** @todo support paInputUnderflow, paOutputOverflow and paNeverDropInput */ + + /* loop until something causes us to stop */ + do{ + /* wait for MME to signal that a buffer is available, or for + the PA abort event to be signaled. + + When this indicates that one or more buffers are available + NoBuffersAreQueued() and Current*BuffersAreDone are used below to + poll for additional done buffers. NoBuffersAreQueued() will fail + to identify an underrun/overflow if the driver doesn't mark all done + buffers prior to signalling the event. Some drivers do this + (eg RME Digi96, and others don't eg VIA PC 97 input). This isn't a + huge problem, it just means that we won't always be able to detect + underflow/overflow. + */ + waitResult = WaitForMultipleObjects( eventCount, events, FALSE /* wait all = FALSE */, timeout ); + if( waitResult == WAIT_FAILED ) + { + result = paUnanticipatedHostError; + /** @todo FIXME/REVIEW: can't return host error info from an asyncronous thread */ + done = 1; + } + else if( waitResult == WAIT_TIMEOUT ) + { + /* if a timeout is encountered, continue */ + } + + if( stream->abortProcessing ) + { + /* Pa_AbortStream() has been called, stop processing immediately */ + done = 1; + } + else if( stream->stopProcessing ) + { + /* Pa_StopStream() has been called or the user callback returned + non-zero, processing will continue until all output buffers + are marked as done. The stream will stop immediately if it + is input-only. + */ + + if( PA_IS_OUTPUT_STREAM_(stream) ) + { + if( NoBuffersAreQueued( &stream->output ) ) + done = 1; /* Will cause thread to return. */ + } + else + { + /* input only stream */ + done = 1; /* Will cause thread to return. */ + } + } + else + { + hostBuffersAvailable = 1; + + /* process all available host buffers */ + do + { + hostInputBufferIndex = -1; + hostOutputBufferIndex = -1; + + if( PA_IS_INPUT_STREAM_(stream) ) + { + if( CurrentInputBuffersAreDone( stream ) ) + { + if( NoBuffersAreQueued( &stream->input ) ) + { + /** @todo + if all of the other buffers are also ready then + we discard all but the most recent. This is an + input buffer overflow. FIXME: these buffers should + be passed to the callback in a paNeverDropInput + stream. + + note that it is also possible for an input overflow + to happen while the callback is processing a buffer. + that is handled further down. + */ + result = CatchUpInputBuffers( stream ); + if( result != paNoError ) + done = 1; + + statusFlags |= paInputOverflow; + } + + hostInputBufferIndex = stream->input.currentBufferIndex; + } + } + + if( PA_IS_OUTPUT_STREAM_(stream) ) + { + if( CurrentOutputBuffersAreDone( stream ) ) + { + /* ok, we have an output buffer */ + + if( NoBuffersAreQueued( &stream->output ) ) + { + /* + if all of the other buffers are also ready, catch up by copying + the most recently generated buffer into all but one of the output + buffers. + + note that this catch up code only handles the case where all + buffers have been played out due to this thread not having + woken up at all. a more common case occurs when this thread + is woken up, processes one buffer, but takes too long, and as + a result all the other buffers have become un-queued. that + case is handled further down. + */ + + result = CatchUpOutputBuffers( stream ); + if( result != paNoError ) + done = 1; + + statusFlags |= paOutputUnderflow; + } + + hostOutputBufferIndex = stream->output.currentBufferIndex; + } + } + + + if( (PA_IS_FULL_DUPLEX_STREAM_(stream) && hostInputBufferIndex != -1 && hostOutputBufferIndex != -1) || + (PA_IS_HALF_DUPLEX_STREAM_(stream) && ( hostInputBufferIndex != -1 || hostOutputBufferIndex != -1 ) ) ) + { + PaStreamCallbackTimeInfo timeInfo = {0,0,0}; /** @todo implement inputBufferAdcTime */ + + + if( PA_IS_OUTPUT_STREAM_(stream) ) + { + /* set timeInfo.currentTime and calculate timeInfo.outputBufferDacTime + from the current wave out position */ + MMTIME mmtime; + double timeBeforeGetPosition, timeAfterGetPosition; + double time; + long framesInBufferRing; + long writePosition; + long playbackPosition; + HWAVEOUT firstWaveOutDevice = ((HWAVEOUT*)stream->output.waveHandles)[0]; + + mmtime.wType = TIME_SAMPLES; + timeBeforeGetPosition = PaUtil_GetTime(); + waveOutGetPosition( firstWaveOutDevice, &mmtime, sizeof(MMTIME) ); + timeAfterGetPosition = PaUtil_GetTime(); + + timeInfo.currentTime = timeAfterGetPosition; + + /* approximate time at which wave out position was measured + as half way between timeBeforeGetPosition and timeAfterGetPosition */ + time = timeBeforeGetPosition + (timeAfterGetPosition - timeBeforeGetPosition) * .5; + + framesInBufferRing = stream->output.bufferCount * stream->bufferProcessor.framesPerHostBuffer; + playbackPosition = mmtime.u.sample % framesInBufferRing; + + writePosition = stream->output.currentBufferIndex * stream->bufferProcessor.framesPerHostBuffer + + stream->output.framesUsedInCurrentBuffer; + + if( playbackPosition >= writePosition ){ + timeInfo.outputBufferDacTime = + time + ((double)( writePosition + (framesInBufferRing - playbackPosition) ) * stream->bufferProcessor.samplePeriod ); + }else{ + timeInfo.outputBufferDacTime = + time + ((double)( writePosition - playbackPosition ) * stream->bufferProcessor.samplePeriod ); + } + } + + + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + + PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, statusFlags ); + + /* reset status flags once they have been passed to the buffer processor */ + statusFlags = 0; + + if( PA_IS_INPUT_STREAM_(stream) ) + { + PaUtil_SetInputFrameCount( &stream->bufferProcessor, 0 /* default to host buffer size */ ); + + channel = 0; + for( i=0; i<stream->input.deviceCount; ++i ) + { + /* we have stored the number of channels in the buffer in dwUser */ + int channelCount = stream->input.waveHeaders[i][ hostInputBufferIndex ].dwUser; + + PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, channel, + stream->input.waveHeaders[i][ hostInputBufferIndex ].lpData + + stream->input.framesUsedInCurrentBuffer * channelCount * + stream->bufferProcessor.bytesPerHostInputSample, + channelCount ); + + + channel += channelCount; + } + } + + if( PA_IS_OUTPUT_STREAM_(stream) ) + { + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, 0 /* default to host buffer size */ ); + + channel = 0; + for( i=0; i<stream->output.deviceCount; ++i ) + { + /* we have stored the number of channels in the buffer in dwUser */ + int channelCount = stream->output.waveHeaders[i][ hostOutputBufferIndex ].dwUser; + + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, channel, + stream->output.waveHeaders[i][ hostOutputBufferIndex ].lpData + + stream->output.framesUsedInCurrentBuffer * channelCount * + stream->bufferProcessor.bytesPerHostOutputSample, + channelCount ); + + channel += channelCount; + } + } + + callbackResult = paContinue; + framesProcessed = PaUtil_EndBufferProcessing( &stream->bufferProcessor, &callbackResult ); + + stream->input.framesUsedInCurrentBuffer += framesProcessed; + stream->output.framesUsedInCurrentBuffer += framesProcessed; + + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed ); + + if( callbackResult == paContinue ) + { + /* nothing special to do */ + } + else if( callbackResult == paAbort ) + { + stream->abortProcessing = 1; + done = 1; + /** @todo FIXME: should probably reset the output device immediately once the callback returns paAbort */ + result = paNoError; + } + else + { + /* User callback has asked us to stop with paComplete or other non-zero value */ + stream->stopProcessing = 1; /* stop once currently queued audio has finished */ + result = paNoError; + } + + + if( PA_IS_INPUT_STREAM_(stream) + && stream->stopProcessing == 0 && stream->abortProcessing == 0 + && stream->input.framesUsedInCurrentBuffer == stream->input.framesPerBuffer ) + { + if( NoBuffersAreQueued( &stream->input ) ) + { + /** @todo need to handle PaNeverDropInput here where necessary */ + result = CatchUpInputBuffers( stream ); + if( result != paNoError ) + done = 1; + + statusFlags |= paInputOverflow; + } + + result = AdvanceToNextInputBuffer( stream ); + if( result != paNoError ) + done = 1; + } + + + if( PA_IS_OUTPUT_STREAM_(stream) && !stream->abortProcessing ) + { + if( stream->stopProcessing && + stream->output.framesUsedInCurrentBuffer < stream->output.framesPerBuffer ) + { + /* zero remaining samples in output output buffer and flush */ + + stream->output.framesUsedInCurrentBuffer += PaUtil_ZeroOutput( &stream->bufferProcessor, + stream->output.framesPerBuffer - stream->output.framesUsedInCurrentBuffer ); + + /* we send the entire buffer to the output devices, but we could + just send a partial buffer, rather than zeroing the unused + samples. + */ + } + + if( stream->output.framesUsedInCurrentBuffer == stream->output.framesPerBuffer ) + { + /* check for underflow before enquing the just-generated buffer, + but recover from underflow after enquing it. This ensures + that the most recent audio segment is repeated */ + int outputUnderflow = NoBuffersAreQueued( &stream->output ); + + result = AdvanceToNextOutputBuffer( stream ); + if( result != paNoError ) + done = 1; + + if( outputUnderflow && !done && !stream->stopProcessing ) + { + /* Recover from underflow in the case where the + underflow occured while processing the buffer + we just finished */ + + result = CatchUpOutputBuffers( stream ); + if( result != paNoError ) + done = 1; + + statusFlags |= paOutputUnderflow; + } + } + } + + if( stream->throttleProcessingThreadOnOverload != 0 ) + { + if( stream->stopProcessing || stream->abortProcessing ) + { + if( stream->processingThreadPriority != stream->highThreadPriority ) + { + SetThreadPriority( stream->processingThread, stream->highThreadPriority ); + stream->processingThreadPriority = stream->highThreadPriority; + } + } + else if( PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ) > 1. ) + { + if( stream->processingThreadPriority != stream->throttledThreadPriority ) + { + SetThreadPriority( stream->processingThread, stream->throttledThreadPriority ); + stream->processingThreadPriority = stream->throttledThreadPriority; + } + + /* sleep to give other processes a go */ + Sleep( stream->throttledSleepMsecs ); + } + else + { + if( stream->processingThreadPriority != stream->highThreadPriority ) + { + SetThreadPriority( stream->processingThread, stream->highThreadPriority ); + stream->processingThreadPriority = stream->highThreadPriority; + } + } + } + } + else + { + hostBuffersAvailable = 0; + } + } + while( hostBuffersAvailable && + stream->stopProcessing == 0 && + stream->abortProcessing == 0 && + !done ); + } + } + while( !done ); + + stream->isActive = 0; + + if( stream->streamRepresentation.streamFinishedCallback != 0 ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + + PaUtil_ResetCpuLoadMeasurer( &stream->cpuLoadMeasurer ); + + return result; +} + + +/* + When CloseStream() is called, the multi-api layer ensures that + the stream has already been stopped or aborted. +*/ +static PaError CloseStream( PaStream* s ) +{ + PaError result; + PaWinMmeStream *stream = (PaWinMmeStream*)s; + + result = CloseHandleWithPaError( stream->abortEvent ); + if( result != paNoError ) goto error; + + TerminateWaveHeaders( &stream->output, 0 /* not isInput */ ); + TerminateWaveHeaders( &stream->input, 1 /* isInput */ ); + + TerminateWaveHandles( &stream->output, 0 /* not isInput */, 0 /* not currentlyProcessingAnError */ ); + TerminateWaveHandles( &stream->input, 1 /* isInput */, 0 /* not currentlyProcessingAnError */ ); + + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + PaUtil_FreeMemory( stream ); + +error: + /** @todo REVIEW: what is the best way to clean up a stream if an error is detected? */ + return result; +} + + +static PaError StartStream( PaStream *s ) +{ + PaError result; + PaWinMmeStream *stream = (PaWinMmeStream*)s; + MMRESULT mmresult; + unsigned int i, j; + int callbackResult; + unsigned int channel; + unsigned long framesProcessed; + PaStreamCallbackTimeInfo timeInfo = {0,0,0}; /** @todo implement this for stream priming */ + + PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); + + if( PA_IS_INPUT_STREAM_(stream) ) + { + for( i=0; i<stream->input.bufferCount; ++i ) + { + for( j=0; j<stream->input.deviceCount; ++j ) + { + mmresult = waveInAddBuffer( ((HWAVEIN*)stream->input.waveHandles)[j], &stream->input.waveHeaders[j][i], sizeof(WAVEHDR) ); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); + goto error; + } + } + } + stream->input.currentBufferIndex = 0; + stream->input.framesUsedInCurrentBuffer = 0; + } + + if( PA_IS_OUTPUT_STREAM_(stream) ) + { + for( i=0; i<stream->output.deviceCount; ++i ) + { + if( (mmresult = waveOutPause( ((HWAVEOUT*)stream->output.waveHandles)[i] )) != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ); + goto error; + } + } + + for( i=0; i<stream->output.bufferCount; ++i ) + { + if( stream->primeStreamUsingCallback ) + { + + stream->output.framesUsedInCurrentBuffer = 0; + do{ + + PaUtil_BeginBufferProcessing( &stream->bufferProcessor, + &timeInfo, + paPrimingOutput | ((stream->input.bufferCount > 0 ) ? paInputUnderflow : 0)); + + if( stream->input.bufferCount > 0 ) + PaUtil_SetNoInput( &stream->bufferProcessor ); + + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, 0 /* default to host buffer size */ ); + + channel = 0; + for( j=0; j<stream->output.deviceCount; ++j ) + { + /* we have stored the number of channels in the buffer in dwUser */ + int channelCount = stream->output.waveHeaders[j][i].dwUser; + + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, channel, + stream->output.waveHeaders[j][i].lpData + + stream->output.framesUsedInCurrentBuffer * channelCount * + stream->bufferProcessor.bytesPerHostOutputSample, + channelCount ); + + /* we have stored the number of channels in the buffer in dwUser */ + channel += channelCount; + } + + callbackResult = paContinue; + framesProcessed = PaUtil_EndBufferProcessing( &stream->bufferProcessor, &callbackResult ); + stream->output.framesUsedInCurrentBuffer += framesProcessed; + + if( callbackResult != paContinue ) + { + /** @todo fix this, what do we do if callback result is non-zero during stream + priming? + + for complete: play out primed waveHeaders as usual + for abort: clean up immediately. + */ + } + + }while( stream->output.framesUsedInCurrentBuffer != stream->output.framesPerBuffer ); + + } + else + { + for( j=0; j<stream->output.deviceCount; ++j ) + { + ZeroMemory( stream->output.waveHeaders[j][i].lpData, stream->output.waveHeaders[j][i].dwBufferLength ); + } + } + + /* we queue all channels of a single buffer frame (accross all + devices, because some multidevice multichannel drivers work + better this way */ + for( j=0; j<stream->output.deviceCount; ++j ) + { + mmresult = waveOutWrite( ((HWAVEOUT*)stream->output.waveHandles)[j], &stream->output.waveHeaders[j][i], sizeof(WAVEHDR) ); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ); + goto error; + } + } + } + stream->output.currentBufferIndex = 0; + stream->output.framesUsedInCurrentBuffer = 0; + } + + + stream->isStopped = 0; + stream->isActive = 1; + stream->stopProcessing = 0; + stream->abortProcessing = 0; + + result = ResetEventWithPaError( stream->input.bufferEvent ); + if( result != paNoError ) goto error; + + result = ResetEventWithPaError( stream->output.bufferEvent ); + if( result != paNoError ) goto error; + + + if( stream->streamRepresentation.streamCallback ) + { + /* callback stream */ + + result = ResetEventWithPaError( stream->abortEvent ); + 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 ); + if( !stream->processingThread ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() ); + goto error; + } + + /** @todo could have mme specific stream parameters to allow the user + to set the callback thread priorities */ + stream->highThreadPriority = THREAD_PRIORITY_TIME_CRITICAL; + stream->throttledThreadPriority = THREAD_PRIORITY_NORMAL; + + if( !SetThreadPriority( stream->processingThread, stream->highThreadPriority ) ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() ); + goto error; + } + stream->processingThreadPriority = stream->highThreadPriority; + } + else + { + /* blocking read/write stream */ + + } + + if( PA_IS_INPUT_STREAM_(stream) ) + { + for( i=0; i < stream->input.deviceCount; ++i ) + { + mmresult = waveInStart( ((HWAVEIN*)stream->input.waveHandles)[i] ); + PA_DEBUG(("Pa_StartStream: waveInStart returned = 0x%X.\n", mmresult)); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); + goto error; + } + } + } + + if( PA_IS_OUTPUT_STREAM_(stream) ) + { + for( i=0; i < stream->output.deviceCount; ++i ) + { + if( (mmresult = waveOutRestart( ((HWAVEOUT*)stream->output.waveHandles)[i] )) != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ); + goto error; + } + } + } + + return result; + +error: + /** @todo FIXME: implement recovery as best we can + This should involve rolling back to a state as-if this function had never been called + */ + return result; +} + + +static PaError StopStream( PaStream *s ) +{ + PaError result = paNoError; + PaWinMmeStream *stream = (PaWinMmeStream*)s; + int timeout; + DWORD waitResult; + MMRESULT mmresult; + signed int hostOutputBufferIndex; + unsigned int channel, waitCount, i; + + /** @todo + REVIEW: the error checking in this function needs review. the basic + idea is to return from this function in a known state - for example + there is no point avoiding calling waveInReset just because + the thread times out. + */ + + if( stream->processingThread ) + { + /* callback stream */ + + /* Tell processing thread to stop generating more data and to let current data play out. */ + stream->stopProcessing = 1; + + /* Calculate timeOut longer than longest time it could take to return all buffers. */ + timeout = (int)(stream->allBuffersDurationMs * 1.5); + if( timeout < PA_MME_MIN_TIMEOUT_MSEC_ ) + timeout = PA_MME_MIN_TIMEOUT_MSEC_; + + PA_DEBUG(("WinMME StopStream: waiting for background thread.\n")); + + waitResult = WaitForSingleObject( stream->processingThread, timeout ); + if( waitResult == WAIT_TIMEOUT ) + { + /* try to abort */ + stream->abortProcessing = 1; + SetEvent( stream->abortEvent ); + waitResult = WaitForSingleObject( stream->processingThread, timeout ); + if( waitResult == WAIT_TIMEOUT ) + { + PA_DEBUG(("WinMME StopStream: timed out while waiting for background thread to finish.\n")); + result = paTimedOut; + } + } + + CloseHandle( stream->processingThread ); + stream->processingThread = NULL; + } + else + { + /* blocking read / write stream */ + + if( PA_IS_OUTPUT_STREAM_(stream) ) + { + if( stream->output.framesUsedInCurrentBuffer > 0 ) + { + /* there are still unqueued frames in the current buffer, so flush them */ + + hostOutputBufferIndex = stream->output.currentBufferIndex; + + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, + stream->output.framesPerBuffer - stream->output.framesUsedInCurrentBuffer ); + + channel = 0; + for( i=0; i<stream->output.deviceCount; ++i ) + { + /* we have stored the number of channels in the buffer in dwUser */ + int channelCount = stream->output.waveHeaders[i][ hostOutputBufferIndex ].dwUser; + + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, channel, + stream->output.waveHeaders[i][ hostOutputBufferIndex ].lpData + + stream->output.framesUsedInCurrentBuffer * channelCount * + stream->bufferProcessor.bytesPerHostOutputSample, + channelCount ); + + channel += channelCount; + } + + PaUtil_ZeroOutput( &stream->bufferProcessor, + stream->output.framesPerBuffer - stream->output.framesUsedInCurrentBuffer ); + + /* we send the entire buffer to the output devices, but we could + just send a partial buffer, rather than zeroing the unused + samples. + */ + AdvanceToNextOutputBuffer( stream ); + } + + + timeout = (stream->allBuffersDurationMs / stream->output.bufferCount) + 1; + if( timeout < PA_MME_MIN_TIMEOUT_MSEC_ ) + timeout = PA_MME_MIN_TIMEOUT_MSEC_; + + waitCount = 0; + while( !NoBuffersAreQueued( &stream->output ) && waitCount <= stream->output.bufferCount ) + { + /* wait for MME to signal that a buffer is available */ + waitResult = WaitForSingleObject( stream->output.bufferEvent, timeout ); + if( waitResult == WAIT_FAILED ) + { + break; + } + else if( waitResult == WAIT_TIMEOUT ) + { + /* keep waiting */ + } + + ++waitCount; + } + } + } + + if( PA_IS_OUTPUT_STREAM_(stream) ) + { + for( i =0; i < stream->output.deviceCount; ++i ) + { + mmresult = waveOutReset( ((HWAVEOUT*)stream->output.waveHandles)[i] ); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ); + } + } + } + + if( PA_IS_INPUT_STREAM_(stream) ) + { + for( i=0; i < stream->input.deviceCount; ++i ) + { + mmresult = waveInReset( ((HWAVEIN*)stream->input.waveHandles)[i] ); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); + } + } + } + + stream->isStopped = 1; + stream->isActive = 0; + + return result; +} + + +static PaError AbortStream( PaStream *s ) +{ + PaError result = paNoError; + PaWinMmeStream *stream = (PaWinMmeStream*)s; + int timeout; + DWORD waitResult; + MMRESULT mmresult; + unsigned int i; + + /** @todo + REVIEW: the error checking in this function needs review. the basic + idea is to return from this function in a known state - for example + there is no point avoiding calling waveInReset just because + the thread times out. + */ + + if( stream->processingThread ) + { + /* callback stream */ + + /* Tell processing thread to abort immediately */ + stream->abortProcessing = 1; + SetEvent( stream->abortEvent ); + } + + + if( PA_IS_OUTPUT_STREAM_(stream) ) + { + for( i =0; i < stream->output.deviceCount; ++i ) + { + mmresult = waveOutReset( ((HWAVEOUT*)stream->output.waveHandles)[i] ); + if( mmresult != MMSYSERR_NOERROR ) + { + PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ); + return paUnanticipatedHostError; + } + } + } + + if( PA_IS_INPUT_STREAM_(stream) ) + { + for( i=0; i < stream->input.deviceCount; ++i ) + { + mmresult = waveInReset( ((HWAVEIN*)stream->input.waveHandles)[i] ); + if( mmresult != MMSYSERR_NOERROR ) + { + PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); + return paUnanticipatedHostError; + } + } + } + + + if( stream->processingThread ) + { + /* callback stream */ + + PA_DEBUG(("WinMME AbortStream: waiting for background thread.\n")); + + /* Calculate timeOut longer than longest time it could take to return all buffers. */ + timeout = (int)(stream->allBuffersDurationMs * 1.5); + if( timeout < PA_MME_MIN_TIMEOUT_MSEC_ ) + timeout = PA_MME_MIN_TIMEOUT_MSEC_; + + waitResult = WaitForSingleObject( stream->processingThread, timeout ); + if( waitResult == WAIT_TIMEOUT ) + { + PA_DEBUG(("WinMME AbortStream: timed out while waiting for background thread to finish.\n")); + return paTimedOut; + } + + CloseHandle( stream->processingThread ); + stream->processingThread = NULL; + } + + stream->isStopped = 1; + stream->isActive = 0; + + return result; +} + + +static PaError IsStreamStopped( PaStream *s ) +{ + PaWinMmeStream *stream = (PaWinMmeStream*)s; + + return stream->isStopped; +} + + +static PaError IsStreamActive( PaStream *s ) +{ + PaWinMmeStream *stream = (PaWinMmeStream*)s; + + return stream->isActive; +} + + +static PaTime GetStreamTime( PaStream *s ) +{ + (void) s; /* unused parameter */ + + return PaUtil_GetTime(); +} + + +static double GetStreamCpuLoad( PaStream* s ) +{ + PaWinMmeStream *stream = (PaWinMmeStream*)s; + + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); +} + + +/* + As separate stream interfaces are used for blocking and callback + streams, the following functions can be guaranteed to only be called + for blocking streams. +*/ + +static PaError ReadStream( PaStream* s, + void *buffer, + unsigned long frames ) +{ + PaError result = paNoError; + PaWinMmeStream *stream = (PaWinMmeStream*)s; + void *userBuffer; + unsigned long framesRead = 0; + unsigned long framesProcessed; + signed int hostInputBufferIndex; + DWORD waitResult; + DWORD timeout = (unsigned long)(stream->allBuffersDurationMs * 0.5); + unsigned int channel, i; + + if( PA_IS_INPUT_STREAM_(stream) ) + { + /* make a local copy of the user buffer pointer(s). this is necessary + because PaUtil_CopyInput() advances these pointers every time + it is called. + */ + if( stream->bufferProcessor.userInputIsInterleaved ) + { + userBuffer = buffer; + } + else + { + userBuffer = alloca( sizeof(void*) * stream->bufferProcessor.inputChannelCount ); + if( !userBuffer ) + return paInsufficientMemory; + for( i = 0; i<stream->bufferProcessor.inputChannelCount; ++i ) + ((void**)userBuffer)[i] = ((void**)buffer)[i]; + } + + do{ + if( CurrentInputBuffersAreDone( stream ) ) + { + if( NoBuffersAreQueued( &stream->input ) ) + { + /** @todo REVIEW: consider what to do if the input overflows. + do we requeue all of the buffers? should we be running + a thread to make sure they are always queued? */ + + result = paInputOverflowed; + } + + hostInputBufferIndex = stream->input.currentBufferIndex; + + PaUtil_SetInputFrameCount( &stream->bufferProcessor, + stream->input.framesPerBuffer - stream->input.framesUsedInCurrentBuffer ); + + channel = 0; + for( i=0; i<stream->input.deviceCount; ++i ) + { + /* we have stored the number of channels in the buffer in dwUser */ + int channelCount = stream->input.waveHeaders[i][ hostInputBufferIndex ].dwUser; + + PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, channel, + stream->input.waveHeaders[i][ hostInputBufferIndex ].lpData + + stream->input.framesUsedInCurrentBuffer * channelCount * + stream->bufferProcessor.bytesPerHostInputSample, + channelCount ); + + channel += channelCount; + } + + framesProcessed = PaUtil_CopyInput( &stream->bufferProcessor, &userBuffer, frames - framesRead ); + + stream->input.framesUsedInCurrentBuffer += framesProcessed; + if( stream->input.framesUsedInCurrentBuffer == stream->input.framesPerBuffer ) + { + result = AdvanceToNextInputBuffer( stream ); + if( result != paNoError ) + break; + } + + framesRead += framesProcessed; + + }else{ + /* wait for MME to signal that a buffer is available */ + waitResult = WaitForSingleObject( stream->input.bufferEvent, timeout ); + if( waitResult == WAIT_FAILED ) + { + result = paUnanticipatedHostError; + break; + } + else if( waitResult == WAIT_TIMEOUT ) + { + /* if a timeout is encountered, continue, + perhaps we should give up eventually + */ + } + } + }while( framesRead < frames ); + } + else + { + result = paCanNotReadFromAnOutputOnlyStream; + } + + return result; +} + + +static PaError WriteStream( PaStream* s, + const void *buffer, + unsigned long frames ) +{ + PaError result = paNoError; + PaWinMmeStream *stream = (PaWinMmeStream*)s; + const void *userBuffer; + unsigned long framesWritten = 0; + unsigned long framesProcessed; + signed int hostOutputBufferIndex; + DWORD waitResult; + DWORD timeout = (unsigned long)(stream->allBuffersDurationMs * 0.5); + unsigned int channel, i; + + + if( PA_IS_OUTPUT_STREAM_(stream) ) + { + /* make a local copy of the user buffer pointer(s). this is necessary + because PaUtil_CopyOutput() advances these pointers every time + it is called. + */ + if( stream->bufferProcessor.userOutputIsInterleaved ) + { + userBuffer = buffer; + } + else + { + userBuffer = alloca( sizeof(void*) * stream->bufferProcessor.outputChannelCount ); + if( !userBuffer ) + return paInsufficientMemory; + for( i = 0; i<stream->bufferProcessor.outputChannelCount; ++i ) + ((const void**)userBuffer)[i] = ((const void**)buffer)[i]; + } + + do{ + if( CurrentOutputBuffersAreDone( stream ) ) + { + if( NoBuffersAreQueued( &stream->output ) ) + { + /** @todo REVIEW: consider what to do if the output + underflows. do we requeue all the existing buffers with + zeros? should we run a separate thread to keep the buffers + enqueued at all times? */ + + result = paOutputUnderflowed; + } + + hostOutputBufferIndex = stream->output.currentBufferIndex; + + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, + stream->output.framesPerBuffer - stream->output.framesUsedInCurrentBuffer ); + + channel = 0; + for( i=0; i<stream->output.deviceCount; ++i ) + { + /* we have stored the number of channels in the buffer in dwUser */ + int channelCount = stream->output.waveHeaders[i][ hostOutputBufferIndex ].dwUser; + + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, channel, + stream->output.waveHeaders[i][ hostOutputBufferIndex ].lpData + + stream->output.framesUsedInCurrentBuffer * channelCount * + stream->bufferProcessor.bytesPerHostOutputSample, + channelCount ); + + channel += channelCount; + } + + framesProcessed = PaUtil_CopyOutput( &stream->bufferProcessor, &userBuffer, frames - framesWritten ); + + stream->output.framesUsedInCurrentBuffer += framesProcessed; + if( stream->output.framesUsedInCurrentBuffer == stream->output.framesPerBuffer ) + { + result = AdvanceToNextOutputBuffer( stream ); + if( result != paNoError ) + break; + } + + framesWritten += framesProcessed; + } + else + { + /* wait for MME to signal that a buffer is available */ + waitResult = WaitForSingleObject( stream->output.bufferEvent, timeout ); + if( waitResult == WAIT_FAILED ) + { + result = paUnanticipatedHostError; + break; + } + else if( waitResult == WAIT_TIMEOUT ) + { + /* if a timeout is encountered, continue, + perhaps we should give up eventually + */ + } + } + }while( framesWritten < frames ); + } + else + { + result = paCanNotWriteToAnInputOnlyStream; + } + + return result; +} + + +static signed long GetStreamReadAvailable( PaStream* s ) +{ + PaWinMmeStream *stream = (PaWinMmeStream*)s; + + if( PA_IS_INPUT_STREAM_(stream) ) + return GetAvailableFrames( &stream->input ); + else + return paCanNotReadFromAnOutputOnlyStream; +} + + +static signed long GetStreamWriteAvailable( PaStream* s ) +{ + PaWinMmeStream *stream = (PaWinMmeStream*)s; + + if( PA_IS_OUTPUT_STREAM_(stream) ) + return GetAvailableFrames( &stream->output ); + else + return paCanNotWriteToAnInputOnlyStream; +} + + +/* NOTE: the following functions are MME-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 GetWinMMEStreamPointer( PaWinMmeStream **stream, PaStream *s ) +{ + PaError result; + PaUtilHostApiRepresentation *hostApi; + PaWinMmeHostApiRepresentation *winMmeHostApi; + + result = PaUtil_ValidateStreamPointer( s ); + if( result != paNoError ) + return result; + + result = PaUtil_GetHostApiRepresentation( &hostApi, paMME ); + if( result != paNoError ) + return result; + + winMmeHostApi = (PaWinMmeHostApiRepresentation*)hostApi; + + /* note, the following would be easier if there was a generic way of testing + that a stream belongs to a specific host API */ + + if( PA_STREAM_REP( s )->streamInterface == &winMmeHostApi->callbackStreamInterface + || PA_STREAM_REP( s )->streamInterface == &winMmeHostApi->blockingStreamInterface ) + { + /* s is a WinMME stream */ + *stream = (PaWinMmeStream *)s; + return paNoError; + } + else + { + return paIncompatibleStreamHostApi; + } +} + + +int PaWinMME_GetStreamInputHandleCount( PaStream* s ) +{ + PaWinMmeStream *stream; + PaError result = GetWinMMEStreamPointer( &stream, s ); + + if( result == paNoError ) + return (PA_IS_INPUT_STREAM_(stream)) ? stream->input.deviceCount : 0; + else + return result; +} + + +HWAVEIN PaWinMME_GetStreamInputHandle( PaStream* s, int handleIndex ) +{ + PaWinMmeStream *stream; + PaError result = GetWinMMEStreamPointer( &stream, s ); + + if( result == paNoError + && PA_IS_INPUT_STREAM_(stream) + && handleIndex >= 0 + && (unsigned int)handleIndex < stream->input.deviceCount ) + return ((HWAVEIN*)stream->input.waveHandles)[handleIndex]; + else + return 0; +} + + +int PaWinMME_GetStreamOutputHandleCount( PaStream* s) +{ + PaWinMmeStream *stream; + PaError result = GetWinMMEStreamPointer( &stream, s ); + + if( result == paNoError ) + return (PA_IS_OUTPUT_STREAM_(stream)) ? stream->output.deviceCount : 0; + else + return result; +} + + +HWAVEOUT PaWinMME_GetStreamOutputHandle( PaStream* s, int handleIndex ) +{ + PaWinMmeStream *stream; + PaError result = GetWinMMEStreamPointer( &stream, s ); + + if( result == paNoError + && PA_IS_OUTPUT_STREAM_(stream) + && handleIndex >= 0 + && (unsigned int)handleIndex < stream->output.deviceCount ) + return ((HWAVEOUT*)stream->output.waveHandles)[handleIndex]; + else + return 0; +} + + + + + diff --git a/src/audio/portaudio/pa_win_wmme/pa_win_wmme.h b/src/audio/portaudio/pa_win_wmme/pa_win_wmme.h new file mode 100644 index 0000000000000000000000000000000000000000..458f88f99ffb75de17bd88861c8c299a4ae534fb --- /dev/null +++ b/src/audio/portaudio/pa_win_wmme/pa_win_wmme.h @@ -0,0 +1,160 @@ +#ifndef PA_WIN_WMME_H +#define PA_WIN_WMME_H +/* + * $Id$ + * PortAudio Portable Real-Time Audio Library + * MME specific extensions + * + * 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. + * + * 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. + * + * 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. + * + */ + +/** @file + @brief WMME-specific PortAudio API extension header file. +*/ + + +#include "portaudio.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +#define paWinMmeUseLowLevelLatencyParameters (0x01) +#define paWinMmeUseMultipleDevices (0x02) /* use mme specific multiple device feature */ + + +/* By default, the mme implementation drops the processing thread's priority + to THREAD_PRIORITY_NORMAL and sleeps the thread if the CPU load exceeds 100% + This flag disables any priority throttling. The processing thread will always + run at THREAD_PRIORITY_TIME_CRITICAL. +*/ +#define paWinMmeDontThrottleOverloadedProcessingThread (0x08) + + +typedef struct PaWinMmeDeviceAndChannelCount{ + PaDeviceIndex device; + int channelCount; +}PaWinMmeDeviceAndChannelCount; + + +typedef struct PaWinMmeStreamInfo{ + unsigned long size; /**< sizeof(PaWinMmeStreamInfo) */ + PaHostApiTypeId hostApiType; /**< paMME */ + unsigned long version; /**< 1 */ + + unsigned long flags; + + /* low-level latency setting support + These settings control the number and size of host buffers in order + to set latency. They will be used instead of the generic parameters + to Pa_OpenStream() if flags contains the PaWinMmeUseLowLevelLatencyParameters + flag. + + If PaWinMmeStreamInfo structures with PaWinMmeUseLowLevelLatencyParameters + are supplied for both input and output in a full duplex stream, then the + input and output framesPerBuffer must be the same, or the larger of the + two must be a multiple of the smaller, otherwise a + paIncompatibleHostApiSpecificStreamInfo error will be returned from + Pa_OpenStream(). + */ + unsigned long framesPerBuffer; + unsigned long bufferCount; /* formerly numBuffers */ + + /* multiple devices per direction support + If flags contains the PaWinMmeUseMultipleDevices flag, + this functionality will be used, otherwise the device parameter to + Pa_OpenStream() will be used instead. + If devices are specified here, the corresponding device parameter + to Pa_OpenStream() should be set to paUseHostApiSpecificDeviceSpecification, + otherwise an paInvalidDevice error will result. + The total number of channels accross all specified devices + must agree with the corresponding channelCount parameter to + Pa_OpenStream() otherwise a paInvalidChannelCount error will result. + */ + PaWinMmeDeviceAndChannelCount *devices; + unsigned long deviceCount; + +}PaWinMmeStreamInfo; + + +/** Retrieve the number of wave in handles used by a PortAudio WinMME stream. + Returns zero if the stream is output only. + + @return A non-negative value indicating the number of wave in handles + or, a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. + + @see PaWinMME_GetStreamInputHandle +*/ +int PaWinMME_GetStreamInputHandleCount( PaStream* stream ); + + +/** Retrieve a wave in handle used by a PortAudio WinMME stream. + + @param stream The stream to query. + @param handleIndex The zero based index of the wave in handle to retrieve. This + should be in the range [0, PaWinMME_GetStreamInputHandle(stream)-1]. + + @return A valid wave in handle, or NULL if an error occurred. + + @see PaWinMME_GetStreamInputHandle +*/ +HWAVEIN PaWinMME_GetStreamInputHandle( PaStream* stream, int handleIndex ); + + +/** Retrieve the number of wave out handles used by a PortAudio WinMME stream. + Returns zero if the stream is input only. + + @return A non-negative value indicating the number of wave out handles + or, a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. + + @see PaWinMME_GetStreamOutputHandle +*/ +int PaWinMME_GetStreamOutputHandleCount( PaStream* stream ); + + +/** Retrieve a wave out handle used by a PortAudio WinMME stream. + + @param stream The stream to query. + @param handleIndex The zero based index of the wave out handle to retrieve. + This should be in the range [0, PaWinMME_GetStreamOutputHandleCount(stream)-1]. + + @return A valid wave out handle, or NULL if an error occurred. + + @see PaWinMME_GetStreamOutputHandleCount +*/ +HWAVEOUT PaWinMME_GetStreamOutputHandle( PaStream* stream, int handleIndex ); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* PA_WIN_WMME_H */ diff --git a/src/audio/portaudio/pablio/README.txt b/src/audio/portaudio/pablio/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..99c7d146ed8e0d29cc8942e624aa286687e809da --- /dev/null +++ b/src/audio/portaudio/pablio/README.txt @@ -0,0 +1,39 @@ +README for PABLIO +Portable Audio Blocking I/O Library +Author: Phil Burk + +PABLIO is a simplified interface to PortAudio that provide +read/write style blocking I/O. + +Please see the .DOC file for documentation. + +/* + * More information on PortAudio at: http://www.portaudio.com + * Copyright (c) 1999-2000 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. + * + * 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. + * + * 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. + * + */ + + diff --git a/src/audio/portaudio/pablio/pablio.c b/src/audio/portaudio/pablio/pablio.c new file mode 100644 index 0000000000000000000000000000000000000000..452eccc4d12c02e5333f8e8d30c7e7c1b4da1802 --- /dev/null +++ b/src/audio/portaudio/pablio/pablio.c @@ -0,0 +1,307 @@ +/* + * $Id$ + * pablio.c + * Portable Audio Blocking Input/Output utility. + * + * Author: Phil Burk, http://www.softsynth.com + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include "portaudio.h" +#include "ringbuffer.h" +#include "pablio.h" +#include <string.h> + +/************************************************************************/ +/******** Constants *****************************************************/ +/************************************************************************/ + +#define FRAMES_PER_BUFFER (256) + +/************************************************************************/ +/******** Prototypes ****************************************************/ +/************************************************************************/ + +static int blockingIOCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ); +static PaError PABLIO_InitFIFO( RingBuffer *rbuf, long numFrames, long bytesPerFrame ); +static PaError PABLIO_TermFIFO( RingBuffer *rbuf ); + +/************************************************************************/ +/******** Functions *****************************************************/ +/************************************************************************/ + +/* Called from PortAudio. + * Read and write data only if there is room in FIFOs. + */ +static int blockingIOCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + PABLIO_Stream *data = (PABLIO_Stream*)userData; + long numBytes = data->bytesPerFrame * framesPerBuffer; + (void) outTime; + + /* This may get called with NULL inputBuffer during initial setup. */ + if( inputBuffer != NULL ) + { + RingBuffer_Write( &data->inFIFO, inputBuffer, numBytes ); + } + if( outputBuffer != NULL ) + { + int i; + int numRead = RingBuffer_Read( &data->outFIFO, outputBuffer, numBytes ); + /* Zero out remainder of buffer if we run out of data. */ + for( i=numRead; i<numBytes; i++ ) + { + ((char *)outputBuffer)[i] = 0; + } + } + + return 0; +} + +/* Allocate buffer. */ +static PaError PABLIO_InitFIFO( RingBuffer *rbuf, long numFrames, long bytesPerFrame ) +{ + long numBytes = numFrames * bytesPerFrame; + char *buffer = (char *) malloc( numBytes ); + if( buffer == NULL ) return paInsufficientMemory; + memset( buffer, 0, numBytes ); + return (PaError) RingBuffer_Init( rbuf, numBytes, buffer ); +} + +/* Free buffer. */ +static PaError PABLIO_TermFIFO( RingBuffer *rbuf ) +{ + if( rbuf->buffer ) free( rbuf->buffer ); + rbuf->buffer = NULL; + return paNoError; +} + +/************************************************************ + * Write data to ring buffer. + * Will not return until all the data has been written. + */ +long WriteAudioStream( PABLIO_Stream *aStream, void *data, long numFrames ) +{ + long bytesWritten; + char *p = (char *) data; + long numBytes = aStream->bytesPerFrame * numFrames; + while( numBytes > 0) + { + bytesWritten = RingBuffer_Write( &aStream->outFIFO, p, numBytes ); + numBytes -= bytesWritten; + p += bytesWritten; + if( numBytes > 0) Pa_Sleep(10); + } + return numFrames; +} + +/************************************************************ + * Read data from ring buffer. + * Will not return until all the data has been read. + */ +long ReadAudioStream( PABLIO_Stream *aStream, void *data, long numFrames ) +{ + long bytesRead; + char *p = (char *) data; + long numBytes = aStream->bytesPerFrame * numFrames; + while( numBytes > 0) + { + bytesRead = RingBuffer_Read( &aStream->inFIFO, p, numBytes ); + numBytes -= bytesRead; + p += bytesRead; + if( numBytes > 0) Pa_Sleep(10); + } + return numFrames; +} + +/************************************************************ + * Return the number of frames that could be written to the stream without + * having to wait. + */ +long GetAudioStreamWriteable( PABLIO_Stream *aStream ) +{ + int bytesEmpty = RingBuffer_GetWriteAvailable( &aStream->outFIFO ); + return bytesEmpty / aStream->bytesPerFrame; +} + +/************************************************************ + * Return the number of frames that are available to be read from the + * stream without having to wait. + */ +long GetAudioStreamReadable( PABLIO_Stream *aStream ) +{ + int bytesFull = RingBuffer_GetReadAvailable( &aStream->inFIFO ); + return bytesFull / aStream->bytesPerFrame; +} + +/************************************************************/ +static unsigned long RoundUpToNextPowerOf2( unsigned long n ) +{ + long numBits = 0; + if( ((n-1) & n) == 0) return n; /* Already Power of two. */ + while( n > 0 ) + { + n= n>>1; + numBits++; + } + return (1<<numBits); +} + +/************************************************************ + * Opens a PortAudio stream with default characteristics. + * Allocates PABLIO_Stream structure. + * + * flags parameter can be an ORed combination of: + * PABLIO_READ, PABLIO_WRITE, or PABLIO_READ_WRITE, + * and either PABLIO_MONO or PABLIO_STEREO + */ +PaError OpenAudioStream( PABLIO_Stream **rwblPtr, double sampleRate, + PaSampleFormat format, long flags ) +{ + long bytesPerSample; + long doRead = 0; + long doWrite = 0; + PaError err; + PABLIO_Stream *aStream; + long minNumBuffers; + long numFrames; + + /* Allocate PABLIO_Stream structure for caller. */ + aStream = (PABLIO_Stream *) malloc( sizeof(PABLIO_Stream) ); + if( aStream == NULL ) return paInsufficientMemory; + memset( aStream, 0, sizeof(PABLIO_Stream) ); + + /* Determine size of a sample. */ + bytesPerSample = Pa_GetSampleSize( format ); + if( bytesPerSample < 0 ) + { + err = (PaError) bytesPerSample; + goto error; + } + aStream->samplesPerFrame = ((flags&PABLIO_MONO) != 0) ? 1 : 2; + aStream->bytesPerFrame = bytesPerSample * aStream->samplesPerFrame; + + /* Initialize PortAudio */ + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + /* Warning: numFrames must be larger than amount of data processed per interrupt + * inside PA to prevent glitches. Just to be safe, adjust size upwards. + */ + minNumBuffers = 2 * Pa_GetMinNumBuffers( FRAMES_PER_BUFFER, sampleRate ); + numFrames = minNumBuffers * FRAMES_PER_BUFFER; + numFrames = RoundUpToNextPowerOf2( numFrames ); + + /* Initialize Ring Buffers */ + doRead = ((flags & PABLIO_READ) != 0); + doWrite = ((flags & PABLIO_WRITE) != 0); + if(doRead) + { + err = PABLIO_InitFIFO( &aStream->inFIFO, numFrames, aStream->bytesPerFrame ); + if( err != paNoError ) goto error; + } + if(doWrite) + { + long numBytes; + err = PABLIO_InitFIFO( &aStream->outFIFO, numFrames, aStream->bytesPerFrame ); + if( err != paNoError ) goto error; + /* Make Write FIFO appear full initially. */ + numBytes = RingBuffer_GetWriteAvailable( &aStream->outFIFO ); + RingBuffer_AdvanceWriteIndex( &aStream->outFIFO, numBytes ); + } + + /* Open a PortAudio stream that we will use to communicate with the underlying + * audio drivers. */ + err = Pa_OpenStream( + &aStream->stream, + (doRead ? Pa_GetDefaultInputDeviceID() : paNoDevice), + (doRead ? aStream->samplesPerFrame : 0 ), + format, + NULL, + (doWrite ? Pa_GetDefaultOutputDeviceID() : paNoDevice), + (doWrite ? aStream->samplesPerFrame : 0 ), + format, + NULL, + sampleRate, + FRAMES_PER_BUFFER, + minNumBuffers, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + blockingIOCallback, + aStream ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( aStream->stream ); + if( err != paNoError ) goto error; + + *rwblPtr = aStream; + return paNoError; + +error: + CloseAudioStream( aStream ); + *rwblPtr = NULL; + return err; +} + +/************************************************************/ +PaError CloseAudioStream( PABLIO_Stream *aStream ) +{ + PaError err; + int bytesEmpty; + int byteSize = aStream->outFIFO.bufferSize; + + /* If we are writing data, make sure we play everything written. */ + if( byteSize > 0 ) + { + bytesEmpty = RingBuffer_GetWriteAvailable( &aStream->outFIFO ); + while( bytesEmpty < byteSize ) + { + Pa_Sleep( 10 ); + bytesEmpty = RingBuffer_GetWriteAvailable( &aStream->outFIFO ); + } + } + + err = Pa_StopStream( aStream->stream ); + if( err != paNoError ) goto error; + err = Pa_CloseStream( aStream->stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + +error: + PABLIO_TermFIFO( &aStream->inFIFO ); + PABLIO_TermFIFO( &aStream->outFIFO ); + free( aStream ); + return err; +} diff --git a/src/audio/portaudio/pablio/pablio.def b/src/audio/portaudio/pablio/pablio.def new file mode 100644 index 0000000000000000000000000000000000000000..a10f95291575fdabbc2a688d103658e851fc93d5 --- /dev/null +++ b/src/audio/portaudio/pablio/pablio.def @@ -0,0 +1,35 @@ +LIBRARY PABLIO +DESCRIPTION 'PABLIO Portable Audio Blocking I/O' + +EXPORTS + ; Explicit exports can go here + Pa_Initialize @1 + Pa_Terminate @2 + Pa_GetHostError @3 + Pa_GetErrorText @4 + Pa_CountDevices @5 + Pa_GetDefaultInputDeviceID @6 + Pa_GetDefaultOutputDeviceID @7 + Pa_GetDeviceInfo @8 + Pa_OpenStream @9 + Pa_OpenDefaultStream @10 + Pa_CloseStream @11 + Pa_StartStream @12 + Pa_StopStream @13 + Pa_StreamActive @14 + Pa_StreamTime @15 + Pa_GetCPULoad @16 + Pa_GetMinNumBuffers @17 + Pa_Sleep @18 + + OpenAudioStream @19 + CloseAudioStream @20 + WriteAudioStream @21 + ReadAudioStream @22 + + Pa_GetSampleSize @23 + + ;123456789012345678901234567890123456 + ;000000000111111111122222222223333333 + + diff --git a/src/audio/portaudio/pablio/pablio.h b/src/audio/portaudio/pablio/pablio.h new file mode 100644 index 0000000000000000000000000000000000000000..c1ad84a8367ef4e54a55bcefaa092a7cfab91a49 --- /dev/null +++ b/src/audio/portaudio/pablio/pablio.h @@ -0,0 +1,109 @@ +#ifndef _PABLIO_H +#define _PABLIO_H + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/* + * $Id$ + * PABLIO.h + * Portable Audio Blocking read/write utility. + * + * Author: Phil Burk, http://www.softsynth.com/portaudio/ + * + * Include file for PABLIO, the Portable Audio Blocking I/O Library. + * PABLIO is built on top of PortAudio, the Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include "portaudio.h" +#include "ringbuffer.h" +#include <string.h> + +typedef struct +{ + RingBuffer inFIFO; + RingBuffer outFIFO; + PortAudioStream *stream; + int bytesPerFrame; + int samplesPerFrame; +} +PABLIO_Stream; + +/* Values for flags for OpenAudioStream(). */ +#define PABLIO_READ (1<<0) +#define PABLIO_WRITE (1<<1) +#define PABLIO_READ_WRITE (PABLIO_READ|PABLIO_WRITE) +#define PABLIO_MONO (1<<2) +#define PABLIO_STEREO (1<<3) + +/************************************************************ + * Write data to ring buffer. + * Will not return until all the data has been written. + */ +long WriteAudioStream( PABLIO_Stream *aStream, void *data, long numFrames ); + +/************************************************************ + * Read data from ring buffer. + * Will not return until all the data has been read. + */ +long ReadAudioStream( PABLIO_Stream *aStream, void *data, long numFrames ); + +/************************************************************ + * Return the number of frames that could be written to the stream without + * having to wait. + */ +long GetAudioStreamWriteable( PABLIO_Stream *aStream ); + +/************************************************************ + * Return the number of frames that are available to be read from the + * stream without having to wait. + */ +long GetAudioStreamReadable( PABLIO_Stream *aStream ); + +/************************************************************ + * Opens a PortAudio stream with default characteristics. + * Allocates PABLIO_Stream structure. + * + * flags parameter can be an ORed combination of: + * PABLIO_READ, PABLIO_WRITE, or PABLIO_READ_WRITE, + * and either PABLIO_MONO or PABLIO_STEREO + */ +PaError OpenAudioStream( PABLIO_Stream **aStreamPtr, double sampleRate, + PaSampleFormat format, long flags ); + +PaError CloseAudioStream( PABLIO_Stream *aStream ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* _PABLIO_H */ diff --git a/src/audio/portaudio/pablio/ringbuffer.c b/src/audio/portaudio/pablio/ringbuffer.c new file mode 100644 index 0000000000000000000000000000000000000000..16031fef67d663137db8dcfa7085b9800e7258d0 --- /dev/null +++ b/src/audio/portaudio/pablio/ringbuffer.c @@ -0,0 +1,199 @@ +/* + * $Id$ + * ringbuffer.c + * Ring Buffer utility.. + * + * Author: Phil Burk, http://www.softsynth.com + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include "ringbuffer.h" +#include <string.h> + +/*************************************************************************** + * Initialize FIFO. + * numBytes must be power of 2, returns -1 if not. + */ +long RingBuffer_Init( RingBuffer *rbuf, long numBytes, void *dataPtr ) +{ + if( ((numBytes-1) & numBytes) != 0) return -1; /* Not Power of two. */ + rbuf->bufferSize = numBytes; + rbuf->buffer = (char *)dataPtr; + RingBuffer_Flush( rbuf ); + rbuf->bigMask = (numBytes*2)-1; + rbuf->smallMask = (numBytes)-1; + return 0; +} +/*************************************************************************** +** Return number of bytes available for reading. */ +long RingBuffer_GetReadAvailable( RingBuffer *rbuf ) +{ + return ( (rbuf->writeIndex - rbuf->readIndex) & rbuf->bigMask ); +} +/*************************************************************************** +** Return number of bytes available for writing. */ +long RingBuffer_GetWriteAvailable( RingBuffer *rbuf ) +{ + return ( rbuf->bufferSize - RingBuffer_GetReadAvailable(rbuf)); +} + +/*************************************************************************** +** Clear buffer. Should only be called when buffer is NOT being read. */ +void RingBuffer_Flush( RingBuffer *rbuf ) +{ + rbuf->writeIndex = rbuf->readIndex = 0; +} + +/*************************************************************************** +** 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. +*/ +long RingBuffer_GetWriteRegions( RingBuffer *rbuf, long numBytes, + void **dataPtr1, long *sizePtr1, + void **dataPtr2, long *sizePtr2 ) +{ + long index; + long available = RingBuffer_GetWriteAvailable( rbuf ); + if( numBytes > available ) numBytes = available; + /* Check to see if write is not contiguous. */ + index = rbuf->writeIndex & rbuf->smallMask; + if( (index + numBytes) > rbuf->bufferSize ) + { + /* Write data in two blocks that wrap the buffer. */ + long firstHalf = rbuf->bufferSize - index; + *dataPtr1 = &rbuf->buffer[index]; + *sizePtr1 = firstHalf; + *dataPtr2 = &rbuf->buffer[0]; + *sizePtr2 = numBytes - firstHalf; + } + else + { + *dataPtr1 = &rbuf->buffer[index]; + *sizePtr1 = numBytes; + *dataPtr2 = NULL; + *sizePtr2 = 0; + } + return numBytes; +} + + +/*************************************************************************** +*/ +long RingBuffer_AdvanceWriteIndex( RingBuffer *rbuf, long numBytes ) +{ + return rbuf->writeIndex = (rbuf->writeIndex + numBytes) & 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. +*/ +long RingBuffer_GetReadRegions( RingBuffer *rbuf, long numBytes, + void **dataPtr1, long *sizePtr1, + void **dataPtr2, long *sizePtr2 ) +{ + long index; + long available = RingBuffer_GetReadAvailable( rbuf ); + if( numBytes > available ) numBytes = available; + /* Check to see if read is not contiguous. */ + index = rbuf->readIndex & rbuf->smallMask; + if( (index + numBytes) > rbuf->bufferSize ) + { + /* Write data in two blocks that wrap the buffer. */ + long firstHalf = rbuf->bufferSize - index; + *dataPtr1 = &rbuf->buffer[index]; + *sizePtr1 = firstHalf; + *dataPtr2 = &rbuf->buffer[0]; + *sizePtr2 = numBytes - firstHalf; + } + else + { + *dataPtr1 = &rbuf->buffer[index]; + *sizePtr1 = numBytes; + *dataPtr2 = NULL; + *sizePtr2 = 0; + } + return numBytes; +} +/*************************************************************************** +*/ +long RingBuffer_AdvanceReadIndex( RingBuffer *rbuf, long numBytes ) +{ + return rbuf->readIndex = (rbuf->readIndex + numBytes) & rbuf->bigMask; +} + +/*************************************************************************** +** Return bytes written. */ +long RingBuffer_Write( RingBuffer *rbuf, void *data, long numBytes ) +{ + long size1, size2, numWritten; + void *data1, *data2; + numWritten = RingBuffer_GetWriteRegions( rbuf, numBytes, &data1, &size1, &data2, &size2 ); + if( size2 > 0 ) + { + + memcpy( data1, data, size1 ); + data = ((char *)data) + size1; + memcpy( data2, data, size2 ); + } + else + { + memcpy( data1, data, size1 ); + } + RingBuffer_AdvanceWriteIndex( rbuf, numWritten ); + return numWritten; +} + +/*************************************************************************** +** Return bytes read. */ +long RingBuffer_Read( RingBuffer *rbuf, void *data, long numBytes ) +{ + long size1, size2, numRead; + void *data1, *data2; + numRead = RingBuffer_GetReadRegions( rbuf, numBytes, &data1, &size1, &data2, &size2 ); + if( size2 > 0 ) + { + memcpy( data, data1, size1 ); + data = ((char *)data) + size1; + memcpy( data, data2, size2 ); + } + else + { + memcpy( data, data1, size1 ); + } + RingBuffer_AdvanceReadIndex( rbuf, numRead ); + return numRead; +} diff --git a/src/audio/portaudio/pablio/ringbuffer.h b/src/audio/portaudio/pablio/ringbuffer.h new file mode 100644 index 0000000000000000000000000000000000000000..dd769bee6db35a348e2c37eea8ae255266d6643e --- /dev/null +++ b/src/audio/portaudio/pablio/ringbuffer.h @@ -0,0 +1,101 @@ +#ifndef _RINGBUFFER_H +#define _RINGBUFFER_H +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/* + * $Id$ + * ringbuffer.h + * Ring Buffer utility.. + * + * Author: Phil Burk, http://www.softsynth.com + * + * This program is distributed with the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * 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. + * + * 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. + * + * 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. + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include "ringbuffer.h" +#include <string.h> + +typedef struct +{ + long bufferSize; /* Number of bytes in FIFO. Power of 2. Set by RingBuffer_Init. */ + long writeIndex; /* Index of next writable byte. Set by RingBuffer_AdvanceWriteIndex. */ + long readIndex; /* Index of next readable byte. Set by RingBuffer_AdvanceReadIndex. */ + long bigMask; /* Used for wrapping indices with extra bit to distinguish full/empty. */ + long smallMask; /* Used for fitting indices to buffer. */ + char *buffer; +} +RingBuffer; +/* + * Initialize Ring Buffer. + * numBytes must be power of 2, returns -1 if not. + */ +long RingBuffer_Init( RingBuffer *rbuf, long numBytes, void *dataPtr ); + +/* Clear buffer. Should only be called when buffer is NOT being read. */ +void RingBuffer_Flush( RingBuffer *rbuf ); + +/* Return number of bytes available for writing. */ +long RingBuffer_GetWriteAvailable( RingBuffer *rbuf ); +/* Return number of bytes available for read. */ +long RingBuffer_GetReadAvailable( RingBuffer *rbuf ); +/* Return bytes written. */ +long RingBuffer_Write( RingBuffer *rbuf, void *data, long numBytes ); +/* Return bytes read. */ +long RingBuffer_Read( RingBuffer *rbuf, void *data, long numBytes ); + +/* 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. +*/ +long RingBuffer_GetWriteRegions( RingBuffer *rbuf, long numBytes, + void **dataPtr1, long *sizePtr1, + void **dataPtr2, long *sizePtr2 ); +long RingBuffer_AdvanceWriteIndex( RingBuffer *rbuf, long numBytes ); + +/* 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. +*/ +long RingBuffer_GetReadRegions( RingBuffer *rbuf, long numBytes, + void **dataPtr1, long *sizePtr1, + void **dataPtr2, long *sizePtr2 ); + +long RingBuffer_AdvanceReadIndex( RingBuffer *rbuf, long numBytes ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* _RINGBUFFER_H */ diff --git a/src/audio/portaudio/pablio/test_rw.c b/src/audio/portaudio/pablio/test_rw.c new file mode 100644 index 0000000000000000000000000000000000000000..7f7e886ac0e53f9f4324ec4db2ad511c66c20dde --- /dev/null +++ b/src/audio/portaudio/pablio/test_rw.c @@ -0,0 +1,99 @@ +/* + * $Id$ + * test_rw.c + * Read input from one stream and write it to another. + * + * Author: Phil Burk, http://www.softsynth.com/portaudio/ + * + * This program uses PABLIO, the Portable Audio Blocking I/O Library. + * PABLIO is built on top of PortAudio, the Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * 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. + * + * 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. + * + * 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. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include "pablio.h" + +/* +** Note that many of the older ISA sound cards on PCs do NOT support +** full duplex audio (simultaneous record and playback). +** And some only support full duplex at lower sample rates. +*/ +#define SAMPLE_RATE (44100) +#define NUM_SECONDS (5) +#define SAMPLES_PER_FRAME (2) +#define FRAMES_PER_BLOCK (64) + +/* Select whether we will use floats or shorts. */ +#if 1 +#define SAMPLE_TYPE paFloat32 +typedef float SAMPLE; +#else +#define SAMPLE_TYPE paInt16 +typedef short SAMPLE; +#endif + +/*******************************************************************/ +int main(void); +int main(void) +{ + int i; + SAMPLE samples[SAMPLES_PER_FRAME * FRAMES_PER_BLOCK]; + PaError err; + PABLIO_Stream *aStream; + + printf("Full duplex sound test using PortAudio and RingBuffers\n"); + fflush(stdout); + + /* Open simplified blocking I/O layer on top of PortAudio. */ + err = OpenAudioStream( &aStream, SAMPLE_RATE, SAMPLE_TYPE, + (PABLIO_READ_WRITE | PABLIO_STEREO) ); + if( err != paNoError ) goto error; + + /* Process samples in the foreground. */ + for( i=0; i<(NUM_SECONDS * SAMPLE_RATE); i += FRAMES_PER_BLOCK ) + { + /* Read one block of data into sample array from audio input. */ + ReadAudioStream( aStream, samples, FRAMES_PER_BLOCK ); + /* Write that same block of data to output. */ + WriteAudioStream( aStream, samples, FRAMES_PER_BLOCK ); + } + + CloseAudioStream( aStream ); + + printf("Full duplex sound test complete.\n" ); + fflush(stdout); + return 0; + +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return -1; +} diff --git a/src/audio/portaudio/pablio/test_rw_echo.c b/src/audio/portaudio/pablio/test_rw_echo.c new file mode 100644 index 0000000000000000000000000000000000000000..9ba8c8d12667b6ccd1f6319e4e301f504e1a5cd9 --- /dev/null +++ b/src/audio/portaudio/pablio/test_rw_echo.c @@ -0,0 +1,123 @@ +/* + * $Id$ + * test_rw_echo.c + * Echo delayed input to output. + * + * Author: Phil Burk, http://www.softsynth.com/portaudio/ + * + * This program uses PABLIO, the Portable Audio Blocking I/O Library. + * PABLIO is built on top of PortAudio, the Portable Audio Library. + * + * Note that if you need low latency, you should not use PABLIO. + * Use the PA_OpenStream callback technique which is lower level + * than PABLIO. + * + * For more information see: http://www.audiomulch.com/portaudio/ + * 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. + * + * 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. + * + * 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. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include "pablio.h" +#include <string.h> + +/* +** Note that many of the older ISA sound cards on PCs do NOT support +** full duplex audio (simultaneous record and playback). +** And some only support full duplex at lower sample rates. +*/ +#define SAMPLE_RATE (22050) +#define NUM_SECONDS (20) +#define SAMPLES_PER_FRAME (2) + +/* Select whether we will use floats or shorts. */ +#if 1 +#define SAMPLE_TYPE paFloat32 +typedef float SAMPLE; +#else +#define SAMPLE_TYPE paInt16 +typedef short SAMPLE; +#endif + +#define NUM_ECHO_FRAMES (2*SAMPLE_RATE) +SAMPLE samples[NUM_ECHO_FRAMES][SAMPLES_PER_FRAME] = {0.0}; + +/*******************************************************************/ +int main(void); +int main(void) +{ + int i; + PaError err; + PABLIO_Stream *aInStream; + PABLIO_Stream *aOutStream; + int index; + + printf("Full duplex sound test using PABLIO\n"); + fflush(stdout); + + /* Open simplified blocking I/O layer on top of PortAudio. */ + /* Open input first so it can start to fill buffers. */ + err = OpenAudioStream( &aInStream, SAMPLE_RATE, SAMPLE_TYPE, + (PABLIO_READ | PABLIO_STEREO) ); + if( err != paNoError ) goto error; + /* printf("opened input\n"); fflush(stdout); /**/ + + err = OpenAudioStream( &aOutStream, SAMPLE_RATE, SAMPLE_TYPE, + (PABLIO_WRITE | PABLIO_STEREO) ); + if( err != paNoError ) goto error; + /* printf("opened output\n"); fflush(stdout); /**/ + + /* Process samples in the foreground. */ + index = 0; + for( i=0; i<(NUM_SECONDS * SAMPLE_RATE); i++ ) + { + /* Write old frame of data to output. */ + /* samples[index][1] = (i&256) * (1.0f/256.0f); /* sawtooth */ + WriteAudioStream( aOutStream, &samples[index][0], 1 ); + + /* Read one frame of data into sample array for later output. */ + ReadAudioStream( aInStream, &samples[index][0], 1 ); + index += 1; + if( index >= NUM_ECHO_FRAMES ) index = 0; + + if( (i & 0xFFFF) == 0 ) printf("i = %d\n", i ); fflush(stdout); /**/ + } + + CloseAudioStream( aOutStream ); + CloseAudioStream( aInStream ); + + printf("R/W echo sound test complete.\n" ); + fflush(stdout); + return 0; + +error: + fprintf( stderr, "An error occured while using PortAudio\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return -1; +} diff --git a/src/audio/portaudio/pablio/test_w_saw.c b/src/audio/portaudio/pablio/test_w_saw.c new file mode 100644 index 0000000000000000000000000000000000000000..d5d4e9be539cb39334c6d3571fa0f59148aac998 --- /dev/null +++ b/src/audio/portaudio/pablio/test_w_saw.c @@ -0,0 +1,108 @@ +/* + * $Id$ + * test_w_saw.c + * Generate stereo sawtooth waveforms. + * + * Author: Phil Burk, http://www.softsynth.com + * + * This program uses PABLIO, the Portable Audio Blocking I/O Library. + * PABLIO is built on top of PortAudio, the Portable Audio Library. + * + * For more information see: http://www.audiomulch.com/portaudio/ + * 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. + * + * 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. + * + * 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. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include "pablio.h" +#include <string.h> + +#define SAMPLE_RATE (44100) +#define NUM_SECONDS (6) +#define SAMPLES_PER_FRAME (2) + +#define FREQUENCY (220.0f) +#define PHASE_INCREMENT (2.0f * FREQUENCY / SAMPLE_RATE) +#define FRAMES_PER_BLOCK (100) + +float samples[FRAMES_PER_BLOCK][SAMPLES_PER_FRAME]; +float phases[SAMPLES_PER_FRAME]; + +/*******************************************************************/ +int main(void); +int main(void) +{ + int i,j; + PaError err; + PABLIO_Stream *aOutStream; + + printf("Generate sawtooth waves using PABLIO.\n"); + fflush(stdout); + + /* Open simplified blocking I/O layer on top of PortAudio. */ + err = OpenAudioStream( &aOutStream, SAMPLE_RATE, paFloat32, + (PABLIO_WRITE | PABLIO_STEREO) ); + if( err != paNoError ) goto error; + + /* Initialize oscillator phases. */ + phases[0] = 0.0; + phases[1] = 0.0; + + for( i=0; i<(NUM_SECONDS * SAMPLE_RATE); i += FRAMES_PER_BLOCK ) + { + /* Generate sawtooth waveforms in a block for efficiency. */ + for( j=0; j<FRAMES_PER_BLOCK; j++ ) + { + /* Generate a sawtooth wave by incrementing a variable. */ + phases[0] += PHASE_INCREMENT; + /* The signal range is -1.0 to +1.0 so wrap around if we go over. */ + if( phases[0] > 1.0f ) phases[0] -= 2.0f; + samples[j][0] = phases[0]; + + /* On the second channel, generate a sawtooth wave a fifth higher. */ + phases[1] += PHASE_INCREMENT * (3.0f / 2.0f); + if( phases[1] > 1.0f ) phases[1] -= 2.0f; + samples[j][1] = phases[1]; + } + + /* Write samples to output. */ + WriteAudioStream( aOutStream, samples, FRAMES_PER_BLOCK ); + } + + CloseAudioStream( aOutStream ); + + printf("Sawtooth sound test complete.\n" ); + fflush(stdout); + return 0; + +error: + fprintf( stderr, "An error occured while using PABLIO\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return -1; +} diff --git a/src/audio/portaudio/pablio/test_w_saw8.c b/src/audio/portaudio/pablio/test_w_saw8.c new file mode 100644 index 0000000000000000000000000000000000000000..0a9d05abd2f0ba12aae14b8ee36a9a876edf2ce2 --- /dev/null +++ b/src/audio/portaudio/pablio/test_w_saw8.c @@ -0,0 +1,106 @@ +/* + * $Id$ + * test_w_saw8.c + * Generate stereo 8 bit sawtooth waveforms. + * + * Author: Phil Burk, http://www.softsynth.com + * + * This program uses PABLIO, the Portable Audio Blocking I/O Library. + * PABLIO is built on top of PortAudio, the Portable Audio Library. + * + * For more information see: http://www.audiomulch.com/portaudio/ + * 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. + * + * 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. + * + * 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. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include "pablio.h" +#include <string.h> + +#define SAMPLE_RATE (22050) +#define NUM_SECONDS (6) +#define SAMPLES_PER_FRAME (2) + + +#define FRAMES_PER_BLOCK (100) + +unsigned char samples[FRAMES_PER_BLOCK][SAMPLES_PER_FRAME]; +unsigned char phases[SAMPLES_PER_FRAME]; + +/*******************************************************************/ +int main(void); +int main(void) +{ + int i,j; + PaError err; + PABLIO_Stream *aOutStream; + + printf("Generate unsigned 8 bit sawtooth waves using PABLIO.\n"); + fflush(stdout); + + /* Open simplified blocking I/O layer on top of PortAudio. */ + err = OpenAudioStream( &aOutStream, SAMPLE_RATE, paUInt8, + (PABLIO_WRITE | PABLIO_STEREO) ); + if( err != paNoError ) goto error; + + /* Initialize oscillator phases to "ground" level for paUInt8. */ + phases[0] = 128; + phases[1] = 128; + + for( i=0; i<(NUM_SECONDS * SAMPLE_RATE); i += FRAMES_PER_BLOCK ) + { + /* Generate sawtooth waveforms in a block for efficiency. */ + for( j=0; j<FRAMES_PER_BLOCK; j++ ) + { + /* Generate a sawtooth wave by incrementing a variable. */ + phases[0] += 1; + /* We don't have to do anything special to wrap when using paUint8 because + * 8 bit arithmetic automatically wraps. */ + samples[j][0] = phases[0]; + + /* On the second channel, generate a higher sawtooth wave. */ + phases[1] += 3; + samples[j][1] = phases[1]; + } + + /* Write samples to output. */ + WriteAudioStream( aOutStream, samples, FRAMES_PER_BLOCK ); + } + + CloseAudioStream( aOutStream ); + + printf("Sawtooth sound test complete.\n" ); + fflush(stdout); + return 0; + +error: + fprintf( stderr, "An error occured while using PABLIO\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return -1; +}